[
  {
    "path": ".git-blame-ignore-revs",
    "content": "# apply pyink\n40a6e074e5224d733f964be00e21e0a1cb98bd2e"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\nProvide as much information as possible. At least, this should include a description of your issue and steps to reproduce the problem. If possible also provide a summary of what steps or workarounds you have already tried.\n\n### System information\n- OS Platform and Distribution (e.g., Linux Ubuntu 16.04):\n- Flax, jax, jaxlib versions (obtain with `pip show flax jax jaxlib`:\n- Python version:\n- GPU/TPU model and memory:\n- CUDA version (if applicable):\n\n\n### Problem you have encountered:\n\n\n### What you expected to happen:\n\n\n### Logs, error messages, etc:\n\n\n\n### Steps to reproduce:\nWhenever possible, please provide a *minimal example*. Please consider submitting it as a Colab link.\n"
  },
  {
    "path": ".github/analytics/README.md",
    "content": "# Repo Analytics\n\nTo run the repo analytics follow the steps below:\n\n1. You must have a Github token, if you don't have one you can create one by following [this guide](https://docs.github.com/en/enterprise-server@3.4/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token).\n2. Install the requirements:\n\n```bash\npip install -r .github/analytics/requirements.txt\n```\n3. Run the analytics:\n\n```bash\nGITHUB_TOKEN=<token> \\\npython .github/analytics/get_repo_metrics.py \\\n  --repo-owner google \\\n  --repo-name flax\n```"
  },
  {
    "path": ".github/analytics/get_repo_metrics.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport os\nfrom datetime import datetime\nfrom collections.abc import Callable\n\nimport matplotlib.dates as mdates\nimport matplotlib.pyplot as plt\nimport pandas as pd\nimport requests\nfrom absl import app, flags\n\ntoken = os.environ['GITHUB_TOKEN']\nendpoint = r'https://api.github.com/graphql'\nheaders = {'Authorization': f'bearer {token}'}\n\n# ------------------------------------------------------------------------------\n# GraphQL\n# ------------------------------------------------------------------------------\n# NOTE: This GraphQL logic was ported and adapted from this script:\n# https://github.com/scientific-python/devstats-data/blob/4c022961abc4ca6061f8719d9c3387e98734b90c/query.py\n# It contains style differences from Google's style guide.\n\n\ndef load_query_from_file(fname, repo_owner, repo_name) -> str:\n  with open(fname) as fh:\n    query = fh.read()\n    # Set target repo from template\n    query = query.replace('_REPO_OWNER_', repo_owner)\n    query = query.replace('_REPO_NAME_', repo_name)\n  return query\n\n\ndef send_query(query, query_type, cursor=None):\n  \"\"\"\n  Sends a GraphQL to the GitHub API.\n\n  No validation is done on the query before sending. GitHub GraphQL is\n  supported with the `cursor` argument.\n\n  Parameters\n  ----------\n  query : str\n    The GraphQL query to be sent\n  query_type : {\"issues\", \"pullRequests\"}\n    The object being queried according to the GitHub GraphQL schema.\n    Currently only issues and pullRequests are supported\n  cursor : str, optional\n    If given, then the cursor is injected into the query to support\n    GitHub's GraphQL pagination.\n\n  Returns\n  -------\n  dict\n    The result of the query (json) parsed by `json.loads`\n\n  Notes\n  -----\n  This is intended mostly for internal use within `get_all_responses`.\n  \"\"\"\n  # TODO: Expand this, either by parsing the query type from the query\n  # directly or manually adding more query_types to the set\n  if query_type not in {'issues', 'pullRequests'}:\n    raise ValueError(\n      \"Only 'issues' and 'pullRequests' queries are currently supported\"\n    )\n  # TODO: Generalize this\n  # WARNING: The cursor injection depends on the specific structure of the\n  # query, this is the main reason why query types are limited to issues/PRs\n  if cursor is not None:\n    cursor_insertion_key = query_type + '('\n    cursor_ind = query.find(cursor_insertion_key) + len(cursor_insertion_key)\n    query = query[:cursor_ind] + f'after:\"{cursor}\", ' + query[cursor_ind:]\n  # Build request payload\n  payload = {'query': query}\n  response = requests.post(endpoint, json=payload, headers=headers)\n  return json.loads(response.content)\n\n\ndef get_all_responses(query, query_type):\n  'Helper function to bypass GitHub GraphQL API node limit.'\n  # Get data from a single response\n  initial_data = send_query(query, query_type)\n  data, last_cursor, total_count = parse_single_query(initial_data, query_type)\n  print(f'Retrieving {len(data)} out of {total_count} values...')\n  # Continue requesting data (with pagination) until all are acquired\n  while len(data) < total_count:\n    rdata = send_query(query, query_type, cursor=last_cursor)\n    pdata, last_cursor, _ = parse_single_query(rdata, query_type)\n    data.extend(pdata)\n    print(f'Retrieving {len(data)} out of {total_count} values...')\n  print('Done.')\n  return data\n\n\ndef parse_single_query(data, query_type):\n  \"\"\"\n  Parses the data returned by `send_query`\n\n  .. warning::\n\n    Like `send_query`, the logic here depends on the specific structure\n    of the query (e.g. it must be an issue or PR query, and must have a\n    total count).\n  \"\"\"\n  try:\n    total_count = data['data']['repository'][query_type]['totalCount']\n    data = data['data']['repository'][query_type]['edges']\n    last_cursor = data[-1]['cursor']\n  except KeyError as e:\n    print(data)\n    raise e\n  return data, last_cursor, total_count\n\n\nclass GithubGrabber:\n  \"\"\"\n  Pulls down data via the GitHub APIv.4 given a valid GraphQL query.\n  \"\"\"\n\n  def __init__(self, query_fname, query_type, repo_owner, repo_name):\n    \"\"\"\n    Create an object to send/recv queries related to the issue tracker\n    for the given repository via the GitHub API v.4.\n\n    The repository to query against is given by:\n    https://github.com/<repo_owner>/<repo_name>\n\n    Parameters\n    ----------\n    query_fname : str\n      Path to a valid GraphQL query conforming to the GitHub GraphQL\n      schema\n    query_type : {\"issues\", \"pullRequests\"}\n      Type of object that is being queried according to the GitHub GraphQL\n      schema. Currently only \"issues\" and \"pullRequests\" are supported.\n    repo_owner : str\n      Repository owner.\n    repo_name : str\n      Repository name.\n    \"\"\"\n    self.query_fname = query_fname\n    self.query_type = query_type  # TODO: Parse this directly from query\n    self.repo_owner = repo_owner\n    self.repo_name = repo_name\n    self.raw_data = None\n    self.load_query()\n\n  def load_query(self):\n    self.query = load_query_from_file(\n      self.query_fname, self.repo_owner, self.repo_name\n    )\n\n  def get(self):\n    self.raw_data = get_all_responses(self.query, self.query_type)\n\n\n# ------------------------------------------------------------------------------\n# metrics helpers\n# ------------------------------------------------------------------------------\n\n\ndef _to_datetime(date_str: str) -> datetime:\n  return datetime.fromisoformat(date_str.replace('Z', ''))\n\n\ndef _get_issues_features(issues):\n  for issue in issues:\n    issue = issue['node']\n\n    created_at = _to_datetime(issue['createdAt'])\n    time_labeled_or_converted = None\n    time_issue_closed = None\n\n    for event in issue['timelineItems']['edges']:\n      event = event['node']\n\n      if event['__typename'] in {'LabeledEvent', 'ConvertedToDiscussionEvent'}:\n        time_labeled_or_converted = _to_datetime(event['createdAt'])\n\n      if event['__typename'] == 'ClosedEvent':\n        time_issue_closed = _to_datetime(event['createdAt'])\n\n    yield {\n      'created_at': created_at,\n      'time_labeled_or_converted': time_labeled_or_converted,\n      'time_issue_closed': time_issue_closed,\n      'issue_closed': issue['state'] == 'CLOSED',\n    }\n\n\ndef _get_pr_features(prs):\n  for pr in prs:\n    pr = pr['node']\n\n    created_at = _to_datetime(pr['createdAt'])\n    ready_for_review_at = _to_datetime(pr['createdAt'])\n    time_labeled_or_assigned = None\n    time_merged_or_closed = None\n    time_review = None\n\n    if pr['reviews']['nodes']:\n      review = pr['reviews']['nodes'][0]\n      time_review = _to_datetime(review['createdAt'])\n\n    for event in pr['timelineItems']['edges']:\n      event = event['node']\n\n      if (\n        time_labeled_or_assigned is None\n        and event['__typename'] == 'LabeledEvent'\n        and 'cla:' not in event['label']['name']\n      ):\n        time_labeled_or_assigned = _to_datetime(event['createdAt'])\n\n      if (\n        time_labeled_or_assigned is None\n        and event['__typename'] == 'AssignedEvent'\n      ):\n        time_labeled_or_assigned = _to_datetime(event['createdAt'])\n\n      if event['__typename'] in {'ClosedEvent', 'MergedEvent'}:\n        time_merged_or_closed = _to_datetime(event['createdAt'])\n\n      if event['__typename'] == 'ReadyForReviewEvent':\n        ready_for_review_at = _to_datetime(event['createdAt'])\n\n    yield {\n      'created_at': created_at,\n      'ready_for_review_at': ready_for_review_at,\n      'time_labeled_or_assigned': time_labeled_or_assigned,\n      'time_merged_or_closed': time_merged_or_closed,\n      'time_review': time_review,\n      'pr_closed': pr['state'] != 'OPEN',\n    }\n\n\ndef _start_of_month(date: datetime) -> datetime:\n  return date.replace(day=1, hour=0, minute=0, second=0, microsecond=0)\n\n\ndef _shift_n_months(date: datetime, n: int) -> datetime:\n  month = ((date.month + n - 1) % 12) + 1\n\n  # shift to next year if necessary\n  if date.month > month:\n    date = date.replace(year=date.year + 1)\n\n  date = date.replace(month=month)\n\n  return date\n\n\ndef _rolling_window(\n  df: pd.DataFrame,\n  f: Callable[[pd.DataFrame], pd.Series],\n  window_size: int = 6,\n  step: int = 1,\n) -> pd.DataFrame:\n  # start of month of the first issue\n  start: datetime = df.iloc[0]['created_at'].replace(\n    day=1, hour=0, minute=0, second=0, microsecond=0\n  )\n  end = _shift_n_months(start, window_size)\n\n  last_month = _start_of_month(df.iloc[-1]['created_at'])\n  last_month = _shift_n_months(last_month, 1)\n\n  rows: list[pd.Series] = []\n  while end < last_month:\n    row = f(df[(df['created_at'] >= start) & (df['created_at'] < end)])\n    row['period_start'] = start\n    row['period_end'] = end\n    rows.append(row)\n    start = _shift_n_months(start, step)\n    end = _shift_n_months(end, step)\n\n  df = pd.DataFrame(rows)\n  df = df[['period_start', 'period_end'] + list(df.columns[:-2])]\n\n  return df\n\n\ndef _process_prs(df: pd.DataFrame) -> pd.Series:\n  return pd.Series(\n    {\n      'pr_response_time': df['pr_response_time'].dt.days.mean(),\n      'pr_resolution_time': df['pr_resolution_time'].dt.days.mean(),\n    }\n  )\n\n\ndef _process_issues(df: pd.DataFrame) -> pd.Series:\n  return pd.Series(\n    {\n      'issue_response_time': df['issue_response_time'].dt.days.mean(),\n      'issue_resolution_time': df['issue_resolution_time'].dt.days.mean(),\n    }\n  )\n\n\n# -----------------------------------------------------------------------------\n# main\n# -----------------------------------------------------------------------------\nFLAGS = flags.FLAGS\nflags.DEFINE_string('repo_owner', 'google', 'User name or organization')\nflags.DEFINE_string('repo_name', 'flax', 'Name of the repository')\n\n\ndef main(_):\n  repo_owner: str = FLAGS.repo_owner\n  repo_name: str = FLAGS.repo_name\n\n  # Download issue data\n  issues = GithubGrabber(\n    '.github/analytics/issue_activity_since_date.gql',\n    'issues',\n    repo_owner=repo_owner,\n    repo_name=repo_name,\n  )\n  issues.get()\n\n  df_issues = df_issues0 = pd.DataFrame(\n    list(_get_issues_features(issues.raw_data))\n  )\n  df_issues['issue_response_time'] = (\n    df_issues['time_labeled_or_converted'] - df_issues['created_at']\n  )\n  df_issues['issue_resolution_time'] = (\n    df_issues['time_issue_closed'] - df_issues['created_at']\n  )\n\n  df_issues = _rolling_window(df_issues, _process_issues)\n\n  prs = GithubGrabber(\n    '.github/analytics/pr_data_query.gql',\n    'pullRequests',\n    repo_owner=repo_owner,\n    repo_name=repo_name,\n  )\n  prs.get()\n\n  df_prs = df_prs0 = pd.DataFrame(list(_get_pr_features(prs.raw_data)))\n  time_response = df_prs[['time_labeled_or_assigned', 'time_review']].min(\n    axis=1\n  )\n  df_prs['pr_response_time'] = time_response - df_prs['ready_for_review_at']\n  df_prs['pr_resolution_time'] = (\n    df_prs['time_merged_or_closed'] - df_prs['ready_for_review_at']\n  )\n\n  df_prs = _rolling_window(df_prs, _process_prs)\n\n  # get cummulative issues\n  df_issues0 = df_issues0.copy()\n  df_issues0['number_of_issues'] = 1\n  df_issues0['number_of_issues'] = df_issues0['number_of_issues'].cumsum()\n\n  # get cummulative prs\n  df_prs0 = df_prs0.copy()\n  df_prs0['number_of_prs'] = 1\n  df_prs0['number_of_prs'] = df_prs0['number_of_prs'].cumsum()\n\n  # plot cumulative issues\n  plt.figure()\n  plt.plot(df_issues0['created_at'], df_issues0['number_of_issues'])\n  plt.xlabel('Date')\n  plt.ylabel('Number of issues')\n  plt.title('Number of issues')\n  plt.gca().xaxis.set_major_locator(plt.MaxNLocator(5))\n  plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n\n  # plot cumulative prs\n  plt.figure()\n  plt.plot(df_prs0['created_at'], df_prs0['number_of_prs'])\n  plt.xlabel('Date')\n  plt.ylabel('Number of PRs')\n  plt.title('Number of PRs')\n  plt.gca().xaxis.set_major_locator(plt.MaxNLocator(5))\n  plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n\n  # plot for isssue_response_time\n  plt.figure()\n  plt.plot(df_issues['period_end'], df_issues['issue_response_time'])\n  plt.xlabel('Date')\n  plt.ylabel('Issue Response Time (days)')\n  plt.title('Issue Response Time')\n  plt.gca().xaxis.set_major_locator(plt.MaxNLocator(5))\n  plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n  plt.ylim(0)\n\n  # plot for issue_resolution_time\n  plt.figure()\n  plt.plot(df_issues['period_end'], df_issues['issue_resolution_time'])\n  plt.xlabel('Date')\n  plt.ylabel('Issue Resolution Time (days)')\n  plt.title('Issue Resolution Time')\n  plt.gca().xaxis.set_major_locator(plt.MaxNLocator(5))\n  plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n  plt.ylim(0)\n\n  # plot for pr_response_time\n  plt.figure()\n  plt.plot(df_prs['period_end'], df_prs['pr_response_time'])\n  plt.xlabel('Date')\n  plt.ylabel('Pull Request Response Time (days)')\n  plt.title('Pull Request Response Time')\n  plt.gca().xaxis.set_major_locator(plt.MaxNLocator(5))\n  plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n  plt.ylim(0)\n\n  # plot for pr_resolution_time\n  plt.figure()\n  plt.plot(df_prs['period_end'], df_prs['pr_resolution_time'])\n  plt.xlabel('Date')\n  plt.ylabel('Pull Request Resolution Time (days)')\n  plt.title('Pull Request Resolution Time')\n  plt.gca().xaxis.set_major_locator(plt.MaxNLocator(5))\n  plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n  plt.ylim(0)\n\n  # show plots\n  plt.show()\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": ".github/analytics/issue_activity_since_date.gql",
    "content": "{\n  # Queries all the issues in a repo. For each issue, we get some basic data such as\n  # the number, state, labels, and title. The most important part is the 'timelineItems'\n  # which are the events that happened to the issue, we can use the information about\n  # the datetime about certain key events to define some metrics. Note that we are\n  # getting more information than is probably needed but its fine for now.\n  repository(owner: \"_REPO_OWNER_\", name: \"_REPO_NAME_\") {\n    issues(first: 100) {\n      totalCount\n      edges {\n        cursor\n        node {\n          number\n          title\n          createdAt\n          state\n          closedAt\n          updatedAt\n          url\n          labels(first: 100) {\n            edges {\n              node {\n                name\n              }\n            }\n          }\n          timelineItems(first: 100, itemTypes: [LABELED_EVENT, CONVERTED_TO_DISCUSSION_EVENT, ISSUE_COMMENT, CLOSED_EVENT]) {\n            totalCount\n            edges {\n              node {\n                __typename\n                ... on ConvertedToDiscussionEvent {\n                  createdAt\n                }\n                ... on IssueComment {\n                  author {\n                    login\n                  }\n                  createdAt\n                }\n                ... on ClosedEvent {\n                  actor {\n                    login\n                  }\n                  createdAt\n                }\n                ... on LabeledEvent {\n                  label {\n                    name\n                  }\n                  createdAt\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\n"
  },
  {
    "path": ".github/analytics/pr_data_query.gql",
    "content": "query {\n  # Queries all the Pull Requests in a repo. For each issue, we get some basic data such as\n  # the number, state, reviews, and title. The most important part is the 'timelineItems'\n  # which are the events that happened to the issue, we can use the information about\n  # the datetime about certain key events to define some metrics. We also use the 'reviews'\n  # as indicators for certain metrics.  Note that we are getting more information than is\n  # probably needed but its fine for now.\n  repository(owner:\"_REPO_OWNER_\", name:\"_REPO_NAME_\") {\n    pullRequests(first:100) {\n      totalCount\n      edges {\n        cursor\n        node {\n          number\n          state\n          title\n          createdAt\n          author{\n            login\n          }\n          mergedAt\n          reviews(first: 100){\n            nodes {\n              createdAt\n            }\n          }\n          timelineItems(first: 100, itemTypes: [LABELED_EVENT, ASSIGNED_EVENT, MERGED_EVENT, READY_FOR_REVIEW_EVENT, CLOSED_EVENT]) {\n            edges {\n              node {\n                __typename\n                ... on ClosedEvent {\n                  actor {\n                    login\n                  }\n                  createdAt\n                }\n                ... on LabeledEvent {\n                  label {\n                    name\n                  }\n                  actor {\n                    login\n                  }\n                  createdAt\n                }\n                ... on MergedEvent {\n                  actor {\n                    login\n                  }\n                  createdAt\n                }\n                ... on ReadyForReviewEvent {\n                  actor {\n                    login\n                  }\n                  createdAt\n                }\n                ... on AssignedEvent {\n                  actor {\n                    login\n                  }\n                  createdAt\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": ".github/analytics/requirements.txt",
    "content": "pandas\nabsl-py\nrequests\nmatplotlib"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "# What does this PR do?\n\n<!--\n\nGreat, you are contributing to Flax!\n\nBut... please read the following carefully so we can make sure your PR is merged\neasily.\n\nReplace this text block with a description of the change and which issue it\nfixes (if applicable). Please also include relevant motivation/context.\n\nOnce you're done, someone in the Flax team will review your PR shortly. They may\nsuggest changes to make the code even better. If no one reviewed your PR after a\nweek has passed, don't hesitate to post a new comment @-mentioning the same\npersons (sometimes notifications get lost).\n-->\n\nFixes # (issue)\n\n## Checklist\n- [ ] This PR fixes a minor issue (e.g.: typo or small bug) or improves the docs (you can dismiss the other checks if that's the case).\n- [ ] This change is discussed in a Github issue/[discussion](https://github.com/google/flax/discussions) (please add a link).\n- [ ] The documentation and docstrings adhere to the [documentation guidelines](https://github.com/google/flax/blob/main/docs/README.md#how-to-write-code-documentation).\n- [ ] This change includes necessary high-coverage tests. (No quality testing = no merge!)\n"
  },
  {
    "path": ".github/workflows/flax_publish.yml",
    "content": "# This workflows will upload a Python Package using Twine when a release is created\n# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries\n\nname: Flax - Build and upload to PyPI\n\non:\n  release:\n    types: [published]\n\njobs:\n  build_package:\n    name: Build package\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2\n      - name: Build package\n        run: pipx run build\n      - name: List files\n        run: ls -l dist/\n      - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08  # v4.6.0\n        with:\n          name: distribution\n          path: |\n            dist/*.tar.gz\n            dist/*.whl\n\n  upload_pypi:\n    name: Release & Upload to PyPI\n    # Only publish release to PyPI when a github release is created.\n    if: github.event_name == 'release' && github.event.action == 'published'\n    needs: build_package\n    runs-on: ubuntu-latest\n    environment: release\n    permissions:\n      id-token: write\n    steps:\n      - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16  # v4.1.8\n        with:\n          name: distribution\n          path: dist\n      - name: List files\n        run: ls -l dist/\n      - name: Publish package distributions to PyPI\n        uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc  # v1.12.4\n\n  discord_release:\n    if: github.repository_owner == 'google'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Get release URL\n        id: get-release-url\n        run: |\n          URL=\"https://github.com/google/flax/releases\"\n          echo \"::set-output name=URL::$URL\"\n      - name: Get content\n        uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757 # v1.4.1\n        id: get-content\n        with:\n          stringToTruncate: |\n            Flax [${{ github.event.release.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>) was just released!\n\n            ${{ github.event.release.body }}\n          maxLength: 2000\n          truncationSymbol: \"...\"\n      - name: Discord Webhook Action\n        uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164 # v5.3.0\n        with:\n          webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}\n          content: ${{ steps.get-content.outputs.string }}\n"
  },
  {
    "path": ".github/workflows/flax_test.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions\n\nname: Flax - Test\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\non:\n  push:\n    branches:\n      - main\n      - 'test_*'\n  pull_request:\n    branches:\n      - main\n\njobs:\n  pre-commit:\n    name: Test pre-commit hooks\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n      - name: Set up Python\n        uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0\n        with:\n          python-version: '3.11'\n      - run: python -m pip install pre-commit\n      - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0\n        with:\n          path: ~/.cache/pre-commit\n          key: pre-commit-${{ env.pythonLocation }}-${{ hashFiles('.pre-commit-config.yaml', 'pyproject.toml') }}\n      - run: pre-commit run --show-diff-on-failure --color=always --all-files\n  commit-count:\n    name: Check commit count\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n    # We allow at most 5 commits in a branch to ensure our CI doesn't break.\n    - name: Check commit count in PR\n      if: always()\n      shell: bash\n      run: |\n        set -x\n        # $GITHUB_REF is in format `refs/heads/<branch_name>`. We fetch it under\n        # the name `commit-count` so we can refer to it below.\n        # Do an unshallow fetch so we retrieve all commits (this is necessary\n        # because ations/checkout@v2 fetches a shallow copy).\n        git fetch origin --unshallow $GITHUB_REF:commit-count\n        git fetch origin main\n        diff=$(git rev-list --count origin/main...commit-count)\n        # $GITHUB_REF adds an additional commit to the commit tree, so $diff is\n        # one too high when executing this as a Github Action.\n        if (( $diff > 6)); then\n          echo \"ERROR! More than 5 commits in PR -- please squash your commits.\"\n          url=https://flax.readthedocs.io/en/latest/contributing.html#too-many-commits-in-a-pull-request\n          echo \"See $url for help on how to resolve this.\"\n          exit 1\n        fi\n  test-import:\n    name: Test import standalone\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: ['3.11', '3.12', '3.13', '3.14']\n    steps:\n    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0\n      with:\n        python-version: ${{ matrix.python-version }}\n    - uses: astral-sh/setup-uv@887a942a15af3a7626099df99e897a18d9e5ab3a # v5.1.0\n      with:\n        version: \"0.8.13\"\n    - name: Install standalone dependencies only\n      run: |\n        uv sync\n    - name: Test importing Flax\n      run: |\n        uv run --no-sync python -c \"import flax\"\n\n  tests:\n    name: Run Tests\n    needs: [pre-commit, commit-count]\n    runs-on: ubuntu-24.04-16core\n    strategy:\n      # Make sure to change `github_check_runs` in `copy.bara.sky` if you change the tests here.\n      matrix:\n        python-version: ['3.11', '3.12', '3.13']\n        test-type: [doctest, pytest]\n        jax-version: [newest]\n        include:\n          # keep in sync with internal type checking\n          - python-version: '3.12'\n            test-type: pytype\n            jax-version: 'newest'\n          - python-version: '3.12'\n            test-type: mypy\n            jax-version: 'newest'\n    steps:\n    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n    - name: Setup uv\n      uses: astral-sh/setup-uv@887a942a15af3a7626099df99e897a18d9e5ab3a # v5.1.0\n      with:\n        version: \"0.8.13\"\n        python-version: ${{ matrix.python-version }}\n        enable-cache: true\n\n    - name: Install dependencies\n      run: |\n        rm -fr .venv\n        uv sync --extra testing --extra docs\n    - name: Install JAX\n      run: |\n        if [[ \"${{ matrix.jax-version }}\" == \"newest\" ]]; then\n          uv pip install -U jax jaxlib\n        else\n          uv pip install \"jax==${{ matrix.jax-version }}\" \"jaxlib==${{ matrix.jax-version }}\"\n        fi\n        if [[ \"${{ matrix.test-type }}\" == \"pytest\" ]]; then\n          uv pip install -U tensorflow-datasets\n        fi\n    - name: Test with ${{ matrix.test-type }}\n      run: |\n        if [[ \"${{ matrix.test-type }}\" == \"doctest\" ]]; then\n          uv run --no-sync tests/run_all_tests.sh --only-doctest\n        elif [[ \"${{ matrix.test-type }}\" == \"pytest\" ]]; then\n          uv run --no-sync tests/run_all_tests.sh --only-pytest\n        elif [[ \"${{ matrix.test-type }}\" == \"pytype\" ]]; then\n          uv run --no-sync tests/run_all_tests.sh --only-pytype\n        elif [[ \"${{ matrix.test-type }}\" == \"mypy\" ]]; then\n          uv run --no-sync tests/run_all_tests.sh --only-mypy\n        else\n          echo \"Unknown test type: ${{ matrix.test-type }}\"\n          exit 1\n        fi\n    - name: Upload coverage to Codecov\n      if: matrix.test-type == 'pytest'\n      uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2\n      env:\n        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n      with:\n        file: ./coverage.xml\n    # The below step just reports the success or failure of tests as a \"commit status\".\n    # This is needed for copybara integration.\n    - name: Report success or failure as github status\n      if: always()\n      shell: bash\n      run: |\n        status=\"${{ job.status }}\"\n        lowercase_status=$(echo $status | tr '[:upper:]' '[:lower:]')\n        curl -sS --request POST \\\n        --url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }} \\\n        --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \\\n        --header 'content-type: application/json' \\\n        --data '{\n           \"state\": \"'$lowercase_status'\",\n           \"target_url\": \"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\",\n           \"description\": \"'$status'\",\n           \"context\": \"github-actions/Build\"\n           }'\n\n  # This is a temporary workflow to test flax on Python 3.14 and\n  # skipping deps like tensorstore, tensorflow etc\n  tests-python314:\n    name: Run Tests on Python 3.14\n    needs: [pre-commit, commit-count]\n    runs-on: ubuntu-24.04-16core\n    steps:\n    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n    - name: Setup uv\n      uses: astral-sh/setup-uv@887a942a15af3a7626099df99e897a18d9e5ab3a # v5.1.0\n      with:\n        version: \"0.9.2\"\n        python-version: \"3.14\"\n        activate-environment: true\n        enable-cache: true\n\n    - name: Install dependencies\n      run: |\n        rm -fr .venv\n        uv sync --extra testing --extra docs\n    - name: Test with pytest\n      run: |\n        export XLA_FLAGS='--xla_force_host_platform_device_count=4'\n        find tests/ -name \"*.py\" | grep -vE 'io_test|tensorboard' | xargs pytest -n auto\n\n"
  },
  {
    "path": ".github/workflows/flaxlib_publish.yml",
    "content": "name: Flaxlib - Build and upload to PyPI\n\n# for testing only:\non:\n  push:\n    branches: [main]\n    paths: ['flaxlib/**']\n  release:\n    types: [published]\n\njobs:\n  build_wheels:\n    if: github.event_name == 'push' && contains(github.event.head_commit.modified, 'flaxlib/')\n    name: Build wheels on ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        # macos-13 is an intel runner, macos-14 is apple silicon\n        os: [ubuntu-latest, windows-latest, macos-13, macos-14]\n\n    steps:\n    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n\n    - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0\n\n    - name: Setup Rust\n      uses: actions-rust-lang/setup-rust-toolchain@11df97af8e8102fd60b60a77dfbf58d40cd843b8 # v1.10.1\n\n    - name: Install cibuildwheel\n      run: python -m pip install cibuildwheel==2.21.0\n\n    - name: Build wheels\n      run: python -m cibuildwheel --output-dir ./flaxlib/wheelhouse ./flaxlib\n      env:\n        # rust doesn't seem to be available for musl linux on i686\n        CIBW_SKIP: \"*-musllinux_i686\"\n        CIBW_ENVIRONMENT: 'PATH=\"$HOME/.cargo/bin:$PATH\" CARGO_TERM_COLOR=\"always\"'\n        CIBW_ENVIRONMENT_WINDOWS: 'PATH=\"$UserProfile\\.cargo\\bin;$PATH\"'\n        CIBW_BEFORE_BUILD: rustup show\n        CIBW_BEFORE_BUILD_LINUX: |\n          curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain=stable --profile=minimal -y &&\n          rustup show\n\n    - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0\n      with:\n        name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}\n        path: ./flaxlib/wheelhouse/*.whl\n\n  build_sdist:\n    if: github.event_name == 'push' && contains(github.event.head_commit.modified, 'flaxlib/')\n    name: Build source distribution\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n\n    - name: Setup Rust\n      uses: actions-rust-lang/setup-rust-toolchain@11df97af8e8102fd60b60a77dfbf58d40cd843b8 # v1.10.1\n\n    - name: Build sdist\n      run: pipx run build --sdist flaxlib\n\n    - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0\n      with:\n        name: cibw-sdist\n        path: ./flaxlib/dist/*.tar.gz\n\n  upload_pypi:\n    if: github.event_name == 'push' && contains(github.event.head_commit.modified, 'flaxlib/')\n    name: Upload to PyPI\n    needs: [build_wheels, build_sdist]\n    runs-on: ubuntu-latest\n    permissions:\n      id-token: write\n    steps:\n    - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0\n      with:\n        python-version: '3.x'\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install setuptools build wheel twine\n    - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8\n      with:\n        # unpacks all CIBW artifacts into dist/\n        pattern: cibw-*\n        path: ./flaxlib/dist\n        merge-multiple: true\n\n    - name: Build and publish\n      env:\n        TWINE_USERNAME: __token__\n        TWINE_PASSWORD: ${{ secrets.FLAXLIB_PYPI_TOKEN }}\n      run: |\n        twine upload flaxlib/dist/*"
  },
  {
    "path": ".github/workflows/jax_nightly.yml",
    "content": "name: CI - with JAX nightly\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\non:\n  schedule:\n    - cron: \"0 12 * * *\" # Daily at 12:00 UTC\n  workflow_dispatch: # allows triggering the workflow run manually\n  pull_request:  # Automatically trigger on pull requests affecting this file\n    branches:\n      - main\n    paths:\n      - '**workflows/jax_nightly.yml'\n\njobs:\n  jax-nightly:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      issues: write  # for failed-build-issue\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.11\"]\n    steps:\n    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2\n    - name: Set up Python ${{ matrix.python-version }}\n      id: setup_python\n      uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b  # v5.3.0\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Setup uv\n      uses: astral-sh/setup-uv@887a942a15af3a7626099df99e897a18d9e5ab3a  # v5.1.0\n      with:\n        version: \"0.8.13\"\n    - name: Install dependencies\n      run: |\n        uv sync --extra testing --extra docs\n    - name: Install JAX\n      run: |\n        uv pip install -U --pre jax jaxlib -i https://us-python.pkg.dev/ml-oss-artifacts-published/jax/simple/\n    - name: Run test suite\n      if: success()\n      run: |\n        uv run tests/run_all_tests.sh --only-pytest\n    - name: Notify failed build\n      uses: jayqi/failed-build-issue-action@1a893bbf43ef1c2a8705e2b115cd4f0fe3c5649b  # v1.2.0\n      if: failure() && github.event.pull_request == null\n      with:\n        github-token: ${{ secrets.GITHUB_TOKEN }}"
  },
  {
    "path": ".gitignore",
    "content": "*~\n\\#*\\#\n*.pyc\n.tfds\n.DS_Store\ndist/\nbuild/\n*.egg-info\n*.rej\n.pytype\n.vscode/*\n/.devcontainer\ndocs*/**/_autosummary\ndocs*/_build\ndocs*/**/tmp\nflaxlib_src/build\nflaxlib_src/builddir\nflaxlib_src/dist\nflaxlib_src/subprojects\n.venv\nvenv/\nvenv.bak/\n\n# used by direnv\n.envrc\n\n# uv\nuv.lock\n\n# custom\n/tmp-files\n.agent/rules/flax.md\n.github/copilot-instructions.md\n.env\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "# Install the pre-commit hooks below with\n# 'pre-commit install'\n\n# Auto-update the version of the hooks with\n# 'pre-commit autoupdate'\n\n# Run the hooks on all files with\n# 'pre-commit run --all'\n\nrepos:\n- repo: https://github.com/mwouts/jupytext\n  rev: v1.13.8\n  hooks:\n  - id: jupytext\n    args: [--sync]\n# diable pyink for now\n# - repo: https://github.com/google/pyink\n#   rev: 23.5.0\n#   hooks:\n#     - id: pyink\n- repo: https://github.com/pre-commit/pre-commit-hooks\n  rev: v5.0.0\n  hooks:\n  - id: check-toml\n  - id: trailing-whitespace\n    exclude: ^docs.*\\.md$\n- repo: https://github.com/kynan/nbstripout\n  rev: 0.6.1\n  hooks:\n    - id: nbstripout\n      exclude: ^examples/.*\n      args: [\n        --keep-output,\n        --keep-count,\n        --extra-keys,\n        \"cell.metadata.executionInfo cell.metadata.id metadata.kernelspec metadata.vscode metadata.colab cell.metadata.executionInfo.user cell.metadata.executionInfo.user_tz cell.metadata.colab\",\n      ]\n- repo: https://github.com/astral-sh/ruff-pre-commit\n  # Ruff version.\n  rev: v0.1.3\n  hooks:\n    # Run the Ruff linter.\n    - id: ruff\n      args: [--fix, --exit-non-zero-on-fix]\n    # Disable Ruff formatter for now\n    # # Run the Ruff formatter.\n    # - id: ruff-format\n- repo: https://github.com/asottile/pyupgrade\n  rev: v3.16.0\n  hooks:\n    - id: pyupgrade\n      args: [--py310-plus]\n"
  },
  {
    "path": ".readthedocs.yml",
    "content": "# deprecated"
  },
  {
    "path": "AUTHORS",
    "content": "# This is the list the Flax authors for copyright purposes.\n#\n# This does not necessarily list everyone who has contributed code, since in\n# some cases, their employer may be the copyright holder.  To see the full list\n# of contributors, see the revision history in source control.\nGoogle LLC\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "Changelog\n----------\n\nvNext\n------\n(Add your change to a random empty line to avoid merge conflicts)\n-\n-\n-\n-\n- removed GeGLU simplistic activation, it should be implemented manually.\n-\n-\n-\n- removed FLAX_LAZY_RNG flag support for old non-lazy PRNG derivation mode\n-\n-\n-\n-\n-\n-\n-\n-\n-\n-\n\n0.8.2\n-----\n- fixed rng guide outputs by @chiamp in https://github.com/google/flax/pull/3685\n- enforce mask kwarg in norm layers by @chiamp in https://github.com/google/flax/pull/3663\n- added kwargs to self.param and self.variable by @chiamp in https://github.com/google/flax/pull/3675\n- added nnx normalization tests by @chiamp in https://github.com/google/flax/pull/3689\n- added NNX init_cache docstring example by @chiamp in https://github.com/google/flax/pull/3688\n- added nnx attention equivalence test by @chiamp in https://github.com/google/flax/pull/3687\n- Fix bug that assumed frozen-dict keys were strings. by @copybara-service in https://github.com/google/flax/pull/3692\n- added nnx rmsnorm by @chiamp in https://github.com/google/flax/pull/3691\n- updated nnx compute_stats by @chiamp in https://github.com/google/flax/pull/3693\n- fixed intercept_methods docstring by @chiamp in https://github.com/google/flax/pull/3694\n- [nnx] Add Sphinx Docs by @cgarciae in https://github.com/google/flax/pull/3678\n- Fix pointless docstring example of nn.checkpoint / nn.remat. by @levskaya in https://github.com/google/flax/pull/3703\n- added default params rng to .apply by @chiamp in https://github.com/google/flax/pull/3698\n- [nnx] add partial_init by @cgarciae in https://github.com/google/flax/pull/3674\n- make make_rng default to 'params' by @chiamp in https://github.com/google/flax/pull/3699\n- Add SimpleCell. by @carlosgmartin in https://github.com/google/flax/pull/3697\n- fix Module.module_paths docstring by @cgarciae in https://github.com/google/flax/pull/3709\n- Guarantee the latest JAX version on CI by @cgarciae in https://github.com/google/flax/pull/3705\n- Replace deprecated API `jax.tree.map` by @copybara-service in https://github.com/google/flax/pull/3715\n- Use `jax.tree_util.tree_map` instead of deprecated `jax.tree.map`. by @copybara-service in https://github.com/google/flax/pull/3714\n- [nnx] simplify readme by @cgarciae in https://github.com/google/flax/pull/3707\n- [nnx] add demo.ipynb by @cgarciae in https://github.com/google/flax/pull/3680\n- Fix Tabulate's compute_flops by @cgarciae in https://github.com/google/flax/pull/3721\n- [nnx] simplify TraceState by @cgarciae in https://github.com/google/flax/pull/3724\n- Add broadcast of `strides` and `kernel_dilation` to `nn.ConvTranspose` by @IvyZX in https://github.com/google/flax/pull/3731\n- [nnx] Fix State.__sub__ by @cgarciae in https://github.com/google/flax/pull/3704\n- [nnx] always fold_in on fork + new ForkedKeys return type by @cgarciae in https://github.com/google/flax/pull/3722\n- [nnx] explicit Variables by @cgarciae in https://github.com/google/flax/pull/3720\n- Improves fingerprint definition for Modules in nn.jit. by @copybara-service in https://github.com/google/flax/pull/3736\n- Flax: avoid key reuse in tests by @copybara-service in https://github.com/google/flax/pull/3740\n- added Einsum layer by @chiamp in https://github.com/google/flax/pull/3710\n- nn.jit: automatic fingerprint definition for dataclass attributes by @cgarciae in https://github.com/google/flax/pull/3737\n- [NVIDIA] Use custom grad accumulation for FP8 params by @kaixih in https://github.com/google/flax/pull/3623\n- removed nnx dataclass by @chiamp in https://github.com/google/flax/pull/3742\n- [nnx] cleanup graph_utils by @cgarciae in https://github.com/google/flax/pull/3728\n- Fix doctest and unbreak head by @IvyZX in https://github.com/google/flax/pull/3753\n- [nnx] add pytree support by @cgarciae in https://github.com/google/flax/pull/3732\n- fixed intercept_methods docstring by @chiamp in https://github.com/google/flax/pull/3752\n- Add ConvLSTMCell to docs. by @carlosgmartin in https://github.com/google/flax/pull/3712\n- [nnx] remove flagslib by @cgarciae in https://github.com/google/flax/pull/3733\n- Fix tests after applying JAX key-reuse checker. See: by @copybara-service in https://github.com/google/flax/pull/3748\n\n0.8.1\n-----\n- Added default collection in `make_rng`.\n- Added `InstanceNorm` and renamed `channel_axes` to `feature_axes`.\n- Added norm equivalence tests.\n- Added `Module.module_paths` and doc.\n- make `Sequential.__call__` compact.\n- Added `nn.compact_name_scope` v3.\n- Add explicit control over frozen/slots setting in `flax.struct.dataclass`.\n- Replacing `jax.tree_util.tree_map` with mapping over leafs.\n- Fixed docs and docstrings.\n\n0.8.0\n-----\n- Added [NNX](https://github.com/google/flax/tree/main/flax/nnx#nnx), a neural network library for JAX that provides a simple yet powerful module system that adheres to standard Python semantics. Its aim is to combine the robustness of Linen with a simplified, Pythonic API akin to that of PyTorch.\n- Added `nn.compact_name_scope` decorator that enables methods to act as compact name scopes as with regular Haiku methods. This makes porting Haiku code easier.\n- Add copy() method to Module.  This is a user-friendly version of the internal clone() method with better\n  defaults for common use cases.\n- Added [`BatchApply`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/layers.html#batchapply) class.\n- Added `sow_weights` option in attention layer.\n- Added [`MultiHeadAttention`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/_autosummary/flax.linen.MultiHeadAttention.html) alias.\n- Added kwargs support for `nn.jit`.\n- Deprecated `normalize` activation function, in favor of `standardize`.\n- Added `GeGLU` activation function.\n- Added `Enum` support for `tabulate` function.\n- Added simple argument-only lifted `nn.grad` function.\n\n0.7.5\n-----\n- Report forward and backward pass FLOPs of modules and submodules in `linen.Module.tabulate` and `summary.tabulate` (in new `flops` and `vjp_flops` table columns). Pass `compute_flops=True` and/or `compute_vjp_flops=True` to include these columns.\n- Re-factored `MultiHeadDotProductAttention`'s call method signature, by adding\n`inputs_k` and `inputs_v` args and switching `inputs_kv`, `mask` and `determistic`\nto keyword arguments. See more details in [#3389](https://github.com/google/flax/discussions/3389).\n- Use new typed PRNG keys throughout flax: this essentially involved changing\n  uses of `jax.random.PRNGKey` to `jax.random.key`.\n  (See [JEP 9263](https://github.com/jax-ml/jax/pull/17297) for details).\n  If you notice dispatch performance regressions after this change, be sure\n  you update `jax` to version 0.4.16 or newer.\n- Added `has_improved` field to EarlyStopping and changed the return signature of\n`EarlyStopping.update` from returning a tuple to returning just the updated class.\nSee more details in [#3385](https://github.com/google/flax/pull/3385)\n\n0.7.4\n-----\nNew features:\n- Add QK-normalization to MultiHeadDotProductAttention\n- Allow apply's method argument to accept submodules\n- Add module path to nn.module.\n- [JAX] Generate new type of PRNG keys\n\nBug fixes:\n- Directly call original method if method interceptor stack is empty.\n- fix stackoverflow when loading pickled module\n- Improve kw_only_dataclass.\n- Allow pass-through implementation of state dict\n- Promote dot_general injections from a function to a module.\n\n0.7.2\n-----\nNew features:\n- make `flax.core.copy` `add_or_replace` optional\n- Add `use_fast_variance` option to `GroupNorm` and `BatchNorm` to allow disabling it.\n\nBug fixes:\n- Use `field_specifiers` instead of `field_descriptors` in `@dataclass_transform`.\n- Fix `nn.Module` typing.\n- [JAX] Replace uses of `jax.experimental.pjit.with_sharding_constraint` with `jax.lax.with_sharding_constraint`.\n\n0.7.1\n-----\nBreaking changes:\n- Migrating Flax from returning FrozenDicts to returning regular dicts. More details can be found in this [announcement](https://github.com/google/flax/discussions/3191)\n\nNew features:\n- Use pyink\n- added dict migration guide to index\n- add scan over layers section\n- Expose options to customize rich.Table\n- add support for initializing carry variables in scan\n- Let Flax-Orbax to not port the shape of `target` arrays when they port the `target` shardings.\n\nBug fixes:\n- Use import `orbax.checkpoint` which is a better import pattern.\n- Use import `orbax.checkpoint as ocp` to avoid the verbosity of using 'orbax.checkpoint` every time.\n- [linen] Add alternative, more numerically stable, variance calculation to `LayerNorm`.\n- [linen] Minor cleanup to normalization code.\n- Fix norm calculation bug for 0-rank arrays.\n- [JAX] Remove references to jax.config.jax_array.\n- [linen] Use `stack` instead of `concatenate` in `compute_stats`, to handle scalar stats case.\n- [linen] More minor cleanup in normalization `compute_stats`.\n- Fix warnings from atari gym.\n- Refactor TypeHandler to operate over batches of values, rather than individual ones. This allows more flexibility for implementations that may operate more efficiently on batches.\n- Fix carry slice logic\n- make flax_basics guide use utility fns\n- Fix checkpointing guide error at head\n- Improve scan docs\n\n0.7.0\n-----\n- RNNCellBase refactor.\n\n0.6.11\n-----\n- Set Orbax-as-backend to be the default checkpointing method.\n- Fix setup trigger issue under sharing and transforms.\n- Add collection to self.scope.reserve(name, col) so that sow works with the same name in different collections.\n- Minor improvements for Sequential.\n- Improve the error message in MultiHeadDotProductAttention.\n- Allow manually specifying the rng key for Dropout.\n- RNN refactor.\n- fixed missing separator for rng fold in.\n\n0.6.10\n-----\n- Rudimentary quantization support: some layers can be parametrized with custom dot_general and conv_general_dilated.\n\n0.6.9\n-----\n- Depend on `orbax-checkpoint` package instead of `orbax`.\n- Refactored setup scripts to `project.toml`.\n- Added pretty_repr utility fn.\n- Fix get_partition_spec on replicated array.\n- Updates imagenet.ipynb to use GPU Colab runtime, and fixed config.\n- Upgrade checkpointing code to `jax.sharding`, and with more warnings.\n\n0.6.8\n-----\n- The automatic checkpoint migration was temporarily rolled back due to legacy compatibility issues.\n  - We still recommend you to use the [upgrade guide](https://flax.readthedocs.io/en/latest/guides/orbax_upgrade_guide.html) and migrate completely to the Orbax API to ensure stability.\n  - Or alternatively, add `flax.config.update('flax_use_orbax_checkpointing', True)` to your project to avoid being impacted by the automatic migration process.\n- Added utility functions to frozen_dict api.\n- Migrated Flax away from `register_keypaths`.\n- Fixes kwargs in convert_to_graphs_tuple_fn.\n- Fixed examples in a few ways:\n  - Bumped the TF version\n  - Used latest checkpoint formats\n  - Other misc fixes.\n\n0.6.7\n-----\n- New checkpoints will be saved using Orbax! Please check out [upgrade guide](https://flax.readthedocs.io/en/latest/guides/orbax_upgrade_guide.html) and consider migrating completely to the Orbax API.\n  - You could `flax.config.update('flax_use_orbax_checkpointing', False)` to temporarily disable this migration, but note that Flax legacy checkpointing will be removed 3 months from Mar 10, 2023.\n- Migrating `FrozenDict` to regular dict: utility functions now work on both.\n- Migrated Flax dataclass and `FrozenDict` to JAX pytree keypath API.\n- Fixed pytype and improved typing for `Module`\n- Fixed up uses of PyTree and PyTreeDef types.\n\n0.6.6\n-----\n- 0.6.5 was yanked so this release contains all that was in 0.6.5 as well.\n- Migrated regular dict to FrozenDict, currently controlled by a flag.\n- Refactored and separate out name relaxation policy changes.\n- Added RMS normalization layer.\n\n0.6.5\n-----\n- Added logical partitioning helpers for using pjit with Flax.\n- Add ``Module.lazy_init`` to avoid compute during Module initialization.\n\n0.6.4\n-----\nNew features:\n- Our [ReadTheDoc](https://flax.readthedocs.io/en/latest/index.html) site is a lot more organized now! More improvements on the way.\n- Flax auto-SPMD parallelism API to work seamlessly with `jax.pjit`: https://flax.readthedocs.io/en/latest/guides/flax_on_pjit.html\n- Added new `zeros_init` and `ones_init` initializers.\n- Adds standardize initializer.\n- Allowed specifying method as a string.\n- Allowed runtime overwrite of `flax.config` flags.\n\nBug fixes:\n- Added missing `dataclass.fields` from `__repr__`.\n- Renamed ConvLSTM to ConvLSTMCell.\n- Fix some tiny inconsistencies between scope.py and module.py.\n- Improved many many docstrings, comments and error messages.\n\n0.6.3\n-----\nNew features:\n- Flax checkpointing now uses [Orbax](https://github.com/google/orbax) for more flexiblity and features.\n- Added support for python 3.10 and removed support for 3.7.\n\nBug fixes:\n- Fixed rng generation in DenseGeneral init.\n- Improved support for Mac M1 chip.\n- Bumped package versions for a bunch of examples.\n- Improved many docstrings and error messages.\n\n0.6.2\n-----\nNew features:\n- Add rng_collection argument to Dropout.\n- Fix flax.linen.stochastic.Dropout.\n- Add flag allow_partial_mpa_restoration in checkpointing.\n- Use `gfile.remove` for files because it doesn't work on GCS files.\n- Added guides for: Flax the Sharp Bits, Checkpointing, Extracting Gradients\n- Improved existed documentation pages.\n- Improved errors, error messages and tests.\n- Removed codebase's trailing whitespaces.\n\nBug fixes:\n- Fixes launch_gce.sh with imagenet example.\n- Properly report AttributeErrors from descriptors.\n- Fixes usages of `pmap`.\n- Return None if no _parent_ref is set.\n- Cap dynamic scale to float32 max.\n- no-op when double wrapping with struct.dataclass.\n- Allow variable_with_axes to have empty axes when axes is set to an empty tuple.\n- Don't create reference cycles among Modules.\n\n0.6.1\n-----\n- Adds axis_name and axis_index_groups to LayerNorm and GroupNorm. by @copybara-service in [#2402](https://github.com/google/flax/pull/2402)\n- Plumb spmd_axis_name through transforms.vmap through to JAX vmap by @copybara-service in [#2398](https://github.com/google/flax/pull/2398)\n- Support multiple inputs in flax lifted vjp/custom_vjp by @copybara-service in [#2399](https://github.com/google/flax/pull/2399)\n- Improve tabulate by @cgarciae in [#2316](https://github.com/google/flax/pull/2316)\n- Add path_aware_map function by @cgarciae in [#2371](https://github.com/google/flax/pull/2371)\n- Add static_argnums to nn.checkpoint by @cgarciae in [#2457](https://github.com/google/flax/pull/2457)\n- Adding \"count_include_pad\" argument to flax.linen.pooling.avg_pool by @dslisleedh in [#2451](https://github.com/google/flax/pull/2451)\n- Add perturb() to allow capturing intermediate gradients by @IvyZX in [#2476](https://github.com/google/flax/pull/2476)\n\n0.6.0\n-----\n\n- Removed deprecated optimizers in `flax.optim` package.\n- Moved `flax.optim.dynamic_scale` to `flax.training.dynamic_scale`.\n- Switched to using `jax.named_scope` for all profile naming, cut some pointless\n  stack traces out.\n\n0.5.3\n-----\nNew features:\n- Added `nn.switch` as a lifted version of `jax.lax.switch`.\n- Added a method for detecting the use of \"init\" functions.\n- Added checkpointing support for `jax.experimental.GlobalDeviceArray`, a useful array type for multiprocess/multihost computing.\n- Added async option to `save_checkpoints()` on single-process scenario.\n- Improved documentation pages.\n\nBug fixes:\n- Fixed variable aliasing in put_variable\n- Fixed missing passthrough of nn.scan unroll arg\n- Fixed the MNIST example\n\n0.5.2\n-----\n- Fixes missing PyYAML dependency.\n\n0.5.1\n-----\nNew features:\n- Added `nn.tabulate` and `Module.tabulate` to generate rich representations of the network structure.\n\n0.5.0\n-----\n- Added `flax.jax_utils.ad_shard_unpad()` by @lucasb-eyer\n- Implemented [default dtype FLIP](https://github.com/google/flax/blob/main/docs/flip/1777-default-dtype.md).\n  This means the default dtype is now inferred from inputs and params rather than being hard-coded to float32.\n  This is especially useful for dealing with complex numbers because the standard Modules will no longer truncate\n  complex numbers to their real component by default. Instead the complex dtype is preserved by default.\n\n\nBug fixes:\n- Fix support for JAX's experimental_name_stack.\n\nBreaking changes:\n- In rare cases the dtype of a layer can change due to [default dtype FLIP](https://github.com/google/flax/blob/main/docs/flip/1777-default-dtype.md). See the \"Backward compatibility\" section of the proposal for more information.\n\n0.4.2\n-----\n\nNew features:\n- Add lifted conditional `nn.cond`.\n- Improved error messages: parameters not found, loading checkpoints.\n- Replace `jax.tree_multimap` (deprecated) with `jax.tree.map`.\n- Add the \"Module Lifecycle\" design note.\n- Add support for JAX dynamic stack-based named_call\n\nBug fixes:\n- Handle rate==1.0 edgecase in Dropout.\n- Fix bug where Linen Module state is reused.\n- Bug fixes and generalizations of nn.partitioning API.\n\n0.4.1\n-----\n\nNew features:\n- Added locally-connected (unshared CNN) layer `flax.linen.ConvLocal`.\n- Improved seq2seq example: Factored our model and input pipeline code.\n- Added Optax update guide and deprecated `flax.optim`.\n- Added `sep` argument to `flax.traverse_util.flatten_dict()`.\n- Implemented Sequential module, in `flax.linen.combinators`.\n\n0.4.0\n------\nBreaking changes:\n- flax.deprecated.nn is removed. Please pin to flax==0.3.6 if you are still using it.\n- PixelCNN++ example is removed. It was not working well on TPU.\n- linen Normalization layers no longer downcast double and complex floats tofloat32\n  when computing the mean and variance.\n\nNew features:\n- Added `flax.linen.custom_vjp` for custom derivatives inside a `Module`.\n- Add `param_dtype` attribute to standard Linen Modules for specifying parameter dtypes.\n\n\n0.3.6\n------\nBreaking changes:\n- Move `flax.nn` to `flax.deprecated.nn`.\n\nNew features:\n- Add experimental checkpoint policy argument. See `flax.linen.checkpoint`\n- Add lifted versions of jvp and vjp.\n- Add lifted transformation for mapping variables. See `flax.linen.map_variables`.\n\n\n0.3.5\n------\n\nBreaking changes:\n - You can no longer pass an int as the `kernel_size` for a `flax.linen.Conv.\n   Instead a type error is raised stating that\n   a tuple/list should be provided. Stride and dilation arguments do support broadcasting a single int value now because this is not\n   ambigious when the kernel rank is known.\n - `flax.linen.enable_named_call` and `flax.linen.disable_named_call` now work anywhere instead of only affecting Modules constructed after the enable/disable call. Additionally, there is now `flax.linen.override_named_call` that provided a context manager to locally disable/enable named_call.\n - NamedTuples are no longer converted to tuples on assignment to a `linen.Module`.\n\nNew features:\n - Flax internal stack frames are now removed from exception state traces.\n - Added `flax.linen.nowrap` to decorate method that should not be transformed\n   because they are stateful.\n - Flax no longer uses implicit rank broadcasting. Thus, you can now use Flax with `--jax_numpy_rank_promotion=raise`.\n\nBugfixes:\n - linen Modules and dataclasses made with `flax.struct.dataclass` or `flax.struct.PyTreeNode` are now correctly recognized as dataclasses by static analysis tools like PyLance. Autocomplete of constructors has been verified to work with VSCode.\n - Fixed a bug in FrozenDict which didn't allow copying dicts with reserved names.\n - Fix the serialization of named tuples. Tuple fields are no longer stored in the state dict and the named tuple class is no longer recreated ([bug](https://github.com/google/flax/issues/1429)).\n - Mixed precision training with float16 now works correctly with the attention layers.\n - auto-generated linen Module `__hash__`, `__eq__`, `__repr__` no longer fail by default on non-init attributes.\n\n\n\n0.3.4\n------\n\nPossibly breaking changes:\n - When calling `init` the 'intermediates' collection is no longer mutable.\n   Therefore, intermediates will no longer be returned from initialization by default.\n - Don't update batch statistics during initialization.\n - When not using any non-determinism (e.g., dropout), it is not longer necessary to specify the `deterministic` argument in `MultiHeadDotProductAttention`.\n\n\nOther changes:\n - Rewrote various examples to use Optax instead of Flax optimizers (e.g., Imagenet, SST2).\n - Added an NLP text classification example (on the SST-2 dataset) to\n   [`examples/sst2`](https://github.com/google/flax/tree/main/examples/sst2).\n   that uses a bidirectional LSTM (BiLSTM) to encode the input text.\n - Added `flax.training.train_state` to simplify using Optax optimizers.\n - `mutable` argument is now available on `Module.init` and `Module.init_with_outputs`\n - Bug fix: Correctly handle non-default parameters of Linen Modules with nested inheritance.\n - Expose `dot_product_attention_weights`, allowing access to attention weights.\n - `BatchNorm` instances will behave correctly during init when called multiple times.\n - Added a more extensive \"how to contribute\" guide in `contributing.md`.\n - Add proper cache behavior for [`lift.jit`](https://flax.readthedocs.io/en/latest/_autosummary/flax.linen.jit.html#flax.linen.jit),\nfixing cache misses.\n - Fix bug in Embed layer: make sure it behaves correctly when embedding is np.array.\n - Fix `linen.Module` for deep inheritance chains.\n - Fix bug in DenseGeneral: correctly expand bias to account for batch & noncontracting dimensions.\n - Allow Flax lifted transforms to work on partially applied Modules.\n - Make `MultiOptimizer` use `apply_gradient` instead of `apply_param_gradient`.\n\n0.3.3\n------\n\nPossible breaking changes:\n - Bug Fix: Disallow modifying attributes in Modules after they are initialized.\n - Raise an error when saving a checkpoint which has a smaller step than the\n   latest checkpoint already saved.\n - MultiOptimizer now rejects the case where multiple sub optimizers update the\n   same parameter.\n\nOther changes:\n - Added custom error classes to many Linen errors. See:\n   https://flax.readthedocs.io/en/latest/flax.errors.html\n - Adds `Module.bind` for binding variables and RNGs to an interactive Module.\n - Adds `nn.apply` and `nn.init` for transforming arbitrary functions that take a `linen.Module` as their first argument.\n - Add option to overwrite existing checkpoints in `save_checkpoint`.\n - Remove JAX omnistaging check for forward compatibility.\n - Pathlib compatibility for checkpoint paths.\n - `is_leaf` argument in `traverse_util.flatten_dict`\n\n0.3.2\n------\n\n`flax.nn` deprecation message no longer appears if you import flax directly.\n\nNOTE: You must now explicitly import `flax.nn` if you want to use the old\n      pre-Linen `flax.nn.Module`.\n\n0.3.1\n------\n\nMany improvements to Linen, and the old `flax.nn` is officially deprecated!\n\nNotably, there's a clean API for extracting intermediates from modules\ndefined using `@nn.compact`, a more ergonomic API for using Batch Norm and Dropout in modules\ndefined using `setup`, support for `MultiOptimizer` with Linen, and multiple safety, performance\nand error message improvements.\n\nPossible breaking changes:\n - Call setup lazily. See #938 for motivation and more details.\n - Linen `Module` instances are now frozen after `setup` has been called.\n   Previously mutations after setup could be dropped silently. Now the stateless requirement\n   is enforced by raising a TypeError in `__setattr__` after `setup`.\n - Pytrees of dicts and lists are transformed into FrozenDict and tuples during\n   attribute assignment.\n   This avoids undetected submodules and inner state.\n - Bug Fix `flax.core.apply` and `Module.apply`. Now it returns a tuple\n   containing the output and a frozen empty\n   collection when `mutable` is specified as an empty list.\n - `broadcast_dims` is now an attribute to `Dropout` instead of a `__call__`\n   argument.\n - `use_running_average` and `deterministic` no longer have a default. They\n   should be passed explicitly\n - Bug Fix `Scope.variable` mutability check, before a variable could only be\n   initialized if the 'params' collection was mutable.\n\nOther Improvements:\n - Re-introduced the `lm1b` language modeling example\n - Recognizes batch free inputs in pooling layers. (for use with vmap)\n - Add Adadelta optimizer\n - Fully deprecate all \"pre-Linen\" `flax.nn` classes and methods.\n - Some Module arguments can now be passed either as dataclass attribute or\n   as argument to `__call__`. See [design note](https://flax.readthedocs.io/en/latest/guides/arguments.html)\n - Add `sow` method to `Module` and `capture_intermediates` argument to `Module.apply`.\n   See [howto](https://flax.readthedocs.io/en/latest/howtos/extracting_intermediates.html) for usage patterns.\n - Support passing in modules directly as attributes to other modules, and\n   deal with them correctly both in top-level modules and in submodules.\n - Don't require the `variable` argument to `Module.apply` to be a FrozenDict\n - Add support for dict/FrozenDict when using `ModelParamTraversal`\n   As a result `MultiOptimizer` can be used properly with linen modules.\n - Added OptimizedLSTM: ~33% faster than the original LSTM when using <=1024 units\n - Fix dtype handling for Adam and LAMB optimizers in 64bit mode.\n - Added `is_mutable()` method to `Variable` and `is_mutable_collection()` to `flax.linen.Module`.\n - Add `axis_name` arg to `flax.linen.vmap`\n - Enable broadcast in `flax.linen.scan`\n - Fix behavior when inner module classes were defined in another module\n - Add automatic giant array chunking in msgpack checkpoints.\n - Log info message when a checkpoint is not found in the directory.\n\nv0.3\n-----\nLinen is now out of Alpha (flax.nn is being deprecated)!\n\n - `flax.core.apply` and linen `Module.apply` will now only return the variables\n   collections that were specified as mutable.\n - Fixed handling of multiple separate subclasses of a Module.\n - We now allow assignment of mixed Module pytrees in setup.\n - Refactored collection creation to fail early when modifying an undefined collection as\n   before an non-existing non-mutable collection would just be silently ignored.\n - Added the silu activation function.\n - Add offset argument to Adafactor optimizer for fine-tuning schedules.\n - Relaxed limit on calling methods on unbound modules.\n - Relaxed parameter attribute check\n - Added centered version of RMSProp.\n - Added GCE getting started kit.\n - Renamed -gpu_type to -accelerator_type.\n - Fixed bug in MultiOptimizer causing it to throw away empty dictionary\n\n### Improvements\n - Made FrozenDict constructor freeze correctly.\n - Made freeze a synonym of the FrozenDict constructor\n - Optimize freezing FrozenDicts by sharing immutable internal state.\n - We simplified __setattr__ handling of trees with Modules.\n - Minor improvements in dtype handling, broadcast option for dropout.\n - Added a dtype specification to Embed layer, made Adafactor use float32\n   state consistently, and added a broadcasting option to the Dropout layer.\n - Improved frozen dict performance.\n - (Massive) docs improvements\n - End to end benchmarks added.\n - Examples were updated to Linen.\n\nv0.2.2\n----\n - Added Reinforcement Learning example (examples/ppo).\n - Fix Adafactor bug that prevented factorization.\n - Fix scan broadcast issue in functional core.\n - Fix initialization RNGs to work with omnistaging for jitted inits.\n - Replaces usage of 'param' kind to 'params' collection.\n - Fix LARS optimizer for zero param initialization.\n - Added various examples in Linen API. See [README.md](https://github.com/google/flax/blob/main/flax/linen/README.md) for more information.\n - Full JAX omnistaging compatibility.\n\nv0.2\n----\n - Added JAX trace-level checks for transforms.\n - BatchNorm added axis_index_groups for control in parallel training.\n - Optimizers broken out into separate directory with base class and implementations.\n - traverse_util added flatten_dict and unflatten_dict utility methods for nested dicts.\n\nv0.1\n----\n\n### API Changes\n - Add ConvTranspose Module to nn.linear\n - Rename the following optional arguments to nn.linear.Conv:\n     `lhs_dilation` -> `input_dilation`,\n     `rhs_dilation` -> `kernel_dilation`\n - Change default layer names from numbers '0', '1', etc. to\n   include the Module class name, e.g. 'Dense_0', 'LayerNorm_1'.\n"
  },
  {
    "path": "LICENSE",
    "content": "                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n<img src=\"https://raw.githubusercontent.com/google/flax/main/images/flax_logo_250px.png\" alt=\"logo\"></img>\n</div>\n\n# Flax: A neural network library and ecosystem for JAX designed for flexibility\n\n[![Flax - Test](https://github.com/google/flax/actions/workflows/flax_test.yml/badge.svg)](https://github.com/google/flax/actions/workflows/flax_test.yml)\n[![PyPI version](https://img.shields.io/pypi/v/flax)](https://pypi.org/project/flax/)\n\n[**Overview**](#overview)\n| [**Quick install**](#quick-install)\n| [**What does Flax look like?**](#what-does-flax-look-like)\n| [**Documentation**](https://flax.readthedocs.io/)\n\nReleased in 2024, Flax NNX is a new simplified Flax API that is designed to make\nit easier to create, inspect, debug, and analyze neural networks in\n[JAX](https://jax.readthedocs.io/). It achieves this by adding first class support\nfor Python reference semantics. This allows users to express their models using\nregular Python objects, enabling reference sharing and mutability.\n\nFlax NNX evolved from the [Flax Linen API](https://flax-linen.readthedocs.io/), which\nwas released in 2020 by engineers and researchers at Google Brain in close collaboration\nwith the JAX team.\n\nYou can learn more about Flax NNX on the [dedicated Flax documentation site](https://flax.readthedocs.io/). Make sure you check out:\n\n* [Flax NNX basics](https://flax.readthedocs.io/en/latest/nnx_basics.html)\n* [MNIST tutorial](https://flax.readthedocs.io/en/latest/mnist_tutorial.html)\n* [Why Flax NNX](https://flax.readthedocs.io/en/latest/why.html)\n* [Evolution from Flax Linen to Flax NNX](https://flax.readthedocs.io/en/latest/guides/linen_to_nnx.html)\n\n**Note:** Flax Linen's [documentation has its own site](https://flax-linen.readthedocs.io/).\n\nThe Flax team's mission is to serve the growing JAX neural network\nresearch ecosystem - both within Alphabet and with the broader community,\nand to explore the use-cases where JAX shines. We use GitHub for almost\nall of our coordination and planning, as well as where we discuss\nupcoming design changes. We welcome feedback on any of our discussion,\nissue and pull request threads.\n\nYou can make feature requests, let us know what you are working on,\nreport issues, ask questions in our [Flax GitHub discussion\nforum](https://github.com/google/flax/discussions).\n\nWe expect to improve Flax, but we don't anticipate significant\nbreaking changes to the core API. We use [Changelog](https://github.com/google/flax/tree/main/CHANGELOG.md)\nentries and deprecation warnings when possible.\n\nIn case you want to reach us directly, we're at flax-dev@google.com.\n\n## Overview\n\nFlax is a high-performance neural network library and ecosystem for\nJAX that is **designed for flexibility**:\nTry new forms of training by forking an example and by modifying the training\nloop, not adding features to a framework.\n\nFlax is being developed in close collaboration with the JAX team and\ncomes with everything you need to start your research, including:\n\n* **Neural network API** (`flax.nnx`): Including [`Linear`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/linear.html#flax.nnx.Linear), [`Conv`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/linear.html#flax.nnx.Conv), [`BatchNorm`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/normalization.html#flax.nnx.BatchNorm), [`LayerNorm`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/normalization.html#flax.nnx.LayerNorm), [`GroupNorm`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/normalization.html#flax.nnx.GroupNorm), [Attention](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/attention.html) ([`MultiHeadAttention`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/attention.html#flax.nnx.MultiHeadAttention)), [`LSTMCell`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/recurrent.html#flax.nnx.nn.recurrent.LSTMCell), [`GRUCell`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/recurrent.html#flax.nnx.nn.recurrent.GRUCell), [`Dropout`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/stochastic.html#flax.nnx.Dropout).\n\n* **Utilities and patterns**: replicated training, serialization and checkpointing, metrics, prefetching on device.\n\n* **Educational examples**: [MNIST](https://flax.readthedocs.io/en/latest/mnist_tutorial.html), [Inference/sampling with the Gemma language model (transformer)](https://github.com/google/flax/tree/main/examples/gemma).\n\n## Quick install\n\nFlax uses JAX, so do check out [JAX installation instructions on CPUs, GPUs and TPUs](https://jax.readthedocs.io/en/latest/installation.html).\n\nYou will need Python 3.8 or later. Install Flax from PyPi:\n\n```\npip install flax\n```\n\nTo upgrade to the latest version of Flax, you can use:\n\n```\npip install --upgrade git+https://github.com/google/flax.git\n```\n\nTo install some additional dependencies (like `matplotlib`) that are required but not included\nby some dependencies, you can use:\n\n```bash\npip install \"flax[all]\"\n```\n\n## What does Flax look like?\n\nWe provide three examples using the Flax API: a simple multi-layer perceptron, a CNN and an auto-encoder.\n\nTo learn more about the `Module` abstraction, check out our [docs](https://flax.readthedocs.io/), our [broad intro to the Module abstraction](https://github.com/google/flax/blob/main/docs/linen_intro.ipynb). For additional concrete demonstrations of best practices, refer to our\n[guides](https://flax.readthedocs.io/en/latest/guides/index.html) and\n[developer notes](https://flax.readthedocs.io/en/latest/developer_notes/index.html).\n\nExample of an MLP:\n\n```py\nclass MLP(nnx.Module):\n  def __init__(self, din: int, dmid: int, dout: int, *, rngs: nnx.Rngs):\n    self.linear1 = nnx.Linear(din, dmid, rngs=rngs)\n    self.dropout = nnx.Dropout(rate=0.1, rngs=rngs)\n    self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n    self.linear2 = nnx.Linear(dmid, dout, rngs=rngs)\n\n  def __call__(self, x: jax.Array):\n    x = nnx.gelu(self.dropout(self.bn(self.linear1(x))))\n    return self.linear2(x)\n```\n\nExample of a CNN:\n\n```py\nclass CNN(nnx.Module):\n  def __init__(self, *, rngs: nnx.Rngs):\n    self.conv1 = nnx.Conv(1, 32, kernel_size=(3, 3), rngs=rngs)\n    self.conv2 = nnx.Conv(32, 64, kernel_size=(3, 3), rngs=rngs)\n    self.avg_pool = partial(nnx.avg_pool, window_shape=(2, 2), strides=(2, 2))\n    self.linear1 = nnx.Linear(3136, 256, rngs=rngs)\n    self.linear2 = nnx.Linear(256, 10, rngs=rngs)\n\n  def __call__(self, x):\n    x = self.avg_pool(nnx.relu(self.conv1(x)))\n    x = self.avg_pool(nnx.relu(self.conv2(x)))\n    x = x.reshape(x.shape[0], -1)  # flatten\n    x = nnx.relu(self.linear1(x))\n    x = self.linear2(x)\n    return x\n```\n\nExample of an autoencoder:\n\n\n```py\nEncoder = lambda rngs: nnx.Linear(2, 10, rngs=rngs)\nDecoder = lambda rngs: nnx.Linear(10, 2, rngs=rngs)\n\nclass AutoEncoder(nnx.Module):\n  def __init__(self, rngs):\n    self.encoder = Encoder(rngs)\n    self.decoder = Decoder(rngs)\n\n  def __call__(self, x) -> jax.Array:\n    return self.decoder(self.encoder(x))\n\n  def encode(self, x) -> jax.Array:\n    return self.encoder(x)\n```\n\n## Citing Flax\n\nTo cite this repository:\n\n```\n@software{flax2020github,\n  author = {Jonathan Heek and Anselm Levskaya and Avital Oliver and Marvin Ritter and Bertrand Rondepierre and Andreas Steiner and Marc van {Z}ee},\n  title = {{F}lax: A neural network library and ecosystem for {JAX}},\n  url = {http://github.com/google/flax},\n  version = {0.12.6},\n  year = {2024},\n}\n```\n\nIn the above bibtex entry, names are in alphabetical order, the version number\nis intended to be that from [flax/version.py](https://github.com/google/flax/blob/main/flax/version.py), and the year corresponds to the project's open-source release.\n\n## Note\n\nFlax is an open source project maintained by a dedicated team at Google DeepMind, but is not an official Google product.\n\n"
  },
  {
    "path": "benchmarks/README.md",
    "content": "# Benchmarks\n\nThese are mini benchmarks to measure the performance of NNX operations.\n\nSample profile command:\n\n```shell\npython -m cProfile -o ~/tmp/overhead.prof benchmarks/nnx_graph_overhead.py --mode=nnx --depth=100 --total_steps=1000\n```\n\nSample profile inspection:\n\n```shell\nsnakeviz ~/tmp/overhead.prof\n```"
  },
  {
    "path": "benchmarks/nnx_graph_overhead.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nimport optax\nfrom time import time\n\nfrom flax import nnx\n\nfrom absl import flags\nfrom absl import app\n\nFLAGS = flags.FLAGS\nflags.DEFINE_enum(\n  'mode', 'nnx', ['all', 'nnx', 'jax'], 'Mode to run the script in'\n)\nflags.DEFINE_integer('total_steps', 100, 'Total number of training steps')\nflags.DEFINE_integer('width', 32, 'Hidden layer size')\nflags.DEFINE_integer('depth', 5, 'Depth of the model')\n\n\nclass Linear(nnx.Module):\n  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n    self.w = nnx.Param(jax.random.uniform(rngs.params(), (din, dout)))\n    self.b = nnx.Param(jnp.zeros((dout,)))\n\n  def __call__(self, x):\n    return x @ self.w + self.b\n\n\nclass Block(nnx.Module):\n  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n    self.linear = Linear(din, dout, rngs=rngs)\n    self.bn = nnx.BatchNorm(dout, rngs=rngs)\n\n  def __call__(self, x):\n    return nnx.relu(self.bn(self.linear(x)))\n\n\nclass Count(nnx.Variable):\n  pass\n\n\nclass MLP(nnx.Module):\n  def __init__(self, din, dhidden, dout, depth, *, rngs: nnx.Rngs):\n    self.count = Count(jnp.array(0))\n    self.linear_in = Block(din, dhidden, rngs=rngs)\n    self.intermediates = nnx.List([\n      Block(dhidden, dhidden, rngs=rngs) for _ in range(depth - 2)\n    ])\n    self.linear_out = Block(dhidden, dout, rngs=rngs)\n\n  def __call__(self, x):\n    self.count.value += 1\n    x = nnx.relu(self.linear_in(x))\n    for layer in self.intermediates:\n      x = nnx.relu(layer(x))\n    x = self.linear_out(x)\n    return x\n\n\ndef main(argv):\n  print(argv)\n  mode: str = FLAGS.mode\n  total_steps: int = FLAGS.total_steps\n  width: int = FLAGS.width\n  depth: int = FLAGS.depth\n\n  print(f'{mode=}, {total_steps=}, {width=}')\n\n  X = np.linspace(0, 1, 100)[:, None]\n  Y = 0.8 * X**2 + 0.1 + np.random.normal(0, 0.1, size=X.shape)\n\n  # ------------------------------------------------------------\n  # NNX\n  # ------------------------------------------------------------\n  if mode in ['all', 'nnx']:\n    model = MLP(din=1, dhidden=width, dout=1, depth=depth, rngs=nnx.Rngs(0))\n    tx = optax.sgd(1e-3)\n    optimizer = nnx.Optimizer(model, tx, wrt=nnx.Param)\n    t0 = time()\n\n    @nnx.jit\n    def step_nnx(model: MLP, optimizer: nnx.Optimizer):\n      pass\n\n    t0 = time()\n    for _ in range(total_steps):\n      step_nnx(model, optimizer)\n\n    total_time = time() - t0\n    time_per_step = total_time / total_steps\n    time_per_layer = time_per_step / depth\n    print('### NNX ###')\n    print('total time:', total_time)\n    print(f'time per step: {time_per_step * 1e6:.2f} µs')\n    print(f'time per layer: {time_per_layer * 1e6:.2f} µs')\n\n  # ------------------------------------------------------------\n  # JAX\n  # ------------------------------------------------------------\n\n  if mode in ['all', 'jax']:\n    model = MLP(din=1, dhidden=width, dout=1, depth=depth, rngs=nnx.Rngs(0))\n    tx = optax.sgd(1e-3)\n    optimizer = nnx.Optimizer(model, tx, wrt=nnx.Param)\n    t0 = time()\n\n    @jax.jit\n    def step_jax(graphdef, state):\n      return graphdef, state\n\n    graphdef, state = nnx.split((model, optimizer))\n    t0 = time()\n    for _ in range(total_steps):\n      graphdef, state = step_jax(graphdef, state)\n\n    total_time = time() - t0\n    time_per_step = total_time / total_steps\n    time_per_layer = time_per_step / depth\n    print('### JAX ###')\n    print('total time:', total_time)\n    print(f'time per step: {time_per_step * 1e6:.2f} µs')\n    print(f'time per layer: {time_per_layer * 1e6:.2f} µs')\n    print()\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": "benchmarks/nnx_mlpmixer_training.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# %%\nfrom functools import partial\nimport jax\nimport jax.numpy as jnp\nfrom flax import nnx\nimport optax\nimport numpy as np\nfrom einop import einop\nfrom time import time\nfrom tqdm import tqdm\n\nfrom flax import nnx\n\nfrom absl import flags\nfrom absl import app\n\nFLAGS = flags.FLAGS\nflags.DEFINE_enum(\n  'mode', 'all', ['all', 'nnx', 'jax'], 'Mode to run the script in'\n)\nflags.DEFINE_integer('total_steps', 10_000, 'Total number of training steps')\nflags.DEFINE_integer('batch_size', 32, 'Batch size')\nflags.DEFINE_integer('width', 32, 'Hidden layer size')\nflags.DEFINE_integer('depth', 4, 'Depth of the model')\n\n\nclass MlpBlock(nnx.Module):\n  def __init__(self, din: int, mlp_dim: int, rngs: nnx.Rngs):\n    self.din, self.mlp_dim = din, mlp_dim\n    self.linear_in = nnx.Linear(din, mlp_dim, rngs=rngs)\n    self.linear_out = nnx.Linear(mlp_dim, din, rngs=rngs)\n\n  def __call__(self, x):\n    return self.linear_out(nnx.gelu(self.linear_in(x)))\n\n\nclass MixerBlock(nnx.Module):\n  def __init__(\n    self,\n    tokens_mlp_dim: int,\n    channels_mlp_dim: int,\n    hidden_dim: int,\n    rngs: nnx.Rngs,\n  ):\n    self.tokens_mlp_dim = tokens_mlp_dim\n    self.channels_mlp_dim = channels_mlp_dim\n    self.hidden_dim = hidden_dim\n    self.token_mixing = MlpBlock(tokens_mlp_dim, hidden_dim, rngs=rngs)\n    self.channel_mixing = MlpBlock(channels_mlp_dim, hidden_dim, rngs=rngs)\n    self.ln1 = nnx.LayerNorm(channels_mlp_dim, rngs=rngs)\n    self.ln2 = nnx.LayerNorm(channels_mlp_dim, rngs=rngs)\n\n  def __call__(self, x):\n    y = self.ln1(x)\n    y = y.swapaxes(1, 2)\n    y = self.token_mixing(y)\n    y = y.swapaxes(1, 2)\n    x = x + y\n    y = self.ln2(x)\n    return x + self.channel_mixing(y)\n\n\nclass MlpMixer(nnx.Module):\n  def __init__(\n    self,\n    din: int,\n    kernel_size: tuple[int, int],\n    strides: tuple[int, int],\n    num_blocks: int,\n    hidden_dim: int,\n    tokens_mlp_dim: int,\n    channels_mlp_dim: int,\n    rngs: nnx.Rngs,\n  ):\n    self.din = din\n    self.kernel_size = kernel_size\n    self.num_blocks = num_blocks\n    self.hidden_dim = hidden_dim\n    self.tokens_mlp_dim = tokens_mlp_dim\n    self.channels_mlp_dim = channels_mlp_dim\n    self.stem = nnx.Conv(\n      din + 1,\n      channels_mlp_dim,\n      kernel_size=kernel_size,\n      strides=strides,\n      rngs=rngs,\n    )\n    self.blocks = [\n      MixerBlock(tokens_mlp_dim, channels_mlp_dim, hidden_dim, rngs=rngs)\n      for _ in range(num_blocks)\n    ]\n    self.pre_head_layer_norm = nnx.LayerNorm(channels_mlp_dim, rngs=rngs)\n    self.conv_t = nnx.ConvTranspose(\n      channels_mlp_dim, din, kernel_size=kernel_size, strides=strides, rngs=rngs\n    )\n\n  def __call__(self, *, x, t):\n    # add time feature to input\n    t = einop(t, 'n -> n h w c', h=x.shape[1], w=x.shape[2], c=1)\n    x = jnp.concatenate([x, t], axis=-1)\n    # create patches\n    x = self.stem(x)\n    h, w = x.shape[1], x.shape[2]\n    x = einop(x, 'n h w c -> n (h w) c')\n    # apply blocks\n    for block in self.blocks:\n      x = block(x)\n    x = self.pre_head_layer_norm(x)\n    # recreate image\n    x = einop(x, 'n (h w) c -> n h w c', h=h, w=w)\n    x = self.conv_t(x)\n    return x\n\n\ndef main(argv):\n  print(argv)\n  mode: str = FLAGS.mode\n  total_steps: int = FLAGS.total_steps\n  batch_size: int = FLAGS.batch_size\n  width: int = FLAGS.width\n  depth: int = FLAGS.depth\n\n  print(f'{mode=}, {total_steps=}, {batch_size=}, {width=}')\n\n  X = np.random.uniform(size=(batch_size, 28, 28, 1))\n\n  if mode == 'nnx' or mode == 'all':\n    rngs = nnx.Rngs(0)\n    flow = MlpMixer(\n      din=1,\n      kernel_size=(2, 2),\n      strides=(2, 2),\n      num_blocks=4,\n      hidden_dim=512,\n      tokens_mlp_dim=196,\n      channels_mlp_dim=512,\n      rngs=rngs,\n    )\n    optimizer = nnx.Optimizer(\n      flow, tx=optax.adamw(1e-4), wrt=nnx.Param\n    )\n    t0 = time()\n\n    mse = lambda a, b: jnp.mean((a - b) ** 2)\n\n    @nnx.jit(donate_argnums=(0, 1, 2))\n    def train_step_nnx(flow, optimizer, rngs, x_1):\n      print('JITTING NNX')\n      x_0 = jax.random.normal(rngs(), x_1.shape)\n      t = jax.random.uniform(rngs(), (len(x_1),))\n\n      x_t = jax.vmap(lambda x_0, x_1, t: (1 - t) * x_0 + t * x_1)(x_0, x_1, t)\n      dx_t = x_1 - x_0\n\n      loss, grads = nnx.value_and_grad(\n        lambda flow: mse(flow(x=x_t, t=t), dx_t)\n      )(flow)\n      optimizer.update(flow, grads)\n      return loss\n\n    losses = []\n    t0 = time()\n    for step in tqdm(range(total_steps), desc='NNX'):\n      loss = train_step_nnx(flow, optimizer, rngs, X)\n      losses.append(loss)\n\n    total_time = time() - t0\n    print('### NNX ###')\n    print(f'final loss: {losses[-1]}')\n    print('total time:', total_time)\n    print(f'time per step: {total_time / total_steps * 1e6:.2f} µs')\n\n  if mode == 'jax' or mode == 'all':\n    rngs = nnx.Rngs(0)\n    flow = MlpMixer(\n      din=1,\n      kernel_size=(2, 2),\n      strides=(2, 2),\n      num_blocks=depth,\n      hidden_dim=width,\n      tokens_mlp_dim=196,\n      channels_mlp_dim=width,\n      rngs=rngs,\n    )\n    optimizer = nnx.Optimizer(\n      flow, tx=optax.adamw(1e-4), wrt=nnx.Param\n    )\n    graphdef, state = nnx.split((flow, optimizer, rngs))\n    t0 = time()\n\n    mse = lambda a, b: jnp.mean((a - b) ** 2)\n\n    @partial(nnx.jit, donate_argnums=0)\n    def train_step_jax(state, x_1):\n      print('JITTING JAX')\n      flow, optimizer, rngs = nnx.merge(graphdef, state)\n      x_0 = jax.random.normal(rngs(), x_1.shape)\n      t = jax.random.uniform(rngs(), (len(x_1),))\n\n      x_t = jax.vmap(lambda x_0, x_1, t: (1 - t) * x_0 + t * x_1)(x_0, x_1, t)\n      dx_t = x_1 - x_0\n\n      loss, grads = nnx.value_and_grad(\n        lambda flow: mse(flow(x=x_t, t=t), dx_t)\n      )(flow)\n      optimizer.update(flow, grads)\n      state = nnx.state((flow, optimizer, rngs))\n      return loss, state\n\n    losses = []\n    t0 = time()\n    for step in tqdm(range(total_steps), desc='JAX'):\n      loss, state = train_step_jax(state, X)\n      losses.append(loss)\n\n    nnx.update((flow, optimizer, rngs), state)\n    total_time = time() - t0\n    print('### JAX ###')\n    print(f'final loss: {losses[-1]}')\n    print('total time:', total_time)\n    print(f'time per step: {total_time / total_steps * 1e6:.2f} µs')\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": "benchmarks/nnx_simple_training.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# %%\nfrom functools import partial\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nimport optax\nfrom time import time\n\nfrom flax import nnx\n\nfrom absl import flags\nfrom absl import app\n\nFLAGS = flags.FLAGS\nflags.DEFINE_enum(\n  'mode', 'all', ['all', 'nnx', 'jax'], 'Mode to run the script in'\n)\nflags.DEFINE_integer('total_steps', 10_000, 'Total number of training steps')\nflags.DEFINE_integer('batch_size', 32, 'Batch size')\nflags.DEFINE_integer('width', 32, 'Hidden layer size')\nflags.DEFINE_integer('depth', 5, 'Depth of the model')\n\n\ndef dataset(X, Y, batch_size):\n  while True:\n    idx = np.random.choice(len(X), size=batch_size)\n    yield X[idx], Y[idx]\n\n\nclass Linear(nnx.Module):\n  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n    self.w = nnx.Param(jax.random.uniform(rngs.params(), (din, dout)))\n    self.b = nnx.Param(jnp.zeros((dout,)))\n\n  def __call__(self, x):\n    return x @ self.w + self.b\n\n\nclass Block(nnx.Module):\n  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n    self.linear = Linear(din, dout, rngs=rngs)\n    self.bn = nnx.BatchNorm(dout, rngs=rngs)\n\n  def __call__(self, x):\n    return nnx.relu(self.bn(self.linear(x)))\n\n\nclass Count(nnx.Variable):\n  pass\n\n\nclass MLP(nnx.Module):\n  def __init__(self, din, dhidden, dout, depth, *, rngs: nnx.Rngs):\n    self.count = Count(jnp.array(0))\n    self.linear_in = Block(din, dhidden, rngs=rngs)\n    self.intermediates = [\n      Block(dhidden, dhidden, rngs=rngs) for _ in range(depth - 2)\n    ]\n    self.linear_out = Block(dhidden, dout, rngs=rngs)\n\n  def __call__(self, x):\n    self.count.value += 1\n    x = nnx.relu(self.linear_in(x))\n    for layer in self.intermediates:\n      x = nnx.relu(layer(x))\n    x = self.linear_out(x)\n    return x\n\n\ndef main(argv):\n  print(argv)\n  mode: str = FLAGS.mode\n  total_steps: int = FLAGS.total_steps\n  batch_size: int = FLAGS.batch_size\n  width: int = FLAGS.width\n  depth: int = FLAGS.depth\n\n  print(f'{mode=}, {total_steps=}, {batch_size=}, {width=}')\n\n  X = np.linspace(0, 1, 100)[:, None]\n  Y = 0.8 * X**2 + 0.1 + np.random.normal(0, 0.1, size=X.shape)\n\n  if mode == 'nnx' or mode == 'all':\n    model = MLP(din=1, dhidden=width, dout=1, depth=depth, rngs=nnx.Rngs(0))\n    tx = optax.sgd(1e-3)\n    optimizer = nnx.Optimizer(model, tx, wrt=nnx.Param)\n    t0 = time()\n\n    @nnx.jit(donate_argnums=(0, 1))\n    def train_step_nnx(model: MLP, optimizer: nnx.Optimizer, batch):\n      x, y = batch\n\n      def loss_fn(model: MLP):\n        y_pred = model(x)\n        return jnp.mean((y - y_pred) ** 2)\n\n      grads: nnx.State = nnx.grad(loss_fn)(model)\n      optimizer.update(model, grads)\n\n    @nnx.jit(donate_argnums=0)\n    def test_step_nnx(model: MLP, batch):\n      x, y = batch\n      y_pred = model(x)\n      loss = jnp.mean((y - y_pred) ** 2)\n      return {'loss': loss}\n\n    for step, batch in enumerate(dataset(X, Y, batch_size)):\n      train_step_nnx(model, optimizer, batch)\n\n      if step % 1000 == 0:\n        logs = test_step_nnx(model, (X, Y))\n\n      if step >= total_steps - 1:\n        break\n\n    print('### NNX ###')\n    print(f'final loss: {logs[\"loss\"]}')\n    total_time = time() - t0\n    print('total time:', total_time)\n    print(f'time per step: {total_time / total_steps * 1e6:.2f} µs')\n    print('times called:', model.count.value)\n\n  if mode == 'jax' or mode == 'all':\n    model = MLP(din=1, dhidden=width, dout=1, depth=depth, rngs=nnx.Rngs(0))\n    tx = optax.sgd(1e-3)\n    optimizer = nnx.Optimizer(model, tx, wrt=nnx.Param)\n    t0 = time()\n\n    @partial(jax.jit, donate_argnums=0)\n    def train_step_jax(state, batch):\n      model, optimizer = nnx.merge(graphdef, state)\n      x, y = batch\n\n      def loss_fn(model: MLP):\n        y_pred = model(x)\n        return jnp.mean((y - y_pred) ** 2)\n\n      grads = nnx.grad(loss_fn)(model)\n      optimizer.update(model,grads)\n\n      return nnx.state((model, optimizer))\n\n    @partial(jax.jit, donate_argnums=0)\n    def test_step_jax(state, batch):\n      model, optimizer = nnx.merge(graphdef, state)\n      x, y = batch\n      y_pred = model(x)\n      loss = jnp.mean((y - y_pred) ** 2)\n      state = nnx.state((model, optimizer))\n      return state, {'loss': loss}\n\n    graphdef, state = nnx.split((model, optimizer))\n\n    for step, batch in enumerate(dataset(X, Y, batch_size)):\n      state = train_step_jax(state, batch)\n\n      if step % 1000 == 0:\n        state, logs = test_step_jax(state, (X, Y))\n\n      if step >= total_steps - 1:\n        break\n\n    model, optimizer = nnx.merge(graphdef, state)\n\n    print('### JAX ###')\n    print(f'final loss: {logs[\"loss\"]}')\n    total_time = time() - t0\n    print('total time:', total_time)\n    print(f'time per step: {total_time / total_steps * 1e6:.2f} µs')\n    print('times called:', model.count.value)\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": "benchmarks/nnx_state_traversal.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Example profile command:\n#   python -m cProfile -o ~/tmp/overhead.prof benchmarks/nnx_graph_overhead.py --mode=nnx --depth=100 --total_steps=1000\n# View profile (need to install snakeviz):\n#   snakeviz ~/tmp/overhead.prof\n\nimport jax\nfrom time import time\n\nfrom flax import nnx\n\nfrom absl import flags\nfrom absl import app\n\nFLAGS = flags.FLAGS\nflags.DEFINE_integer('total_steps', 1000, 'Total number of training steps')\nflags.DEFINE_integer('width', 4, 'Width of each level')\nflags.DEFINE_integer('depth', 4, 'Depth of the model')\n\n\nclass NestedClass(nnx.Module):\n  def __init__(self, width, depth):\n    self.x = nnx.Variable(jax.numpy.ones((depth+1, )))\n    if depth > 0:\n      for i in range(width):\n        setattr(self, f'child{i}', NestedClass(width, depth-1))\n\n\ndef main(argv):\n  print(argv)\n  total_steps: int = FLAGS.total_steps\n  width: int = FLAGS.width\n  depth: int = FLAGS.depth\n\n\n  model = NestedClass(width, depth)\n  to_test = nnx.state(model)\n\n  print(f'{total_steps=}, {width=}')\n\n  #------------------------------------------------------------\n  # tree_flatten_with_path\n  #------------------------------------------------------------\n  t0 = time()\n  for _ in range(total_steps):\n    jax.tree_util.tree_flatten_with_path(to_test)\n\n  total_time = time() - t0\n  time_per_step = total_time / total_steps\n  time_per_layer = time_per_step / depth\n  print(\"### tree_flatten_with_path ###\")\n  print('total time:', total_time)\n  print(f'time per step: {time_per_step * 1e6:.2f} µs')\n  print(f'time per layer: {time_per_layer * 1e6:.2f} µs')\n\n\n  #------------------------------------------------------------\n  # tree_map_with_path\n  #------------------------------------------------------------\n\n  t0 = time()\n  for _ in range(total_steps):\n    jax.tree_util.tree_map_with_path(lambda _, x: x, to_test)\n\n  total_time = time() - t0\n  time_per_step = total_time / total_steps\n  time_per_layer = time_per_step / depth\n  print(\"### tree_map_with_path ###\")\n  print('total time:', total_time)\n  print(f'time per step: {time_per_step * 1e6:.2f} µs')\n  print(f'time per layer: {time_per_layer * 1e6:.2f} µs')\n\n\n  #------------------------------------------------------------\n  # tree_flatten\n  #------------------------------------------------------------\n\n  t0 = time()\n  for _ in range(total_steps):\n    jax.tree_util.tree_flatten(to_test)\n\n  total_time = time() - t0\n  time_per_step = total_time / total_steps\n  time_per_layer = time_per_step / depth\n  print(\"### tree_flatten ###\")\n  print('total time:', total_time)\n  print(f'time per step: {time_per_step * 1e6:.2f} µs')\n  print(f'time per layer: {time_per_layer * 1e6:.2f} µs')\n\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": "benchmarks/tracing/README.md",
    "content": "# Tracing and lowering benchmarks for Flax examples\n\nSee Flax\n[documentation](https://flax.readthedocs.io/en/latest/examples/index.html) on\ntheir examples.\n\n## Getting started\nbash\n```\npip install -r benchmarks/tracing/requirements.txt\n\n# Benchmark trace and lower timing for all workloads.\npython tracing_benchmark.py\n\n# Profile a single example.\npython tracing_benchmark.py --example=wmt\n\n# Profile just tracing for a single example.\npython tracing_benchmark.py --example=wmt --mode=trace\n```"
  },
  {
    "path": "benchmarks/tracing/__init__.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "benchmarks/tracing/gemma.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Gemma helper functions.\"\"\"\n\nfrom typing import Any\n\nfrom flax import nnx\nfrom flax.benchmarks.tracing import tracing_benchmark\nfrom flax.examples.gemma import transformer as transformer_lib\nfrom flax.examples.gemma import utils\nfrom flax.examples.gemma.configs import default as gemma_config\nfrom flax.training import common_utils\nimport google_benchmark\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\nimport numpy as np\nimport optax\n\n\ndef rsqrt_schedule(init_value: float, shift: int = 0):\n  def schedule(count):\n    return init_value * (count + shift) ** -0.5 * shift**0.5\n  return schedule\n\n\ndef create_learning_rate_schedule(learning_rate: float, warmup_steps: int):\n  return optax.join_schedules(\n      [\n          optax.linear_schedule(\n              init_value=0,\n              end_value=learning_rate,\n              transition_steps=warmup_steps,\n          ),\n          rsqrt_schedule(init_value=learning_rate, shift=warmup_steps),\n      ],\n      boundaries=[warmup_steps],\n  )\n\n\ndef compute_weighted_cross_entropy(\n    logits, targets, weights=None, label_smoothing=0.0\n):\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        'Incorrect shapes. Got shape %s logits and %s targets'\n        % (str(logits.shape), str(targets.shape))\n    )\n  vocab_size = logits.shape[-1]\n  confidence = 1.0 - label_smoothing\n  low_confidence = (1.0 - confidence) / (vocab_size - 1)\n  normalizing_constant = -(\n      confidence * jnp.log(confidence)\n      + (vocab_size - 1) * low_confidence * jnp.log(low_confidence + 1e-20)\n  )\n  soft_targets = common_utils.onehot(\n      targets, vocab_size, on_value=confidence, off_value=low_confidence\n  )\n\n  loss = -jnp.sum(soft_targets * nnx.log_softmax(logits), axis=-1)\n  loss = loss - normalizing_constant\n\n  normalizing_factor = np.prod(targets.shape)\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_weighted_accuracy(logits, targets, weights=None):\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        'Incorrect shapes. Got shape %s logits and %s targets'\n        % (str(logits.shape), str(targets.shape))\n    )\n  loss = jnp.equal(jnp.argmax(logits, axis=-1), targets)\n  normalizing_factor = np.prod(logits.shape[:-1])\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_metrics(logits, labels, weights, label_smoothing=0.0):\n  loss, weight_sum = compute_weighted_cross_entropy(\n      logits, labels, weights, label_smoothing\n  )\n  acc, _ = compute_weighted_accuracy(logits, labels, weights)\n  metrics = {\n      'loss': loss,\n      'accuracy': acc,\n      'denominator': weight_sum,\n  }\n  return metrics\n\n\ndef train_step(\n    state: utils.TrainState,\n    batch,\n    learning_rate_fn,\n    label_smoothing=0.0,\n):\n  train_keys = ['inputs', 'inputs_position', 'inputs_segmentation', 'targets']\n  (inputs, inputs_positions, inputs_segmentation, targets) = (\n      batch.get(k, None) for k in train_keys\n  )\n\n  pad_id = 0\n  weights = jnp.where(inputs > pad_id, 1, 0).astype(jnp.float32)\n  input_mask = inputs > pad_id\n  attention_mask = transformer_lib.make_causal_attn_mask(input_mask)\n  mask = (\n      inputs_segmentation[:, :, None] == inputs_segmentation[:, None, :]\n  )\n  attention_mask = jnp.logical_and(mask, attention_mask)\n\n  def loss_fn(params):\n    module = nnx.merge(state.graphdef, params)\n\n    logits, _ = module(\n        inputs,\n        positions=inputs_positions,\n        attention_mask=attention_mask,\n        cache=None,\n    )\n\n    loss, weight_sum = compute_weighted_cross_entropy(\n        logits, targets, weights, label_smoothing\n    )\n    mean_loss = loss / weight_sum\n    return mean_loss, logits\n\n  step = state.step\n  lr = learning_rate_fn(step)\n  grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n  (_, logits), grads = grad_fn(state.params)\n  new_state = state.apply_gradients(grads=grads)\n  metrics = compute_metrics(logits, targets, weights)\n  metrics['learning_rate'] = lr\n\n  return new_state, metrics\n\n\ndef get_fake_batch(batch_size: int) -> Any:\n  rng = jax.random.PRNGKey(0)\n  batch = {}\n  for k in (\n      'inputs',\n      'inputs_position',\n      'inputs_segmentation',\n      'targets',\n      'targets_position',\n      'targets_segmentation',\n  ):\n    batch[k] = jax.random.randint(rng, (batch_size, 128), 0, 9999999, jnp.int32)\n  return batch\n\n\ndef get_apply_fn_and_args(\n    config: ml_collections.ConfigDict,\n    vocab_size: int | None = None,\n) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:\n  if vocab_size is None:\n    vocab_size = config.vocab_size\n\n  if config.transformer_name is not None:\n    model_config = transformer_lib.TransformerConfig.from_version_name(\n        config.transformer_name,\n        num_embed=vocab_size,\n        dtype=jnp.bfloat16 if config.use_bfloat16 else jnp.float32,\n        axis_rules=config.axis_rules,\n    )\n  else:\n    assert config.transformer_params is not None\n    model_config = transformer_lib.TransformerConfig.from_dict(\n        **config.transformer_params,\n        num_embed=vocab_size,\n        dtype=jnp.bfloat16 if config.use_bfloat16 else jnp.float32,\n        axis_rules=config.axis_rules,\n    )\n\n  devices_array = utils.create_device_mesh(config)\n  mesh = jax.sharding.Mesh(devices_array, config.mesh_axes)\n\n  rng = jax.random.PRNGKey(config.seed)\n  rng, init_rng = jax.random.split(rng)\n\n  def constructor(config: transformer_lib.TransformerConfig, key: jax.Array):\n    return transformer_lib.Transformer(config, rngs=nnx.Rngs(params=key))\n\n  learning_rate_fn = create_learning_rate_schedule(\n      learning_rate=config.learning_rate, warmup_steps=config.warmup_steps\n  )\n\n  optimizer = optax.adamw(\n      learning_rate_fn,\n      b1=0.9,\n      b2=0.98,\n      eps=1e-9,\n      weight_decay=config.weight_decay,\n  )\n\n  state, state_sharding = utils.setup_initial_state(\n      constructor, optimizer, model_config, init_rng, mesh\n  )\n  data_sharding = jax.NamedSharding(mesh, jax.P(config.data_sharding))\n  jit_train_step = jax.jit(\n      train_step,\n      in_shardings=(\n          state_sharding,\n          data_sharding,\n      ),  # type: ignore\n      out_shardings=(state_sharding, None),  # type: ignore\n      static_argnames=('learning_rate_fn', 'label_smoothing'),\n      donate_argnums=0,\n  )\n\n  batch = get_fake_batch(config.per_device_batch_size)\n  batch = jax.tree.map(lambda x: jnp.asarray(x, device=data_sharding), batch)\n\n  return (\n      jit_train_step,\n      (state, batch, learning_rate_fn, 0.0),\n      dict(),\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_gemma_trace(state):\n  tracing_benchmark.benchmark_tracing(\n      get_apply_fn_and_args, gemma_config.get_config, state\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_gemma_lower(state):\n  tracing_benchmark.benchmark_lowering(\n      get_apply_fn_and_args, gemma_config.get_config, state\n  )\n\n\nif __name__ == '__main__':\n  tracing_benchmark.run_benchmarks()\n"
  },
  {
    "path": "benchmarks/tracing/imagenet.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"ImageNet helper functions for benchmarking.\"\"\"\n\nimport functools\nfrom typing import Any\n\nfrom flax import linen as nn\nfrom flax.benchmarks.tracing import tracing_benchmark\nfrom flax.examples.imagenet import models\nfrom flax.examples.imagenet.configs import default as imagenet_config\nfrom flax.training import common_utils\nfrom flax.training import dynamic_scale as dynamic_scale_lib\nfrom flax.training import train_state\nimport google_benchmark\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\nimport optax\n\nNUM_CLASSES = 1000\n\n\nclass TrainState(train_state.TrainState):\n  batch_stats: Any\n  dynamic_scale: dynamic_scale_lib.DynamicScale\n\n\ndef create_model(*, model_cls, half_precision, **kwargs):\n  platform = jax.local_devices()[0].platform\n  if half_precision:\n    if platform == 'tpu':\n      model_dtype = jnp.bfloat16\n    else:\n      model_dtype = jnp.float16\n  else:\n    model_dtype = jnp.float32\n  return model_cls(num_classes=NUM_CLASSES, dtype=model_dtype, **kwargs)\n\n\ndef initialized(key, image_size, model):\n  input_shape = (1, image_size, image_size, 3)\n\n  @jax.jit\n  def init(*args):\n    return model.init(*args)\n\n  variables = init({'params': key}, jnp.ones(input_shape, model.dtype))\n  return variables['params'], variables['batch_stats']\n\n\ndef cross_entropy_loss(logits, labels):\n  one_hot_labels = common_utils.onehot(labels, num_classes=NUM_CLASSES)\n  xentropy = optax.softmax_cross_entropy(logits=logits, labels=one_hot_labels)\n  return jnp.mean(xentropy)\n\n\ndef create_train_state(\n    rng, config: ml_collections.ConfigDict, model, image_size, learning_rate_fn\n):\n  dynamic_scale = None\n  platform = jax.local_devices()[0].platform\n  if config.half_precision and platform == 'gpu':\n    dynamic_scale = dynamic_scale_lib.DynamicScale()\n\n  params, batch_stats = initialized(rng, image_size, model)\n  tx = optax.sgd(\n      learning_rate=learning_rate_fn,\n      momentum=config.momentum,\n      nesterov=True,\n  )\n  state = TrainState.create(\n      apply_fn=model.apply,\n      params=params,\n      tx=tx,\n      batch_stats=batch_stats,\n      dynamic_scale=dynamic_scale,\n  )\n  return state\n\n\ndef get_fake_batch(batch_size: int = 128) -> dict[str, jnp.ndarray]:\n  images = jax.random.uniform(\n      jax.random.key(0), (batch_size, 224, 224, 3), dtype=jnp.float32\n  )\n  labels = jax.random.randint(\n      jax.random.key(1), (batch_size,), minval=0, maxval=1000, dtype=jnp.int32\n  )\n  return {'image': images, 'label': labels}\n\n\nclass BenchmarkResNet(models.ResNet):\n\n  @nn.compact\n  def __call__(self, x, train: bool = True):\n    conv = functools.partial(self.conv, use_bias=False, dtype=self.dtype)\n    norm = functools.partial(\n        nn.BatchNorm,\n        use_running_average=not train,\n        momentum=0.9,\n        epsilon=1e-5,\n        dtype=self.dtype,\n        axis_name=None,\n    )\n\n    x = conv(\n        self.num_filters,\n        (7, 7),\n        (2, 2),\n        padding=[(3, 3), (3, 3)],\n        name='conv_init',\n    )(x)\n    x = norm(name='bn_init')(x)\n    x = nn.relu(x)\n    x = nn.max_pool(x, (3, 3), strides=(2, 2), padding='SAME')\n    for i, block_size in enumerate(self.stage_sizes):\n      for j in range(block_size):\n        strides = (2, 2) if i > 0 and j == 0 else (1, 1)\n        x = self.block_cls(\n            self.num_filters * 2**i,\n            strides=strides,\n            conv=conv,\n            norm=norm,\n            act=self.act,\n        )(x)\n    x = jnp.mean(x, axis=(1, 2))\n    x = nn.Dense(self.num_classes, dtype=self.dtype)(x)\n    x = jnp.asarray(x, self.dtype)\n    return x\n\n\ndef get_apply_fn_and_args(\n    config: ml_collections.ConfigDict,\n) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:\n  if config.model == 'ResNet50':\n    model_cls = functools.partial(\n        BenchmarkResNet,\n        stage_sizes=[3, 4, 6, 3],\n        block_cls=models.BottleneckResNetBlock,\n    )\n  else:\n    model_cls = getattr(models, config.model)\n\n  model = create_model(\n      model_cls=model_cls, half_precision=config.half_precision\n  )\n\n  learning_rate_fn = lambda step: 0.1\n\n  rng = jax.random.key(0)\n  image_size = 224\n  state = create_train_state(\n      rng, config, model, image_size, learning_rate_fn\n  )\n\n  batch = get_fake_batch(config.batch_size)\n\n  return (\n      bench_train_step,\n      (state, batch, learning_rate_fn),\n      {},\n  )\n\n\n@functools.partial(jax.jit, static_argnums=(2,))\ndef bench_train_step(state, batch, learning_rate_fn):\n\n  def compute_metrics(logits, labels):\n    loss = cross_entropy_loss(logits, labels)\n    accuracy = jnp.mean(jnp.argmax(logits, -1) == labels)\n    metrics = {\n        'loss': loss,\n        'accuracy': accuracy,\n    }\n    return metrics\n\n  def loss_fn(params):\n    logits, new_model_state = state.apply_fn(\n        {'params': params, 'batch_stats': state.batch_stats},\n        batch['image'],\n        mutable=['batch_stats'],\n    )\n    loss = cross_entropy_loss(logits, batch['label'])\n    weight_penalty_params = jax.tree_util.tree_leaves(params)\n    weight_decay = 0.0001\n    weight_l2 = sum(jnp.sum(x**2) for x in weight_penalty_params if x.ndim > 1)\n    weight_penalty = weight_decay * 0.5 * weight_l2\n    loss = loss + weight_penalty\n    return loss, (new_model_state, logits)\n\n  step = state.step\n  dynamic_scale = state.dynamic_scale\n  lr = learning_rate_fn(step)\n\n  if dynamic_scale:\n    grad_fn = dynamic_scale.value_and_grad(loss_fn, has_aux=True)\n    dynamic_scale, is_fin, aux, grads = grad_fn(state.params)\n  else:\n    grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n    aux, grads = grad_fn(state.params)\n\n  new_model_state, logits = aux[1]\n  metrics = compute_metrics(logits, batch['label'])\n  metrics['learning_rate'] = lr\n\n  new_state = state.apply_gradients(\n      grads=grads,\n      batch_stats=new_model_state['batch_stats'],\n  )\n  if dynamic_scale:\n    new_state = new_state.replace(\n        opt_state=jax.tree_util.tree_map(\n            functools.partial(jnp.where, is_fin),\n            new_state.opt_state,\n            state.opt_state,\n        ),\n        params=jax.tree_util.tree_map(\n            functools.partial(jnp.where, is_fin), new_state.params, state.params\n        ),\n        dynamic_scale=dynamic_scale,\n    )\n    metrics['scale'] = dynamic_scale.scale\n\n  return new_state, metrics\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_imagenet_trace(state):\n  tracing_benchmark.benchmark_tracing(\n      get_apply_fn_and_args, imagenet_config.get_config, state\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_imagenet_lower(state):\n  tracing_benchmark.benchmark_lowering(\n      get_apply_fn_and_args, imagenet_config.get_config, state\n  )\n\n\nif __name__ == '__main__':\n  tracing_benchmark.run_benchmarks()\n"
  },
  {
    "path": "benchmarks/tracing/lm1b.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"LM1B helper functions for benchmarking.\"\"\"\n\nimport functools\nfrom typing import Any\n\nfrom flax import linen as nn\nfrom flax.benchmarks.tracing import tracing_benchmark\nfrom flax.examples.lm1b import models\nfrom flax.examples.lm1b.configs import default as lm1b_config\nfrom flax.training import common_utils\nfrom flax.training import train_state\nimport google_benchmark\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\nimport numpy as np\nimport optax\n\n\ndef rsqrt_schedule(init_value: float, shift: int = 0):\n  def schedule(count):\n    return init_value * (count + shift) ** -0.5 * shift**0.5\n  return schedule\n\n\ndef create_learning_rate_schedule(learning_rate: float, warmup_steps: int):\n  return optax.join_schedules(\n      [\n          optax.linear_schedule(\n              init_value=0,\n              end_value=learning_rate,\n              transition_steps=warmup_steps,\n          ),\n          rsqrt_schedule(init_value=learning_rate, shift=warmup_steps),\n      ],\n      boundaries=[warmup_steps],\n  )\n\n\ndef compute_weighted_cross_entropy(\n    logits, targets, weights=None, label_smoothing=0.0\n):\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        'Incorrect shapes. Got shape %s logits and %s targets'\n        % (str(logits.shape), str(targets.shape))\n    )\n  vocab_size = logits.shape[-1]\n  confidence = 1.0 - label_smoothing\n  low_confidence = (1.0 - confidence) / (vocab_size - 1)\n  normalizing_constant = -(\n      confidence * jnp.log(confidence)\n      + (vocab_size - 1) * low_confidence * jnp.log(low_confidence + 1e-20)\n  )\n  soft_targets = common_utils.onehot(\n      targets, vocab_size, on_value=confidence, off_value=low_confidence\n  )\n\n  loss = -jnp.sum(soft_targets * nn.log_softmax(logits), axis=-1)\n  loss = loss - normalizing_constant\n\n  normalizing_factor = np.prod(targets.shape)\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_weighted_accuracy(logits, targets, weights=None):\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        'Incorrect shapes. Got shape %s logits and %s targets'\n        % (str(logits.shape), str(targets.shape))\n    )\n  loss = jnp.equal(jnp.argmax(logits, axis=-1), targets)\n  normalizing_factor = np.prod(logits.shape[:-1])\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef get_fake_batch(config: ml_collections.ConfigDict) -> dict[str, jnp.ndarray]:\n  batch_size = config.per_device_batch_size\n  max_len = config.max_target_length\n\n  inputs = jax.random.randint(\n      jax.random.key(0),\n      (batch_size, max_len),\n      minval=0,\n      maxval=config.vocab_size,\n      dtype=jnp.int32,\n  )\n  inputs_position = jnp.tile(\n      jnp.arange(max_len, dtype=jnp.int32), (batch_size, 1)\n  )\n  inputs_segmentation = jnp.ones((batch_size, max_len), dtype=jnp.int32)\n\n  return {\n      'inputs': inputs,\n      'inputs_position': inputs_position,\n      'inputs_segmentation': inputs_segmentation,\n  }\n\n\n@functools.partial(jax.jit, static_argnums=(2, 3))\ndef bench_train_step(state, batch, config, learning_rate_fn):\n\n  def compute_metrics(logits, labels, weights):\n    loss, weight_sum = compute_weighted_cross_entropy(\n        logits, labels, weights, 0.0\n    )\n    acc, _ = compute_weighted_accuracy(logits, labels, weights)\n    metrics = {\n        'loss': loss,\n        'accuracy': acc,\n        'denominator': weight_sum,\n    }\n    return metrics\n\n  inputs = batch['inputs']\n  inputs_positions = batch['inputs_position']\n  inputs_segmentation = batch['inputs_segmentation']\n\n  weights = jnp.where(inputs > 0, 1, 0).astype(jnp.float32)\n  dropout_rng = jax.random.fold_in(jax.random.key(0), state.step)\n\n  def loss_fn(params):\n    logits = models.TransformerLM(config).apply(\n        {'params': params},\n        inputs,\n        inputs_positions=inputs_positions,\n        inputs_segmentation=inputs_segmentation,\n        rngs={'dropout': dropout_rng},\n    )\n\n    loss, weight_sum = compute_weighted_cross_entropy(\n        logits, inputs, weights, 0.0\n    )\n    mean_loss = loss / weight_sum\n    return mean_loss, logits\n\n  step = state.step\n  lr = learning_rate_fn(step)\n  grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n  (_, logits), grads = grad_fn(state.params)\n  new_state = state.apply_gradients(grads=grads)\n\n  metrics = compute_metrics(logits, inputs, weights)\n  metrics['learning_rate'] = lr\n\n  return new_state, metrics\n\n\ndef get_apply_fn_and_args(\n    config: ml_collections.ConfigDict,\n) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:\n  train_config = models.TransformerConfig(\n      vocab_size=config.vocab_size,\n      output_vocab_size=config.vocab_size,\n      logits_via_embedding=config.logits_via_embedding,\n      dtype=jnp.bfloat16 if config.use_bfloat16 else jnp.float32,\n      emb_dim=config.emb_dim,\n      num_heads=config.num_heads,\n      num_layers=config.num_layers,\n      qkv_dim=config.qkv_dim,\n      mlp_dim=config.mlp_dim,\n      max_len=max(config.max_target_length, config.max_eval_target_length),\n      dropout_rate=config.dropout_rate,\n      attention_dropout_rate=config.attention_dropout_rate,\n      deterministic=False,\n      decode=False,\n      kernel_init=jax.nn.initializers.xavier_uniform(),\n      bias_init=jax.nn.initializers.normal(stddev=1e-6),\n  )\n\n  model = models.TransformerLM(train_config)\n\n  learning_rate_fn = create_learning_rate_schedule(\n      learning_rate=config.learning_rate, warmup_steps=config.warmup_steps\n  )\n\n  optimizer = optax.adamw(\n      learning_rate_fn,\n      b1=0.9,\n      b2=0.98,\n      eps=1e-9,\n      weight_decay=config.weight_decay,\n  )\n\n  rng = jax.random.key(0)\n  init_rng, _ = jax.random.split(rng)\n\n  initial_variables = model.init(\n      init_rng,\n      jnp.ones(\n          (config.per_device_batch_size, config.max_target_length), jnp.int32\n      ),\n      jnp.ones(\n          (config.per_device_batch_size, config.max_target_length), jnp.int32\n      ),\n      jnp.ones(\n          (config.per_device_batch_size, config.max_target_length), jnp.int32\n      ),\n  )\n\n  state = train_state.TrainState.create(\n      apply_fn=model.apply,\n      params=initial_variables['params'],\n      tx=optimizer,\n  )\n\n  batch = get_fake_batch(config)\n\n  return (\n      bench_train_step,\n      (state, batch, train_config, learning_rate_fn),\n      {},\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_lm1b_trace(state):\n  tracing_benchmark.benchmark_tracing(\n      get_apply_fn_and_args, lm1b_config.get_config, state\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_lm1b_lower(state):\n  tracing_benchmark.benchmark_lowering(\n      get_apply_fn_and_args, lm1b_config.get_config, state\n  )\n\n\nif __name__ == '__main__':\n  tracing_benchmark.run_benchmarks()\n"
  },
  {
    "path": "benchmarks/tracing/mnist.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"MNIST helper functions.\"\"\"\n\nfrom functools import partial\nfrom typing import Any\n\nfrom flax import nnx\nfrom flax.benchmarks.tracing import tracing_benchmark\nfrom flax.examples.mnist.configs import default as mnist_config\nimport google_benchmark\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\nimport optax\n\n\nclass CNN(nnx.Module):\n\n  def __init__(self, rngs: nnx.Rngs):\n    self.conv1 = nnx.Conv(1, 32, kernel_size=(3, 3), rngs=rngs)\n    self.batch_norm1 = nnx.BatchNorm(32, rngs=rngs)\n    self.dropout1 = nnx.Dropout(rate=0.025)\n    self.conv2 = nnx.Conv(32, 64, kernel_size=(3, 3), rngs=rngs)\n    self.batch_norm2 = nnx.BatchNorm(64, rngs=rngs)\n    self.avg_pool = partial(nnx.avg_pool, window_shape=(2, 2), strides=(2, 2))\n    self.linear1 = nnx.Linear(3136, 256, rngs=rngs)\n    self.dropout2 = nnx.Dropout(rate=0.025)\n    self.linear2 = nnx.Linear(256, 10, rngs=rngs)\n\n  def __call__(self, x, rngs: nnx.Rngs):\n    x = self.avg_pool(nnx.relu(self.batch_norm1(self.dropout1(self.conv1(x), rngs=rngs))))\n    x = self.avg_pool(nnx.relu(self.batch_norm2(self.conv2(x))))\n    x = x.reshape(x.shape[0], -1)  # flatten\n    x = nnx.relu(self.dropout2(self.linear1(x), rngs=rngs))\n    x = self.linear2(x)\n    return x\n\n\ndef loss_fn(model: CNN, batch, rngs):\n  logits = model(batch['image'], rngs)\n  loss = optax.softmax_cross_entropy_with_integer_labels(\n    logits=logits, labels=batch['label']\n  ).mean()\n  return loss, logits\n\n\ndef get_fake_batch(batch_size: int) -> dict[str, Any]:\n  rng = jax.random.PRNGKey(0)\n  images = jax.random.normal(rng, (batch_size, 28, 28, 1), jnp.float32)\n  labels = jax.random.randint(rng, (batch_size,), 0, 10, jnp.int32)\n  return {'image': images, 'label': labels}\n\n\ndef get_apply_fn_and_args(\n    config: ml_collections.ConfigDict,\n) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:\n  model = CNN(rngs=nnx.Rngs(0))\n  batch = get_fake_batch(config.batch_size)\n  rngs = nnx.Rngs(0)\n  loss_fn_jit = jax.jit(loss_fn)\n  return (\n      loss_fn_jit,\n      (model, batch, rngs),\n      dict(),\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_mnist_trace(state):\n  tracing_benchmark.benchmark_tracing(\n      get_apply_fn_and_args, mnist_config.get_config, state\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_mnist_lower(state):\n  tracing_benchmark.benchmark_lowering(\n      get_apply_fn_and_args, mnist_config.get_config, state\n  )\n\n\nif __name__ == '__main__':\n  tracing_benchmark.run_benchmarks()\n"
  },
  {
    "path": "benchmarks/tracing/nlp_seq.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"NLP Sequence Tagging helper functions for benchmarking.\"\"\"\n\nimport functools\nfrom typing import Any\n\nfrom flax import linen as nn\nfrom flax.benchmarks.tracing import tracing_benchmark\nfrom flax.examples.nlp_seq import models\nfrom flax.examples.nlp_seq.configs import default as nlp_seq_config\nfrom flax.training import common_utils\nfrom flax.training import train_state\nimport google_benchmark\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\nimport numpy as np\nimport optax\n\n\ndef create_learning_rate_scheduler(\n    factors='constant * linear_warmup * rsqrt_decay',\n    base_learning_rate=0.5,\n    warmup_steps=8000,\n    decay_factor=0.5,\n    steps_per_decay=20000,\n    steps_per_cycle=100000,\n):\n  factors = [n.strip() for n in factors.split('*')]\n\n  def step_fn(step):\n    ret = 1.0\n    for name in factors:\n      if name == 'constant':\n        ret *= base_learning_rate\n      elif name == 'linear_warmup':\n        ret *= jnp.minimum(1.0, step / warmup_steps)\n      elif name == 'rsqrt_decay':\n        ret /= jnp.sqrt(jnp.maximum(step, warmup_steps))\n      elif name == 'rsqrt_normalized_decay':\n        ret *= jnp.sqrt(warmup_steps)\n        ret /= jnp.sqrt(jnp.maximum(step, warmup_steps))\n      elif name == 'decay_every':\n        ret *= decay_factor ** (step // steps_per_decay)\n      elif name == 'cosine_decay':\n        progress = jnp.maximum(\n            0.0, (step - warmup_steps) / float(steps_per_cycle)\n        )\n        ret *= jnp.maximum(\n            0.0, 0.5 * (1.0 + jnp.cos(jnp.pi * (progress % 1.0)))\n        )\n      else:\n        raise ValueError('Unknown factor %s.' % name)\n    return jnp.asarray(ret, dtype=jnp.float32)\n\n  return step_fn\n\n\ndef compute_weighted_cross_entropy(logits, targets, weights=None):\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        'Incorrect shapes. Got shape %s logits and %s targets'\n        % (str(logits.shape), str(targets.shape))\n    )\n  onehot_targets = common_utils.onehot(targets, logits.shape[-1])\n  loss = -jnp.sum(onehot_targets * nn.log_softmax(logits), axis=-1)\n  normalizing_factor = onehot_targets.sum()\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_weighted_accuracy(logits, targets, weights=None):\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        'Incorrect shapes. Got shape %s logits and %s targets'\n        % (str(logits.shape), str(targets.shape))\n    )\n  loss = jnp.equal(jnp.argmax(logits, axis=-1), targets)\n  normalizing_factor = np.prod(logits.shape[:-1])\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef get_fake_batch(config: ml_collections.ConfigDict) -> dict[str, jnp.ndarray]:\n  batch_size = config.batch_size\n  max_len = config.max_length\n  vocab_size = config.vocab_size\n\n  inputs = jax.random.randint(\n      jax.random.key(0),\n      (batch_size, max_len),\n      minval=0,\n      maxval=vocab_size,\n      dtype=jnp.int32,\n  )\n  targets = jax.random.randint(\n      jax.random.key(1),\n      (batch_size, max_len),\n      minval=0,\n      maxval=config.output_vocab_size,\n      dtype=jnp.int32,\n  )\n\n  return {\n      'inputs': inputs,\n      'targets': targets,\n  }\n\n\n@functools.partial(jax.jit, static_argnums=(2, 3))\ndef bench_train_step(state, batch, config, learning_rate_fn):\n\n  def local_compute_metrics(logits, labels, weights):\n    loss, weight_sum = compute_weighted_cross_entropy(\n        logits, labels, weights\n    )\n    acc, _ = compute_weighted_accuracy(logits, labels, weights)\n    metrics = {\n        'loss': loss,\n        'accuracy': acc,\n        'denominator': weight_sum,\n    }\n    return metrics\n\n  inputs = batch['inputs']\n  targets = batch['targets']\n\n  weights = jnp.where(targets > 0, 1, 0).astype(jnp.float32)\n  dropout_rng = jax.random.fold_in(jax.random.key(0), state.step)\n\n  def loss_fn(params):\n    model = models.Transformer(config)\n    logits = model.apply(\n        {'params': params},\n        inputs=inputs,\n        train=True,\n        rngs={'dropout': dropout_rng},\n    )\n\n    loss, weight_sum = compute_weighted_cross_entropy(\n        logits, targets, weights\n    )\n    mean_loss = loss / weight_sum\n    return mean_loss, logits\n\n  step = state.step\n  lr = learning_rate_fn(step)\n  grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n  (_, logits), grads = grad_fn(state.params)\n  new_state = state.apply_gradients(grads=grads)\n\n  metrics = local_compute_metrics(logits, targets, weights)\n  metrics['learning_rate'] = lr\n\n  return new_state, metrics\n\n\ndef get_apply_fn_and_args(\n    config: ml_collections.ConfigDict,\n) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:\n  if not hasattr(config, 'vocab_size'):\n    config.vocab_size = 30000\n  if not hasattr(config, 'output_vocab_size'):\n    config.output_vocab_size = 50\n\n  model_config = models.TransformerConfig(\n      vocab_size=config.vocab_size,\n      output_vocab_size=config.output_vocab_size,\n      max_len=config.max_length,\n  )\n\n  model = models.Transformer(model_config)\n\n  learning_rate_fn = create_learning_rate_scheduler(\n      base_learning_rate=config.learning_rate\n  )\n\n  optimizer = optax.adamw(\n      learning_rate_fn,\n      b1=0.9,\n      b2=0.98,\n      eps=1e-9,\n      weight_decay=config.weight_decay,\n  )\n\n  rng = jax.random.key(0)\n  init_rng, _ = jax.random.split(rng)\n\n  init_batch = jnp.ones((config.batch_size, config.max_length), jnp.int32)\n  initial_variables = model.init(init_rng, inputs=init_batch, train=False)\n\n  state = train_state.TrainState.create(\n      apply_fn=model.apply,\n      params=initial_variables['params'],\n      tx=optimizer,\n  )\n\n  batch = get_fake_batch(config)\n\n  return (\n      bench_train_step,\n      (state, batch, model_config, learning_rate_fn),\n      {},\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_nlp_seq_trace(state):\n  tracing_benchmark.benchmark_tracing(\n      get_apply_fn_and_args, nlp_seq_config.get_config, state\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_nlp_seq_lower(state):\n  tracing_benchmark.benchmark_lowering(\n      get_apply_fn_and_args, nlp_seq_config.get_config, state\n  )\n\n\nif __name__ == '__main__':\n  tracing_benchmark.run_benchmarks()\n"
  },
  {
    "path": "benchmarks/tracing/ogbg_molpcba.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"OGBG-MolPCBA helper functions for benchmarking.\"\"\"\n\nfrom typing import Any\n\nfrom clu import metrics\nimport flax\nimport flax.linen as nn\nfrom flax.benchmarks.tracing import tracing_benchmark\nfrom flax.examples.ogbg_molpcba import models\nfrom flax.examples.ogbg_molpcba.configs import default as ogbg_config\nfrom flax.training import train_state\nimport google_benchmark\nimport jax\nimport jax.numpy as jnp\nimport jraph\nimport ml_collections\nimport optax\n\n\ndef create_model(\n    config: ml_collections.ConfigDict, deterministic: bool\n) -> nn.Module:\n  if config.model == 'GraphNet':\n    return models.GraphNet(\n        latent_size=config.latent_size,\n        num_mlp_layers=config.num_mlp_layers,\n        message_passing_steps=config.message_passing_steps,\n        output_globals_size=config.num_classes,\n        dropout_rate=config.dropout_rate,\n        skip_connections=config.skip_connections,\n        layer_norm=config.layer_norm,\n        use_edge_model=config.use_edge_model,\n        deterministic=deterministic,\n    )\n  if config.model == 'GraphConvNet':\n    return models.GraphConvNet(\n        latent_size=config.latent_size,\n        num_mlp_layers=config.num_mlp_layers,\n        message_passing_steps=config.message_passing_steps,\n        output_globals_size=config.num_classes,\n        dropout_rate=config.dropout_rate,\n        skip_connections=config.skip_connections,\n        layer_norm=config.layer_norm,\n        deterministic=deterministic,\n    )\n  raise ValueError(f'Unsupported model: {config.model}.')\n\n\ndef create_optimizer(\n    config: ml_collections.ConfigDict,\n) -> optax.GradientTransformation:\n  if config.optimizer == 'adam':\n    return optax.adam(learning_rate=config.learning_rate)\n  if config.optimizer == 'sgd':\n    return optax.sgd(\n        learning_rate=config.learning_rate, momentum=config.momentum\n    )\n  raise ValueError(f'Unsupported optimizer: {config.optimizer}.')\n\n\ndef binary_cross_entropy_with_mask(\n    *, logits: jnp.ndarray, labels: jnp.ndarray, mask: jnp.ndarray\n):\n  assert logits.shape == labels.shape == mask.shape\n  assert len(logits.shape) == 2\n\n  labels = jnp.where(mask, labels, -1)\n\n  positive_logits = logits >= 0\n  relu_logits = jnp.where(positive_logits, logits, 0)\n  abs_logits = jnp.where(positive_logits, logits, -logits)\n  return relu_logits - (logits * labels) + (jnp.log(1 + jnp.exp(-abs_logits)))\n\n\ndef predictions_match_labels(\n    *, logits: jnp.ndarray, labels: jnp.ndarray, **kwargs\n) -> jnp.ndarray:\n  del kwargs\n  preds = logits > 0\n  return (preds == labels).astype(jnp.float32)\n\n\ndef replace_globals(graphs: jraph.GraphsTuple) -> jraph.GraphsTuple:\n  return graphs._replace(globals=jnp.ones([graphs.n_node.shape[0], 1]))\n\n\ndef get_predicted_logits(\n    state: train_state.TrainState,\n    graphs: jraph.GraphsTuple,\n    rngs: dict[str, jnp.ndarray] | None,\n) -> jnp.ndarray:\n  pred_graphs = state.apply_fn(state.params, graphs, rngs=rngs)\n  logits = pred_graphs.globals\n  return logits\n\n\ndef get_valid_mask(\n    labels: jnp.ndarray, graphs: jraph.GraphsTuple\n) -> jnp.ndarray:\n  labels_mask = ~jnp.isnan(labels)\n  graph_mask = jraph.get_graph_padding_mask(graphs)\n  return labels_mask & graph_mask[:, None]\n\n\n@flax.struct.dataclass\nclass TrainMetrics(metrics.Collection):\n  accuracy: metrics.Average.from_fun(predictions_match_labels)\n  loss: metrics.Average.from_output('loss')\n\n\n@jax.jit\ndef ogbg_train_step(\n    state: train_state.TrainState,\n    graphs: jraph.GraphsTuple,\n    rngs: dict[str, jnp.ndarray],\n) -> tuple[train_state.TrainState, metrics.Collection]:\n\n  def loss_fn(params, graphs):\n    curr_state = state.replace(params=params)\n\n    labels = graphs.globals\n\n    graphs = replace_globals(graphs)\n\n    logits = get_predicted_logits(curr_state, graphs, rngs)\n    mask = get_valid_mask(labels, graphs)\n    loss = binary_cross_entropy_with_mask(\n        logits=logits, labels=labels, mask=mask\n    )\n    mean_loss = jnp.sum(jnp.where(mask, loss, 0)) / jnp.sum(mask)\n\n    return mean_loss, (loss, logits, labels, mask)\n\n  grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n  (_, (loss, logits, labels, mask)), grads = grad_fn(state.params, graphs)\n  state = state.apply_gradients(grads=grads)\n\n  metrics_update = TrainMetrics.single_from_model_output(\n      loss=loss, logits=logits, labels=labels, mask=mask\n  )\n  return state, metrics_update\n\n\ndef get_fake_graphs(config: ml_collections.ConfigDict) -> jraph.GraphsTuple:\n  rng = jax.random.key(0)\n  n_graphs = config.batch_size\n  n_total_nodes = n_graphs * 20\n  n_total_edges = n_graphs * 40\n\n  nodes = jax.random.normal(rng, (n_total_nodes, 9))\n  edges = jax.random.normal(rng, (n_total_edges, 3))\n  senders = jax.random.randint(rng, (n_total_edges,), 0, n_total_nodes)\n  receivers = jax.random.randint(rng, (n_total_edges,), 0, n_total_nodes)\n  n_node = jnp.full((n_graphs,), 20, dtype=jnp.int32)\n  n_edge = jnp.full((n_graphs,), 40, dtype=jnp.int32)\n  globals_ = jax.random.bernoulli(\n      rng, shape=(n_graphs, config.num_classes)\n  ).astype(jnp.float32)\n\n  return jraph.GraphsTuple(\n      nodes=nodes,\n      edges=edges,\n      senders=senders,\n      receivers=receivers,\n      n_node=n_node,\n      n_edge=n_edge,\n      globals=globals_,\n  )\n\n\ndef get_apply_fn_and_args(\n    config: ml_collections.ConfigDict,\n) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:\n  rng = jax.random.key(0)\n  rng, init_rng, dropout_rng = jax.random.split(rng, 3)\n\n  graphs = get_fake_graphs(config)\n\n  init_net = create_model(config, deterministic=True)\n  init_graphs = replace_globals(graphs)\n  params = jax.jit(init_net.init)(init_rng, init_graphs)\n\n  tx = create_optimizer(config)\n\n  net = create_model(config, deterministic=False)\n  state = train_state.TrainState.create(\n      apply_fn=net.apply, params=params, tx=tx\n  )\n\n  return (\n      ogbg_train_step,\n      (state, graphs, {'dropout': dropout_rng}),\n      {},\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_ogbg_molpcba_trace(state):\n  tracing_benchmark.benchmark_tracing(\n      get_apply_fn_and_args, ogbg_config.get_config, state\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_ogbg_molpcba_lower(state):\n  tracing_benchmark.benchmark_lowering(\n      get_apply_fn_and_args, ogbg_config.get_config, state\n  )\n\n\nif __name__ == '__main__':\n  tracing_benchmark.run_benchmarks()\n"
  },
  {
    "path": "benchmarks/tracing/ppo.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"PPO helper functions for RL benchmarking.\"\"\"\n\nfrom typing import Any\n\nfrom flax.benchmarks.tracing import tracing_benchmark\nfrom flax.examples.ppo import models\nfrom flax.examples.ppo import ppo_lib\nfrom flax.examples.ppo.configs import default as ppo_config\nimport google_benchmark\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\n\n\ndef get_fake_batch(batch_size: int = 256) -> tuple[jnp.ndarray, ...]:\n  \"\"\"Generate a batch of fake Atari observations and trajectories.\n\n  Args:\n    batch_size: Size of the minibatch.\n\n  Returns:\n    A tuple of (states, actions, old_log_probs, returns, advantages).\n  \"\"\"\n  # Atari observations: (batch_size, height, width, stacked_frames)\n  states = jax.random.randint(\n      jax.random.key(0),\n      (batch_size, 84, 84, 4),\n      minval=0,\n      maxval=256,\n      dtype=jnp.int32,\n  )\n\n  # Actions: discrete action space (e.g., 6 actions for Pong)\n  actions = jax.random.randint(\n      jax.random.key(1), (batch_size,), minval=0, maxval=6, dtype=jnp.int32\n  )\n\n  # Old log probabilities from behavior policy\n  old_log_probs = jax.random.normal(\n      jax.random.key(2), (batch_size,), dtype=jnp.float32\n  )\n\n  # Returns (discounted cumulative rewards)\n  returns = jax.random.normal(\n      jax.random.key(3), (batch_size,), dtype=jnp.float32\n  )\n\n  # Advantages (GAE advantages)\n  advantages = jax.random.normal(\n      jax.random.key(4), (batch_size,), dtype=jnp.float32\n  )\n\n  return (states, actions, old_log_probs, returns, advantages)\n\n\ndef get_apply_fn_and_args(\n    config: ml_collections.ConfigDict,\n) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:\n  \"\"\"Returns the apply function and args for the given config.\n\n  Args:\n    config: The training configuration.\n\n  Returns:\n    A tuple of the apply function, args, and kwargs.\n  \"\"\"\n  # Create model (6 actions for Pong)\n  num_outputs = 6\n  model = models.ActorCritic(num_outputs=num_outputs)\n\n  # Initialize model parameters\n  rng = jax.random.key(0)\n  init_shape = jnp.ones((1, 84, 84, 4), jnp.float32)\n  initial_params = model.init(rng, init_shape)['params']\n\n  # Create train state\n  # For benchmarking, we don't need actual training loops, just one step\n  train_steps = 1000  # Dummy value for state creation\n  state = ppo_lib.create_train_state(initial_params, model, config, train_steps)\n\n  # Generate fake trajectories\n  trajectories = get_fake_batch(config.batch_size)\n\n  # PPO hyperparameters\n  clip_param = config.clip_param\n  vf_coeff = config.vf_coeff\n  entropy_coeff = config.entropy_coeff\n\n  # ppo_lib.train_step is already JIT-compiled with static batch_size\n  return (\n      ppo_lib.train_step,\n      (state, trajectories, config.batch_size),\n      {\n          'clip_param': clip_param,\n          'vf_coeff': vf_coeff,\n          'entropy_coeff': entropy_coeff,\n      },\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_ppo_trace(state):\n  tracing_benchmark.benchmark_tracing(\n      get_apply_fn_and_args, ppo_config.get_config, state\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_ppo_lower(state):\n  tracing_benchmark.benchmark_lowering(\n      get_apply_fn_and_args, ppo_config.get_config, state\n  )\n\n\nif __name__ == '__main__':\n  tracing_benchmark.run_benchmarks()\n"
  },
  {
    "path": "benchmarks/tracing/requirements.txt",
    "content": "absl-py\nflax\ngoogle-benchmark\njax\nml_collections\nnumpy\noptax"
  },
  {
    "path": "benchmarks/tracing/run_all_benchmarks.sh",
    "content": "#!/bin/bash\nset -e\n\nexport XLA_FLAGS=--xla_force_host_platform_device_count=8\n\nTARGETS=(\n  mnist\n  vae\n  sst2\n  gemma\n  imagenet\n  seq2seq\n  lm1b\n  nlp_seq\n  ogbg_molpcba\n  wmt\n  ppo\n)\n\nfor target in \"${TARGETS[@]}\"; do\n  echo \"============================================\"\n  echo \"Running benchmark: ${target}\"\n  echo \"============================================\"\n  benchy \"third_party/py/flax/benchmarks/tracing:${target}\"\n  echo \"\"\ndone\n"
  },
  {
    "path": "benchmarks/tracing/seq2seq.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Seq2Seq helper functions.\"\"\"\n\nfrom typing import Any\n\nfrom flax import linen as nn\nfrom flax.benchmarks.tracing import tracing_benchmark\nfrom flax.examples.seq2seq import models\nfrom flax.examples.seq2seq.configs import default as seq2seq_config\nfrom flax.examples.seq2seq.input_pipeline import CharacterTable\nfrom flax.examples.seq2seq.input_pipeline import get_sequence_lengths\nfrom flax.examples.seq2seq.input_pipeline import mask_sequences\nfrom flax.training import train_state\nimport google_benchmark\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\nimport optax\n\n\ndef cross_entropy_loss(logits, labels, lengths):\n  xe = jnp.sum(nn.log_softmax(logits) * labels, axis=-1)\n  masked_xe = jnp.mean(mask_sequences(xe, lengths))\n  return -masked_xe\n\n\ndef compute_metrics(logits, labels, eos_id):\n  lengths = get_sequence_lengths(labels, eos_id)\n  loss = cross_entropy_loss(logits, labels, lengths)\n  token_accuracy = jnp.argmax(logits, -1) == jnp.argmax(labels, -1)\n  sequence_accuracy = (\n      jnp.sum(mask_sequences(token_accuracy, lengths), axis=-1) == lengths\n  )\n  accuracy = jnp.mean(sequence_accuracy)\n  metrics = {\n      'loss': loss,\n      'accuracy': accuracy,\n  }\n  return metrics\n\n\n@jax.jit\ndef seq2seq_train_step(state, batch, lstm_rng, eos_id):\n  labels = batch['answer'][:, 1:]\n  lstm_key = jax.random.fold_in(lstm_rng, state.step)\n\n  def loss_fn(params):\n    logits, _ = state.apply_fn(\n        {'params': params},\n        batch['query'],\n        batch['answer'],\n        rngs={'lstm': lstm_key},\n    )\n    loss = cross_entropy_loss(\n        logits, labels, get_sequence_lengths(labels, eos_id)\n    )\n    return loss, logits\n\n  grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n  (_, logits), grads = grad_fn(state.params)\n  state = state.apply_gradients(grads=grads)\n  metrics = compute_metrics(logits, labels, eos_id)\n\n  return state, metrics\n\n\ndef get_fake_batch(batch_size: int, ctable: CharacterTable) -> dict[str, Any]:\n  return ctable.get_batch(batch_size)\n\n\ndef get_apply_fn_and_args(\n    config: ml_collections.ConfigDict,\n) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:\n  rng = jax.random.key(0)\n  ctable = CharacterTable(\"0123456789+= \", config.max_len_query_digit)\n\n  model = models.Seq2seq(\n      teacher_force=False,\n      hidden_size=config.hidden_size,\n      eos_id=ctable.eos_id,\n      vocab_size=ctable.vocab_size,\n  )\n\n  rng1, rng2 = jax.random.split(rng)\n  params = model.init(\n      {'params': rng1, 'lstm': rng2},\n      jnp.ones(ctable.encoder_input_shape, jnp.float32),\n      jnp.ones(ctable.decoder_input_shape, jnp.float32),\n  )['params']\n\n  tx = optax.adam(config.learning_rate)\n  state = train_state.TrainState.create(\n      apply_fn=model.apply, params=params, tx=tx\n  )\n\n  batch = get_fake_batch(config.batch_size, ctable)\n  return seq2seq_train_step, (state, batch, rng, ctable.eos_id), {}\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_seq2seq_trace(state):\n  tracing_benchmark.benchmark_tracing(\n      get_apply_fn_and_args, seq2seq_config.get_config, state\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_seq2seq_lower(state):\n  tracing_benchmark.benchmark_lowering(\n      get_apply_fn_and_args, seq2seq_config.get_config, state\n  )\n\n\nif __name__ == \"__main__\":\n  tracing_benchmark.run_benchmarks()\n"
  },
  {
    "path": "benchmarks/tracing/sst2.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"SST2 helper functions.\"\"\"\n\nfrom typing import Any\n\nfrom flax.benchmarks.tracing import tracing_benchmark\nfrom flax.examples.sst2 import models\nfrom flax.examples.sst2.configs import default as sst2_config\nfrom flax.training import train_state as train_state_lib\nimport google_benchmark\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\nimport optax\n\nArray = jnp.ndarray\nTrainState = train_state_lib.TrainState\n\n\n@jax.vmap\ndef sigmoid_cross_entropy_with_logits(*, labels: Array, logits: Array) -> Array:\n  zeros = jnp.zeros_like(logits, dtype=logits.dtype)\n  condition = logits >= zeros\n  relu_logits = jnp.where(condition, logits, zeros)\n  neg_abs_logits = jnp.where(condition, -logits, logits)\n  return relu_logits - logits * labels + jnp.log1p(jnp.exp(neg_abs_logits))\n\n\ndef model_from_config(config: ml_collections.ConfigDict):\n  model = models.TextClassifier(\n      embedding_size=config.embedding_size,\n      hidden_size=config.hidden_size,\n      vocab_size=config.vocab_size,\n      output_size=config.output_size,\n      dropout_rate=config.dropout_rate,\n      word_dropout_rate=config.word_dropout_rate,\n      unk_idx=config.unk_idx,\n  )\n  return model\n\n\ndef get_initial_params(rng, model):\n  token_ids = jnp.ones((2, 3), jnp.int32)\n  lengths = jnp.ones((2,), dtype=jnp.int32)\n  variables = model.init(rng, token_ids, lengths, deterministic=True)\n  return variables['params']\n\n\ndef create_train_state(rng, config: ml_collections.ConfigDict, model):\n  params = get_initial_params(rng, model)\n  tx = optax.chain(\n      optax.sgd(learning_rate=config.learning_rate, momentum=config.momentum),\n      optax.add_decayed_weights(weight_decay=config.weight_decay),\n  )\n  state = TrainState.create(apply_fn=model.apply, params=params, tx=tx)\n  return state\n\n\ndef compute_metrics(*, labels: Array, logits: Array):\n  if labels.ndim == 1:\n    labels = jnp.expand_dims(labels, axis=1)\n  loss = sigmoid_cross_entropy_with_logits(labels=labels, logits=logits)\n  binary_predictions = logits >= 0.0\n  binary_accuracy = jnp.equal(binary_predictions, labels)\n  return {\n      'loss': jnp.sum(loss),\n      'accuracy': jnp.sum(binary_accuracy),\n      'count': logits.shape[0],\n  }\n\n\ndef train_step(\n    state: TrainState,\n    batch: dict[str, Array],\n    rngs: dict[str, Any],\n) -> tuple[TrainState, Any]:\n  step = state.step\n  rngs = {name: jax.random.fold_in(rng, step) for name, rng in rngs.items()}\n\n  def loss_fn(params):\n    variables = {'params': params}\n    logits = state.apply_fn(\n        variables,\n        batch['token_ids'],\n        batch['length'],\n        deterministic=False,\n        rngs=rngs,\n    )\n\n    labels = batch['label']\n    if labels.ndim == 1:\n      labels = jnp.expand_dims(labels, 1)\n    loss = jnp.mean(\n        sigmoid_cross_entropy_with_logits(labels=labels, logits=logits)\n    )\n    return loss, logits\n\n  grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n  value, grads = grad_fn(state.params)\n  (_, logits) = value\n\n  new_state = state.apply_gradients(grads=grads)\n  metrics = compute_metrics(labels=batch['label'], logits=logits)\n  return new_state, metrics\n\n\ndef get_fake_batch(batch_size: int) -> dict[str, Any]:\n  rng = jax.random.key(0)\n  max_length = 60\n  token_ids = jax.random.randint(\n      rng, (batch_size, max_length), 0, 1000, jnp.int32\n  )\n  lengths = jnp.full((batch_size,), max_length, jnp.int32)\n  labels = jax.random.uniform(rng, (batch_size,), jnp.float32)\n  return {\n      'token_ids': token_ids,\n      'length': lengths,\n      'label': labels,\n  }\n\n\ndef get_apply_fn_and_args(\n    config: ml_collections.ConfigDict,\n) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:\n  rng = jax.random.key(0)\n  config = config.copy_and_resolve_references()\n  if config.vocab_size is None:\n    config.vocab_size = 1000\n  model = model_from_config(config)\n  state = create_train_state(rng, config, model)\n  batch = get_fake_batch(config.batch_size)\n  _, dropout_rng = jax.random.split(rng)\n  rngs = {'dropout': dropout_rng}\n  train_step_jit = jax.jit(train_step)\n  return train_step_jit, (state, batch, rngs), {}\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_sst2_trace(state):\n  tracing_benchmark.benchmark_tracing(\n      get_apply_fn_and_args, sst2_config.get_config, state\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_sst2_lower(state):\n  tracing_benchmark.benchmark_lowering(\n      get_apply_fn_and_args, sst2_config.get_config, state\n  )\n\n\nif __name__ == '__main__':\n  tracing_benchmark.run_benchmarks()\n"
  },
  {
    "path": "benchmarks/tracing/tracing_benchmark.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Shared benchmark utilities for Jax tracing flax examples.\"\"\"\n\nfrom collections.abc import Callable\nimport sys\nfrom typing import Any\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nimport google_benchmark\nimport jax\n\nflags.DEFINE_enum(\n    \"mode\",\n    \"trace_and_lower\",\n    [\"trace\", \"lower\", \"trace_and_lower\"],\n    \"Measure trace, lower, or trace_and_lower.\",\n)\n\n\ndef clear_caches(state):\n  state.pause_timing()\n  jax.clear_caches()\n  state.resume_timing()\n\n\ndef benchmark_tracing(\n    get_apply_fn_and_args: Callable[..., Any],\n    get_config: Callable[[], Any],\n    state: Any,\n) -> None:\n  \"\"\"Benchmark for tracing a flax example.\"\"\"\n  config = get_config()\n  apply_fn, args, kwargs = get_apply_fn_and_args(config)\n  while state:\n    if flags.FLAGS.mode == 'trace' or flags.FLAGS.mode == 'trace_and_lower':\n      _ = apply_fn.trace(*args, **kwargs)\n      clear_caches(state)\n\n\ndef benchmark_lowering(\n    get_apply_fn_and_args: Callable[..., Any],\n    get_config: Callable[[], Any],\n    state: Any,\n    platform: str = 'tpu',\n) -> None:\n  \"\"\"Benchmark for lowering a flax example.\"\"\"\n  config = get_config()\n  apply_fn, args, kwargs = get_apply_fn_and_args(config)\n  traced = apply_fn.trace(*args, **kwargs)\n  while state:\n    if flags.FLAGS.mode == 'lower' or flags.FLAGS.mode == 'trace_and_lower':\n      _ = traced.lower(lowering_platforms=(platform,))\n      clear_caches(state)\n\n\ndef run_single_example(\n    get_apply_fn_and_args: Callable[..., Any],\n    get_config: Callable[[], Any],\n) -> None:\n  \"\"\"Run a single example for profiling.\"\"\"\n\n  def main(argv):\n    del argv\n    if flags.FLAGS.mode == 'lower':\n      raise ValueError(\n          '`--mode=lower` is not supported when profiling a single example.'\n      )\n    config = get_config()\n    apply_fn, args, kwargs, *_ = get_apply_fn_and_args(config)\n    traced = apply_fn.trace(*args, **kwargs)\n    lowered = traced.lower(lowering_platforms=('tpu',))\n    logging.info('lowered: %s', lowered.as_text('hlo'))\n\n  app.run(main)\n\n\ndef run_benchmarks() -> None:\n  \"\"\"Run registered google_benchmark benchmarks.\"\"\"\n  flags.FLAGS(sys.argv, known_only=True)\n  flags.FLAGS.mark_as_parsed()\n  google_benchmark.main()\n"
  },
  {
    "path": "benchmarks/tracing/vae.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"VAE helper functions.\"\"\"\n\nfrom typing import Any\n\nfrom flax import linen as nn\nfrom flax.benchmarks.tracing import tracing_benchmark\nfrom flax.examples.vae import models\nfrom flax.examples.vae.configs import default as vae_config\nfrom flax.training import train_state\nimport google_benchmark\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\nimport optax\n\n\n@jax.vmap\ndef binary_cross_entropy_with_logits(logits, labels):\n  logits = nn.log_sigmoid(logits)\n  return -jnp.sum(\n      labels * logits + (1.0 - labels) * jnp.log(-jnp.expm1(logits))\n  )\n\n\n@jax.vmap\ndef kl_divergence(mean, logvar):\n  return -0.5 * jnp.sum(1 + logvar - jnp.square(mean) - jnp.exp(logvar))\n\n\ndef train_step(state, batch, z_rng, latents):\n  def loss_fn(params):\n    recon_x, mean, logvar = models.model(latents).apply(\n        {'params': params}, batch, z_rng\n    )\n    bce_loss = binary_cross_entropy_with_logits(recon_x, batch).mean()\n    kld_loss = kl_divergence(mean, logvar).mean()\n    loss = bce_loss + kld_loss\n    return loss\n\n  grads = jax.grad(loss_fn)(state.params)\n  return state.apply_gradients(grads=grads)\n\n\ndef get_fake_batch(batch_size: int) -> Any:\n  return jnp.ones((batch_size, 784), jnp.float32)\n\n\ndef get_apply_fn_and_args(\n    config: ml_collections.ConfigDict,\n) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:\n  rng = jax.random.key(0)\n  rng, key = jax.random.split(rng)\n  batch = get_fake_batch(config.batch_size)\n  params = models.model(config.latents).init(key, batch, rng)['params']\n  state = train_state.TrainState.create(\n      apply_fn=models.model(config.latents).apply,\n      params=params,\n      tx=optax.adam(config.learning_rate),\n  )\n  train_step_jit = jax.jit(train_step, static_argnames=('latents',))\n  return (\n      train_step_jit,\n      (state, batch, rng, config.latents),\n      dict(),\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_vae_trace(state):\n  tracing_benchmark.benchmark_tracing(\n      get_apply_fn_and_args, vae_config.get_config, state\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_vae_lower(state):\n  tracing_benchmark.benchmark_lowering(\n      get_apply_fn_and_args, vae_config.get_config, state\n  )\n\n\nif __name__ == '__main__':\n  tracing_benchmark.run_benchmarks()\n"
  },
  {
    "path": "benchmarks/tracing/wmt.py",
    "content": "# Copyright 2025 The JAX Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"WMT helper functions for benchmarking.\"\"\"\n\nimport functools\nfrom typing import Any\n\nfrom flax import linen as nn\nfrom flax.benchmarks.tracing import tracing_benchmark\nfrom flax.examples.wmt import models\nfrom flax.examples.wmt.configs import default as wmt_config\nfrom flax.training import common_utils\nfrom flax.training import dynamic_scale as dynamic_scale_lib\nfrom flax.training import train_state\nimport google_benchmark\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\nimport numpy as np\nimport optax\n\n\nclass TrainState(train_state.TrainState):\n  dynamic_scale: dynamic_scale_lib.DynamicScale\n\n\ndef rsqrt_schedule(init_value: float, shift: int = 0):\n  def schedule(count):\n    return init_value * (count + shift) ** -0.5 * shift**0.5\n  return schedule\n\n\ndef create_learning_rate_schedule(learning_rate: float, warmup_steps: int):\n  return optax.join_schedules(\n      [\n          optax.linear_schedule(\n              init_value=0,\n              end_value=learning_rate,\n              transition_steps=warmup_steps,\n          ),\n          rsqrt_schedule(init_value=learning_rate, shift=warmup_steps),\n      ],\n      boundaries=[warmup_steps],\n  )\n\n\ndef preferred_dtype(config):\n  platform = jax.local_devices()[0].platform\n  if config.use_mixed_precision:\n    if platform == 'tpu':\n      return jnp.bfloat16\n    elif platform == 'gpu':\n      return jnp.float16\n  return jnp.float32\n\n\ndef compute_weighted_cross_entropy(\n    logits, targets, weights=None, label_smoothing=0.0\n):\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        'Incorrect shapes. Got shape %s logits and %s targets'\n        % (str(logits.shape), str(targets.shape))\n    )\n  vocab_size = logits.shape[-1]\n  confidence = 1.0 - label_smoothing\n  low_confidence = (1.0 - confidence) / (vocab_size - 1)\n  normalizing_constant = -(\n      confidence * jnp.log(confidence)\n      + (vocab_size - 1) * low_confidence * jnp.log(low_confidence + 1e-20)\n  )\n  soft_targets = common_utils.onehot(\n      targets, vocab_size, on_value=confidence, off_value=low_confidence\n  )\n\n  loss = -jnp.sum(soft_targets * nn.log_softmax(logits), axis=-1)\n  loss = loss - normalizing_constant\n\n  normalizing_factor = np.prod(targets.shape)\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_weighted_accuracy(logits, targets, weights=None):\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        'Incorrect shapes. Got shape %s logits and %s targets'\n        % (str(logits.shape), str(targets.shape))\n    )\n  loss = jnp.equal(jnp.argmax(logits, axis=-1), targets)\n  normalizing_factor = np.prod(logits.shape[:-1])\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_metrics(logits, labels, weights, label_smoothing=0.0):\n  loss, weight_sum = compute_weighted_cross_entropy(\n      logits, labels, weights, label_smoothing\n  )\n  acc, _ = compute_weighted_accuracy(logits, labels, weights)\n  metrics = {\n      'loss': loss,\n      'accuracy': acc,\n      'denominator': weight_sum,\n  }\n  return metrics\n\n\ndef wmt_train_step(\n    state,\n    batch,\n    config,\n    learning_rate_fn,\n    label_smoothing=0.0,\n    dropout_rng=None,\n):\n  train_keys = [\n      'inputs',\n      'targets',\n      'inputs_position',\n      'targets_position',\n      'inputs_segmentation',\n      'targets_segmentation',\n  ]\n  (\n      inputs,\n      targets,\n      inputs_positions,\n      targets_positions,\n      inputs_segmentation,\n      targets_segmentation,\n  ) = (batch.get(k, None) for k in train_keys)\n\n  weights = jnp.where(targets > 0, 1, 0).astype(jnp.float32)\n\n  dropout_rng = jax.random.fold_in(dropout_rng, state.step)\n\n  def loss_fn(params):\n    logits = models.Transformer(config).apply(\n        {'params': params},\n        inputs,\n        targets,\n        inputs_positions=inputs_positions,\n        targets_positions=targets_positions,\n        inputs_segmentation=inputs_segmentation,\n        targets_segmentation=targets_segmentation,\n        rngs={'dropout': dropout_rng},\n    )\n\n    loss, weight_sum = compute_weighted_cross_entropy(\n        logits, targets, weights, label_smoothing\n    )\n    mean_loss = loss / weight_sum\n    return mean_loss, logits\n\n  step = state.step\n\n  if state.dynamic_scale:\n    grad_fn = state.dynamic_scale.value_and_grad(loss_fn, has_aux=True)\n    dynamic_scale, is_fin, (_, logits), grads = grad_fn(state.params)\n    state = state.replace(dynamic_scale=dynamic_scale)\n  else:\n    grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n    (_, logits), grads = grad_fn(state.params)\n\n  new_state = state.apply_gradients(grads=grads)\n  metrics = compute_metrics(logits, targets, weights)\n  metrics['learning_rate'] = learning_rate_fn(step)\n\n  if state.dynamic_scale:\n    select_fn = functools.partial(jnp.where, is_fin)\n    new_state = new_state.replace(\n        opt_state=jax.tree_util.tree_map(\n            select_fn, new_state.opt_state, state.opt_state\n        ),\n        params=jax.tree_util.tree_map(\n            select_fn, new_state.params, state.params\n        ),\n    )\n    metrics['loss_scale'] = dynamic_scale.scale * metrics['denominator']\n\n  return new_state, metrics\n\n\ndef get_fake_batch(config: ml_collections.ConfigDict) -> dict[str, Any]:\n  rng = jax.random.key(0)\n  batch_size = config.per_device_batch_size\n  max_len = config.max_target_length\n\n  inputs = jax.random.randint(\n      rng, (batch_size, max_len), 0, config.vocab_size, jnp.int32\n  )\n  targets = jax.random.randint(\n      rng, (batch_size, max_len), 0, config.vocab_size, jnp.int32\n  )\n\n  return {\n      'inputs': inputs,\n      'targets': targets,\n      'inputs_position': None,\n      'targets_position': None,\n      'inputs_segmentation': None,\n      'targets_segmentation': None,\n  }\n\n\ndef get_apply_fn_and_args(\n    config: ml_collections.ConfigDict,\n) -> tuple[Any, tuple[Any, ...], dict[str, Any]]:\n  dtype = preferred_dtype(config)\n\n  train_config = models.TransformerConfig(\n      vocab_size=config.vocab_size,\n      output_vocab_size=config.vocab_size,\n      share_embeddings=config.share_embeddings,\n      logits_via_embedding=config.logits_via_embedding,\n      dtype=dtype,\n      emb_dim=config.emb_dim,\n      num_heads=config.num_heads,\n      num_layers=config.num_layers,\n      qkv_dim=config.qkv_dim,\n      mlp_dim=config.mlp_dim,\n      max_len=max(config.max_target_length, config.max_eval_target_length),\n      dropout_rate=config.dropout_rate,\n      attention_dropout_rate=config.attention_dropout_rate,\n      deterministic=False,\n      decode=False,\n      kernel_init=jax.nn.initializers.xavier_uniform(),\n      bias_init=jax.nn.initializers.normal(stddev=1e-6),\n  )\n\n  model = models.Transformer(train_config)\n\n  learning_rate_fn = create_learning_rate_schedule(\n      learning_rate=config.learning_rate, warmup_steps=config.warmup_steps\n  )\n\n  optimizer = optax.adamw(\n      learning_rate_fn,\n      b1=0.9,\n      b2=0.98,\n      eps=1e-9,\n      weight_decay=config.weight_decay,\n  )\n\n  rng = jax.random.key(0)\n  init_rng, dropout_rng = jax.random.split(rng)\n\n  batch = get_fake_batch(config)\n  inputs = batch['inputs']\n  targets = batch['targets']\n\n  initial_variables = model.init(init_rng, inputs, targets)\n\n  dynamic_scale = None\n  platform = jax.local_devices()[0].platform\n  if config.use_mixed_precision and platform == 'gpu':\n    dynamic_scale = dynamic_scale_lib.DynamicScale()\n\n  state = TrainState.create(\n      apply_fn=model.apply,\n      params=initial_variables['params'],\n      tx=optimizer,\n      dynamic_scale=dynamic_scale,\n  )\n\n  jit_train_step = jax.jit(\n      wmt_train_step,\n      static_argnums=(2, 3, 4),\n  )\n\n  return (\n      jit_train_step,\n      (state, batch, train_config, learning_rate_fn, 0.0, dropout_rng),\n      {},\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_wmt_trace(state):\n  tracing_benchmark.benchmark_tracing(\n      get_apply_fn_and_args, wmt_config.get_config, state\n  )\n\n\n@google_benchmark.register\n@google_benchmark.option.unit(google_benchmark.kMillisecond)\ndef test_flax_wmt_lower(state):\n  tracing_benchmark.benchmark_lowering(\n      get_apply_fn_and_args, wmt_config.get_config, state\n  )\n\n\nif __name__ == '__main__':\n  tracing_benchmark.run_benchmarks()\n"
  },
  {
    "path": "contributing.md",
    "content": "# How to Contribute\n\nPlease see https://flax.readthedocs.io/en/latest/contributing.html for more information.\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "_formatted_howtos\n"
  },
  {
    "path": "docs/.readthedocs.yaml",
    "content": "# .readthedocs.yml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.12\"\n\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n  configuration: docs/conf.py\n\n# Optionally build your docs in additional formats such as PDF and ePub\nformats:\n  - htmlzip\n  - epub\n  # - pdf\n\n# Optionally set the version of Python and requirements required to build your docs\npython:\n  install:\n    - method: pip\n      path: .\n      extra_requirements:\n        - all\n        - testing\n        - docs\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the environment for the first two.\nSPHINXOPTS    ?=\nSPHINXBUILD   ?= sphinx-build\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Deprecation\n\nThis folder contains the deprecated Flax Linen documentation. For the latest Flax NNX docs, check out the `docs_nnx` folder.\n\n# Where to find the docs\n\nThe FLAX Linen documentation can be found here: https://flax-linen.readthedocs.io/en/latest/\n\n# How to build the docs\n\n1. Clone the `flax` repository with `git clone https://github.com/google/flax.git`.\n1. In the main `flax` folder, install the required dependencies using `uv pip install -e .[docs]`.\n1. [Optional] If you need to make any local changes to the docs, create and switch to a branch. Make your changes to the docs in that branch.\n1. To build the docs, in the `flax/docs` folder run the make script: `make html`. Alternatively, install [`entr`](https://github.com/eradman/entr/), which helps run arbitrary commands when files change. Then run `find ../ ! -regex '.*/[\\.|\\_].*' | entr -s 'make html'`.\n1. If the build is successful, you should get the `The HTML pages are in _build/html.` message. You can preview the docs in `flax/docs/_build/html`.\n\n# How to run embedded code tests\n\nWe use `doctest` blocks for embedded code in documents, that are also\ntested. Learn more at https://www.sphinx-doc.org/en/master/usage/extensions/doctest.html\n\nTo run tests locally, run `make doctest`\n\n# How to write code documentation\n\nOur documentation is written in reStructuredText for Sphinx. It is a\nmeta-language that is compiled into online documentation. For more details, \ncheck out\n[Sphinx's documentation](https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html).\nAs a result, our docstrings adhere to a specific syntax that has to be kept in\nmind. Below we provide some guidelines.\n\nTo learn how to contribute to Jupyter Notebooks or other formats in Flax docs,\nrefer to the dedicated\n[Contributing](https://flax.readthedocs.io/en/latest/contributing.html) page.\n\n## How much information to put in a docstring\n\nDocstring should be informative. We prefer to err on the side of too much\ndocumentation than too little. For instance, providing a one-line explanation\nto a new `Module` which implements new functionality is not sufficient.\n\nFurthermore, we highly encourage adding examples to your docstrings, so users\ncan directly see how code can be used.\n\n## How to write inline tested code\n\nWe use [doctest](https://docs.python.org/3/library/doctest.html) syntax for\nwriting examples in documentation. These examples are ran as tests as part of\nour CI process. In order to write `doctest` code in your documentation, please\nuse the following notation:\n\n```bash\n# Example code::\n#\n#   def sum(a, b):\n#     return a + b\n#\n#   sum(0, 1)\n```\n\nThe `Example code` string at the beginning can be replaced by anything as long\nas there are two semicolons and a newline following it, and the code is\nindented.\n\n## How to use \"code font\"\n\nWhen writing code font in a docstring, please use double backticks. Example:\n\n```bash\n# This returns a ``str`` object.\n```\n\nNote that argument names and objects like True, None or any strings should\nusually be put in `code`.\n\n## How to create cross-references/links\n\nIt is possible to create cross-references to other classes, functions, and\nmethods. In the following, `obj_typ` is either `class`, `func`, or `meth`.\n\n```bash\n# First method:\n# <obj_type>:`path_to_obj`\n\n# Second method:\n# :<obj_type>:`description <path_to_obj>`\n```\n\nYou can use the second method if the `path_to_obj` is very long. Some examples:\n\n```bash\n# Create: a reference to class flax.linen.Module.\n# :class:`flax.linen.Module`\n\n# Create a reference to local function my_func.\n# :func:`my_func`\n\n# Create a reference \"Module.apply()\" to method flax.linen.Module.apply.\n# :meth:`Module.apply() <flax.linen.Module.apply>`  #\n```\n\nTo creata a hyperlink, use the following syntax:\n```bash\n# Note the double underscore at the end:\n# `Link to Google <http://www.google.com>`__\n```\n\n### How to specify arguments for classes and methods\n\n*  Class attributes should be specified using the `Attributes:` tag.\n*  Method argument should be specified using the `Args:` tags.\n*  All attributes and arguments should have types.\n\nHere is an example from our library:\n\n```python\nclass DenseGeneral(Module):\n  \"\"\"A linear transformation with flexible axes.\n    Attributes:\n      features: int or tuple with number of output features.\n      axis: int or tuple with axes to apply the transformation on. For instance,\n        (-2, -1) will apply the transformation to the last two axes.\n      batch_dims: tuple with batch axes.\n      use_bias: whether to add a bias to the output (default: True).\n      dtype: the dtype of the computation (default: float32).\n      kernel_init: initializer function for the weight matrix.\n      bias_init: initializer function for the bias.\n      precision: numerical precision of the computation see `jax.lax.Precision`\n        for details.\n  \"\"\"\n  features: Union[int, Iterable[int]]\n  axis: Union[int, Iterable[int]] = -1\n  batch_dims: Iterable[int] = ()\n  use_bias: bool = True\n  dtype: Dtype = jnp.float32\n  kernel_init: Callable[[PRNGKey, Shape, Dtype], Array] = default_kernel_init\n  bias_init: Callable[[PRNGKey, Shape, Dtype], Array] = zeros\n  precision: Any = None\n\n  @compact\n  def __call__(self, inputs: Array) -> Array:\n    \"\"\"Applies a linear transformation to the inputs along multiple dimensions.\n    Args:\n      inputs: The nd-array to be transformed.\n    Returns:\n      The transformed input.\n    \"\"\"\n    ...\n```"
  },
  {
    "path": "docs/_ext/codediff.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Sphinx directive for creating code diff tables.\n\nUse directive as follows:\n\n.. codediff::\n  :title: <LEFT_CODE_BLOCK_TITLE>, <RIGHT_CODE_BLOCK_TITLE>\n\n  <CODE_BLOCK_LEFT>\n  ---\n  <CODE_BLOCK_RIGHT>\n\nIn order to highlight a line of code, append \"#!\" to it.\n\"\"\"\n\n\nimport sphinx\nfrom docutils import nodes\nfrom docutils.parsers.rst import directives\nfrom docutils.statemachine import ViewList\nfrom sphinx.util.docutils import SphinxDirective\n\nMISSING = object()\n\n\nclass CodeDiffParser:\n  def parse(\n    self,\n    lines: list[str],\n    title: str,\n    groups: list[str] | None = None,\n    skip_test: str | None = None,\n    code_sep: str = '---',\n    sync: object = MISSING,\n  ):\n    \"\"\"Parse the code diff block and format it so that it\n    renders in different tabs and is tested by doctest.\n\n    For example:\n\n      .. testcode:: tab0, tab2, tab3\n\n        <CODE_BLOCK_A>\n\n      .. codediff::\n        :title: Tab 0, Tab 1, Tab 2, Tab 3\n        :groups: tab0, tab1, tab2, tab3\n        :skip_test: tab1, tab3\n\n        <CODE_BLOCK_B0>\n\n        ---\n\n        <CODE_BLOCK_B1>\n\n        ---\n\n        <CODE_BLOCK_B2>\n\n        ---\n\n        <CODE_BLOCK_B3>\n\n    For group tab0: <CODE_BLOCK_A> and <CODE_BLOCK_B0> are executed.\n    For group tab1: Nothing is executed.\n    For group tab2: <CODE_BLOCK_A> and <CODE_BLOCK_B2> are executed.\n    For group tab3: <CODE_BLOCK_A> is executed.\n\n    Arguments:\n      lines: a string list, where each element is a single string code line\n      title: a single string that contains the titles of each tab (they should\n        be separated by commas)\n      groups: a single string that contains the group of each tab (they should\n        be separated by commas). Code snippets that are part of the same group\n        will be executed together. If groups=None, then the group names will\n        default to the tab title names.\n      skip_test: a single string denoting which group(s) to skip testing (they\n        should be separated by commas). This is useful for legacy code snippets\n        that no longer run correctly anymore. If skip_test=None, then no tests\n        are skipped.\n      code_sep: the separator character(s) used to denote a separate code block\n        for a new tab. The default code separator is '---'.\n      sync: an option for Sphinx directives, that will sync all tabs together.\n        This means that if the user clicks to switch to another tab, all tabs\n        will switch to the new tab.\n    \"\"\"\n    titles = [t.strip() for t in title.split(',')]\n    num_tabs = len(titles)\n\n    sync = sync is not MISSING\n    # skip legacy code snippets in upgrade guides\n    if skip_test is not None:\n      skip_tests = {index.strip() for index in skip_test.split(',')}\n    else:\n      skip_tests = set()\n\n    code_blocks = '\\n'.join(lines)\n    if code_blocks.count(code_sep) != num_tabs - 1:\n      raise ValueError(\n        f'Expected {num_tabs-1} code separator(s) for {num_tabs} tab(s), but got {code_blocks.count(code_sep)} code separator(s) instead.'\n      )\n    code_blocks = [\n      code_block.split('\\n')\n      for code_block in code_blocks.split(code_sep + '\\n')\n    ]  # list[code_tab_list1[string_line1, ...], ...]\n\n    # by default, put each code snippet in a different group denoted by an index number, to be executed separately\n    if groups is not None:\n      groups = [group_name.strip() for group_name in groups.split(',')]\n    else:\n      groups = titles\n    if len(groups) != num_tabs:\n      raise ValueError(\n        f'Expected {num_tabs} group assignment(s) for {num_tabs} tab(s), but got {len(groups)} group assignment(s) instead.'\n      )\n\n    tabs = []\n    test_codes = []\n    for i, code_block in enumerate(code_blocks):\n      if groups[i] not in skip_tests:\n        test_codes.append((code_block, groups[i]))\n      tabs.append((titles[i], self._code_block(code_block)))\n    output = self._tabs(*tabs, sync=sync)\n\n    return output, test_codes\n\n  def _code_block(self, lines):\n    \"\"\"Creates a codeblock.\"\"\"\n    # Remove right trailing whitespace so we can detect the comments.\n    lines = [x.rstrip() for x in lines]\n    highlight = lambda x: x.endswith('#!')\n    code = map(lambda x: x[:-2].rstrip() if highlight(x) else x, lines)\n    highlights = [i + 1 for i in range(len(lines)) if highlight(lines[i])]\n    highlights = ','.join(str(i) for i in highlights)\n\n    directive = ['.. code-block:: python']\n    if highlights:\n      directive += [f'  :emphasize-lines: {highlights}']\n\n    # Indent code and add empty line so the code is picked up by the directive.\n    return directive + [''] + list(map(lambda x: '  ' + x, code))\n\n  def _tabs(self, *contents: tuple[str, list[str]], sync):\n    output = ['.. tab-set::'] + ['  ']\n\n    for title, content in contents:\n      output += [f'  .. tab-item:: {title}']\n\n      if sync:\n        key = title.strip()\n        output += [f'    :sync: {key}']\n\n      output += ['    ']\n      output += ['    ' + line for line in content]\n\n    return output\n\n\nclass CodeDiffDirective(SphinxDirective):\n  has_content = True\n  option_spec = {\n    'title': directives.unchanged,\n    'groups': directives.unchanged,\n    'skip_test': directives.unchanged,\n    'code_sep': directives.unchanged,\n    'sync': directives.flag,\n  }\n\n  def run(self):\n    table_code, test_codes = CodeDiffParser().parse(\n      list(self.content), **self.options\n    )\n\n    # Create a test node as a comment node so it won't show up in the docs.\n    # We add attribute \"testnodetype\" so it is be picked up by the doctest\n    # builder. This functionality is not officially documented but can be found\n    # in the source code:\n    # https://github.com/sphinx-doc/sphinx/blob/master/sphinx/ext/doctest.py\n    # (search for 'testnodetype').\n    test_nodes = []\n    for test_code, group in test_codes:\n      test_node = nodes.comment(\n        '\\n'.join(test_code),\n        '\\n'.join(test_code),\n        testnodetype='testcode',\n        groups=[group],\n      )\n      self.set_source_info(test_node)\n      test_node['options'] = {}\n      test_node['language'] = 'python3'\n      test_nodes.append(test_node)\n\n    # The table node is the side-by-side diff view that will be shown on RTD.\n    table_node = nodes.paragraph()\n    self.content = ViewList(table_code, self.content.parent)\n    self.state.nested_parse(self.content, self.content_offset, table_node)\n\n    return [table_node] + test_nodes\n\n\ndef setup(app):\n  app.add_directive('codediff', CodeDiffDirective)\n\n  return {\n    'version': sphinx.__display_version__,\n    'parallel_read_safe': True,\n    'parallel_write_safe': True,\n  }\n"
  },
  {
    "path": "docs/_ext/codediff_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for codediff Sphinx extension.\"\"\"\n\nfrom absl.testing import parameterized\nfrom codediff import CodeDiffParser\n\n\nclass CodeDiffTest(parameterized.TestCase):\n  def test_parse(self):\n    input_text = r\"\"\"@jax.jit #!\ndef get_initial_params(key):   #!\n  init_val = jnp.ones((1, 28, 28, 1), jnp.float32)\n  initial_params = CNN().init(key, init_val)['params']\n  extra_line\n  return initial_params\n---\n@jax.pmap #!\ndef get_initial_params(key):\n  init_val = jnp.ones((1, 28, 28, 1), jnp.float32)\n  initial_params = CNN().init(key, init_val)['params']\n  return initial_params\"\"\"\n\n    expected_table = \"\"\".. tab-set::\\n  \\n  .. tab-item:: Single device\\n    \\n    .. code-block:: python\\n      :emphasize-lines: 1,2\\n    \\n      @jax.jit\\n      def get_initial_params(key):\\n        init_val = jnp.ones((1, 28, 28, 1), jnp.float32)\\n        initial_params = CNN().init(key, init_val)['params']\\n        extra_line\\n        return initial_params\\n      \\n  .. tab-item:: Ensembling on multiple devices\\n    \\n    .. code-block:: python\\n      :emphasize-lines: 1\\n    \\n      @jax.pmap\\n      def get_initial_params(key):\\n        init_val = jnp.ones((1, 28, 28, 1), jnp.float32)\\n        initial_params = CNN().init(key, init_val)['params']\\n        return initial_params\"\"\"\n\n    expected_testcodes = [\n      r\"\"\"@jax.jit #!\ndef get_initial_params(key):   #!\n  init_val = jnp.ones((1, 28, 28, 1), jnp.float32)\n  initial_params = CNN().init(key, init_val)['params']\n  extra_line\n  return initial_params\n\"\"\",\n      r\"\"\"@jax.pmap #!\ndef get_initial_params(key):\n  init_val = jnp.ones((1, 28, 28, 1), jnp.float32)\n  initial_params = CNN().init(key, init_val)['params']\n  return initial_params\"\"\",\n    ]\n\n    title_left = 'Single device'\n    title_right = 'Ensembling on multiple devices'\n\n    actual_table, actual_testcodes = CodeDiffParser().parse(\n      lines=input_text.split('\\n'),\n      title=f'{title_left}, {title_right}',\n    )\n\n    actual_table = '\\n'.join(actual_table)\n    actual_testcodes = ['\\n'.join(testcode) for testcode, _ in actual_testcodes]\n\n    self.assertEqual(expected_table, actual_table)\n    self.assertEqual(expected_testcodes[0], actual_testcodes[0])\n    self.assertEqual(expected_testcodes[1], actual_testcodes[1])\n\n  @parameterized.parameters(\n    {\n      'input_text': r\"\"\"x = 1\n  ---\n  x = 2\n\"\"\",\n      'title': 'Tab 0, Tab1, Tab2',\n      'groups': None,\n      'error_msg': 'Expected 2 code separator\\\\(s\\\\) for 3 tab\\\\(s\\\\), but got 1 code separator\\\\(s\\\\) instead.',\n    },\n    {\n      'input_text': r\"\"\"x = 1\n  ---\n  x = 2\n  ---\n  x = 3\n  ---\n  x = 4\n\"\"\",\n      'title': 'Tab 0, Tab1, Tab2',\n      'groups': None,\n      'error_msg': 'Expected 2 code separator\\\\(s\\\\) for 3 tab\\\\(s\\\\), but got 3 code separator\\\\(s\\\\) instead.',\n    },\n    {\n      'input_text': r\"\"\"x = 1\n  ---\n  x = 2\n  ---\n  x = 3\n\"\"\",\n      'title': 'Tab 0, Tab1, Tab2',\n      'groups': 'tab0, tab2',\n      'error_msg': 'Expected 3 group assignment\\\\(s\\\\) for 3 tab\\\\(s\\\\), but got 2 group assignment\\\\(s\\\\) instead.',\n    },\n    {\n      'input_text': r\"\"\"x = 1\n  ---\n  x = 2\n  ---\n  x = 3\n\"\"\",\n      'title': 'Tab 0, Tab1, Tab2',\n      'groups': 'tab0, tab1, tab2, tab3',\n      'error_msg': 'Expected 3 group assignment\\\\(s\\\\) for 3 tab\\\\(s\\\\), but got 4 group assignment\\\\(s\\\\) instead.',\n    },\n  )\n  def test_parse_errors(self, input_text, title, groups, error_msg):\n    with self.assertRaisesRegex(ValueError, error_msg):\n      _, _ = CodeDiffParser().parse(\n        lines=input_text.split('\\n'),\n        title=title,\n        groups=groups,\n      )\n"
  },
  {
    "path": "docs/_ext/flax_module.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Sphinx directive for visualizing Flax modules.\n\nUse directive as follows:\n\n.. flax_module::\n  :module: flax.linen\n  :class: Dense\n\"\"\"\n\nimport importlib\n\nimport sphinx\nimport sphinx.ext.autosummary.generate as ag\nfrom docutils import nodes\nfrom docutils.parsers.rst import directives\nfrom docutils.statemachine import ViewList\nfrom sphinx.util.docutils import SphinxDirective\n\nfrom docs.conf_sphinx_patch import generate_autosummary_content\n\n\ndef render_module(modname: str, qualname: str, app):\n  parent = importlib.import_module(modname)\n  obj = getattr(parent, qualname)\n  template = ag.AutosummaryRenderer(app)\n  template_name = 'flax_module'\n  imported_members = False\n  recursive = False\n  context = {}\n  return generate_autosummary_content(\n    qualname,\n    obj,\n    parent,\n    template,\n    template_name,\n    imported_members,\n    app,\n    recursive,\n    context,\n    modname,\n    qualname,\n  )\n\n\nclass FlaxModuleDirective(SphinxDirective):\n  has_content = True\n  option_spec = {\n    'module': directives.unchanged,\n    'class': directives.unchanged,\n  }\n\n  def run(self):\n    module_template = render_module(\n      self.options['module'], self.options['class'], self.env.app\n    )\n    module_template = module_template.splitlines()\n\n    # Create a container for the rendered nodes\n    container_node = nodes.container()\n    self.content = ViewList(module_template, self.content.parent)\n    self.state.nested_parse(self.content, self.content_offset, container_node)\n\n    return [container_node]\n\n\ndef setup(app):\n  app.add_directive('flax_module', FlaxModuleDirective)\n\n  return {\n    'version': sphinx.__display_version__,\n    'parallel_read_safe': True,\n    'parallel_write_safe': True,\n  }\n"
  },
  {
    "path": "docs/_static/css/flax_theme.css",
    "content": "@import url(\"theme.css\");\n\n.wy-nav-content {\n  max-width: 1290px;\n}\n\n.rst-content table.docutils {\n  width: 100%;\n}\n\n.rst-content table.docutils td {\n  vertical-align: top;\n  padding: 0;\n}\n\n.rst-content table.docutils td p {\n  padding: 8px;\n}\n\n.rst-content div[class^=highlight] {\n  border: 0;\n  margin: 0;\n}\n"
  },
  {
    "path": "docs/_templates/autosummary/flax_module.rst",
    "content": "{{ fullname | escape | underline }}\n\n.. currentmodule:: {{ module }}\n\n.. autoclass:: {{ objname }}\n   :exclude-members:\n\n   .. automethod:: __call__\n\n   {% block methods %}\n\n   {% for item in methods %}\n   {%- if item not in inherited_members and item not in annotations and not item in ['__init__', 'setup'] %}\n   .. automethod:: {{ item }}\n   {%- endif %}\n   {%- endfor %}\n\n   {% if methods %}\n   .. rubric:: Methods\n\n   .. autosummary::\n\n   {% for item in methods %}\n   {%- if item not in inherited_members and item not in annotations and not item in ['__init__', 'setup'] %}\n       ~{{ name }}.{{ item }}\n   {%- endif %}\n   {%- endfor %}\n   {% endif %}\n   {% endblock %}"
  },
  {
    "path": "docs/api_reference/flax.core.frozen_dict.rst",
    "content": "\nflax.core.frozen_dict package\n=============================\n\n.. currentmodule:: flax.core.frozen_dict\n\n.. autoclass:: FrozenDict\n  :members: pretty_repr, copy, pop, unfreeze, tree_flatten\n\n.. autofunction:: freeze\n\n.. autofunction:: unfreeze\n\n.. autofunction:: copy\n\n.. autofunction:: pop\n\n.. autofunction:: pretty_repr\n"
  },
  {
    "path": "docs/api_reference/flax.cursor.rst",
    "content": "\nflax.cursor package\n=============================\n\nThe Cursor API allows for mutability of pytrees. This API provides a more\nergonomic solution to making partial-updates of deeply nested immutable\ndata structures, compared to making many nested ``dataclasses.replace`` calls.\n\nTo illustrate, consider the example below::\n\n  >>> from flax.cursor import cursor\n  >>> import dataclasses\n  >>> from typing import Any\n\n  >>> @dataclasses.dataclass(frozen=True)\n  >>> class A:\n  ...   x: Any\n\n  >>> a = A(A(A(A(A(A(A(0)))))))\n\nTo replace the int ``0`` using ``dataclasses.replace``, we would have to write many nested calls::\n\n  >>> a2 = dataclasses.replace(\n  ...   a,\n  ...   x=dataclasses.replace(\n  ...     a.x,\n  ...     x=dataclasses.replace(\n  ...       a.x.x,\n  ...       x=dataclasses.replace(\n  ...         a.x.x.x,\n  ...         x=dataclasses.replace(\n  ...           a.x.x.x.x,\n  ...           x=dataclasses.replace(\n  ...             a.x.x.x.x.x,\n  ...             x=dataclasses.replace(a.x.x.x.x.x.x, x=1),\n  ...           ),\n  ...         ),\n  ...       ),\n  ...     ),\n  ...   ),\n  ... )\n\nThe equivalent can be achieved much more simply using the Cursor API::\n\n  >>> a3 = cursor(a).x.x.x.x.x.x.x.set(1)\n  >>> assert a2 == a3\n\nThe Cursor object keeps tracks of changes made to it and when ``.build`` is called,\ngenerates a new object with the accumulated changes. Basic usage involves\nwrapping the object in a Cursor, making changes to the Cursor object and\ngenerating a new copy of the original object with the accumulated changes.\n\n.. currentmodule:: flax.cursor\n\n.. autofunction:: cursor\n\n.. autoclass:: Cursor\n  :members: apply_update, build, find, find_all, set\n\n\n"
  },
  {
    "path": "docs/api_reference/flax.errors.rst",
    "content": "\nflax.errors package\n===================\n\nFlax has the following classes of errors.\n\n.. automodule:: flax.errors\n    :members:\n    :exclude-members: FlaxError"
  },
  {
    "path": "docs/api_reference/flax.jax_utils.rst",
    "content": "\nflax.jax_utils package\n========================\n\n.. currentmodule:: flax.jax_utils\n\n.. automodule:: flax.jax_utils\n\n\n.. autofunction:: partial_eval_by_shape\n\n\nMulti device utilities\n------------------------\n\n.. autofunction:: replicate\n.. autofunction:: unreplicate\n\n.. autofunction:: prefetch_to_device\n\n.. autofunction:: pmean\n\n.. autofunction:: pad_shard_unpad\n"
  },
  {
    "path": "docs/api_reference/flax.linen/activation_functions.rst",
    "content": "\nActivation functions\n------------------------\n\n.. automodule:: flax.linen.activation\n.. currentmodule:: flax.linen.activation\n\n.. autoclass:: PReLU\n  :members:\n  :special-members: __call__\n\n.. autofunction:: celu\n.. autofunction:: elu\n.. autofunction:: gelu\n.. autofunction:: glu\n.. autofunction:: hard_sigmoid\n.. autofunction:: hard_silu\n.. autofunction:: hard_swish\n.. autofunction:: hard_tanh\n.. autofunction:: leaky_relu\n.. autofunction:: log_sigmoid\n.. autofunction:: log_softmax\n.. autofunction:: logsumexp\n.. autofunction:: one_hot\n.. autofunction:: relu\n.. autofunction:: relu6 as relu6,\n.. autofunction:: selu\n.. autofunction:: sigmoid\n.. autofunction:: silu\n.. autofunction:: soft_sign\n.. autofunction:: softmax\n.. autofunction:: softplus\n.. autofunction:: standardize\n.. autofunction:: swish\n.. autofunction:: tanh\n"
  },
  {
    "path": "docs/api_reference/flax.linen/decorators.rst",
    "content": "Decorators\n----------------------\n\n.. currentmodule:: flax.linen\n\n.. autofunction:: compact\n.. autofunction:: nowrap\n"
  },
  {
    "path": "docs/api_reference/flax.linen/index.rst",
    "content": "\nflax.linen\n==========\n\nLinen is the Flax Module system. Read more about our design goals in the `Linen README <https://github.com/google/flax/blob/main/flax/linen/README.md>`_.\n\n.. toctree::\n  :maxdepth: 2\n\n  module\n  init_apply\n  layers\n  activation_functions\n  initializers\n  transformations\n  inspection\n  variable\n  spmd\n  decorators\n  profiling"
  },
  {
    "path": "docs/api_reference/flax.linen/init_apply.rst",
    "content": "\nInit/Apply\n==============\n\n.. currentmodule:: flax.linen\n\n.. autofunction:: apply\n.. autofunction:: init\n.. autofunction:: init_with_output\n"
  },
  {
    "path": "docs/api_reference/flax.linen/initializers.rst",
    "content": "Initializers\n------------------------\n\n.. automodule:: flax.linen.initializers\n.. currentmodule:: flax.linen.initializers\n\n.. autofunction:: constant\n.. autofunction:: delta_orthogonal\n.. autofunction:: glorot_normal\n.. autofunction:: glorot_uniform\n.. autofunction:: he_normal\n.. autofunction:: he_uniform\n.. autofunction:: kaiming_normal\n.. autofunction:: kaiming_uniform\n.. autofunction:: lecun_normal\n.. autofunction:: lecun_uniform\n.. autofunction:: normal\n.. autofunction:: truncated_normal\n.. autofunction:: ones\n.. autofunction:: ones_init\n.. autofunction:: orthogonal\n.. autofunction:: uniform\n.. autofunction:: variance_scaling\n.. autofunction:: xavier_normal\n.. autofunction:: xavier_uniform\n.. autofunction:: zeros\n.. autofunction:: zeros_init\n"
  },
  {
    "path": "docs/api_reference/flax.linen/inspection.rst",
    "content": "\nInspection\n----------------------\n\n.. currentmodule:: flax.linen\n\n.. autofunction:: tabulate\n"
  },
  {
    "path": "docs/api_reference/flax.linen/layers.rst",
    "content": "Layers\n======\n\n.. currentmodule:: flax.linen\n\nLinear Modules\n------------------------\n\n.. flax_module::\n  :module: flax.linen\n  :class: Dense\n\n.. flax_module::\n  :module: flax.linen\n  :class: DenseGeneral\n\n.. flax_module::\n  :module: flax.linen\n  :class: Conv\n\n.. flax_module::\n  :module: flax.linen\n  :class: ConvTranspose\n\n.. flax_module::\n  :module: flax.linen\n  :class: ConvLocal\n\n.. flax_module::\n  :module: flax.linen\n  :class: Einsum\n\n.. flax_module::\n  :module: flax.linen\n  :class: Embed\n\nPooling\n------------------------\n\n.. autofunction:: max_pool\n.. autofunction:: avg_pool\n.. autofunction:: pool\n\nNormalization\n------------------------\n\n.. flax_module::\n  :module: flax.linen\n  :class: BatchNorm\n\n.. flax_module::\n  :module: flax.linen\n  :class: LayerNorm\n\n.. flax_module::\n  :module: flax.linen\n  :class: GroupNorm\n\n.. flax_module::\n  :module: flax.linen\n  :class: RMSNorm\n\n.. flax_module::\n  :module: flax.linen\n  :class: InstanceNorm\n\n.. flax_module::\n  :module: flax.linen\n  :class: SpectralNorm\n\n.. flax_module::\n  :module: flax.linen\n  :class: WeightNorm\n\n\nCombinators\n------------------------\n\n.. flax_module::\n  :module: flax.linen\n  :class: Sequential\n\nStochastic\n------------------------\n\n.. flax_module::\n  :module: flax.linen\n  :class: Dropout\n\nAttention\n------------------------\n\n.. flax_module::\n  :module: flax.linen\n  :class: MultiHeadDotProductAttention\n\n.. flax_module::\n  :module: flax.linen\n  :class: MultiHeadAttention\n\n.. flax_module::\n  :module: flax.linen\n  :class: SelfAttention\n\n.. autofunction:: dot_product_attention_weights\n.. autofunction:: dot_product_attention\n.. autofunction:: make_attention_mask\n.. autofunction:: make_causal_mask\n\nRecurrent\n------------------------\n\n.. flax_module::\n  :module: flax.linen\n  :class: RNNCellBase\n\n.. flax_module::\n  :module: flax.linen\n  :class: LSTMCell\n\n.. flax_module::\n  :module: flax.linen\n  :class: OptimizedLSTMCell\n\n.. flax_module::\n  :module: flax.linen\n  :class: ConvLSTMCell\n\n.. flax_module::\n  :module: flax.linen\n  :class: SimpleCell\n\n.. flax_module::\n  :module: flax.linen\n  :class: GRUCell\n\n.. flax_module::\n  :module: flax.linen\n  :class: MGUCell\n\n.. flax_module::\n  :module: flax.linen\n  :class: RNN\n\n.. flax_module::\n  :module: flax.linen\n  :class: Bidirectional\n\nBatchApply\n------------------------\n\n.. flax_module::\n  :module: flax.linen\n  :class: BatchApply\n"
  },
  {
    "path": "docs/api_reference/flax.linen/module.rst",
    "content": "Module\n------------------------\n\n.. automodule:: flax.linen\n.. currentmodule:: flax.linen\n\n.. autoclass:: Module\n   :members: setup, variable, param, bind, unbind, apply, init, init_with_output, copy, make_rng, sow, variables, Variable, __setattr__, tabulate, module_paths, is_initializing, perturb, put_variable, has_variable, has_rng, lazy_init, get_variable, path, is_mutable_collection\n\n.. autofunction:: apply\n.. autofunction:: init\n.. autofunction:: init_with_output\n.. autofunction:: intercept_methods\n.. autofunction:: share_scope\n"
  },
  {
    "path": "docs/api_reference/flax.linen/profiling.rst",
    "content": "Profiling\n----------------------\n\n.. currentmodule:: flax.linen\n\n.. autofunction:: enable_named_call\n.. autofunction:: disable_named_call\n.. autofunction:: override_named_call\n"
  },
  {
    "path": "docs/api_reference/flax.linen/spmd.rst",
    "content": "\nSPMD\n----------------------\n\n.. automodule:: flax.linen.spmd\n.. currentmodule:: flax.linen\n\n.. autofunction:: Partitioned\n.. autofunction:: with_partitioning\n.. autofunction:: get_partition_spec\n.. autofunction:: get_sharding\n.. autofunction:: LogicallyPartitioned\n.. autofunction:: logical_axis_rules\n.. autofunction:: set_logical_axis_rules\n.. autofunction:: get_logical_axis_rules\n.. autofunction:: logical_to_mesh_axes\n.. autofunction:: logical_to_mesh\n.. autofunction:: logical_to_mesh_sharding\n.. autofunction:: with_logical_constraint\n.. autofunction:: with_logical_partitioning\n"
  },
  {
    "path": "docs/api_reference/flax.linen/transformations.rst",
    "content": "Transformations\n----------------------\n\n.. automodule:: flax.linen.transforms\n.. currentmodule:: flax.linen\n\n.. autofunction:: vmap\n.. autofunction:: scan\n.. autofunction:: jit\n.. autofunction:: remat\n.. autofunction:: remat_scan\n.. autofunction:: map_variables\n.. autofunction:: jvp\n.. autofunction:: vjp\n.. autofunction:: custom_vjp\n.. autofunction:: while_loop\n.. autofunction:: cond\n.. autofunction:: switch\n"
  },
  {
    "path": "docs/api_reference/flax.linen/variable.rst",
    "content": "\nVariable dictionary\n----------------------\n\n.. automodule:: flax.core.variables\n.. autoclass:: flax.linen.Variable\n"
  },
  {
    "path": "docs/api_reference/flax.serialization.rst",
    "content": "\nflax.serialization package\n============================\n\n.. currentmodule:: flax.serialization\n\n.. automodule:: flax.serialization\n\n\nState dicts\n------------------------\n\n.. autofunction:: from_state_dict\n.. autofunction:: to_state_dict\n\n.. autofunction:: register_serialization_state\n\n\nSerialization with MessagePack\n--------------------------------\n\n.. autofunction:: msgpack_serialize\n.. autofunction:: msgpack_restore\n\n.. autofunction:: to_bytes\n.. autofunction:: from_bytes\n"
  },
  {
    "path": "docs/api_reference/flax.struct.rst",
    "content": "\nflax.struct package\n=====================\n\n.. currentmodule:: flax.struct\n\n.. automodule:: flax.struct\n\n\n.. autofunction:: dataclass\n\n\n.. autoclass:: PyTreeNode"
  },
  {
    "path": "docs/api_reference/flax.traceback_util.rst",
    "content": "flax.traceback_util package\n============================\n\n.. currentmodule:: flax.traceback_util\n\n.. automodule:: flax.traceback_util\n\n\nTraceback filtering utils\n--------------------------\n\n.. autofunction:: hide_flax_in_tracebacks\n\n.. autofunction:: show_flax_in_tracebacks\n"
  },
  {
    "path": "docs/api_reference/flax.training.rst",
    "content": "\nflax.training package\n=====================\n\nCheckpoints\n------------------------\n\n.. currentmodule:: flax.training.checkpoints\n\n.. automodule:: flax.training.checkpoints\n\n.. autofunction:: save_checkpoint\n\n.. autofunction:: save_checkpoint_multiprocess\n\n.. autofunction:: latest_checkpoint\n\n.. autofunction:: restore_checkpoint\n\n.. autofunction:: convert_pre_linen\n\nLearning rate schedules\n------------------------\n\n.. currentmodule:: flax.training.lr_schedule\n\n.. automodule:: flax.training.lr_schedule\n\n.. autofunction:: create_constant_learning_rate_schedule\n\n.. autofunction:: create_stepped_learning_rate_schedule\n\n.. autofunction:: create_cosine_learning_rate_schedule\n\nTrain state\n------------------------\n\n.. currentmodule:: flax.training.train_state\n\n.. autoclass:: TrainState\n    :members: apply_gradients, create\n\nEarly Stopping\n------------------------\n\n.. currentmodule:: flax.training.early_stopping\n\n.. autoclass:: EarlyStopping\n    :members: reset, update\n\nCommon Utilities\n------------------------\n\n.. currentmodule:: flax.training.common_utils\n\n.. autofunction:: shard\n\n.. autofunction:: shard_prng_key\n\n.. autofunction:: stack_forest\n\n.. autofunction:: get_metrics\n\n.. autofunction:: onehot\n"
  },
  {
    "path": "docs/api_reference/index.rst",
    "content": "API Reference\n=============\n\n.. toctree::\n   :maxdepth: 4\n\n   flax.config\n   flax.core.frozen_dict\n   flax.cursor\n   flax.errors\n   flax.jax_utils\n   flax.linen/index\n   flax.serialization\n   flax.struct\n   flax.traceback_util\n   flax.training\n   flax.traverse_util"
  },
  {
    "path": "docs/conf.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Configuration file for the Sphinx documentation builder.\"\"\"\n\n\n# This file only contains a selection of the most common options. For a full\n# list see the documentation:\n# https://www.sphinx-doc.org/en/master/usage/configuration.html\n\n# -- Path setup --------------------------------------------------------------\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#\n# import os\n# import sys\n# sys.path.insert(0, os.path.abspath('.'))\n\nimport os\nimport sys\nimport doctest\n\nsys.path.insert(0, os.path.abspath('..'))\n# Include local extension.\nsys.path.append(os.path.abspath('./_ext'))\n\n# patch sphinx\n# -- Project information -----------------------------------------------------\n\nproject = 'Flax'\ncopyright = '2023, The Flax authors'  # pylint: disable=redefined-builtin\nauthor = 'The Flax authors'\n\n\n# -- General configuration ---------------------------------------------------\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n  'sphinx.ext.autodoc',\n  'sphinx.ext.autosummary',\n  'sphinx.ext.autosectionlabel',\n  'sphinx.ext.doctest',\n  'sphinx.ext.intersphinx',\n  'sphinx.ext.mathjax',\n  'sphinx.ext.napoleon',\n  'sphinx.ext.viewcode',\n  'myst_nb',\n  'codediff',\n  'flax_module',\n  'sphinx_design',\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path.\nexclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']\n\n# The suffix(es) of source filenames.\n# Note: important to list ipynb before md here: we have both md and ipynb\n# copies of each notebook, and myst will choose which to convert based on\n# the order in the source_suffix list. Notebooks which are not executed have\n# outputs stored in ipynb but not in md, so we must convert the ipynb.\nsource_suffix = ['.rst', '.ipynb', '.md']\n\nautosummary_generate = True\n\nmaster_doc = 'index'\n\nautodoc_typehints = 'none'\n\n\n# -- Options for HTML output -------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\n# html_theme = 'pydata_sphinx_theme'\nhtml_theme = 'sphinx_book_theme'\nhtml_css_files = ['css/flax_theme.css']\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\nhtml_logo = './flax.png'\nhtml_favicon = './flax.png'\n\n# title of the website\nhtml_title = ''\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named 'default.css' will overwrite the builtin 'default.css'.\nhtml_static_path = ['_static']\n\nhtml_extra_path = ['robots.txt']\n\n# href with no underline and white bold text color\nannouncement = \"\"\"\n<a\n  href=\"https://flax.readthedocs.io/en/latest/index.html\"\n  style=\"text-decoration: none; color: white;\"\n>\n  This site covers the old Flax Linen API. <span style=\"color: lightgray;\">[Explore the new <b>Flax NNX</b> API ✨]</span>\n</a>\n\"\"\"\n\nhtml_theme_options = {\n  'repository_url': 'https://github.com/google/flax',\n  'use_repository_button': True,  # add a 'link to repository' button\n  'use_issues_button': False,  # add an 'Open an Issue' button\n  'path_to_docs': (\n    'docs'\n  ),  # used to compute the path to launch notebooks in colab\n  'launch_buttons': {\n    'colab_url': 'https://colab.research.google.com/',\n  },\n  'prev_next_buttons_location': None,\n  'show_navbar_depth': 1,\n  'announcement': announcement,\n}\n\n# -- Options for myst ----------------------------------------------\n# uncomment line below to avoid running notebooks during development\n# nb_execution_mode = 'off'\n# Notebook cell execution timeout; defaults to 30.\nnb_execution_timeout = 100\n# List of patterns, relative to source directory, that match notebook\n# files that will not be executed.\nmyst_enable_extensions = ['dollarmath']\nnb_execution_excludepatterns = [\n  'quick_start.ipynb',  # <-- times out\n  'transfer_learning.ipynb',  # <-- transformers requires flax<=0.7.0\n  'flax/nnx',  # exclude nnx\n  'guides/quantization/fp8_basics.ipynb',\n  'guides/training_techniques/use_checkpointing.ipynb',  # TODO(IvyZX): needs to be updated\n]\n# raise exceptions on execution so CI can catch errors\nnb_execution_allow_errors = False\nnb_execution_raise_on_error = True\n\n# -- Extension configuration -------------------------------------------------\n\n# Tell sphinx-autodoc-typehints to generate stub parameter annotations including\n# types, even if the parameters aren't explicitly documented.\nalways_document_param_types = True\n\n# -- doctest configuration -------------------------------------------------\ndoctest_default_flags = doctest.NORMALIZE_WHITESPACE\ndoctest_global_setup = \"\"\"\nimport jax\nimport jax.numpy as jnp\nfrom flax import nnx\n\nimport logging as slog\nfrom absl import logging as alog\n\n# Avoid certain absl logging messages to break doctest\nfiltered_message = [\n  'SaveArgs.aggregate is deprecated',\n  '',\n]\n\nclass _CustomLogFilter(slog.Formatter):\n  def format(self, record):\n    message = super(_CustomLogFilter, self).format(record)\n    for m in filtered_message:\n      if m in message:\n        return ''\n    return message\n\nalog.use_absl_handler()\nalog.get_absl_handler().setFormatter(_CustomLogFilter())\n\"\"\"\n"
  },
  {
    "path": "docs/conf_sphinx_patch.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Patch Sphinx to improve documentation aesthetics.\"\"\"\n\n# TODO(cgarciae): Send a PR to sphinx to upstream this fix. Issue: https://github.com/google/flax/issues/2196\n# This patch is needed to make autosummary provide the \"annotations\"\n# variable so we can exclude function attributes from the methods list\n# in flax_module.rst. The patch as such only adds this single line:\n#\n#     ns['annotations'] = list(getattr(obj, '__annotations__', {}).keys())'\n#\n# We should consider sending a PR to sphinx so we can get rid of this.\n# Original source: https://github.com/sphinx-doc/sphinx/blob/0aedcc9a916daa92d477226da67d33ce1831822e/sphinx/ext/autosummary/generate.py#L211-L351\nfrom typing import Any\n\nimport sphinx.ext.autodoc\nimport sphinx.ext.autosummary.generate as ag\n\n\ndef generate_autosummary_content(\n  name: str,\n  obj: Any,\n  parent: Any,\n  template: ag.AutosummaryRenderer,\n  template_name: str,\n  imported_members: bool,\n  app: Any,\n  recursive: bool,\n  context: dict,\n  modname: str = None,\n  qualname: str = None,\n) -> str:\n  doc = ag.get_documenter(app, obj, parent)\n\n  def skip_member(obj: Any, name: str, objtype: str) -> bool:\n    try:\n      return app.emit_firstresult(\n        'autodoc-skip-member', objtype, name, obj, False, {}\n      )\n    except Exception as exc:\n      ag.logger.warning(\n        __(\n          'autosummary: failed to determine %r to be documented, '\n          'the following exception was raised:\\n%s'\n        ),\n        name,\n        exc,\n        type='autosummary',\n      )\n      return False\n\n  def get_class_members(obj: Any) -> dict[str, Any]:\n    members = sphinx.ext.autodoc.get_class_members(\n      obj, [qualname], ag.safe_getattr\n    )\n    return {name: member.object for name, member in members.items()}\n\n  def get_module_members(obj: Any) -> dict[str, Any]:\n    members = {}\n    for name in ag.members_of(obj, app.config):\n      try:\n        members[name] = ag.safe_getattr(obj, name)\n      except AttributeError:\n        continue\n    return members\n\n  def get_all_members(obj: Any) -> dict[str, Any]:\n    if doc.objtype == 'module':\n      return get_module_members(obj)\n    elif doc.objtype == 'class':\n      return get_class_members(obj)\n    return {}\n\n  def get_members(\n    obj: Any,\n    types: set[str],\n    include_public: list[str] = [],\n    imported: bool = True,\n  ) -> tuple[list[str], list[str]]:\n    items: list[str] = []\n    public: list[str] = []\n\n    all_members = get_all_members(obj)\n    for name, value in all_members.items():\n      documenter = ag.get_documenter(app, value, obj)\n      if documenter.objtype in types:\n        # skip imported members if expected\n        if imported or getattr(value, '__module__', None) == obj.__name__:\n          skipped = skip_member(value, name, documenter.objtype)\n          if skipped is True:\n            pass\n          elif skipped is False:\n            # show the member forcedly\n            items.append(name)\n            public.append(name)\n          else:\n            items.append(name)\n            if name in include_public or not name.startswith('_'):\n              # considers member as public\n              public.append(name)\n    return public, items\n\n  def get_module_attrs(members: Any) -> tuple[list[str], list[str]]:\n    \"\"\"Find module attributes with docstrings.\"\"\"\n    attrs, public = [], []\n    try:\n      analyzer = ag.ModuleAnalyzer.for_module(name)\n      attr_docs = analyzer.find_attr_docs()\n      for namespace, attr_name in attr_docs:\n        if namespace == '' and attr_name in members:\n          attrs.append(attr_name)\n          if not attr_name.startswith('_'):\n            public.append(attr_name)\n    except ag.PycodeError:\n      pass  # give up if ModuleAnalyzer fails to parse code\n    return public, attrs\n\n  def get_modules(obj: Any) -> tuple[list[str], list[str]]:\n    items: list[str] = []\n    for _, modname, _ispkg in ag.pkgutil.iter_modules(obj.__path__):\n      fullname = name + '.' + modname\n      try:\n        module = ag.import_module(fullname)\n        if module and hasattr(module, '__sphinx_mock__'):\n          continue\n      except ImportError:\n        pass\n\n      items.append(fullname)\n    public = [x for x in items if not x.split('.')[-1].startswith('_')]\n    return public, items\n\n  ns: dict[str, Any] = {}\n  ns.update(context)\n\n  if doc.objtype == 'module':\n    scanner = ag.ModuleScanner(app, obj)\n    ns['members'] = scanner.scan(imported_members)\n    ns['functions'], ns['all_functions'] = get_members(\n      obj, {'function'}, imported=imported_members\n    )\n    ns['classes'], ns['all_classes'] = get_members(\n      obj, {'class'}, imported=imported_members\n    )\n    ns['exceptions'], ns['all_exceptions'] = get_members(\n      obj, {'exception'}, imported=imported_members\n    )\n    ns['attributes'], ns['all_attributes'] = get_module_attrs(ns['members'])\n    ispackage = hasattr(obj, '__path__')\n    if ispackage and recursive:\n      ns['modules'], ns['all_modules'] = get_modules(obj)\n  elif doc.objtype == 'class':\n    ns['members'] = dir(obj)\n    ns['inherited_members'] = set(dir(obj)) - set(obj.__dict__.keys())\n    ns['methods'], ns['all_methods'] = get_members(\n      obj, {'method'}, ['__init__']\n    )\n    ns['attributes'], ns['all_attributes'] = get_members(\n      obj, {'attribute', 'property'}\n    )\n    ns['annotations'] = list(getattr(obj, '__annotations__', {}).keys())\n\n  if modname is None or qualname is None:\n    modname, qualname = ag.split_full_qualified_name(name)\n\n  if doc.objtype in ('method', 'attribute', 'property'):\n    ns['class'] = qualname.rsplit('.', 1)[0]\n\n  if doc.objtype in ('class',):\n    shortname = qualname\n  else:\n    shortname = qualname.rsplit('.', 1)[-1]\n\n  ns['fullname'] = name\n  ns['module'] = modname\n  ns['objname'] = qualname\n  ns['name'] = shortname\n\n  ns['objtype'] = doc.objtype\n  ns['underline'] = len(name) * '='\n\n  if template_name:\n    return template.render(template_name, ns)\n  else:\n    return template.render(doc.objtype, ns)\n\n\nag.generate_autosummary_content = generate_autosummary_content\n"
  },
  {
    "path": "docs/developer_notes/index.rst",
    "content": "Developer notes\n===============\n\n.. toctree::\n   :maxdepth: 1\n\n   module_lifecycle\n   lift\n   FLIPs <https://github.com/google/flax/tree/main/docs/flip>\n"
  },
  {
    "path": "docs/developer_notes/lift.md",
    "content": "# Lifted transformations\n\n⚠️ Advanced topic ⚠️\n\nThis design note explains the underlying implementation of `flax.linen.transform`, which enables JAX transformations inside Flax `Module`s.\n\n\n## Introduction\n\nJAX uses a functional API meaning that it only guarantees correct behavior when using functions without side effects ([JAX docs](https://jax.readthedocs.io/en/latest/jax-101/01-jax-basics.html#differences-from-numpy)).\nTypically, these side effects are the result of mutating an object that lives outside the function.\n\nThe functional paradigm has some advantages like the ability to explicitly reason about state and stochasticity.\nThe function output only changes when an input argument changes.\nTherefore, a function is guaranteed to behave deterministically.\n\nBut pure functions offer another big advantage to JAX: specifically, they enable functional transformations.\nFor example `jax.vmap(f)` will vectorize a function `f`.\nBecause `f` cannot have side effects the vectorized/parallel version of `f` is well-defined. To see why we need this restriction, consider what happens if `f` would increment a counter or draw a random number.\nWould `f` draw the same or a different random number for each item in the vector?\nWould each item in the batch have its own counter or is the counter shared among the items?\nAnd in what order is the counter incremented if `f` is computed in parallel?\nThe answer to all these questions is \"it depends\".\nThe behavior is ambiguous and the functional constraint elegantly avoids this problem.\n\nFlax introduces a safe way to have limited randomness and stateful variables in a JAX-compatible form.\nThe reason why the state in Flax is not problematic is because it is local: inside a Flax `Module` there are variables and PRNG sequences,\nbut on the outside there are only JAX Arrays and PRNG keys.\n\nFor most use cases, Flax is used to define models in a stateful way.\nBecause a `Module` behaves like a pure function externally, we can fully utilize JAX with all of its transformations.\nThere are, however, cases when we want to have the best of both worlds by using transformations and `Module` together.\nThis design note explains how we extend JAX's functional transformation to work on `Module`s that have internal state and randomness.\n\n\n## Functionalization\n\nBefore we jump into the details let's consider a simple example where we would like to use `vmap` inside a `Module`.\n\nFirst, we define a simple MLP without any transformations:\n\n```python\nimport jax\nfrom jax import random, numpy as jnp\nfrom flax import linen as nn\n\nclass MLP(nn.Module):\n  @nn.compact\n  def __call__(self, xs):\n    h = nn.Dense(4, name='hidden')(xs)\n    h = nn.relu(h)\n    return nn.Dense(1, name='out')(h)\n```\n\nNow what if we want to have separate MLP parameters for each item in `xs`?\nIf this were \"vanilla JAX\" we could imagine writing something like `jax.vmap(apply_mlp)(mlp_params, xs)`.\nBut doing something like this in Linen will actually fail:\n\n```python\nclass NaiveVmapMLP(nn.Module):\n  @nn.compact\n  def __call__(self, xs):\n    mlp = MLP()\n    return jax.vmap(lambda mlp, x: mlp(x))(mlp, xs)  # fails\n```\n\nJAX will raise an error when `vmap` is used on `mlp` because it's not a JAX array or a simple container of arrays.\nWe can not really blame JAX for refusing to perform this under-specified job.\nAfter all, it's not even clear what should happen here.\nThe parameters inside the MLP are not even initialized yet and we will need a separate PRNG key for each group of parameters.\n`jax.vmap` can only broadcast or map over an axis but it cannot automatically split an PRNG key.\nTherefore, we have to call `jax.random.split` manually.\n\nWe can fix this problem by first turning `MLP` into a pure init and apply function.\nAfterwards, we use the `param` method to store the parameters:\n\n```python\nclass ManualVmapMLP(nn.Module):\n  @nn.compact\n  def __call__(self, xs):\n    mlp = MLP(parent=None)\n    init_fn = lambda rng, xs: jax.vmap(mlp.init, in_axes=0)(random.split(rng, xs.shape[0]), xs)['params']\n    apply_fn = jax.vmap(mlp.apply, in_axes=0)\n    mlp_params = self.param('mlp', init_fn, xs)\n    return apply_fn({'params': mlp_params}, xs)\n\nxs = jnp.ones((3, 4))\nvariables = ManualVmapMLP().init(random.key(0), xs)\nprint(jax.tree_util.tree_map(jnp.shape, variables['params']))\n\"\"\"==>\n{\n    mlp: {\n        hidden: {\n            bias: (3, 4),\n            kernel: (3, 4, 4),\n        },\n        out: {\n            bias: (3, 1),\n            kernel: (3, 4, 1),\n        },\n    },\n}\n\"\"\"\n```\n\nHere, `MLP(parent=None)` creates a detached instance of `MLP`.\nThis avoids reserving a name for the submodule inside the current module.\nAlthough not strictly necessary, this also ensures we cannot accidentally use the MLP instance in a stateful way and we are forced to use it through either `.init` or `.apply`.\n\nThis example is still relatively concise but it already takes a few extra \"bookkeeping\" statements to make it work.\nHowever, this implementation has a number of limitations:\n1. During initialization, we call the submodule twice through `init_fn` and `apply_fn`. If the submodule used the same trick to do\n   functional transformation we will end up executing a lot of code as the number of module calls grows like 2^d where d is the number of\n   nested function transformations.\n2. The implementation assumes the submodule only requires the parameter RNG sequence.\n3. The implementation assumes we only create variables in the \"params\" collection during `init`. However, it does not support other variable collections and creating/updating variables in `apply`.\n\nPoint 3 in particular makes manual functionalization cumbersome.\nFeel free to try and extend the above example with a `nn.BatchNorm` layer in the `MLP` module.\nThis will require dealing with some additional complexity like storing the updated batch stats and making sure the batch stats are not mutable inside `vmap` when it should be immutable (e.g.: eval mode).\n\n\nWe call the process of transforming a stateful Module into a pure function \"functionalization\".\nBy temporarily turning a stateful `Module` into a function we make it compatible with JAX's functional transformations.\n\n## Lifting\n\nFlax provides an alternative for manual functionalization which we call lifted transformation.\nLifted transformations are defined in `flax.core.lift`.\nAll the lifted JAX transformations are defined with a single generic lifting API called `pack`.\n\nA number of decisions had to be made in order to define `pack`. The implementation\nof `pack` controls how variables and rngs are lifted and how fine-grained the user control is.\nIt must also decide whether lifting decisions are made at variable or transformation definition.\n\n\n### Lifting granularity\n\n\nWith the Linen API, users can define arbitrary variable collections and PRNG sequences.\nEach variable in a collection is lifted in the same way.\n\nCollections are typically given a semantically meaningful name like \"params\" or \"batch_stats\" rather than a general purpose name like \"state\".\nBecause collections carry semantic meaning we can decide at the transformation level how each collection should be lifted.\nFor example, we want to share all parameter variables when we add a batch dimension to a model.\n\nAt the same time we can write generic code that uses transformations without knowing exactly what kind of variables the submodules will create.\nCollections thus strike a balance between fine-grained control and generality.\nWe also avoid brittle string matching code that loops over all variables and tries to split up collections in an ad-hoc way based on\nnaming conventions like: target all variables with the name prefix \"kernel\".\nIf more fine-grained control is necessary a user can simply split up a set of variables over multiple collections that should be handled differently.\n\n\n### Transformation vs variable control\n\n\nLifting behavior could be defined either at the transformation level or during variable definition.\nWe use transformation level definitions of lifting behavior.\nThe reason for this choice is that there are many different transformations with various behaviors.\nFor example: `vmap` has broadcasted and vectorized arguments, while `scan` has scan, carry, and broadcast arguments.\nA variable would have to define its behavior for all these transformations otherwise a `Module` would not be compatible with\nthese transformations. Alternatively, we would have to make default decisions for how transformations are handled.\nHowever, this could lead to silent bugs because the behavior might not actually be valid given the users intent.\n\nThe lift package also provides a general purpose `transform`, which allows an arbitrary function to transform a variable collection.\nFor example, this can be used to tie the weights in a tied auto-encoder by transposing the weights.\nIt is unclear whether a similar general purpose transform could be defined if lifting decisions were made at variable definition.\n\n\n### Linen\n\nThe lifting module does not know about the Linen `Module` API.\nInstead it operates directly on instances of `flax.core.Scope`.\nA `Scope` instance contains the variables and PRNG sequences of a `Module`.\nEach `Module` instance has a `Scope` instance in the `.scope` field if it has a parent or it was created using `init` or `apply`.\nTypically, the top-level `Module` instance — on which you call `init` or `apply` — is the only `Module` instance that does not have a `Scope` bound to it.\n\nWhen a `Module` is transformed, we use the `flax.core.lift` APIs to lift the scope and use `Module.clone()` to create a new `Module` instance with the lifted scope bound to it.\n\n`flax.linen.transforms` exposes wrappers for the transformations in `flax.core.lift`. The core lifting APIs operate on functions while\nthe Linen wrappers can transform either a `Module` class or a `Module` method.\n\nThus, lifting is implemented independently from the Linen API. This separation of concern simplifies the implementation, while potentially allowing alternative `Module` abstractions to build upon a common core for lifting and state management.\n\n\n### Implementation\n\nThe `pack(fn, in_vars, out_vars, rngs)` API goes through the following stages:\n\n\n1. *Scope de-duplication*\n\n    This stage is only relevant if multiple Scopes are lifted together.\n    In this case we must first find the set of root scopes.\n    A scope is a root if none of its ancestors are in the set of scopes that need to be lifted.\n\n    By only lifting roots we avoid lifting the same variables twice.\n\n    For non-root scopes we store a reference to its ancestor scope and a path such that we can later reconstruct it (stage 4).\n\n2. *Filter stage*\n\n    Variables and PRNG sequences are split up into groups. This way `fn` can lift each group into the transformation separately.\n    A group is defined by a filter specified as:\n    - a list of collections/prng names\n    - `True` (match everything)\n    - `False` (match nothing)\n    - `DenyList(filter)` (match everything but the specified collections (e.g.: `DenyList(['params'])` matches everything except the 'params' collection.)).\n\n    A collection or PRNG sequence can only be put into a single group. If a collection matches multiple filters, it will be put into the first group with a matching filter.\n    If a collection or PRNG sequence does not match any filter it will not be lifted.\n    This means that it cannot be used inside the transformation and attempting to do this will cause an error to be raised.\n    For example, `in_vars = ([\"params\"], True)` will cause the \"params\" collection to be put in the first group and all other collection to be put in the second group.\n\n    For each PRNG sequence that is matched we seed a new PRNG sequence by calling `make_rng`.\n    This avoids the need to update the PRNG state after the lifted transformation is complete.\n\n3. *Transform-specific lifting*\n\n    `fn` is called with the variable and PRNG groups.\n    JAX transforms have varying signatures and lifting options. Arguably the cleanest example is `vmap`.\n    In the case of vmap the function arguments, PRNGs and variable collections are passed into a `jax.vmap` wrapped function.\n\n4. *Scope reconstruction*\n\n    Now that the variables and PRNGs are lifted inside the transformation, we want to recreate the lifted scopes. Pack calls\n    `fn` with a `scope_fn` that takes the lifted variables and PRNGs and returns the reconstructed scopes with the lifted variables and rng sequences.\n\n5. *Repack stage*\n\n    After we have used the lifted scopes we have to retrieve the updated variables (PRNG sequences can simply be discarded).\n    pack passes the `repack_fn` to support this.\n    This stage is similar to stage 2 except that we only lift variables and immutable variables are ignored.\n    Immutable variables cannot be updated. Therefore, they should not be returned from the transformed function.\n\n6. *Commit stage*\n\n    `pack` expects `fn` to return a pair where the first item will simply be returned from pack and the second item should be the repacked variables.\n    The updated variables are stored in the original/un-lifted scopes such that the mutations that happen inside the transformation survive after the transformation completes.\n\n\n### Using pack example\n\n\nA minimal example of using `pack` to transpose each matrix in a variable collection:\n\n```python\nfrom flax.core import lift\nfrom flax.core import Scope, init, apply, nn as core_nn\n\ndef lift_transpose(fn, target='params', variables=True, rngs=True):\n  # by default we transpose 'params' and simply pass through all other variables.\n  def wrapper(scope_fn, repack_fn, variable_groups, rng_groups, *args):\n    # normally we would first call into a JAX transformed function here...\n    target, rest = variable_groups\n    def trans(x):\n      if x.ndim == 2:\n        return x.T\n      return x\n    target = jax.tree_util.tree_map(trans, target)\n    variable_groups = (target, rest)\n    scope = scope_fn(variable_groups, rng_groups)\n    y = fn(scope, *args)\n    out_variables = repack_fn(scope)\n    return y, out_variables\n  return lift.pack(\n      wrapper,\n      in_variable_filters=(target, variables),\n      out_variable_filters=(variables,),\n      rng_filters=(rngs,))\n\nx = jnp.ones((3, 2))\ny, params = init(lift_transpose(core_nn.dense))(random.key(0), x, 4)\n```\n\nNOTE that most users should not need to interact with `pack` directly.\nPlease open a GitHub issue when you find a use case that is not supported yet by the existing lifted transformations.\n\n### Supported transformations\n\n| Jax Transform | Supported in Linen? | Comments |\n|-|-|-|\n| vmap | ✅ |  |\n| scan | ✅ | Carry variables cannot be initialized inside the scan body. |\n| remat | ✅ |  |\n| jit | ✅ | Current implementation might cause unnecessary recompilation. |\n| jvp | ✅ |  |\n| vjp | ✅ |  |\n| custom_vjp | ✅ |  |\n| custom_jvp | ❌ |  |\n| while_loop | ✅ | Carry variables cannot be initialized inside the while_loop body. |\n| cond | ✅ | Variable initialization / mutation must structurally match across branches. |\n| switch | ✅ | Variable initialization / mutation must structurally match across branches. |\n| pmap | ❌ |  |\n| xmap | ❌ |  |\n\nReferences:\n- [Linen transforms documentation](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/transformations.html)\n- [Linen transforms source code](https://github.com/google/flax/blob/main/flax/linen/transforms.py)\n- [Core lifting source code](https://github.com/google/flax/blob/main/flax/core/lift.py)\n\n### Linen examples\n\nGoing back to our original example, we can now use `nn.vmap` to simplify our implementation:\n\n```python\nclass LinenVmapMLP(nn.Module):\n  @nn.compact\n  def __call__(self, xs):\n    VmapMLP = nn.vmap(MLP, variable_axes={'params': 0}, split_rngs={'params': True}, in_axes=0)\n    return VmapMLP(name='mlp')(xs)\n\nvariables = LinenVmapMLP().init(random.key(0), xs)\nprint(jax.tree_util.tree_map(jnp.shape, variables['params']))\n\"\"\"==>\n{\n    mlp: {\n        Dense_0: {\n            bias: (3, 4),\n            kernel: (3, 2, 4),\n        },\n        Dense_1: {\n            bias: (3, 1),\n            kernel: (3, 4, 1),\n        },\n    },\n}\n\"\"\"\n```\n\nHere we use `variable_axes={'params': 0}` to indicate that parameters are vectorized rather than shared and `split_rngs={'params': True}` means each set of parameters is initialized independently.\n\nWe can also extend the example with some inner state by adding a `BatchNorm` layer:\n\n```python\nclass StatefulMLP(nn.Module):\n  @nn.compact\n  def __call__(self, x, *, train):\n    h = nn.Dense(4, name='hidden')(x)\n    h = nn.BatchNorm(axis_name='batch')(h, use_running_average=not train)\n    h = nn.relu(h)\n    return nn.Dense(1, name='out')(h)\n\nclass LinenStatefulVmapMLP(nn.Module):\n  @nn.compact\n  def __call__(self, xs, *, train):\n    VmapMLP = nn.vmap(StatefulMLP, variable_axes={'params': 0, 'batch_stats': 0}, split_rngs={'params': True}, in_axes=0)\n    return VmapMLP(name='mlp')(xs, train=train)\nvariables = LinenStatefulVmapMLP().init(random.key(0), xs)\n```\n\nAll we had to add to `nn.vmap` is `'batch_stats': 0`, indicating that the batch stats are vectorized rather than shared along the first axis.\n\n\n## Alternatives\n\nOther numerical computation frameworks consider variables a first-class citizen.\nAn alternative to functionalization would be to use a variable system either integrated or on top of JAX.\nAn advantage of this is that per-variable lifting becomes easier.\nIf variables are part of the JAX IR (JAXPR), we could inspect which variables have to be lifted in a certain computation.\nOptionally, they could be annotated with a collection tag to decide on various lifting options.\n\nThe downside of this approach is that a variable system is more complicated.\nVariables are related references and break a core assumption of Functional Programming (see [referential transparency](https://en.wikipedia.org/wiki/Referential_transparency))\nOther APIs that currently have a functional interface would probably require integration as well (e.g.: checkpointing and optimization APIs).\n"
  },
  {
    "path": "docs/developer_notes/module_lifecycle.rst",
    "content": "The Flax Module lifecycle\n#########################\n\n.. testsetup::\n\n  from typing import Any, Callable, Iterable\n  import flax\n  from flax import linen as nn\n  from jax import random\n  import jax\n\n\nThis design note is intended for users who are already familiar with Flax Linen Modules but want to understand more about the design principles behind the abstraction. This note should give you a good understanding of the assumptions and guarantees the Module API is built upon. If you have no practical experience with Modules yet, check out the `Quickstart guide <https://flax.readthedocs.io/en/latest/quick_start.html>`_.\n\nFlax Linen Modules offer a Pythonic abstraction on top of Flax core. The `Module <https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html>`_ abstraction allows you to create classes that have state, parameters and randomness on top of JAX. This is a practical guide to the design and behavior of the ``Module`` class. By the end, you should feel comfortable to go off the beaten track and use Modules in new ways.\n\n\nOverview\n***********\n\nDefinition\n=============\n\nLet's start with a high-level overview of the Module lifecycle. First, define a simple Module:\n\n\n.. testcode::\n\n  class MLP(nn.Module):\n    # 1. Attribute annotations\n    hidden_size: int\n    out_size: int\n\n    # 2. The ``setup`` method\n    def setup(self):\n      self.hidden = nn.Dense(self.hidden_size)\n      self.out = nn.Dense(self.out_size)\n\n    # 3. User methods\n    def __call__(self, x):\n      a = self.hidden(x)\n      h = nn.relu(a)\n      return self.out(h)\n\n\nThis Module consists of:\n\n#. **Attribute annotations**, defined as `dataclass <https://docs.python.org/3/library/dataclasses.html>`_ fields. These annotations automatically define a constructor.\n#. **The ``setup`` method**, which creates submodules and assigns them to attributes.\n#. **User methods**. By convention, most Modules have just one  ``__call__`` method, but you can define multiple methods or use different method names.\n\nConstruction/initialization\n=============================\n\nNow we want to construct and use the ``MLP`` Module:\n\n\n.. testcode::\n\n  mlp = MLP(hidden_size=5, out_size=3)\n  x = jax.numpy.ones((1, 2))\n  variables = mlp.init(random.key(0), x)\n  y = mlp.apply(variables, x)\n\n\nFirst, we construct an instance of ``MLP`` and pass the construction attributes. Note that construction here is different from what you might expect if you are not used to Functional Programming patterns. The ``MLP`` constructor does not actually create variables or any internal state whatsoever. It's best to think of it as a specification or template of the Module that contains functionality but no data.\n\nLet's take a closer look at initialization. Surprisingly, there actually is no separate initialization path in Flax. Calling ``init`` is just a special case of ``apply``, which you can also write as:\n\n\n.. testcode::\n\n  # equivalent to: variables = mlp.init(random.key(0), x)\n  _, variables = mlp.apply({}, x, rngs={\"params\": random.key(0)}, mutable=True)\n\n\nThus, ``init`` is nothing more than a wrapper around ``apply`` where:\n\n#. We call a Module without any initial variables (an empty dict).\n#. A PRNG generator named ``\"params\"`` is always passed for randomly initializing parameters (using the parameter initialization function).\n#. All variable collections are set to mutable (``mutable=True``). When a collection is mutable, existing variables can be updated and new variables can be created. Thus, inside ``init`` variables can be initialized in any variable collection and they are all added to the returned variable dictionary.\n\nLifecycle\n=============\n\n\nNow that you have learned about ``init`` being a special case of ``apply``, let's look at ``.apply(...)`` in more detail. In fact, most of the complexity of Modules resides in the ``apply`` method. The \"Module lifecycle\" consists of constructing and ``apply``-ing a Module. We can summarize the Module lifecycle as follows:\n\n\n#. We construct ``mlp = MLP(hidden_size=5, out_size=3)``, such that ``mlp.hidden_size=5`` and ``mlp.out_size=3``.\n\n#. Then, call ``mlp.apply``, which:\n\n   #. Makes a clone of ``mlp``, let's call it ``mlp_copy``.\n\n   #. Calls ``mlp_copy.setup()``.\n\n   #. Returns the output of ``mlp_copy.__call__()`` and optionally the variable collections that were specified as mutable using the keyword argument ``mutable=``.\n\nNotice that the lifecycle includes cloning the Module instance. This is done to ensure that ``apply`` can be treated as a pure function (i.e., if you pass the same arguments in, it will return the same outputs). You will learn about this in more detail later in the  :ref:`Top-level Modules` section.\n\nVariables\n==========\n\nThe word “variable” is ubiquitous in programming and math. However, it's important to have a good understanding of what variables are in the context of JAX and Flax. Inside Flax Modules, `variables <https://flax.readthedocs.io/en/latest/api_reference/flax.linen/variable.html>`_ act like you expect from Python. They are initialized once, read, and perhaps even updated every so often. However, JAX has no concept of variables. Instead, values are stored in arrays similar to NumPy arrays - with one important difference: they are immutable.\n\nThe ``init`` and ``apply`` methods return the variables as a nested dictionary with string keys and JAX arrays at the leaves. At the top level each key corresponds to a variable collection. Inside each collection the nested dict structure corresponds with the ``Module`` hierarchy. The variable dict is immutable and therefore really just a snapshot of state the variables are in. When ``apply`` is called again, the variable dict is passed as an argument. Such that the variables are in the same state as when the previous ``init`` / ``apply`` call finished.\n\n\n.. note::\n   Module fields are declared using the `field_name: TypeHint` syntax (same as dataclasses). Without a type hint, an attribute is considered a static property of the class. In case you cannot specify the type you can use ``typing.Any`` as a wildcard type.\n\n\nCompact Modules\n******************\n\nLinen provides an alternative API for defining modules more compactly. This is especially useful for the common case where the Module consists of only one method that uses parameters and/or sub-modules. Using the compact API the MLP can be rewritten as follows:\n\n\n.. testcode::\n\n  class CompactMLP(nn.Module):\n    hidden_size: int\n    out_size: int\n\n    @nn.compact\n    def __call__(self, x):\n      a = nn.Dense(self.hidden_size)(x)\n      h = nn.relu(a)\n      return nn.Dense(self.out_size)(h)\n\n\nA compact ``Module`` is similar in spirit to a function. It offers a concise notation and restricts external interaction to the inputs and return values of the function. In this case the concise notation might make it easier for others to understand what the Module does. There is no need to jump back and forth between the ``setup`` and ``__call__`` method to understand what the submodules are doing. Instead, simply reading the ``__call__`` method from top to bottom once should provide a concise overview. This can make a significant difference if you are implementing complex Modules with many hyperparameters. See `setup or compact <https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/setup_or_nncompact.html>`_ for a practical guide on deciding between setup and compact.\n\nAnother benefit of defining submodules and/or variables inline is that you can add arguments to your method when constructing variables. The most common example of this is using shape information to determine the shape of a parameter like this:\n\n\n.. testcode::\n\n  class CompactScaledMLP(nn.Module):\n    hidden_size: int\n    out_size: int\n\n    @nn.compact\n    def __call__(self, x):\n      scale = self.param(\"scale\", nn.initializers.ones_init(), x.shape[-1:])\n      x *= scale[None]\n      a = nn.Dense(self.hidden_size)(x)\n      h = nn.relu(a)\n      return nn.Dense(self.out_size)(h)\n\n\n.. testcode::\n  :hide:\n\n  mdl = CompactScaledMLP(hidden_size=4, out_size=5)\n  x = jax.numpy.ones((3, 2))\n  vars = mdl.init(random.key(0), x)\n  assert vars[\"params\"][\"scale\"].shape == (2,)\n\nMany of the standard Linen Modules like ``nn.Dense`` use shape inference already to avoid the need to specify input shapes (like the number of input features to a Dense layer).\n\nCompact control flow\n=====================\n\nThe order in which you define submodules determines the name of a submodule if none is provided explicitly (using the ``name=`` keyword argument passed to the Module's constructor). Because the ``name`` determines how parameters are mapped to submodules, you must be careful about mixing control flow with auto-generated names. Using control flow can change the order or remove certain submodules altogether. This is useful in case a submodule should only exist depending on some construction argument. However, when control flow depends on the input arguments to the Module, you should be careful. For example, the following Module will break:\n\n\n.. testcode::\n\n  class WrongModule(nn.Module):\n    @nn.compact\n    def __call__(self, x, mode):\n      if mode == \"encode\":\n        return nn.Dense(features=8)(x)\n      elif mode == \"decode\":\n        return nn.Dense(features=4)(x)\n\n\nThe above Module will break because either the encoder or decoder path will construct a Module named \"Dense_0\". This means the two Modules will share parameters which is not intended here. Actually, the two Modules cannot share parameters because they each have a different number of features.\n\nThis problem can be solved in various ways:\n - Provide explicit names\n - create the modules in ``setup``\n - or move the constructor out of the control flow.\n\nThe latter is done as follows:\n\n.. testcode::\n\n  class CorrectModule(nn.Module):\n    @nn.compact\n    def __call__(self, x, mode):\n      encoder = nn.Dense(8)\n      decoder = nn.Dense(4)\n      if mode == \"encode\":\n        return encoder(x)\n      elif mode == \"decode\":\n        return decoder(x)\n\n.. testcode::\n  :hide:\n\n  def init_fn(mdl):\n    x = jax.numpy.ones((3, 2))\n    z = mdl(x, \"encode\")\n    return mdl(z, \"decode\")\n\n  mdl = CorrectModule()\n  vars = nn.init(init_fn, mdl)(random.key(0))\n  assert vars[\"params\"][\"Dense_0\"][\"kernel\"].shape == (2, 8)\n  assert vars[\"params\"][\"Dense_1\"][\"kernel\"].shape == (8, 4)\n\n\nIn the above example the construction order is fixed. After construction the submodules can be used in an arbitrary order.\n\n.. note::\n   compact modules show a strong resemblance to `React hooks <https://reactjs.org/docs/hooks-custom.html>`_.\n\n\nTop-level Modules\n*****************\n\nWhen a Module instance is created at the \"top-level\", it will be in an \"unbound\" state - that is, it has no variables attached. \"Top-level\" means it is not constructed as a sub-Module inside another Module class. Apart from calling ``init`` and ``apply``, there is not much you can do with an unbound Module. Note also that ``setup`` is not called on unbound Modules, so you can only access the construction arguments. Refer to the :ref:`Future work` section to learn how this might change in the future.\n\nWhy are top-level Modules always unbound?\n===============================================\n\nWhen we call ``apply``, a copy of the top-level Module is created which will actually hold the variables and PRNG sequences. This stateful, \"bound\", clone only exists while we are executing the apply method. The reason for this is that if you create a stateful object and destroy it before the apply function returns, the ``apply`` function itself behaves like a pure function. A pure function has two constraints:\n\n#. If you put the same arguments in, it will return the same outputs\n#. It does not change anything outside the function. This means you cannot manipulate stateful objects that are accessible outside the pure function.\n\n\nPure functions have many advantages but when using JAX they are often essential. For example, most code requires compilation using ``jax.jit`` to be fast and once you created a Module you probably want to optimize its parameters using ``jax.grad``. However, these APIs expect a pure function and don't work on stateful bound ``Module`` instances directly. Moreover, pure functions allow for flexible interoperability with other libraries. For example, We recommend `Optax <https://github.com/deepmind/optax>`_ for optimizing parameters. The optimizers in Optax expect and return a PyTree of JAX arrays to optimize, just like the ``apply`` function of a Linen Module.\n\nCloning\n===============================================\n\nTo make this approach work reliably we need well-defined cloning behavior. Rather than relying on a complex nested cloning procedure like Python's ``deepcopy``, Flax enforces that a ``Module`` is exactly defined by its construction arguments. Therefore cloning a Module reduces to calling the constructor with its original construction arguments. Because ``Module`` acts as an immutable dataclass, the construction arguments are mapped directly to instance attributes. Non-construction attributes that are computed in ``setup`` or ``__post_init__`` should also depend only on the construciton arguments to ensure a well-defined clone.\n\nBind\n===============================================\n\nSometimes it's useful to have a bound, top-level Module without having to wrap the code in a function. For example: to interact with a Module inside a Jupyter notebook. The `bind <https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.bind>`_ method returns a bound clone with an unlimited lifetime. The downside of this is that you cannot combine it with JAX transformations or integrate it into a vanilla JAX codebase that expects stateless code. For example, `Optax <https://github.com/deepmind/optax>`_ can optimize a Pytree of parameters but it cannot directly optimize a bound ``Module`` instance created with ``.bind`` (because that's not a Pytree). Thus, you cannot combine the ``bind`` API with a functional optimizer API like Optax.\n\n\nSetup\n**********\n\nThe ``setup`` method is often used like the constructor hook (``__init__``) in normal Python classes. However, for more advanced use cases it's good to realize that it is not quite the same as a constructor.\n\n``setup`` is only called after a Module becomes bound. Normally, this is not an issue because most Modules are bound (almost) immediately (as part of ``init`` and ``apply``). Inside ``setup``, sub-modules become bound when they are assigned to an attribute. Inside an ``nn.compact`` decorated method, sub-modules are bound immediately when constructed. As explained in the previous section, top-level Modules are never bound and thus setup is not called when they are constructed. This means you cannot access attributes assigned in setup from an unbound, top-level module.\n\n.. testcode::\n\n  class TopLevelAccess(nn.Module):\n\n    def setup(self):\n      self.foo = nn.Dense(2)\n\n  mdl = TopLevelAccess()\n  assert not hasattr(mdl, \"foo\")  # foo is not defined because setup is not called\n\nThe ``setup`` method is not called immediately after the ``Module`` becomes bound but only when you interact with the ``Module`` instance (e.g.: call a method or access an attribute). This should not impact the behavior of a ``Module`` but the lazy execution does sometimes affect log statements and stack traces during debugging. The section on :ref:`Functionalization` will explain why we need ``setup`` to be lazy in the first place.\n\n\nFunctionalization\n******************\n\nSo far we had a pure ``apply`` function that is typically transformed with some JAX transformations and inside ``apply`` we have a stateful Module instance to work with. In other words: Outside of a Module we are in a functional world where we have the power of JAX's functional transformations and inside the Module we get the power of Flax's stateful variables and PRNG sequence, and the ``apply`` method is our bridge between these two worlds.\n\nBut what if we want to use JAX transformations **inside** Modules? The answer to this is functionalization.\n\nThis procedure itself is tedious and error-prone but handled internally by Flax. At a high-level we can summarize it as follows. For a method ``fn`` defined within a Module:\n\n#. Collect the state (variables & PRNG sequences) of the Module(s) that should be available inside the JAX transformation and take a snapshot of it.\n\n#. Call the JAX transformation with the original arguments and the collected state. Then inside the transformation:\n\n   #. Unpack the state and recreate the Modules\n\n   #. Call the user code ``fn``\n\n   #. Collect the updated variables and rng and return it together with the original return values from ``fn``\n\n#. Update the original state with the updated state returned from the transformation.\n\nA more in depth explanation of functionalization and lifting can be found in the `Lifted Transformation <https://flax.readthedocs.io/en/latest/developer_notes/lift.html>`_ design note.\n\nPractical consequences\n==========================\n\nFor the most part functionalization is something that is handled automatically for you. Still there are some constraints that you must take into account. Most importantly, Flax only handles the stateful primitives (Linen variables and RNGs) and not arbitrary stateful Python code. Most importantly: You cannot close over stateful objects and ``Module`` objects because they are invisible to Flax's internals (and to JAX in general).\n\n\n.. testcode::\n\n  class Foo(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      dense = nn.Dense(x.shape[-1])\n      fn = lambda x: dense(x) + 1\n      # simply calling inner works fine\n      # return self.inner(x, fn)\n      # but applying a transformation doesn't:\n      vmap_inner = nn.vmap(Foo.inner, in_axes=0, variable_axes={\"params\": 0}, split_rngs={\"params\": True})\n      return vmap_inner(self, x, fn)\n\n    def inner(self, x, fn):\n      for i in range(3):\n        x = fn(x)\n      return x\n\nHere ``inner`` takes a function that closes over a Module instance. In this example, that works fine because we are not transforming the inner method with a lifted transformation. Most methods are not transformed but it is good to know how to make Module methods transformable.\n\nThe main obstacle for transformability are types that JAX does not recognize. JAX only understands `Pytree <https://jax.readthedocs.io/en/latest/jax-101/05.1-pytrees.html>`_ arguments; i.e. arbitrarily nested Python containers (dict, list, tuple) of (Jax) numpy ndarrays and Python numbers/bools. Flax allows to define dataclasses which are Pytree compatible using the `flax.struct <https://flax.readthedocs.io/en/latest/flax.struct.html>`_ API.\n\nFunction closure is the most common way to accidentally hide a JAX array or Linen Module from a transformation. There is however an easy workaround if you want to pass closures that are also compatible with JAX and Linen transformations:\n\n\n.. testcode::\n\n  class Partial(flax.struct.PyTreeNode):\n    fn: Callable = flax.struct.field(pytree_node=False)\n    args: Iterable[Any]\n\n    def __call__(self, *args, **kwargs):\n      return self.fn(*(tuple(self.args) + args), **kwargs)\n\n  class Foo(nn.Module):\n\n    @nn.compact\n    def __call__(self, x):\n      dense = nn.Dense(x.shape[-1])\n      fn = lambda mdl, x: mdl(x) + 1\n      vmap_inner = nn.vmap(Foo.inner, in_axes=0, variable_axes={\"params\": 0}, split_rngs={\"params\": True})\n      return vmap_inner(self, x, Partial(fn, [dense]))\n\n    def inner(self, x, fn):\n      for i in range(3):\n        x = fn(x)\n      return x\n\n\n.. testcode::\n  :hide:\n\n  x = jax.numpy.ones((3, 2))\n  mdl = Foo()\n  vars = mdl.init(random.key(0), x)\n  assert vars['params']['Dense_0']['kernel'].shape == (3, 2, 2)\n\n\n\nHere the closure is implemented using a Flax dataclass. The function itself is annotated with ``flax.struct.field(pytree_node=False)`` to indicate that it does not contain JAX Arrays or Linen Modules. The partially applied ``args`` on the other hand is treated as a pytree container. We rewrite the closure to use Partial. Now the inner method can be transformed using lifted transformations.\n\n\nFuture work\n*************\n\n\nSetup for unbound Modules\n===========================\n\nThe current Module abstraction is particularly restrictive when it comes to initializing fields after construction. In the current Module API, the ``setup`` method is the place to initialize the fields of the Module instance. Because ``setup`` is only called on a bound Module, the full Module API is available inside ``setup``, including variable declaration. However, oftentimes we don't actually require any stateful API's to initialize a field. In fact, most commonly we simply want to declare a submodule. More importantly, it's often useful to inspect submodules for debugging or to partially run the model. Consider for example:\n\n\n.. testcode::\n\n  class AutoEncoder(nn.Module):\n    def setup(self):\n      self.encoder = Encoder(...)\n      self.decoder = Decoder(...)\n\n\nImagine we want to call just the decoder using `auto_encoder.decoder.apply(decoder_variables, x)`. With the current setup API this does not work because we must first bind the variables before setup is called and the decoder attribute is defined. Of course we can manually construct the Decoder Module with the same attributes as in setup but this is not ideal in many cases.\n\nThere are two possible solutions to make this use case more ergonomic. First, setup could be made to run immediately after construction before it becomes bound. This means you can still create sub modules but you can no longer define or manipulate variables. Therefore, this would be a breaking change and it would require a new API for defining variables lazily\n\nAlternatively, an additional special method could be introduced that runs right away after Module construction and before it becomes bound. In this case, the ``setup`` method would preserve its original semantics.\n"
  },
  {
    "path": "docs/examples/community_examples.rst",
    "content": "Community examples\n==================\n\nIn addition to the `curated list of official Flax examples on GitHub <https://github.com/google/flax/tree/main/examples>`__,\nthere is a growing community of people using Flax to build new types of machine\nlearning models. We are happy to showcase any example built by the community here!\n\nIf you want to submit your own Flax example, you can start by forking\none of the `official Flax examples on GitHub <https://github.com/google/flax/tree/main/examples>`__.\n\nModels\n******\n.. list-table::\n    :header-rows: 1\n\n    * - Link\n      - Author\n      - Task type\n      - Reference\n    * - `matthias-wright/flaxmodels <https://github.com/matthias-wright/flaxmodels>`__\n      - `@matthias-wright <https://github.com/matthias-wright>`__\n      - Various\n      - GPT-2, ResNet, StyleGAN-2, VGG, ...\n    * - `DarshanDeshpande/jax-models <https://github.com/DarshanDeshpande/jax-models>`__\n      - `@DarshanDeshpande <https://github.com/DarshanDeshpande>`__\n      - Various\n      - Segformer, Swin Transformer, ... also some stand-alone layers\n    * - `google/vision_transformer <https://github.com/google-research/vision_transformer>`__\n      - `@andsteing <https://github.com/andsteing>`__\n      - Image classification, image/text\n      - https://arxiv.org/abs/2010.11929, https://arxiv.org/abs/2105.01601, https://arxiv.org/abs/2111.07991, ...\n    * - `jax-resnet <https://github.com/n2cholas/jax-resnet>`__\n      - `@n2cholas <https://github.com/n2cholas>`__\n      - Various resnet implementations\n      - `torch.hub <https://pytorch.org/docs/stable/hub.html>`__\n    * - `Wav2Vec2 finetuning <https://github.com/vasudevgupta7/speech-jax>`__\n      - `@vasudevgupta7 <https://github.com/vasudevgupta7>`__\n      - Automatic Speech Recognition\n      - https://arxiv.org/abs/2006.11477\n\nExamples\n********\n\n.. list-table::\n    :header-rows: 1\n\n    * - Link\n      - Author\n      - Task type\n      - Reference\n    * - `JAX-RL <https://github.com/henry-prior/jax-rl>`__\n      - `@henry-prior <https://github.com/henry-prior>`__\n      - Reinforcement learning\n      - N/A\n    * - `BigBird Fine-tuning <https://github.com/huggingface/transformers/tree/master/examples/research_projects/jax-projects/big_bird>`__\n      - `@vasudevgupta7 <https://github.com/vasudevgupta7>`__\n      - Question-Answering\n      - https://arxiv.org/abs/2007.14062\n    * - `DCGAN <https://github.com/bkkaggle/jax-dcgan>`__\n      - `@bkkaggle <https://github.com/bkkaggle>`__\n      - Image Synthesis\n      - https://arxiv.org/abs/1511.06434\n    * - `denoising-diffusion-flax <https://github.com/yiyixuxu/denoising-diffusion-flax>`__\n      - `@yiyixuxu <https://github.com/yiyixuxu>`__\n      - Image generation\n      - https://arxiv.org/abs/2006.11239\n\nTutorials\n*********\n\n.. currently left empty as a placeholder for tutorials\n.. list-table::\n    :header-rows: 1\n\n    * - Link\n      - Author\n      - Task type\n      - Reference\n    * -\n      -\n      -\n      -\n\nContributing policy\n*******************\n\nIf you are interested in adding a project to the Community Examples section, take the following\ninto consideration:\n\n* **Code examples**: Examples must contain a README that is helpful, clear, and explains\n  how to run the code. The code itself should be easy to follow.\n* **Tutorials**: These docs should preferrably be a Jupyter Notebook format\n  (refer to `Contributing <https://flax.readthedocs.io/en/latest/contributing.html>`__\n  to learn how to convert a Jupyter Notebook into a Markdown file with `jupytext`).\n  Your tutorial should be well-written, and discuss/describe an interesting topic/task.\n  To avoid duplication, the content of these docs must be different from\n  `existing docs on the Flax documentation site <https://flax.readthedocs.io/>`__\n  or other community examples mentioned in this document.\n* **Models**: repositories with models ported to Flax must provide at least one of the following:\n\n  * Metrics that are comparable to the original work when the model is trained to completion. Having\n    available plots of the metric's history during training is highly encouraged.\n  * Tests to verify numerical equivalence against a well known implementation (same inputs\n    + weights = same outputs) preferably using pretrained weights.\n\nIn all cases mentioned above, the code must work with the latest stable versions of the\nfollowing packages: ``jax``, ``flax``, and ``optax``, and make substantial use of Flax.\nNote that both ``jax`` and ``optax`` are `required packages <https://github.com/google/flax/blob/main/setup.py>`__\nof ``flax`` (refer to the `installation instructions <https://github.com/google/flax/blob/main/README.md#quick-install>`__\nfor more details).\n"
  },
  {
    "path": "docs/examples/core_examples.rst",
    "content": "Core examples\n=============\n\nCore examples are hosted on the GitHub Flax repository in the `examples <https://github.com/google/flax/tree/main/examples>`__\ndirectory.\n\nEach example is designed to be **self-contained and easily forkable**, while\nreproducing relevant results in different areas of machine learning.\n\nAs discussed in `#231 <https://github.com/google/flax/issues/231>`__, we decided\nto go for a standard pattern for all examples including the simplest ones (like MNIST).\nThis makes every example a bit more verbose, but once you know one example, you\nknow the structure of all of them. Having unit tests and integration tests is also\nvery useful when you fork these examples.\n\nSome of the examples below have a link \"Interactive🕹\" that lets you run them\ndirectly in Colab.\n\nImage classification\n********************\n\n- :octicon:`mark-github;0.9em` `MNIST <https://github.com/google/flax/tree/main/examples/mnist/>`__ -\n  `Interactive🕹 <https://colab.research.google.com/github/google/flax/blob/main/examples/mnist/mnist.ipynb>`__:\n  Convolutional neural network for MNIST classification (featuring simple\n  code).\n\n- :octicon:`mark-github;0.9em` `ImageNet <https://github.com/google/flax/tree/main/examples/imagenet/>`__ -\n  `Interactive🕹 <https://colab.research.google.com/github/google/flax/blob/main/examples/imagenet/imagenet.ipynb>`__:\n  Resnet-50 on ImageNet with weight decay (featuring multi-host SPMD, custom\n  preprocessing, checkpointing, dynamic scaling, mixed precision).\n\nReinforcement learning\n**********************\n\n- :octicon:`mark-github;0.9em` `Proximal Policy Optimization <https://github.com/google/flax/tree/main/examples/ppo/>`__:\n  Learning to play Atari games (featuring single host SPMD, RL setup).\n\nNatural language processing\n***************************\n\n-  :octicon:`mark-github;0.9em` `Sequence to sequence for number\n   addition <https://github.com/google/flax/tree/main/examples/seq2seq/>`__:\n   (featuring simple code, LSTM state handling, on the fly data generation).\n-  :octicon:`mark-github;0.9em` `Parts-of-speech\n   tagging <https://github.com/google/flax/tree/main/examples/nlp_seq/>`__: Simple\n   transformer encoder model using the universal dependency dataset.\n-  :octicon:`mark-github;0.9em` `Sentiment\n   classification <https://github.com/google/flax/tree/main/examples/sst2/>`__:\n   with a LSTM model.\n-  :octicon:`mark-github;0.9em` `Transformer encoder/decoder model trained on\n   WMT <https://github.com/google/flax/tree/main/examples/wmt/>`__:\n   Translating English/German (featuring multihost SPMD, dynamic bucketing,\n   attention cache, packed sequences, recipe for TPU training on GCP).\n-  :octicon:`mark-github;0.9em` `Transformer encoder trained on one billion word\n   benchmark <https://github.com/google/flax/tree/main/examples/lm1b/>`__:\n   for autoregressive language modeling, based on the WMT example above.\n\nGenerative models\n*****************\n\n-  :octicon:`mark-github;0.9em` `Variational\n   auto-encoder <https://github.com/google/flax/tree/main/examples/vae/>`__:\n   Trained on binarized MNIST (featuring simple code, vmap).\n\nGraph modeling\n**************\n\n- :octicon:`mark-github;0.9em` `Graph Neural Networks <https://github.com/google/flax/tree/main/examples/ogbg_molpcba/>`__:\n  Molecular predictions on ogbg-molpcba from the Open Graph Benchmark.\n\nContributing to core Flax examples\n**********************************\n\nMost of the `core Flax examples on GitHub <https://github.com/google/flax/tree/main/examples>`__\nfollow a structure that the Flax dev team found works well with Flax projects.\nThe team strives to make these examples easy to explore and fork. In particular\n(as per GitHub Issue `#231 <https://github.com/google/flax/issues/231>`__):\n\n- README: contains links to paper, command line, `TensorBoard <https://tensorboard.dev/>`__ metrics.\n- Focus: an example is about a single model/dataset.\n- Configs: we use ``ml_collections.ConfigDict`` stored under ``configs/``.\n- Tests: executable ``main.py`` loads ``train.py`` which has ``train_test.py``.\n- Data: is read from `TensorFlow Datasets <https://www.tensorflow.org/datasets>`__.\n- Standalone: every directory is self-contained.\n- Requirements: versions are pinned in ``requirements.txt``.\n- Boilerplate: is reduced by using `clu <https://pypi.org/project/clu/>`__.\n- Interactive: the example can be explored with a `Colab <https://colab.research.google.com/>`__."
  },
  {
    "path": "docs/examples/google_research_examples.rst",
    "content": "########################\nGoogle Research examples\n########################\n\nA collection of research by Google Research made with Flax.\n\nAttention\n*********\n\nFast Attention (FAVOR+) and Rethinking Attention with Performers\n================================================================\n\n- Code on GitHub:\n\n  - `Performer's Fast Attention (FAVOR+) module <https://github.com/google-research/google-research/tree/master/performer/fast_attention>`__\n\n- Research paper:\n\n  - `Rethinking Attention with Performers <https://arxiv.org/abs/2009.14794>`__ (Choromanski et al., 2020)\n\n    - Introduces *\"Performers, Transformer architectures which can estimate regular (softmax) full-rank-attention Transformers with provable accuracy, but using only linear (as opposed to quadratic) space and time complexity, without relying on any priors such as sparsity or low-rankness. To approximate softmax attention-kernels, Performers use a novel Fast Attention Via positive Orthogonal Random features approach (FAVOR+), which may be of independent interest for scalable kernel methods. FAVOR+ can be also used to efficiently model kernelizable attention mechanisms beyond softmax.\"*\n\nSelf-attention Does Not Need O(n^2) Memory\n==========================================\n\n- `Code on GitHub <https://github.com/google-research/google-research/tree/master/memory_efficient_attention>`__\n- `Colab notebook <https://github.com/google-research/google-research/blob/master/memory_efficient_attention/memory_efficient_attention.ipynb>`__\n\n- Research paper:\n\n  - `Self-attention Does Not Need O(n^2) Memory <https://arxiv.org/abs/2112.05682>`__ (Rabe and Staats, 2021)\n\n    - *\"We present a very simple algorithm for attention that requires O(1) memory with respect to sequence length and an extension to self-attention that requires O(log n) memory. This is in contrast with the frequently stated belief that self-attention requires O(n^2) memory. While the time complexity is still O(n^2), device memory rather than compute capability is often the limiting factor on modern accelerators. Thus, reducing the memory requirements of attention allows processing of longer sequences than might otherwise be feasible...\"*\n\nComputer vision\n***************\n\nColorization Transformer (ColTran)\n==================================\n\n- `Code on GitHub <https://github.com/google-research/google-research/tree/master/coltran>`__\n\n- Research paper:\n\n  - `Colorization Transformer <https://openreview.net/forum?id=5NA1PinlGFu>`__ (Kumar et al., 2020)\n\n    - *\"We presented the Colorization Transformer (ColTran), an architecture that entirely relies on self-attention for image colorization. We introduce conditional transformer layers, a novel building block for conditional, generative models based on self-attention. Our ablations show the superiority of employing this mechanism over a number of different baselines. Finally, we demonstrate that ColTran can generate diverse, high-fidelity colorizations on ImageNet, which are largely indistinguishable from the ground-truth even for human raters.\"*\n\nVision Transformer (ViT), MLP-Mixer Architectures *and* Big Vision\n==================================================================\n\n- Code on GitHub:\n\n  - `Vision Transformer and MLP-Mixer Architectures <https://github.com/google-research/vision_transformer>`__\n\n  - `Big Vision <https://github.com/google-research/big_vision>`__\n\n    - *\"This codebase is designed for training large-scale vision models using Cloud TPU VMs or GPU machines. It is based on Jax/Flax libraries, and uses tf.data and TensorFlow Datasets for scalable and reproducible input pipelines.\"*\n\n- `Colab notebooks <https://github.com/google-research/vision_transformer#colab>`__:\n\n  - The JAX code of Vision Transformers and MLP Mixers\n  - More than 50k Vision Transformer and hybrid checkpoints that were used to generate the data of \"How to train your ViT?\"\n\n- Research papers:\n\n  - `An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale <https://arxiv.org/abs/2010.11929>`__ (Dosovitskiy et al., 2020)\n\n    - *\"In vision, attention is either applied in conjunction with convolutional networks, or used to replace certain components of convolutional networks while keeping their overall structure in place. We show that this reliance on CNNs is not necessary and a pure transformer applied directly to sequences of image patches can perform very well on image classification tasks. When pre-trained on large amounts of data and transferred to multiple mid-sized or small image recognition benchmarks (ImageNet, CIFAR-100, VTAB, etc.), Vision Transformer (ViT) attains excellent results compared to state-of-the-art convolutional networks while requiring substantially fewer computational resources to train.\"*\n\n  - `MLP-Mixer: An All-MLP Architecture for Vision <https://arxiv.org/abs/2105.01601>`__ (Tolstikhin et al., 2021)\n\n    - *\"In this paper we show that while convolutions and attention are both sufficient for good performance, neither of them are necessary. We present MLP-Mixer, an architecture based exclusively on multi-layer perceptrons (MLPs). MLP-Mixer contains two types of layers: one with MLPs applied independently to image patches (i.e. \"mixing\" the per-location features), and one with MLPs applied across patches (i.e. \"mixing\" spatial information). When trained on large datasets, or with modern regularization schemes, MLP-Mixer attains competitive scores on image classification benchmarks, with pre-training and inference cost comparable to state-of-the-art models.\"*\n\n  - `How to Train Your ViT? Data, Augmentation, and Regularization in Vision Transformers <https://arxiv.org/abs/2106.10270>`__ (Steiner et al., 2021)\n\n    - *\"Vision Transformers (ViT) have been shown to attain highly competitive performance for a wide range of vision applications, such as image classification, object detection and semantic image segmentation. In comparison to convolutional neural networks, the Vision Transformer's weaker inductive bias is generally found to cause an increased reliance on model regularization or data augmentation (\"AugReg\" for short) when training on smaller training datasets. We conduct a systematic empirical study in order to better understand the interplay between the amount of training data, AugReg, model size and compute budget.\"*\n\n  - `When Vision Transformers Outperform ResNets without Pretraining or Strong Data Augmentations <https://arxiv.org/abs/2106.01548>`__ (X. Chen et al., 2021)\n\n    - *\"Vision Transformers (ViTs) and MLPs signal further efforts on replacing hand-wired features or inductive biases with general-purpose neural architectures. Existing works empower the models by massive data, such as large-scale pre-training and/or repeated strong data augmentations, and still report optimization-related problems (e.g., sensitivity to initialization and learning rates). Hence, this paper investigates ViTs and MLP-Mixers from the lens of loss geometry, intending to improve the models' data efficiency at training and generalization at inference.\"*\n\n  - `LiT: Zero-Shot Transfer with Locked-image Text Tuning <https://arxiv.org/abs/2111.07991>`__ (X. Zhai et al., 2021)\n\n    - *\"This paper presents contrastive-tuning, a simple method employing contrastive training to align image and text models while still taking advantage of their pre-training. In our empirical study we find that locked pre-trained image models with unlocked text models work best. We call this instance of contrastive-tuning \"Locked-image Tuning\" (LiT), which just teaches a text model to read out good representations from a pre-trained image model for new tasks. A LiT model gains the capability of zero-shot transfer to new vision tasks, such as image classification or retrieval. The proposed LiT is widely applicable; it works reliably with multiple pre-training methods (supervised and unsupervised) and across diverse architectures (ResNet, Vision Transformers and MLP-Mixer) using three different image-text datasets.\"*\n\nScaling Vision with Sparse Mixture of Experts (MoE)\n===================================================\n\n- `Code on GitHub <https://github.com/google-research/vmoe>`__\n- Research paper:\n\n  - `Scaling Vision with Sparse Mixture of Experts <https://arxiv.org/abs/2106.05974>`__ (Riquelme et al., 2021)\n\n    - *\"Sparsely-gated Mixture of Experts networks (MoEs) have demonstrated excellent scalability in Natural Language Processing. In Computer Vision, however, almost all performant networks are \"dense\", that is, every input is processed by every parameter. We present a Vision MoE (V-MoE), a sparse version of the Vision Transformer, that is scalable and competitive with the largest dense networks... we demonstrate the potential of V-MoE to scale vision models, and train a 15B parameter model that attains 90.35% on ImageNet...\"*\n\nDiffusion\n*********\n\nVariational Diffusion Models\n============================\n\n- `Code on GitHub <https://github.com/google-research/vdm/tree/main>`__\n- `Colab notebooks <https://github.com/google-research/vdm/tree/main/colab>`__\n- Research paper:\n\n  - `Variational Diffusion Models <https://arxiv.org/abs/2107.00630>`__ (Kingma et al., 2021)\n\n    - *\"Diffusion-based generative models have demonstrated a capacity for perceptually impressive synthesis, but can they also be great likelihood-based models? We answer this in the affirmative, and introduce a family of diffusion-based generative models that obtain state-of-the-art likelihoods on standard image density estimation benchmarks. Unlike other diffusion-based models, our method allows for efficient optimization of the noise schedule jointly with the rest of the model. We show that the variational lower bound (VLB) simplifies to a remarkably short expression in terms of the signal-to-noise ratio of the diffused data, thereby improving our theoretical understanding of this model class. Using this insight, we prove an equivalence between several models proposed in the literature. In addition, we show that the continuous-time VLB is invariant to the noise schedule, except for the signal-to-noise ratio at its endpoints. This enables us to learn a noise schedule that minimizes the variance of the resulting VLB estimator, leading to faster optimization...\"*\n\nDomain adaptation\n*****************\n\nGIFT (Gradual Interpolation of Features toward Target)\n======================================================\n\n- `Code on GitHub <https://github.com/google-research/google-research/tree/master/gift>`__\n- Research paper:\n\n  - `Gradual Domain Adaptation in the Wild: When Intermediate Distributions are Absent <https://arxiv.org/abs/2106.06080>`__ (Abnar et al., 2021)\n\n    - *\"We focus on the problem of domain adaptation when the goal is shifting the model towards the target distribution, rather than learning domain invariant representations. It has been shown that under the following two assumptions: (a) access to samples from intermediate distributions, and (b) samples being annotated with the amount of change from the source distribution, self-training can be successfully applied on gradually shifted samples to adapt the model toward the target distribution. We hypothesize having (a) is enough to enable iterative self-training to slowly adapt the model to the target distribution, by making use of an implicit curriculum. In the case where (a) does not hold, we observe that iterative self-training falls short. We propose GIFT, a method that creates virtual samples from intermediate distributions by interpolating representations of examples from source and target domains...\"*\n\nGeneralization\n**************\n\nSurrogate Gap Minimization Improves Sharpness-Aware Training\n============================================================\n\n- `Code on GitHub <https://github.com/google-research/big_vision/tree/main/big_vision/trainers/proj/gsam>`__\n- Research paper:\n\n  - `Surrogate Gap Minimization Improves Sharpness-Aware Training <https://arxiv.org/abs/2203.08065>`__ (J. Zhuang et al., 2022)\n\n    - *\"The recently proposed Sharpness-Aware Minimization (SAM) improves generalization by minimizing a perturbed loss defined as the maximum loss within a neighborhood in the parameter space. However, we show that both sharp and flat minima can have a low perturbed loss, implying that SAM does not always prefer flat minima. Instead, we define a surrogate gap, a measure equivalent to the dominant eigenvalue of Hessian at a local minimum when the radius of neighborhood (to derive the perturbed loss) is small. The surrogate gap is easy to compute and feasible for direct minimization during training. Based on the above observations, we propose Surrogate Gap Guided Sharpness-Aware Minimization (GSAM), a novel improvement over SAM with negligible computation overhead...\"*\n\nMeta learning\n*************\n\n``learned_optimization``\n=======================\n\n- Code on GitHub: `learned_optimization <https://github.com/google/learned_optimization>`__\n- `Colab notebooks <https://github.com/google/learned_optimization#learned_optimization-tutorial-sequence>`__\n\n- Research papers:\n\n  - `Unbiased Gradient Estimation in Unrolled Computation Graphs with Persistent Evolution Strategies <http://proceedings.mlr.press/v139/vicol21a.html>`__ (Vicol et al., 2021)\n\n    - *\"We introduce a method called Persistent Evolution Strategies (PES), which divides the computation graph into a series of truncated unrolls, and performs an evolution strategies-based update step after each unroll. PES eliminates bias from these truncations by accumulating correction terms over the entire sequence of unrolls. PES allows for rapid parameter updates, has low memory usage, is unbiased, and has reasonable variance characteristics.\"*\n\n  - `Gradients Are Not All You Need <https://arxiv.org/abs/2111.05803>`__\t(Metz et al., 2021)\n\n    - *\"...In this short report, we discuss a common chaos based failure mode which appears in a variety of differentiable circumstances, ranging from recurrent neural networks and numerical physics simulation to training learned optimizers. We trace this failure to the spectrum of the Jacobian of the system under study, and provide criteria for when a practitioner might expect this failure to spoil their differentiation based optimization algorithms.\"*\n\nModel efficiency\n****************\n\nEfficiently Scaling Transformer Inference\n=========================================\n\n- Code on GitHub:\n\n  - `T5X <https://github.com/google-research/t5x>`__\n  - `AQT: Accurate Quantized Training <http://github.com/google/aqt>`__\n\n- Research paper:\n\n  - `Efficiently Scaling Transformer Inference <https://arxiv.org/abs/2211.05102>`__ (Pope et al., 2022)\n\n    - *\"We develop a simple analytical model for inference efficiency to select the best multi-dimensional partitioning techniques optimized for TPU v4 slices based on the application requirements. We combine these with a suite of low-level optimizations to achieve a new Pareto frontier on the latency and model FLOPS utilization (MFU) tradeoffs on 500B+ parameter models that outperforms the FasterTransformer suite of benchmarks. We further show that with appropriate partitioning, the lower memory requirements of multiquery attention (i.e. multiple query heads share single key/value head) enables scaling up to 32× larger context lengths.\"*\n\nNeural rendering / NeRF\n***********************\n\nGeneralizable Patch-Based Neural Rendering\n==========================================\n\n- `Code on GitHub <https://github.com/google-research/google-research/tree/master/gen_patch_neural_rendering>`__\n- Research paper:\n\n  - `Generalizable Patch-Based Neural Rendering <https://arxiv.org/abs/2207.10662>`__ (Suhail et al., 2022)\n\n    - *\"...We propose a different paradigm, where no deep features and no NeRF-like volume rendering are needed. Our method is capable of predicting the color of a target ray in a novel scene directly, just from a collection of patches sampled from the scene.\"*\n\nVoxel-based Radiance Fields in JAX and Flax\n===========================================\n\n- `Colab notebook <https://github.com/google-research/google-research/blob/master/trainable_grids/Voxel_based_Radiance_Fields.ipynb>`__ (Velez and Dellaert, 2022)\n\n  - *\"In this notebook we show how with JAX/Flax, it is relatively easy to quickly get a voxel-based NeRF variant up and running. Specifically, we will develop a simplified version of DVGO that directly regresses color instead of having a small MLP. It works remarkably well.\"*\n\nOptimization\n************\n\nAmos Optimizer *and* JEstimator\n===============================\n\n- Code on GitHub:\n\n  - `Amos and JEstimator <https://github.com/google-research/jestimator>`__\n\n    - *\"... implements Amos, an optimizer compatible with the optax library, and JEstimator, a light-weight library with a tf.Estimator-like interface to manage T5X-compatible checkpoints for machine learning programs in JAX, which we use to run experiments in the paper.\"*\n\n- Research paper:\n\n  - `Amos: An Adam-style Optimizer with Adaptive Weight Decay towards Model-Oriented Scale <https://arxiv.org/abs/2210.11693>`__ (Tian and Parikh, 2022)\n\n    - Presents *\"Amos, an optimizer compatible with the optax library, and JEstimator, a light-weight library with a tf.Estimator-like interface to manage T5X-compatible checkpoints for machine learning programs in JAX.\"* *\"When used for pre-training BERT variants and T5, Amos consistently converges faster than the state-of-the-art settings of AdamW, achieving better validation loss within <=70% training steps and time, while requiring <=51% memory for slot variables.\"*\n\nQuantization\n************\n\nPareto-Optimal Quantized ResNet Is Mostly 4-bit *and* AQT: Accurate Quantized Training\n======================================================================================\n\n- Code on GitHub:\n\n  - `AQT: Accurate Quantized Training <http://github.com/google/aqt>`__\n\n- Research paper:\n\n  - `Pareto-Optimal Quantized ResNet Is Mostly 4-bit <https://arxiv.org/abs/2105.03536>`__ (Abdolrashidi et al., 2021)\n\n    - *\"In this work, we use ResNet as a case study to systematically investigate the effects of quantization on inference compute cost-quality tradeoff curves. Our results suggest that for each bfloat16 ResNet model, there are quantized models with lower cost and higher accuracy; in other words, the bfloat16 compute cost-quality tradeoff curve is Pareto-dominated by the 4-bit and 8-bit curves, with models primarily quantized to 4-bit yielding the best Pareto curve... The quantization method we used is optimized for practicality: It requires little tuning and is designed with hardware capabilities in mind... As part of this work, we contribute a quantization library written in JAX...\"*\n\nReinforcement learning\n**********************\n\nContinuous Control with Action Quantization from Demonstrations (AQuaDem)\n=========================================================================\n\n- `Code on GitHub <https://github.com/google-research/google-research/tree/master/aquadem>`__\n\n- Research paper:\n\n  - `Continuous Control with Action Quantization from Demonstrations <https://arxiv.org/abs/2110.10149>`__ (Dadashi et al., 2021)\n\n    - Proposes *\"a novel Reinforcement Learning (RL) framework for problems with continuous action spaces: Action Quantization from Demonstrations (AQuaDem). The proposed approach consists in learning a discretization of continuous action spaces from human demonstrations. This discretization returns a set of plausible actions (in light of the demonstrations) for each input state, thus capturing the priors of the demonstrator and their multimodal behavior. By discretizing the action space, any discrete action deep RL technique can be readily applied to the continuous control problem. Experiments show that the proposed approach outperforms state-of-the-art methods such as SAC in the RL setup, and GAIL in the Imitation Learning setup.\"*\n\nSequence models / Model parallelism\n***********************************\n\nT5X: Scaling Up Models and Data with ``t5x`` and ``seqio``\n==========================================================\n\n- `Code on GitHub <https://github.com/google-research/t5x>`__\n\n  - *\"T5X is a modular, composable, research-friendly framework for high-performance, configurable, self-service training, evaluation, and inference of sequence models (starting with language) at many scales.\"*\n\n- Research paper:\n\n  - `T5X: Scaling Up Models and Data with t5x and seqio <https://arxiv.org/abs/2203.17189>`__ (Roberts et al., 2022)\n\n    - *\"Recent neural network-based language models have benefited greatly from scaling up the size of training datasets and the number of parameters in the models themselves. Scaling can be complicated due to various factors including the need to distribute computation on supercomputer clusters (e.g., TPUs), prevent bottlenecks when infeeding data, and ensure reproducible results. In this work, we present two software libraries that ease these issues: t5x simplifies the process of building and training large language models at scale while maintaining ease of use, and seqio provides a task-based API for simple creation of fast and reproducible training data and evaluation pipelines. These open-source libraries have been used to train models with hundreds of billions of parameters on datasets with multiple terabytes of training data. Along with the libraries, we release configurations and instructions for T5-like encoder-decoder models as well as GPT-like decoder-only architectures.\"*\n\nSimulation\n**********\n\nBrax - A Differentiable Physics Engine for Large Scale Rigid Body Simulation\n============================================================================\n\n- `Code on GitHub <https://github.com/google/brax>`__\n- `Colab notebooks <https://github.com/google/brax#quickstart-colab-in-the-cloud>`__\n- Research paper:\n\n  - `Brax - A Differentiable Physics Engine for Large Scale Rigid Body Simulation <https://arxiv.org/abs/2106.13281>`__ (Freeman et al., 2021)\n\n    - *\"We present Brax, an open source library for rigid body simulation with a focus on performance and parallelism on accelerators, written in JAX. We present results on a suite of tasks inspired by the existing reinforcement learning literature, but remade in our engine. Additionally, we provide reimplementations of PPO, SAC, ES, and direct policy optimization in JAX that compile alongside our environments, allowing the learning algorithm and the environment processing to occur on the same device, and to scale seamlessly on accelerators.\"*\n"
  },
  {
    "path": "docs/examples/index.rst",
    "content": "Examples\n========\n\n.. toctree::\n   :maxdepth: 2\n\n   core_examples\n   google_research_examples\n   repositories_that_use_flax\n   community_examples\n\n\n"
  },
  {
    "path": "docs/examples/repositories_that_use_flax.rst",
    "content": "Repositories that use Flax\n==========================\n\nThe following code bases use Flax and provide training frameworks and a wealth\nof examples. In many cases, you can also find pre-trained weights:\n\n\n🤗 Hugging Face\n***************\n\n`🤗 Hugging Face <https://huggingface.co/flax-community>`__ is a\nvery popular library for building, training, and deploying state of the art\nmachine learning models.\nThese models can be applied on text, images, and audio. After organizing the\n`JAX/Flax community week <https://github.com/huggingface/transformers/blob/master/examples/research_projects/jax-projects/README.md>`__,\nthey have now over 5,000\n`Flax/JAX models <https://huggingface.co/models?library=jax&sort=downloads>`__ in\ntheir repository.\n\n🥑 DALLE Mini\n*************\n\n`🥑 DALLE Mini <https://huggingface.co/dalle-mini>`__ is a Transformer-based\ntext-to-image model implemented in JAX/Flax that follows the ideas from the\noriginal `DALLE <https://openai.com/blog/dall-e/>`__ paper by OpenAI.\n\nScenic\n******\n\n`Scenic <https://github.com/google-research/scenic>`__ is a codebase/library\nfor computer vision research and beyond. Scenic's main focus is around\nattention-based models. Scenic has been successfully used to develop\nclassification, segmentation, and detection models for multiple modalities\nincluding images, video, audio, and multimodal combinations of them.\n\nBig Vision\n**********\n\n`Big Vision <https://github.com/google-research/big_vision/>`__ is a codebase\ndesigned for training large-scale vision models using Cloud TPU VMs or GPU\nmachines. It is based on Jax/Flax libraries, and uses tf.data and TensorFlow\nDatasets for scalable and reproducible input pipelines. This is the original\ncodebase of ViT, MLP-Mixer, LiT, UViM, and many more models.\n\nT5X\n***\n\n`T5X <https://github.com/google-research/t5x>`__ is a modular, composable,\nresearch-friendly framework for high-performance, configurable, self-service\ntraining, evaluation, and inference of sequence models (starting with\nlanguage) at many scales."
  },
  {
    "path": "docs/faq.rst",
    "content": "Frequently Asked Questions (FAQ)\n================================\n\nThis is a collection of answers to frequently asked questions (FAQ). You can contribute to the Flax FAQ by starting a new topic in `GitHub Discussions <https://github.com/google/flax/discussions>`__.\n\nWhere to search for an answer to a Flax-related question?\n*********************************************************\n\nThere are a number of official Flax resources to search for information:\n\n- `Flax Documentation on ReadTheDocs <https://flax.readthedocs.io/en/latest/>`__ (this site): Use the `search bar <https://flax.readthedocs.io/en/search.html>`__ or the table of contents on the left-hand side.\n- `google/flax GitHub Discussions <https://github.com/google/flax/discussions>`__: Search for an existing topic or start a new one. If you can't find what you're looking for, feel free to ask the Flax team or community a question.\n- `google/flax GitHub Issues <https://github.com/google/flax/issues>`__: Use the search bar to look for an existing issue or a feature request, or start a new one.\n\nHow to take the derivative with respect to an intermediate value (using :code:`Module.perturb`)?\n************************************************************************************************\n\nTo take the derivative(s) or gradient(s) of the output with respect to a hidden/intermediate activation inside a model layer, you can use :meth:`flax.linen.Module.perturb`. You define a zero-value :class:`flax.linen.Module` \"perturbation\" parameter – :code:`perturb(...)` – in the forward pass with the same shape as the intermediate activation, define the loss function with :code:`'perturbations'` as an added standalone argument, perform a JAX derivative operation with :code:`jax.grad` on the perturbation argument.\n\nFor full examples and detailed documentation, go to:\n\n- The :meth:`flax.linen.Module.perturb` API docs\n- The `Extracting gradients of intermediate values <https://flax.readthedocs.io/en/latest/guides/model_inspection/extracting_intermediates.html#extracting-gradients-of-intermediate-values>`_ guide\n- `Flax GitHub Discussions #1152 <https://github.com/google/flax/discussions/1152>`__\n\nIs Flax Linen :code:`remat_scan()` the same as :code:`scan(remat(...))`?\n************************************************************************\n\nFlax :code:`remat_scan()` (:meth:`flax.linen.remat_scan()`) and :code:`scan(remat(...))` (:meth:`flax.linen.scan` over :meth:`flax.linen.remat`) are not the same, and :code:`remat_scan()` is limited in cases it supports. Namely, :code:`remat_scan()` treats the inputs and outputs as carries (hidden states that are carried through the training loop). You are recommended to use :code:`scan(remat(...))`, as typically you would need the extra parameters, such as ``in_axes`` (for input array axes) or ``out_axes`` (output array axes), which :meth:`flax.linen.remat_scan` does not expose.\n\nWhat are the recommended training loop libraries?\n*************************************************\n\nConsider using CLU (Common Loop Utils) `google/CommonLoopUtils <https://github.com/google/CommonLoopUtils>`__. To get started, go to this `CLU Synopsis Colab <https://colab.research.google.com/github/google/CommonLoopUtils/blob/main/clu_synopsis.ipynb>`__. You can find answers to common questions about CLU with Flax on `google/flax GitHub Discussions <https://github.com/google/flax/discussions?discussions_q=clu>`__.\n\nCheck out the official `google/flax Examples <https://github.com/google/flax/tree/main/examples>`__ for examples of using the training loop with  (CLU) metrics. For example, this is `Flax ImageNet's train.py <https://github.com/google/flax/blob/main/examples/imagenet/train.py>`__.\n\nFor computer vision research, consider `google-research/scenic <https://github.com/google-research/scenic>`__. Scenic is a set of shared light-weight libraries solving commonly encountered tasks when training large-scale vision models (with examples of several projects). Scenic is developed in JAX with Flax. To get started, go to the `README page on GitHub <https://github.com/google-research/scenic#getting-started>`__."
  },
  {
    "path": "docs/flip/0000-template.md",
    "content": "- Start Date: (fill me in with today's date, YYYY-MM-DD)\n- FLIP PR: [#0000](https://github.com/google/flax/pull/0000)\n- FLIP Issue: [#0000](https://github.com/google/flax/issues/0000)\n\n(Below sections are just a possible structure - please adapt to your FLIP.)\n\n# Summary\n[summary]: #summary\n\nOne paragraph explanation of the FLIP.\n\n# Motivation\n[motivation]: #motivation\n\nWhy are we doing this? What use cases does it support? What is the expected outcome?\n\n# Implementation\n[implementation]: #implementation\n\nThe technical part.\n\n# Discussion\n[discussion]: #discussion\n\nSummarize the discussion from the original issue and from the pull request.\n"
  },
  {
    "path": "docs/flip/1009-optimizer-api.md",
    "content": "- Start Date: 2021-02-08\n- FLIP PR: [#1011](https://github.com/google/flax/pull/1011)\n- FLIP Issue: [#1009](https://github.com/google/flax/issues/1009)\n\nTable of contents:\n\n- [Summary]\n- [Motivation]\n- [Using Optax]\n  - [Gradient Transformations]\n  - [Optax Training Step]\n  - [Multi Optimizer]\n  - [Train State]\n- [Previous API]\n  - [Optimizer and OptimizerDef]\n  - [Previous Training Step]\n- [Update Plan]\n- [Appendix]\n  - [Setup Code]\n\n# Summary\n[Summary]: #summary\n\nThis FLIP proposes to replace our current `flax.optim` API (referred to as\n[previous API] in this document) with [Optax], DeepMind's optimizer library.\n\n# Motivation\n[motivation]: #motivation\n\nOur current API (referred to as [previous API] in this document) uses a pattern\nwhere an `Optimizer` dataclass is created from a pytree of `target` variables\nand from an `OptimizerDef` that defines how to update optimizer state,\nhyperparameters, and target variables. This pattern is relatively complex for\nimplementing a simple optimizer, while being quite verbose in the typical Linen\ntrain step (especially when using mutable state collections).\n\nThis package `flax.optim` contains some optimizers, but that list is far from\nexhaustive and ideally we would instead use JAX optimizers from a dedicated PyPi\npackage.\n\nDeepMind already has a dedicated library — [Optax] — that implements a wide\nrange of interesting optimizers and provides a framework to compose new\noptimizers from reusable gradient transformations.\n\n[Optax]: https://github.com/deepmind/optax\n\n# Using Optax\n[Using Optax]: #using-optax\n\n## Gradient Transformations\n[Gradient Transformations]: #gradient-transformations\n\nWhile [Optax] does provide predefined optimizers (like `optax.adam`, or\n`optax.sgd` with momentum), it is really a library of *gradient transformations*\nand the idiomatic way of instantiating an optimizer is by providing a\ncombination of these gradient transformations. To emulate the momentum\noptimizer from the example when using the [previous API] we would write:\n\n```python\nimport optax\n\ntx = optax.chain(\n    optax.trace(decay=0.9, nesterov=False),\n    optax.scale_by_schedule(lambda step: -get_learning_rate(step)),\n)\n```\n\nRemarks:\n\n- Above gradient transformation would be equivalent with the example under\n  [Optimizer and OptimizerDef] where we define a Momentum optimizer without\n  Nesterov momentum (note that the `beta` parameter corresponds to the `decay`\n  parameter of the `optax.trace()` transformation, and the learning rate is\n  applied in a second chained transformation).\n- Note that hyper parameters like `decay` or `nesterov` only exist in the inner\n  scope of the higher order functions returning the `GradientTransformation`.\n  Such a gradient transformation is currently defined as a `NamedTuple` of the\n  `init()` and the `update()` function. In principle this pattern could be\n  extended to also store hyperparameters, maybe a point to discuss on the\n  [Optax] repo.\n- We can use a `get_learning_rate()` that returns the learning rate depending on\n  the step number when defining the Optax gradient update transformation. Above\n  code illustrates how this can be a drop-in replacement for a function we also\n  use in our [previous training step], where this update function already exists\n  (notice how we need to invert the sign because we add the gradient update to\n  the parameters). In addition, you can use\n  [`inject_hyperparams()`](https://github.com/deepmind/optax/pull/48) to\n  schedule arbitrary hyper parameters with Optax.\n\n## Optax Training Step\n[Optax Training Step]: #optax-training-step\n\n```python\n@functools.partial(jax.jit, static_argnums=(4, 5))\ndef train_step(opt_state, variables, inputs, labels, apply_fn, tx_update_fn):\n\n  def loss_fn(params):\n    logits, new_model_state = apply_fn(\n        {**variables, 'params': params}, inputs, mutable=['batch_stats'])\n    loss = xent_loss(logits, labels)\n    return loss, new_model_state\n\n  variables, params = variables.pop('params')\n  (loss, new_model_state), grads = jax.value_and_grad(loss_fn, has_aux=True)(\n      params)\n  updates, new_opt_state = tx_update_fn(grads, opt_state, params)\n  new_params = optax.apply_updates(params, updates)\n  new_variables = {**variables, **new_model_state, 'params': new_params}\n  return new_opt_state, new_variables, loss\n\n\nopt_state = tx.init(variables['params'])\nfor batch in ds.as_numpy_iterator():\n  opt_state, variables, loss = train_step(\n      opt_state, variables, batch['image'], batch['label'], model.apply,\n      tx.update)\n  print(loss)\n```\n\nRemarks:\n\n- Since `tx.update()` only transforms the gradient, we still need to call\n  `optax.apply_updates()` to apply these transformed gradients to the\n  parameters.\n- Compared with the [previous API], we can now keep the entire `variables`\n  including the `params` as an input and output to the `train_step()`.\n- Splitting `params` from `variables` is still necessary inside the train step\n  because we only want to compute gradients with respect to `params` and not the\n  entire `variables`.\n- We can still log internal optimizer state, such as the learning rate, as long\n  as Optax transformations expose that information in their respective state.\n  For example, `optax.scale_by_schedule()` currently only exposes\n  `opt_state.count` but could easily be extend to also expose the `step_size`.\n  The same is true for internal optimizer states that change over time.\n\n## Multi Optimizer\n[Multi Optimizer]: #multi-optimizer\n\nThe [previous API] defined `flax.optim.MultiOptimizer` for processing different\nparts of the parameter tree with different optimizers:\n\n```python\nbiases_traversal = flax.optim.ModelParamTraversal(\n    lambda path, _: path.endswith('/bias'))\nnot_biases_traversal = flax.optim.ModelParamTraversal(\n    lambda path, _: not path.endswith('/bias'))\n\noptimizer_def = flax.optim.MultiOptimizer(\n    (biases_traversal, flax.optim.GradientDescent(learning_rate=0.1)),\n    (not_biases_traversal, flax.optim.GradientDescent(learning_rate=0.05)),\n)\n```\n\nNote how we first define a traversal that selects parameters based on their\npath (which is the concatenation of module scopes and variable name), and then\ncreate a `MultiOptimizer` that binds a different optimizer for each of these\nseparate traversals.\n\nOptax has recently implemented `optax.masked()` that can be used for specifying\ngradient transformations that only applied to a subset of the gradients:\n\n```python\ndef flattened_traversal(fn):\n  def mask(data):\n    flat = traverse_util.flatten_dict(data)\n    return traverse_util.unflatten_dict({k: fn(k, v) for k, v in flat.items()})\n  return mask\n\ntx = optax.chain(\n    optax.masked(optax.sgd(learning_rate=0.1),\n                 mask=flattened_traversal(lambda path, _: path[-1] == 'bias')),\n    optax.masked(optax.sgd(learning_rate=0.05),\n                 mask=flattened_traversal(lambda path, _: path[-1] != 'bias')),\n)\n```\n\n## Train State\n[Train State]: #train-state\n\nIn Flax it is common to hand around a `TrainState` object that can then be\nused for checkpointing. This simplifies the above [Optax training step] a bit by\nreducing the number of arguments and getting rid of the `static_argnums`.\n\nWe can define a `TrainState` dataclass that wraps the common pattern of updating\nthe optimizer state and parameters by applying the gradients.\n\n```python\n# Small helper class in flax.training\nclass TrainState(flax.struct.PyTreeNode):\n  step: int\n  apply_fn: Callable = flax.struct.field(pytree_node=False)\n  params: flax.core.FrozenDict[str, Any]\n  tx: optax.GradientTransformation = flax.struct.field(pytree_node=False)\n  opt_state: optax.OptState\n\n  def apply_gradients(self, *, grads, **kwargs):\n    updates, new_opt_state = self.tx.update(\n        grads, self.opt_state, self.params)\n    new_params = optax.apply_updates(self.params, updates)\n    return self.replace(\n        step=self.step + 1,\n        params=new_params,\n        opt_state=new_opt_state,\n        **kwargs,\n    )\n\n  @classmethod\n  def create(cls, *, apply_fn, params, tx, **kwargs):\n    opt_state = tx.init(params)\n    return cls(\n        step=0,\n        apply_fn=apply_fn,\n        params=params,\n        tx=tx,\n        opt_state=opt_state,\n        **kwargs,\n    )\n```\n\nUsers can then derive from this dataclass and add more fields, for example\nmutable model state:\n\n```python\nfrom flax.training import train_state\n\nclass TrainState(train_state.TrainState):\n  batch_stats: flax.core.FrozenDict[str, Any]\n```\n\nWith this the [Optax Training Step] becomes:\n\n```python\n@jax.jit\ndef train_step(state, inputs, labels):\n\n  def loss_fn(params):\n    outputs, new_model_state = state.apply_fn(\n        {'params': params, 'batch_stats': state.batch_stats},\n        inputs,\n        mutable=['batch_stats'])\n    loss = xent_loss(outputs, labels)\n    return loss, new_model_state\n\n  (loss, new_model_state), grads = jax.value_and_grad(\n      loss_fn, has_aux=True)(state.params)\n  new_state = state.apply_gradients(\n      grads=grads,\n      batch_stats=new_model_state['batch_stats'],\n  )\n\n  return new_state, loss\n\n\nstate = TrainState.create(\n    apply_fn=model.apply,\n    params=variables['params'],\n    tx=tx,\n    batch_stats=variables['batch_stats'],\n)\nfor batch in ds.as_numpy_iterator():\n  state, loss = train_step(state, batch['image'], batch['label'])\n```\n\nThe train step without mutable state reduces to:\n\n```python\n@jax.jit\ndef train_step(state, inputs, labels):\n\n  def loss_fn(params):\n    outputs = state.apply_fn({'params': params}, inputs)\n    loss = xent_loss(outputs, labels)\n    return loss\n\n  loss, grads = jax.value_and_grad(loss_fn)(state.params)\n  new_state = state.update(grads=grads)\n\n  return new_state, loss\n\n\nstate = flax.training.TrainState.create(\n    apply_fn=model.apply,\n    params=variables['params'],\n    tx=tx,\n)\nfor batch in ds.as_numpy_iterator():\n  state, loss = train_step(state, batch['image'], batch['label'])\n```\n\nRemarks:\n\n- It is a common pattern in Flax training loops to have a `TrainState` dataclass\n  that is updated with new state after every step.\n- The simple solution proposed in `flax.training.train_state` an be extended\n  with additional data, but advanced usecases (e.g. multiple different models\n  and/or optimizers) are not supported. Users should instead fork the dataclass\n  and re-implement it to their needs.\n- As opposed to the `Optimizer` abstraction in the [previous API], the\n  `TrainState` now directly contains the `.params`, without having to to through\n  `.optimizer`\n\n# Previous API\n[previous API]: #previous-api\n\n## Optimizer and OptimizerDef\n[Optimizer and OptimizerDef]: #optimizer-and-optimizerdef\n\nThe optimizer itself would be implemented by creating a new class derived\nfrom `OpimizerDef`:\n\n```python\n# flax/optim/momentum.py\n\n@flax.struct.dataclass\nclass _MomentumHyperParams:\n  learning_rate: jnp.ndarray\n  beta: jnp.ndarray\n\n\n@flax.struct.dataclass\nclass _MomentumParamState:\n  momentum: np.ndarray\n\n\nclass Momentum(flax.optim.OptimizerDef):\n\n  def __init__(self, learning_rate=None, beta=0.9):\n    super().__init__(\n      _MomentumHyperParams(learning_rate, beta)\n    )\n\n  def init_param_state(self, param):\n    return _MomentumParamState(jnp.zeros_like(param))\n\n  def apply_param_gradient(self, step, hyper_params, param, state, grad):\n    del step\n    assert hyper_params.learning_rate is not None\n    new_momentum = state.momentum * hyper_params.beta + grad\n    new_params = param - hyper_params.learning_rate * new_momentum\n    return new_params, _MomentumParamState(new_momentum)\n```\n\nRemarks:\n\n- Note the relationship between `OptimizerDef` and `Optimizer` : When the\n  function `Optimizer.apply_gradient()` is called from the user code, it calls\n  into `OptimizerDef.apply_gradient()` (among other things) which in turn will\n  call `OptimizerDef.apply_param_gradient()` (implemented by subclasses of\n  `OptimizerDef`).\n- The functions `init_param_state()` and `apply_param_gradient()` are called\n  for every leaf in the params/grads pytree. This makes it possible to write the\n  calculations directly without `jax.tree_util.tree_map()`.\n- The interface was defined in pre-Linen without the distinction of `params` vs.\n  other collections in `variables` in mind. The original API was elegant because\n  one only needed to pass around the optimizer, which included the parameters,\n  optimizer state, optimizer hyperparameters, and a reference to the\n  `OptimizerDef` to perform the param/state update.\n\n## Previous Training Step\n[Previous Training Step]: #previous-training-step\n\nAn optimizer would first be constructed from its definition and the pytree of\ntarget params:\n\n```python\noptimizer_def = flax.optim.Momentum(learning_rate=0.1, beta=0.9)\noptimizer = optimizer_def.create(variables['params'])\n```\n\nThen, the target variables would optimized in the train step (assuming a single\nnon-params collection \"batch_stats\"):\n\n```python\ndef make_train_step(apply_fn):\n  @jax.jit\n  def train_step(optimizer, batch_stats, inputs, labels):\n\n    def loss_fn(params):\n      variables = {'params': params, 'batch_stats': batch_stats}\n      logits, new_model_state = apply_fn(\n          variables, inputs, mutable=['batch_stats'])\n      loss = xent_loss(logits, labels)\n      return loss, new_model_state['batch_stats']\n\n    (loss, new_batch_stats), grad = jax.value_and_grad(loss_fn, has_aux=True)(\n        optimizer.target)\n    lr = get_learning_rate(step)\n    new_optimizer = optimizer.apply_gradient(grad, learning_rate=lr)\n    return new_optimizer, new_batch_stats, loss\n\n  return train_step\n\n\nbatch_stats = variables['batch_stats']\ntrain_step = make_train_step(model.apply)\nfor step, batch in enumerate(ds)\n  optimizer, batch_stats, loss = train_step(\n      optimizer, batch_stats, batch['image'], batch['label'])\n```\n\nRemarks:\n\n- Notice how `optimizer.apply_gradient()` can take additional arguments to\n  update hyperparameters, such as learning rate from an independent function\n  `get_learning_rate()` in this case.\n\n\n# Update Plan\n[Update Plan]: #update-plan\n\n1. Finalize discussions on this FLIP\n2. Add [equivalence tests] to Optax that guarantee that existing `flax.optim`\n   optimizers return identical values with corresponding `optax` optimizers.\n3. Update examples to use Optax and verify that they reach the same final\n   performance with the same computational cost.\n4. Port missing optimizers to Optax (e.g. Adafactor) - and verify above points.\n5. Update all documentation (including README, Flax guided tour, HOWTOs, ...) to\n   talk exclusively about Optax optimizers.\n6. Create a transition guide for updating users from `flax.optim` to using\n   Optax. This transition guide should also point to Optax's [equivalence tests]\n   and the pull requests updating the examples.\n7. Mark optimizers in `flax.optim` as deprecated.\n\n[equivalence tests]: https://github.com/deepmind/optax/blob/master/optax/_src/equivalence_test.py\n\nNote that all current Flax examples use an optimizer that is already available\nin Optax:\n\n| Example  |      Flax      |    Optax    |              Comments               |\n| -------- | -------------- | ----------- | ----------------------------------- |\n| imagenet | optim.Momentum | optax.sgd   | DynamicScale can be used unchanged. |\n| mnist    | optim.Momentum | optax.sgd   |                                     |\n| nlp_seq  | optim.Adam     | optax.adamw |                                     |\n| pixelcnn | optim.Adam     | optax.adam  |                                     |\n| ppo      | optim.Adam     | optax.adam  |                                     |\n| seq2seq  | optim.Adam     | optax.adam  |                                     |\n| vae      | optim.Adam     | optax.adam  |                                     |\n| wmt      | optim.Adam     | optax.adamw |                                     |\n\n(Flax's Adam implementation has an optional parameter for weight decay, but in\nOptax Adam with and without weight decay are two different aliases.)\n\n# Appendix\n[Appendix]: #appendix\n\n## Setup Code\n[Setup Code]: #setup-code\n\nThe following setup code can be used for running the code snippets in this\nFLIP:\n\n```python\nimport functools\nfrom typing import Callable, Sequence\n\nimport jax\nimport jax.numpy as jnp\nimport flax\nimport flax.linen as nn\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\n\ndef pp(features):\n  return {\n      'image': tf.cast(features['image'], tf.float32) / 255 - 0.5,\n      'label': features['label'],\n  }\n\n\nclass Model(nn.Module):\n\n  @nn.compact\n  def __call__(self, inputs):\n    x = inputs.reshape([inputs.shape[0], -1])\n    x = nn.normalization.BatchNorm(True)(x)\n    x = nn.Dense(10)(x)\n    x = nn.log_softmax(x)\n    return x\n\n\ndef onehot(labels, num_classes, on_value=1.0, off_value=0.0):\n  x = (labels[..., None] == jnp.arange(num_classes)[None])\n  x = jax.lax.select(\n      x, jnp.full(x.shape, on_value), jnp.full(x.shape, off_value))\n  return x.astype(jnp.float32)\n\n\ndef xent_loss(logits, labels):\n  return -jnp.sum(\n      onehot(labels, num_classes=10) * logits) / labels.size\n\n\ndef get_learning_rate(step):\n  return 0.1\n\n\nmodel = Model()\nrng = jax.random.key(0)\nds = tfds.load('mnist')['train'].take(160).map(pp).batch(16)\nbatch = next(iter(ds))\nvariables = model.init(rng, jnp.array(batch['image'][:1]))\njax.tree_util.tree_map(jnp.shape, variables)\n```\n"
  },
  {
    "path": "docs/flip/1777-default-dtype.md",
    "content": "# FLIP: Default dtypes\n\n\n- Start Date: 2022-01-11\n- FLIP PR: [#1776](https://github.com/google/flax/pull/1776)\n- FLIP Issue: [#1777](https://github.com/google/flax/issues/1777)\n- Status: Implemented\n\n\n## Summary\n\nThis FLIP proposes to replace the default dtype which is currently fixed to float32, and instead use the JAX type promotion results to derive a default dtype from the input and parameters of a layer.\n\n\n## Motivation\n\nCurrently, Linen Modules always produce `module.dtype` (defaults to float32) outputs regardless of input and parameter dtypes. Half-precision types like float16 and bfloat16 are supported by explicitly passing the half-precision type to each Module. The way this is currently implemented is that each Module has a dtype argument with float32 as the default value. The layer guarantees that this dtype will be the return type of the result returned by `__call__`.\n\nThe current behavior is problematic and results in silent bugs, especially for dtypes that do not fit inside float32 (complex, float64). Also, the Linen dtype behavior is significantly different from how NumPy and by extension JAX handle dtypes.\n\n\n### Dtypes in JAX\n\nJAX uses a NumPy-inspired [dtype promotion](https://github.com/jax-ml/jax/blob/main/jax/_src/dtypes.py) mechanism as explained [here](https://jax.readthedocs.io/en/latest/type_promotion.html?highlight=lattice#type-promotion-semantics). The type promotion rules are summarized by the following type lattice:\n\n![JAX type promotion lattice](https://jax.readthedocs.io/en/latest/_images/type_lattice.svg)\n\n\n## Dtypes in Linen\n\nBesides input arguments, state and in particular parameters could affect dtype promotion. For example: we might feed a float64 input to a Dense layer with float32 parameters. Currently, the result would be truncated to float32. If the input is a complex number the result is even worse because the imaginary part will be silently dropped when casting to float32.\n\nBy using the dtype promotion rules already available in JAX we can avoid this issue. A public API is available called `jax.numpy.result_dtype(*args)`, which returns the dtype that JAX would promote the given arguments to, in accordance with the type promotion lattice. For Linen layers the arguments would be the layer inputs together with the parameters. For example, for a linear layer this would be inputs, kernel, and bias.\n\nNote that there is also a `param_dtype` attribute in standard Linen Modules that also defaults to flaot32. This behavior is left untouched and encodes the common case of having float32 parameters.\nThere are a few reasons why float32 is almost always the correct dtype for parameters:\n1. Storing weights in half-precision often leads to underflow during optimization.\n2. Double precision is rarely used because it severely slows down modern accelerators (GPU, TPU). Therefore, such a cost should be explicitly opted-in for.\n3. Complex Modules are relatively uncommon. Even within complex networks, the complex inputs can be projected with a real matrix.\n\n\n# Implementation\n\nA simplified example implementation:\n\n\n```python\ndef promote_arrays(*xs, dtype):\n if dtype is None:\n   dtype = jnp.result_type(*jax.tree_util.tree_leaves(xs))\n return jax.tree_util.tree_map(lambda x: jnp.asarray(x, dtype), xs)\n\nDtype = Any\nclass Dense(nn.Module):\n features: int\n kernel_init: Callable\n bias_init: Callable\n dtype: Optional[Dtype] = None\n param_dtype: Dtype = jnp.float32\n\n @nn.compact\n def __call__(self, x):\n   kernel = self.param(\"kernel\",\n                       self.kernel_init,\n                       (x.shape[-1], self.features), self.param_dtype)\n   bias = self.param(\"bias\", self.bias_init, (self.features,), self.param_dtype)\n   x, kernel, bias = promote_arrays(x, kernel, bias, dtype=self.dtype)\n   return x @ kernel + bias\n```\n\n\n## Half-precision dtypes\n\nSome layers don’t work with half-precision dtypes internally. For example: The normalization layers currently compute mean and variance in float32 even when a half-precision dtype is specified to avoid numerical issues. We can replicate this behavior by calling result_dtype with a dummy argument that has the minimum precision for the sub computation to work correctly.\n\n\n## Backward compatibility\n\nThis proposal causes some layers to behave differently in cases where the dtype is not specified to a Linen Module. By default, parameters are in float32. Therefore, passing in half or float32 precision inputs will cause a float32 dtype and no functional differences with current behavior.\n\nWhen passing complex or float64 precision, the result will no longer truncate the imaginary component or the precision. The silent truncation is problematic and has caused [user complaints](https://github.com/google/flax/issues/805#issuecomment-981468837). Therefore, this change can be considered a bugfix.\n\nThus, although this proposal strictly speaking changes behavior it is unlikely to cause problems for users. There are 2 exceptions to this which should be rare and easy to fix:\n1. A user relies on the enforced float32 to downcast a double precision value.\n2. A user relies on the float32 to explicitly upcast a half precision value even though the weights are in half precision.\n\n\n## Corner cases\n\nIn this section we describe corner cases where the implementation of the proposal is not obvious. The two main concerns are how complex numbers are handled in existing layers and how to determine the dtype of state variables.\n\n**Autoregressive decoding cache**\n\nCurrently, only attention implements autoregressive caching and the stored key and value mirror the dtype of the key and value passed to the layer. Forcing the cache dtype to be the same as the output dtype could result in reduced precision during cached decoding vs uncached. This seems undesirable. Decision: keep the current behavior.\n\n**Batch statistics**\n\nBatchNorm layers are often used with a half precision output dtype. However, calculating statistics is by default always done in float32 to avoid numerical precision issues and over/underflow for float16. With float64 this would actually cause a downcast so we should now use `np.promote_types(float32, dtype)` such that the precision is at least float32. The running batch statistics will be stored with the same dtype for consistency.\n\n**Complex number support**\n\nCurrently, our complex number support is brittle because the default behavior is to truncate the output to the real part. This issue will be fixed by the automatic type promotion proposed in this FLIP. However, some layers require some additional thought to extend to complex numbers correctly:\n\n1. Normalization layers use the complex conjugate to calculate norms instead of normal squaring.\n2. Attention: It’s not exactly clear how the dot product and softmax are defined in this case. Raise an error on complex inputs.\n3. Recurrent layers: might require special gating / activation functions to function correctly, but these can be specified by the user.\n\n\n# Discussion\n\nSummarizing the main points from the discussion:\n\n\n## Consider implicit complex truncation an error\n\nQ:\nI'm wondering if we should always raise an error if one of the xs tree leaves is complex but dtype is not. Users should maybe remove imaginary part by themselves if that's really what they want to do.\n(Maybe it's a contrived example, but I can imagine cases where layers have their dtype set by parent modules based on assumptions without complex numbers in mind)\n\nA:\nThis is worth considering in a follow-up CL but this might as well be solved in JAX directly where the safeguard would apply more generally. In NumPy this was also considered but abandoned because it is not backwards compatible.\n\n\n## Dtype attribute names\n\nQ:\nAre the dtype and param_dtype arguments confusing? In particular, should dtype perhaps be called output_dtype to make the difference between the two dtypes more explicit?\n\nA:\nThis would be a large and orthogonal change wrt to this proposal so leaving it out for now.\nAlso, this breaks with the standard dtype argument in NumPY/JAX.\nAlthough dtype indeed constrains the output dtype it is also a hint for the dtype we would like the computation to happen in.\n\n"
  },
  {
    "path": "docs/flip/2396-rnn.md",
    "content": "# RNN Flip\n\n- Start Date: 2022-08-18\n- FLIP PR: [#2604](https://github.com/google/flax/pull/2604)\n- FLIP Issue: [#2396](https://github.com/google/flax/issues/2396)\n- Authors: Jasmijn Bastings (@bastings) and Cristian Garcia (@cgarciae)\n\n## Summary\nThis FLIP adds support for higher-level recurrent layers (RNN, GRU, LSTM) that can help users process input sequences using the recurrent cells already available in Flax.\n\n## Motivation\nImplementing well known recurrent architectures is tricky and prone to user errors, even a simple LSTM layers involves the manual creation and handling of the carry/memory and correctly setting up `nn.scan`:\n\n```python\n@nn.compact\ndef __call__(self, x):\n  LSTM = nn.scan(\n    nn.LSTMCell, variable_broadcast=\"params\", split_rngs={\"params\": False}\n  )\n  carry = LSTM.initialize_carry(\n    jax.random.key(0), batch_dims=x.shape[:1], size=self.hidden_size\n  )\n  carry, x = LSTM()(carry, x)\n  return x\n```\nSlightly more complicated cases involving padding like in the [seq2seq](https://github.com/google/flax/blob/main/examples/seq2seq/models.py) example require even more work but couple potentially be simplified to a couple of lines with the right abstractions. We propose providing users with clean, correct, and efficient abstractions to use recurrent cells. \n\n## Requirements\n\n* **Masking**: We need to support a batch of sequences that contain padding at the end of each sequence. \n   * We do not intend to support non-contiguous padding, i.e. padding that is not at the end of a sequence, for performance reasons, except in the case of packing (see below).\n* **Bidirectionality**: The ability to process a sequence in both the forward and reverse directions, respecting padding (i.e., the reverse direction should start with the actual inputs, not with padding values).\n* **Performance**: The proposed classes should be benchmarked to provide the best performance in terms of step time and/or memory use.\n* **Recurrent Dropout**: Support for recurrent dropout in cells (e.g. dropout on the state of the cell).\n\n## Implementation\n### High-level structure\n\nWe propose to have these 3 levels of abstraction:\n\n* **Cells (unchanged)**: all RNNCellBase subclasses such as LSTMCell and GRUCell, these implement the stepwise logic. These already exist in Flax today.\n* **Layers (new)**: a class (RNN) that takes a cell and scans over a sequence respecting possible padding values and optionally also allows packed sequences.\n* **Bidirectional (new)**: a single class that takes a forward and a backward RNN instance and correctly processes the input sequence in both directions and merges the results.\n\n### Example of proposed API\nWe start with a code example of what you could do with the proposed API, and then we discuss the API in detail below.\n\n```python\ncell = nn.LSTMCell()\n# Encodes a batch of input sequences.\ncarry, outputs = nn.RNN(cell, cell_size)(inputs, seq_lengths)\n```\n\nA Bidirectional layer with a LSTM RNNs for the forward and backward directions respectively would look like this:\n\n```python\nforward_rnn = nn.RNN(nn.LSTMCell(), cell_size=32)\nbackward_rnn = nn.RNN(nn.LSTMCell(), cell_size=32)\n# Bidirectional combinator.\nbi_rnn = nn.Bidirectional(forward_rnn, backward_rnn)\n# Encodes a batch of input sequences in both directions.\ncarry, outputs = bi_rnn(inputs, seq_lengths) \n```\n\nNext we will discuss `RNN`, `Bidirectional`, and proposed changes to `RNNCellBase`.\n\n### RNNBase\nThe `RNNBase` class serves as a base class for the `RNN` class, it specifies\nthe API that all RNN layers should implement to be compatible with the `Bidirectional`.\n`RNNBase` contains the `__call__` and `flip_sequences` methods:\n\n```python\nclass RNNBase(Protocol):\n  def __call__(\n      self,\n      inputs: jax.Array,\n      *,\n      initial_carry: Optional[Carry] = None,\n      init_key: Optional[random.KeyArray] = None,\n      seq_lengths: Optional[Array] = None,\n      return_carry: Optional[bool] = None,\n      time_major: Optional[bool] = None,\n      reverse: Optional[bool] = None,\n      keep_order: Optional[bool] = None,\n  ) -> Union[Output, Tuple[Carry, Output]]:\n    ...\n```\nWhere:\n\n* `inputs`: the input sequence.\n* `initial_carry`: the initial carry, if not provided it will be initialized\n  using the cell's :meth:`RNNCellBase.initialize_carry` method.\n* `init_key`: a PRNG key used to initialize the carry, if not provided\n  ``jax.random.key(0)`` will be used. Most cells will ignore this\n  argument.\n* `seq_lengths`: an optional integer array of shape ``(*batch)`` indicating\n  the length of each sequence, elements whose index in the time dimension\n  is greater than the corresponding length will be considered padding and\n  will be ignored.\n* `return_carry`: if ``return_carry=False`` (default) only the output sequence is returned,\n  else it will return a tuple of the final carry and the output sequence.\n* `time_major`: if ``time_major=False`` (default) it will expect inputs with shape\n  ``(*batch, time, *features)``, else it will expect inputs with shape ``(time, *batch, *features)``.\n* `reverse`: if ``reverse=False`` (default) the sequence is\n  processed from left to right and returned in the original order, else it will be processed\n  from right to left, and returned in reverse order. If ``seq_lengths`` is passed,\n  padding will always remain at the end of the sequence.\n* `keep_order`: if ``keep_order=True``, when ``reverse=True``\n  the output will be reversed back to the original order after processing, this is\n  useful to align sequences in bidirectional RNNs. If ``keep_order=False`` (default),\n  the output will remain in the order specified by ``reverse``.\n* `Returns`: if ``return_carry=False`` (default) only the output sequence is returned,\nelse it will return a tuple of the final carry and the output sequence.\n\n### RNN\nThe `RNN` module inherits from `RNNBase`, it main function is to apply an `RNNCellBase` instance over a batch of input sequences, it can be used with any type of cell (e.g., `GRUCell`, `LSTMCell`, etc). It accepts the following parameters:\n\n```python\nclass RNN(RNNBase):\n  cell: RNNCellBase,\n  cell_size: int | Tuple[int, ...]\n  time_axis: int = -2,\n  variable_axes = FrozenDict(),\n  variable_broadcast: CollectionFilter = 'params'\n  variable_carry: CollectionFilter = False\n  split_rngs = FrozenDict({'params': False})\n  # implement RNNBase\n  ...\n```\n\nAttributes like `variable_axes`, `variable_broadcast`, `variable_carry`, and `split_rngs` are directly passed to `nn.scan`, their default values are set such that common cells like `LSTMCell` and `GRUCell` work out of the box.\n\n### Masking\n`seq_lengths` is defined as an integer array of shape `(*batch,)` indicating the length of each sequence.\n\n<details><summary>Discussion</summary>\n\nThere are various masking formats found in other frameworks, here are some of the most popular ones:\n\n* **Binary masking**: specifies per-sample and timestep whether that data point should be included or not in the computation, it can be non-contigous (e.g., [1, 1, 0, 1]). This is used by Keras.\n* **Sequence length masking**: specifies per-sample the number of non-padding examples contained in the sequence, any padding contained in the sequence should be stacked at the end. This is used by FlaxFormer.\n* **Segmentation Mask**: specifies row and timestep to which sample the data point belongs to, this format allows more than one sample per row which potentially reduces the total amount of padding needed (e.g. [1, 1, 1, 2, 2, 0, 0]). Pytorch uses this representation (see [pack_padded_sequence](https://pytorch.org/docs/stable/generated/torch.nn.utils.rnn.pack_padded_sequence.html)).\n\nWhile Sequence packing (see [LM1B example](https://github.com/google/flax/blob/main/examples/lm1b/input_pipeline.py#L90-L92)) is is more powerful, its implementation is more complex and it is not clear whether it is worth the effort. The simplest format is sequence length masking, which is the one we propose to use.\n\n</details>\n\n### Bidirectional\nBidirectional processing can be achieved via a Module that accepts a `forward_rnn` Module and a `backward_rnn` Module, both of which should be `RNN` instances, in order to process the input sequence in both directions. Here we present some pseudo code of the implementation:\n\n```python\ndef __call__(self, inputs, seq_lengths):\n  # Encode in the forward direction.\n  carry_forward, outputs_forward = self.forward_rnn(\n    inputs, seq_lengths=seq_lengths, \n    return_carry=True, reverse=False,\n  )\n  # Encode in the reverse order.\n  carry_backward, outputs_backward = self.backward_rnn(\n    inputs, seq_lengths=seq_lengths,\n    return_carry=True, reverse=True, # process in reverse order\n    keep_order=True, # but return the sequence in the original order\n  )\n  # Merge both sequences.\n  outputs = jax.tree.map(self.merge_fn, outputs_forward, outputs_backward)\n\n  return (carry_forward, carry_backward), outputs\n```\n\nHere `merge_fn` a function that takes both outputs and fuses them (`concat` by default). As showcased in the beginning of this document, usage would look like this:\n\n```python\nforward_rnn = nn.RNN(nn.LSTMCell(), cell_size=32)\nbackward_rnn = nn.RNN(nn.GRUCell(), cell_size=32)\n# Bidirectional combinator.\nbi_rnn = nn.Bidirectional(forward_rnn, backward_rnn)\n# Encodes a batch of input sequences in both directions.\ncarry, outputs = bi_rnn(inputs, seq_lengths) \n```\n\n### Recurrent Dropout\nThere are two main uses of dropout in RNNs:\n1. Input dropout: regular dropout applied to the inputs, different for every step.\n4. Recurrent dropout: applies dropout to a recurrent input/output, same for every step.\n\nFlax's `nn.scan` can easily express both types of dropout via `split_rns`, input dropout would split rngs while recurrent dropout would not. [#2540](https://github.com/google/flax/pull/2540) was introduces such that the `rng_name` in `nn.Dropout` can now be defined by the user, this way Cells could define both types of dropout e.g:\n\n```python\nself.dropout = nn.Dropout(...) # input dropout\nself.recurrent_dropout = nn.Dropout(..., rng_collection='recurrent_dropout')\n```\nBased on this, `nn.scan` / `nn.RNN` can now specify `split_rngs` accordingly e.g:\n```\nnn.scan(scan_fn, ..., split_rngs={'dropout': True, 'recurrent_dropout': False})\n```\n\n# Future ideas\n\n<details><summary>show</summary>\n\n### Sequence Packing\nAllow packing multiple sequences to make efficient use of space/memory. This might result in a trade-off where step time is higher (because at each step we need to check whether we are starting a new sequence and reset the carry/initial state), but where less padding is used increasing efficiency overall.\n\n### RNNCell redesign\n\n#### Make initialize_state an instance method\nFirst altenative is to make `initialize_carry` a instance method. With this change hyperparameters can be passed directly to the cell, it signature would look like this:\n\n```python\ndef initialize_carry(self, sample_input) -> Carry:\n  ...\n```\n\nUsage would look like this:\n\n```python\nLSTM = nn.scan(\n  nn.LSTMCell, variable_broadcast='params', \n  split_rngs={'dropout': True})\nlstm = LSTM(features=32)\ncarry = lstm.initialize_carry(x[:, 0])\ncarry, y = lstm(carry, x)\n```\n\n#### Remove initialize_carry\n\nAn alternative is to remove `initialize_carry` entirely and have the carry state be handled as a carry collection. This would simplify usage quite a bit:\n\n```python\nLSTM = nn.scan(\n  nn.LSTMCell, variable_broadcast='params', \n  split_rngs={'dropout': True})\ny = LSTM(features=32)(carry, x)\n```\n\nHowever, this would require `nn.scan` to support initialization of carry collections which is currently not possible. Also, users would have to specify that a collection is mutable e.g. `mutable=['carry']`, even if they are not interested in the output carry state.\n\n</details>\n"
  },
  {
    "path": "docs/flip/2434-general-metadata.md",
    "content": "# FLIP: Axis Metadata\n\n\n- Start Date: 2022-08-08\n- FLIP Issue: [#2434](https://github.com/google/flax/issues/2434)\n- FLIP PR: [#2435](https://github.com/google/flax/pull/2435)\n- Status: Proposal\n\n\n## Summary\n\nThis FLIP proposes to extend Flax's variable collections with a generic axis metadata API.\nThe core of the API is an abstract base class that is recognized by lifting transformations that can add an axis (vmap, scan).\nUsers can extend the base class to keep track of per-axis metadata in a way that works with lifted transformations.\n\n\n## Motivation\n\nGenerally, there is no way in Flax to track metadata for variables across lifted transformations.\nAxis metadata is used to keep track of semantic information about axes into other (Flax independent) APIs.\nFor example, optimizers like AdaFactor can be configured on a per-axis level and partitioning APIs\nin JAX like xmap or pjit require per variable annotations to map effectiently to parallel hardware.\n\nCurrently, there is an experimental [API](https://github.com/google/flax/blob/main/flax/linen/partitioning.py)\nsupporting partitioning annotations with wrappers around lifted transforms that change axes (``nn.scan_with_axes``, ``nn.vmap_with_axes``)\nand a special APIs to create variables (``param_with_axes`` and ``variable_with_axes``).\nThe experimental partitioning API stores the metadata in a separate collection named \"[collection]_axes\".\n\n\nThe experimental API has a number of shortcomings that we like to solve:\n1. The current API works for tracking PartitionSpecs but not for other types of metadata like optimizer annotations.\n2. The implementation using an \"xxx_axes\" collection requires error-prone and non-composable string manipulation.\n3. Special, partioning-aware variable creators and lifted transforms are required\n4. The partioning API is hard to use with pre-existing Modules that aren't partioning aware.\n\n\n## Proposal\n\nTo generalize metadata tracking and keep the specific metadata out of core Flax we propose the following abstract base class:\n\n```python\nTAxisMetadata = TypeVar(\"TAxisMetadata\", bound=\"AxisMetadata\")\n\nclass AxisMetadata(metaclass=abc.ABCMeta):\n  \"\"\"Abstract base class for boxed Metadata.\n\n  ``AxisMetadata`` enables arbitrary, per axis metadata for variables.\n  By using ``unbox`` the metadata is stripped away to obtain the original\n  variables. By using unboxing, most code handling variables does not need\n  to handle ``AxisMetadata`` specifically, but can directly operate on the JAX\n  arrays that they wrap.\n\n  Additionally, ``AxisMetadata`` supports updating metadata whenever an axis\n  is added or removed by a functional transformation\n  (e.g.: ``nn.scan`` or ``nn.vmap``) using the ``add_axis`` and ``remove_axis``\n  methods.\n\n  By extending ``AxisMetadata``, custom metadata can be stored. See\n  ``Partitioned`` for a specific implementation.\n  \"\"\"\n\n  @abc.abstractmethod\n  def unbox(self) -> Any:\n    \"\"\"Returns the content of the AxisMetadata box.\n\n    Note that unlike ``meta.unbox`` the unbox call should recursively unbox\n    metadata. It should simply return value that it wraps directly even\n    if that value itself is an instance of AxisMetadata.\n\n    In practise, AxisMetadata subclasses should be registred as PyTree nodes to\n    support passing instances to JAX and Flax APIs. The leaves returned for this\n    note should correspond to the value returned by unbox.\n\n    Returns:\n      The unboxed value.\n    \"\"\"\n    pass\n\n  @abc.abstractmethod\n  def add_axis(self: TAxisMetadata, index: int,\n               params: Dict[Any, Any]) -> TAxisMetadata:\n    \"\"\"Adds a new axis to the axis metadata.\n\n    Note that add_axis and remove_axis should act as each other's inverse\n    (meaning: ``x.add_axis(i, p).remove_axis(i, p) == x``)\n\n    Args:\n      index: The position at which the new axis will be inserted\n      params: An arbitrary dictionary of parameters passed by the transformation\n        that introduces the new axis (e.g.: ``nn.scan`` or ``nn.vmap``). The\n        user passes this dictionary as the `metadata_param` argument to the\n        transformation.\n    Returns:\n      A new instance of the same type as self and with the same ``unbox``\n      content with updated axis metadata.\n    \"\"\"\n    pass\n\n  @abc.abstractmethod\n  def remove_axis(self: TAxisMetadata, index: int,\n                  params: Dict[Any, Any]) -> TAxisMetadata:\n    \"\"\"Removes an axis from the axis metadata.\n\n    Note that add_axis and remove_axis should act as each other's inverse\n    (meaning: ``x.remove_axis(i, p).add_axis(i, p) == x``)\n\n    Args:\n      index: The position of the axis that is to be removed\n      params: An arbitrary dictionary of parameters passed by the transformation\n        that introduced the axis (e.g.: ``nn.scan`` or ``nn.vmap``). The\n        user passes this dictionary as the `metadata_param` argument to the\n        transformation.\n    Returns:\n      A new instance of the same type as self and with the same ``unbox``\n      content with updated axis metadata.\n    \"\"\"\n    pass\n```\n\nWe call this type of class wrapping a value and keeping track of some additional data a **box**.\nBy defining an abstract base class for this box, the API does not need to be aware of the specifics of the metadata that is tracked.\nThis should make the API future proof and modular.\n\nThe ``add_axis`` and ``remove_axis`` method return an instance of their own type instead of mutating in-place.\nTypically, an implementation would be a ``flax.struct.PyTreeNode`` because the box should still be a valid JAX value and must therefore be handled by the PyTree API.\nCalling ``jax.tree.map`` on a boxed value will simply map over the value in the box.\nThe lifted transforms that need to handle metadata will call ``jax.tree.map(..., is_leaf=lambda x: isinstance(x, AxisMetadata))`` to find the AxisMetadata instances within a PyTree.\n\nAdvantages of the boxing approach:\n1. Boxing can be used outside of Flax and metadata is automatically \"inherited\". For example, the optimizer state will\n   have the same partitioning spec as the parameters, because the state is initialized using a ``jax.tree.map`` over the boxed parameters.\n2. Boxes are composable.\n3. Boxing avoids string manipulation and generally avoids having to handle additional auxiliary collections like \"param_axes\" in the current\n   partitioning API.\n4. No need to lift metadata collections separately.\n\n\nDisadvantages:\n1. Adding the boxes changes the PyTree hierarchy and introduces dataclasses within the otherwise plain, nested dict of variables.\n3. Custom Pytree nodes have a small runtime overhead. It's hard to observe this in practise because JAX calls are async.\n\n\n### Init syntax\n\n\nBoxes can be created directly by the init function of a variable. Therefore, we propose to create metadata using higher-order initializers.\nThe main advantage of this is that we can decouple metadata handling completely from the Module definition. Also, most Modules already overwrite\nattributes to override the default initialzers so users can add metadata to existing Modules without requiring any code changes.\n\nTo illustrate this, let's consider a metadata class that keeps track of PartitionSpecs used by ``pjit``:\n\n```python\nclass Partitioned(flax.struct.PyTreeNode, AxisMetadata):\n  value: Any\n  names: Tuple[Optional[str], ...] = flax.struct.field(pytree_node=False)\n\n  def add_axis(self, index: int, params: Dict[Any, Any]) -> TAxisMetadata:\n    axis_name = self._get_partition_name(params)\n    names = list(self.names)\n    names.insert(index, axis_name)\n    return self.replace(names=tuple(names))\n\n  def remove_axis(self, index: int, params: Dict[Any, Any]) -> TAxisMetadata:\n    axis_name = self._get_partition_name(params)\n    names = list(self.names)\n    assert names.pop(index) == axis_name\n    return self.replace(names=tuple(names))\n\ndef with_partitioning(init_fn, names):\n  def wrapper(*args, **kwargs):\n    return Partitioned(init_fn(*args, **kwargs), names)\n  return wrapper\n```\n\nHere we also defined a small utility called ``with_partitioning`` that we can use to wrap existing initialzers to add metadata:\n\n\n```python\n# init kernel with lecun normal and split the output features over the data axis\npartitioned_dense = nn.Dense(features, kernel_init=with_partitioning(nn.initializers.lecun_normal, (None, \"data\")))\n```\n\nInitializing a model that creates partitioned weights would result in the following variable structure:\n\n```python\nvariables = partitioned_dense.init(rng, jnp.ones((4,)))\njax.tree.map(np.shape, variables)  # => {\"params\": {\"kernel\": Partitioned(value=(4, 8), names=(None, \"data\")), bias: (8,)}}\n```\n\nThe variable tree with metadata can be used to integrate with other libraries and APIs.\nFor example, we can turn the ``Partitioned`` metadata into ``jax.pjit`` sharding annotations:\n\n```python\ndef to_sharding_spec(x):\n  if isinstance(x, Partitioned):\n    return PartitionSpec(*x.names)\n  else:\n    # fully replicated\n    return PartitionSpec()\n\n# Result: {\"params\": {\"kernel\": PartitionSpec(None, \"data\"), bias: PartitionSpec()}}\nvariables_pspec = jax.tree.map(to_sharding_spec, variables, is_leaf=lambda x: isinstance(x, Partitioned))\n```\n\n### Unbox syntax\n\n\nMetadata typically doesn't need to be handled by Modules directly. Therefore, we propose to make Modules agnostic to Metadata boxes by default.\nThe ``unbox`` method can be used to unpack a variable such that only the original JAX arrays remain. Users can manually call unbox but to make\nsure Module classes don't have to call it everywhere we add an unbox keyword arg to variable returning APIs (e.g.: ``.param``, ``.variable``, ``.get_variable``).\nThe keyword arg ``unbox`` will default to ``True`` such that a Modules are metadata agnostic by default. This also means existing Modules will be backward compatible\nwith the new API.\n\n```python\nkernel = self.param(\"kernel\", self.kernel_init, shape)  # No AxisMetadata instances\nkernel_box = self.get_variable(\"param\", \"kernel\", unbox=False)  # AxisMetadata boxes are preserved\n```\n\n\n### Lift syntax\n\nWhen calling a lifted transformation that adds an axis you will now be able to pass a dictionary with arguments.\nThese params will be passed to ``AxisMetadata`` add_axis/remove_axis callbacks:\n\n```python\nnn.scan(..., variable_axes={\"params\": 0}, metadata_params={nn.Partitioned.AXIS_NAME: \"layers\"})\n```\n\nA dict is used such that users can add their own arguments to custom AxisMetadata classes.\n\n"
  },
  {
    "path": "docs/flip/2974-kw-only-dataclasses.md",
    "content": "# FLIP: kw_only dataclasses\nAuthors: Brennan Saeta, Ivy Zheng\n\n - Start Date: Mar 23, 2023\n - FLIP Issue: [TBD]\n - FLIP PR: #2974\n - Status: Implementing\n\n\n## Summary\n\nPython 3.10 adds support for `kw_only` dataclasses. Subclasses of `flax.linen.Module` are automatically converted to `dataclasses` on users' behalf, but today, Flax doesn't allow setting the `kw_only` parameter to this dataclass transform, even if users are running Python 3.10. This proposal allows users to use this new feature with `nn.Module`'s.\n\n\n## Motivation\n\nIn larger Flax-based codebases (e.g. [`PaxML`](https://github.com/google/paxml) / [`Praxis`](https://github.com/google/praxis)), it’s not uncommon to define an (abstract) subclass of nn.Module that contains shared functionality that is itself further subclassed for specific implementations (e.g. [`BaseLayer`](https://github.com/google/praxis/blob/main/praxis/base_layer.py), or [`StackedTransformerRepeat`](https://github.com/google/praxis/blob/81479b260fcc13de8549cdbfb0fdf5c3f188ac90/praxis/layers/transformers.py#L1836) which is further subclassed by [`PipelineCompatibleStackedTransformerRepeat`](https://github.com/google/praxis/blob/81479b260fcc13de8549cdbfb0fdf5c3f188ac90/praxis/layers/transformers.py#L2198)).\n\nOften, these parent types define hyperparameters (constructor arguments), often with default values. Without `kw_only` on the `dataclass` transform, default values must be specified for all child layers hyperparameters. This is suboptimal, because users could forget to set them when instantiating the modules. For example, `Child` must set a default value for `num_heads` (because a non-defaulted argument can’t come after a defaulted argument if they are positional), but no reasonable default is available:\n\n```python\nclass BaseLayer(nn.Module):\n  mesh: Optional[jax.experimental.mesh.Mesh] = None\n\n  def with_sharding(self, some_variable, some_sharding):\n    if self.mesh:\n      # Do something useful here.\n\nclass Child(BaseLayer):\n  num_heads: int  # Don't want to have to set a default argument!\n\n  def __call__(self, x):\n    ...\n```\n\nNote: Flax already has this problem, which is why `nn.Module` has its own fancy `kw_only_dataclasses.dataclass` transform: it moves the `name` and `parent` dataclass fields to the end, so they can have defaults.\n\n\n## Implementation\n\nTo allow modules to optionally opt into this `kw_only` dataclass behavior, we leverage arguments to `__init_subclass__`. This would look as follows:\n\n```python\nclass BaseLayer(nn.Module, kw_only=True):\n  ...\n\nclass Child(BaseLayer):\n  ...\n```\n\nThe implementation of `nn.Module`’s `__init_subclass__` will be tweaked as follows:\n\n```python\nclass Module(ModuleBase):\n  def __init_subclass__(self, kw_only: Optional[bool] = None):\n    # ...\n    if kw_only:\n     if is_python_310_or_above():\n       dataclass_transform_args = {'kw_only': True}\n     else:\n       raise TypeError(\"Can't use `kw_only` before Py3.10.\")\n    else:\n       dataclass_transform_args = {}\n\n    kw_only_dataclasses.dataclass(\n      cls, unsafe_hash='__hash__' not in cls.__dict__,\n      repr=False,\n      **dataclass_transform_args)\n```\n\n### Forward compatibility\n\nFor future simplification, if `kw_only` is requested and the Python version is 3.10 or above, bypass the `kw_only_dataclasses` implementation and just use the regular `dataclasses` transform.\n\nThat means we may one day remove `flax/linen/kw_only_dataclasses.py` when Flax rolls over 3.10.\n\n\n## Discussion\n\n### Aligned with Python `dataclass`\n\nWe prefer to keep the behavior of `nn.Module`’s `kw_only` aligned with the Python dataclasses. Note that this means `kw_only` will not be inheritable, and this could happen:\n\n```python\nclass BaseLayer(nn.Module, kw_only=True):\n  base_muliplier: Optional[int] = -1\n\nclass ChildLayer(BaseLayer):\n  child_multiplier: int\n\nBaseLayer(2)   # This will throw error\nChildLayer(2)  # But this will not\n```\n\n### `flax.struct.dataclass`\n\nThere’s a potentially related feature to allow `kw_only` to be specified for `flax.struct.dataclass`. This should be considered an orthogonal decision.\n\n\n"
  },
  {
    "path": "docs/flip/3099-rnnbase-refactor.md",
    "content": "# Refactor RNNCellBase in FLIP\n\nAuthors: Cristian Garcia, Marcus Chiam, Jasmijn Bastings\n\n - Start Date: May 1, 2023\n - FLIP Issue: [TBD]\n - FLIP PR: #3053\n - Status: Implemented\n\n## Summary\nThis proposal aims to improve the usability of the `RNNCellBase` class by refactoring the `initialize_carry` method and other relevant components.\n\n## Motivation\n\nCurrently, `initialize_carry` is used to both initialize the carry and pass crucial metadata like the number of features. The API can be unintuitive as it requires users to manually calculate things that could typically be inferred by the modules themselves, such as the shape of batch dimensions and the shape of feature dimensions.\n\n### Example: ConvLSTM\nThe current API can be unintuitive in cases like `ConvLSTM` where a the `size` parameter contains both the input image shape and output feature dimensions:\n\n```python\nx = jnp.ones((2, 4, 4, 3)) # (batch, *image_shape, channels)\n\n#                                        image shape: vvvvvvv\ncarry = nn.ConvLSTMCell.initialize_carry(key1, (16,), (64, 64, 16))\n#                                   batch size: ^^             ^^ :output features\n\nlstm = nn.ConvLSTMCell(features=6, kernel_size=(3, 3))\n(carry, y), initial_params = lstm.init_with_output(key2, carry, x)\n```\n\nThis FLIP will propose some changes to `initialize_carry` such that the previous example can be simplified to:\n\n```python\nx = jnp.ones((2, 4, 4, 3)) # (batch, *image_shape, channels)\n\nlstm = nn.ConvLSTMCell(features=6, kernel_size=(3, 3))\ncarry = lstm.initialize_carry(key1, input_shape=x.shape)\n\n(carry, y), initial_params = lstm.init_with_output(key2, carry, x)\n```\n\n## Implementation\nThe proposal suggests the following changes:\n\n### initialize_carry\n`initialize_carry` should be refactored as an instance method with the following signature:\n\n```python\ndef initialize_carry(self, key, sample_input):\n```\n\n`sample_input` should be an array of the same shape that will be processed by the cell, excluding the time axis.\n\n### Refactor RNNCellBase subclasses\n`RNNCellBase` should be refactored to include the metadata required to initialize the cell and execute its forward pass. For `LSTMCell` and `GRUCell`, this means adding a `features` attribute that should be provided by the user upon construction. This change aligns with the structure of most other `Module`s, making them more familiar to users.\n\n```python\nx = jnp.ones((2, 100, 10)) # (batch, time, features)\n\ncell = nn.LSTMCell(features=32)\ncarry = cell.initialize_carry(PRNGKey(0), x[:, 0]) # sample input\n\n(carry, y), variables = cell.init_with_output(PRNGKey(1), carry, x)\n```\n\n### num_feature_dims\nTo simplify the handling of `RNNCellBase` instances in abstractions like `RNN`, each cell should implement the `num_feature_dims` property. For most cells, such as `LSTMCell` and `GRUCell`, this is always 1. For cells like `ConvLSTM`, this depends on their `kernel_size`.\n\n## Discussion\n### Alternative Approaches\n* To eliminate the need for `num_feature_dims`, `RNN` could support only a single batch dimension, i.e., inputs of the form `(batch, time, *features)`. Currently, it supports both multiple batch dimensions and multiple feature dimensions.\n* Another approach could be a complete redesign of how Flax deals with recurrent states. For example, a `memory` collection could be handled as part of the variables. However, this introduces challenges such as handling stateless cells during training, passing state from one layer to another, and performing initialization inside `scan`.\n\n### Refactor Cost\nInitial TGP results showed 761 broken and 110 failed tests. However, after fixing one test, TGP results in 231 broken and 13 failed tests so there seems to be a lot\nof overlap between the broken tests.\n\nTo minimize refactor costs, the current implementation will be kept for Google internal users under a deprecated name. This will allow users to migrate to the new API at their own pace. For Open Source users we should bump Flax version to\n`0.7.0` so existing users can continue to depend on `0.6.x` versions.\n"
  },
  {
    "path": "docs/flip/4105-jax-style-nnx-transforms.md",
    "content": "# JAX-style NNX Transforms\n\n- Authors: Cristian Garcia, Anselm Levskaya\n- Date: Jun/2024\n- FLIP PR: #4107\n- Status: Implementing\n\n## Motivation\n\nNNX allows users to utilize Modules at the top level due to their eager initialization and self-contained state. This naturally leads users to want to use them with transforms and soon start playing with NNX transforms. Since NNX Modules resemble PyTrees in that they contain Arrays, new users often attempt to apply JAX conventions, for example:\n\n```py\n@nnx.vmap(in_axes=(1, 0))\ndef f(m1: Module, m2: Module):\n  ...\n```\n\nHowever, this can be misleading. Currently, NNX transforms follow Linen's convention of treating input Modules as a single unit (all Modules are split together to preserve shared references) and provide APIs for transforming that State separately. The previous example effectively translates to:\n\n```py\n# this is what is really happening\n@nnx.vmap(in_axes=(IGNORE, IGNORE), state_axes={BatchStat: None, ...: 0})\ndef f(m1: Module, m2: Module):\n  ...\n```\n\nNote that `IGNORE` is not a real symbol, but represents the fact that any value placed here won't affect the outcome, as Modules are replaced by empty PyTree placeholders (similar to `None`). The `state_axes` parameter controls how the State is vectorized through a mapping of high-level `Filter`s to their desired axes. In this example, `...` (ellipsis) is a filter that accepts everything, so by default all States are vectorized on the 0th axis.\n\nTo express their original intention, users must resort to more complex custom filters that guess the index of each Module in the monolith. While this is straightforward in simple cases, users generally need to calculate the index (Modules appear in the order specified by `jax.tree.leaves` over the `args`):\n\n```py\nselect_m1 = lambda path, value: path[0] == 0\nselect_m2 = lambda path, value: path[0] == 1\n\n# To select modules individually, you must create a filter (which can be tricky)\n@nnx.vmap(state_axes={select_m1: 1, select_m2: 0})\ndef f(m1: Module, m2: Module):\n  ...\n```\n\n## What if JAX conventions Just Worked™?\n\nThis proposal aims to align NNX transforms with user's expectations based on their JAX experience, making the syntax work as intuitively as possible. The original example would function **as if** `m1` and `m2` were PyTrees vectorized in axes `1` and `0` respectively:\n\n```py\n@nnx.vmap(in_axes=(1, 0))\ndef f(m1: Module, m2: Module):\n  ...\n```\n\nThe primary advantage of this approach is that for `vmap` and `scan`, we could eliminate the `state_axes` and `split_rngs` arguments, relying solely on the `in_axes` API. This syntax alone would likely suffice for 80-90% of use cases, as users tend to manage state in predictable ways.\n\n### The Lift symbols\n\nTo enable more fine-grained state control within each Module, we introduce the `Lift` API. By using special types containing State Filters in place of a tree prefix, state lifting can now be done **structurally**. This allows different Filters to be applied to different Modules in the arguments without the need for complex path-based filters. Ideally, each transform would support its own Lift type, adding the desired behavior through existing JAX APIs.\n\nFor example, in `vmap`, we could allow `StateAxes` instances (vmap's Lift type) to be accepted by `in/out_axes` to control how substates are handled by mapping state `Filter`s to an axis specifier:\n\n```py\nstate_axes = StateAxes({Param: 1, BatchStat: None})\n\n@nnx.vmap(in_axes=(state_axes, 0))\ndef f(m1: Module, m2: Module):\n  ...\n```\n\nIn this case, `m1`'s `Param`s are vectorized in axis `1` while its `BatchStat`s are broadcasted, and `m2`'s entire state is vectorized in axis `0`.\n\nFor `nnx.grad`, we could allow `DiffState` to be used in the `argnums` parameter to specify both the position of the argument to be differentiated and a Filter specifying the differentiable State of the Module:\n\n```py\ngrads = nnx.grad(loss_fn, argnums=(DiffState(0, LoRAParam),))(model, x, y)\n```\n\n## Rng Handling\n\nTo simplify RNG state handling, we propose removing the separate `split_rngs` parameter in `vmap` and `scan`. Instead, we suggest introducing a new `nnx.split_rngs` API that would manage RNG handling before and after the transformation. This approach provides more explicit control to the user and aligns better with JAX transform behavior.\n\n## Consistent Aliasing\n\nTo ensure the correctness of transformations with objects that obey reference semantics, we must enforce consistent lifting/lowering specifications for all aliases of a reference. Transforms must adhere to two rules:\n\n1. All aliases of a reference must receive the **exact same** lifting/lowering specification.\n2. Captured references are not allowed on the output of transformed functions.\n\nFor example:\n\n```py\n@nnx.vmap(in_axes=(m1_axes, m2_axes, m1_axes), out_axes=m2_axes)\ndef f(m1, m2, m1_alias):\n  return m2\n\nm2 = f(m1, m2, m1)\n```\n\nHere, `m1` has two input aliases as it is passed as the first and third input to `f`, but this is acceptable because `m1_axes` is assigned to both in `in_axes`. `m2` is passed as the second input and has an output alias, which is also acceptable because `m2_axes` is assigned in both `in_axes` and `out_axes`.\n\nLet's examine some examples of programs that should be **rejected** based on these criteria:\n\n### Inconsistent input aliases\n\nConsider a function with two arguments `m1` and `m2` being vectorized in axis `0` and `1` respectively. Passing the same Module as both arguments would be inconsistent:\n\n```py\n@nnx.vmap(in_axes=(0, 1))\ndef f(m1: Module, m2: Module):\n  ...\n\nf(m, m)  # This should be rejected\n```\n\n### Inconsistent input / output aliases\n\nNow consider an identity function `g` under `vmap` with `in_axes=0` and `out_axes=1`. In JAX, this would result in transposing the arrays in the inputs:\n\n```py\n@nnx.vmap(in_axes=0, out_axes=1)\ndef g(m: Module):\n  return m\n```\n\nWhile this appears correct, in NNX this behavior is not well-defined because shared mutable references behave as auxiliary outputs. Under the hood, `g` is converted into a function that has the inputs as an extra first output, and `out_axes` is set to the same values as `in_axes` for that output:\n\n```py\n@nnx.vmap(in_axes=0, out_axes=(0, 1))\ndef g_real(m: Module):\n  return m, m\n```\n\nThis return structure reveals an inconsistency: we're attempting to lower `m` with both `out_axes=0` and `out_axes=1`.\n\n### Inconsistent aliases in nested structures\n\nSimilar issues can arise in less obvious cases, such as when `m` is contained within another structure:\n\n```py\n@nnx.vmap(in_axes=0, out_axes=1)\ndef f(m: Module):\n  return SomeModule(m)\n```\n\nThis means we must traverse the entire graph of both inputs and outputs to check for consistent assignments. The same problem occurs when passing shared reference inputs/outputs with different specifications:\n\n```py\nshared = Shared()\nm1, m2 = Foo(shared), Foo(shared)\n\n@nnx.vmap(in_axes=(0, 1))\ndef f(m1, m2):  # shared is passed through both\n  ...\n```\n\n### Captured Modules cannot be outputs\n\nFinally, let's consider the second consistent aliasing rule, which states that captured Modules cannot be outputs. The main issue here is that NNX needs to split all input references together to track changes, but captured Modules bypass this process. Treating them as new references would result in **implicit cloning**:\n\n```py\nm = SomeModule()\n\n@nnx.vmap(out_axes=0, axis_size=5)\ndef f():\n  return m\n\nassert m is not f()  # implicit cloning\n```\n\nTo preserve reference identity, we must disallow captured Modules as outputs. In practice, we can detect captured Modules using the trace level context machinery used to restrict stateful updates on Modules from a different level.\n\n## Recap\n\nIn this document, we have:\n\n* Discussed issues with the current implementation that make it unintuitive for JAX users.\n* Proposed refactoring NNX transforms to allow users to use regular JAX semantics when interacting with objects, removing extra arguments introduced by NNX transforms.\n* Introduced the use of Lift types in JAX APIs to compensate for the lack of a \"prefix\" notion in NNX objects, enabling independent lifting of Module substates.\n* Proposed a new `nnx.split_rngs` API to replace the `split_rngs` arguments in `vmap` and `scan`, making RNG handling an explicit operation and giving users more control.\n* Analyzed edge cases resulting from aliasing shared mutable references and proposed enforcing **consistent aliasing** on all transforms with semantics over the inputs."
  },
  {
    "path": "docs/flip/README.md",
    "content": "# FLIP: Flax Improvement Process\n\nMost changes can be discussed with simple issues/discussions and pull requests.\n\nSome changes though are a bit larger in scope or require more discussion, and\nthese should be implemented as FLIPs. This allows for writing longer documents\nthat can be discussed in a pull request themselves.\n\nThe structure of FLIPs is kept as lightweight as possible to start and might\nbe extended later on.\n\n## When you should use a FLIP\n\n- When your change requires a design doc. We prefer collecting the designs as\n  FLIPs for better discoverability and further reference.\n\n- When your change requires extensive discussion. It's fine to have relatively\n  short discussions on issues or pull requests, but when the discussion gets\n  longer this becomes unpractical for later digestion. FLIPs allow to update the\n  main document with a summary of the discussion and these updates can be\n  discussed themselves in the pull request adding the FLIP.\n\n## How to start a FLIP\n\nFirst, create an issue with the [FLIP label]. All pull requests that relate to\nthe FLIP (i.e. adding the FLIP itself as well as any implementing pull requests)\nshould be linked to this issue.\n\nThen create a pull request that consists of a copy of the `0000-template.md`\nrenamed to `%04d-{short-title}.md` - with the number being the issue number.\n\n[FLIP label]: https://github.com/google/flax/issues?q=label%3AFLIP\n"
  },
  {
    "path": "docs/glossary.rst",
    "content": "*********\nGlossary\n*********\n\nFor additional terms, refer to the `Jax glossary <https://jax.readthedocs.io/en/latest/glossary.html>`__.\n\n.. glossary::\n\n    Bound Module\n      When a :class:`Module <flax.linen.Module>`\n      is created through regular Python object construction (e.g. `module = SomeModule(args...)`, it is in an *unbound* state. This means that only\n      dataclass attributes are set, and no variables are bound to the module. When the pure\n      functions :meth:`Module.init() <flax.linen.Module.init>`\n      or :meth:`Module.apply() <flax.linen.Module.apply>`\n      are called, Flax clones the Module and binds the variables to it, and the module's method code is\n      executed in a locally bound state, allowing things like calling submodules directly without\n      providing variables. For more details, refer to the\n      `module lifecycle <https://flax.readthedocs.io/en/latest/developer_notes/module_lifecycle.html>`__.\n\n    Compact / Non-compact Module\n      Modules with a single method are able to declare submodules and variables inline by\n      using the  :func:`@nn.compact <flax.linen.compact>` decorator.\n      These are referred to as “compact-style modules”,\n      whereas modules defining a :meth:`setup() <flax.linen.Module.setup>` method\n      (usually but not always with multiple callable methods)\n      are referred to as “setup-style modules”. To learn more, refer to the\n      `setup vs compact guide <https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/setup_or_nncompact.html>`__.\n\n    `Folding in <https://jax.readthedocs.io/en/latest/_autosummary/jax.random.fold_in.html>`__\n      Generating a new PRNG key given an input PRNG key and integer. Typically used when you want to\n      generate a new key but still be able to use the original rng key afterwards. You can also do this with\n      `jax.random.split <https://jax.readthedocs.io/en/latest/_autosummary/jax.random.split.html>`__\n      but this will effectively create two RNG keys, which is slower. See how Flax generates new PRNG keys\n      automatically within ``Modules`` in our\n      `RNG guide <https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/rng_guide.html#how-self-make-rng-works-under-the-hood>`__.\n\n    `FrozenDict <https://flax.readthedocs.io/en/latest/api_reference/flax.core.frozen_dict.html#flax.core.frozen_dict.FrozenDict>`__\n      An immutable dictionary which can be “`unfrozen <https://flax.readthedocs.io/en/latest/api_reference/flax.core.frozen_dict.html#flax.core.frozen_dict.unfreeze>`__”\n      to a regular, mutable dictionary. Internally, Flax uses FrozenDicts to ensure variable dicts\n      aren't accidentally mutated. Note: We are considering returning to regular dicts from our APIs,\n      and only using FrozenDicts internally.\n      (see `#1223 <https://github.com/google/flax/issues/1223>`__).\n\n    Functional core\n      The flax core library implements the simple container Scope API for threading\n      variables and PRNGs through a model, as well as the lifting machinery needed to\n      transform functions passing Scope objects. The python class-based module API\n      is built on top of this core library.\n\n    Lazy initialization\n      Variables in Flax are initialized late, only when needed. That is, during normal\n      execution of a module, if a requested variable name isn’t found in the provided\n      variable collection data, we call the initializer function to create it. This\n      allows us to treat initialization and application under the same code-paths,\n      simplifying the use of JAX transforms with layers.\n\n    Lifted transformation\n      Refer to the `Flax docs <https://flax.readthedocs.io/en/latest/developer_notes/lift.html>`__.\n\n    Module\n      A dataclass allowing the definition and initialization of parameters in a\n      referentially-transparent form. This is responsible for storing and updating variables\n      and parameters within itself. Modules can be readily transformed into functions,\n      allowing them to be trivially used with JAX transformations like `vmap` and `scan`.\n\n    Params / parameters\n      \"params\" is the canonical variable collection in the variable dictionary (dict).\n      The “params” collection generally contains the trainable weights.\n\n    RNG sequences\n      Inside Flax :class:`Modules <flax.linen.Module>`, you can obtain a new\n      `PRNG <https://en.wikipedia.org/wiki/Pseudorandom_number_generator>`__\n      key through :meth:`Module.make_rng() <flax.linen.Module.make_rng>`.\n      These keys can be used to generate random numbers through\n      `JAX's functional random number generators <https://jax.readthedocs.io/en/latest/jax-101/05-random-numbers.html>`__.\n      Having different RNG sequences (e.g. for \"params\" and \"dropout\") allows fine-grained\n      control in a multi-host setup (e.g. initializing parameters identically on different\n      hosts, but have different dropout masks) and treating these sequences differently when\n      `lifting transformations <https://flax.readthedocs.io/en/latest/developer_notes/lift.html>`__.\n      See the `RNG guide <https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/rng_guide.html>`__\n      for more details.\n\n    Scope\n      A container class for holding the variables and PRNG keys for each layer.\n\n    Shape inference\n      Modules do not need to specify the shape of the input array in their definitions.\n      Flax upon initialization inspects the input array, and infers the correct shapes\n      for parameters in the model.\n\n    TrainState\n      Refer to :class:`flax.training.train_state.TrainState`.\n\n    Variable\n      The `weights / parameters / data / arrays <https://flax.readthedocs.io/en/latest/api_reference/flax.linen/variable.html#flax.linen.Variable>`__\n      residing in the leaves of :term:`variable collections<Variable collections>`.\n      Variables are defined inside modules using :meth:`Module.variable() <flax.linen.Module.variable>`.\n      A variable of collection \"params\" is simply called a param and can be set using\n      :meth:`Module.param() <flax.linen.Module.param>`.\n\n    Variable collections\n      Entries in the variable dict, containing weights / parameters / data / arrays that\n      are used by the model. “params” is the canonical collection in the variable dict.\n      They are typically differentiable, updated by an outer SGD-like loop / optimizer,\n      rather than modified directly by forward-pass code.\n\n    `Variable dictionary <https://flax.readthedocs.io/en/latest/api_reference/flax.linen/variable.html>`__\n      A dictionary containing :term:`variable collections<Variable collections>`.\n      Each variable collection is a mapping from a string name\n      (e.g., \":term:`params<Params / parameters>`\" or \"batch_stats\") to a (possibly nested)\n      dictionary with :term:`Variables<Variable>` as leaves, matching the submodule tree structure.\n      Read more about pytrees and leaves in the `Jax docs <https://jax.readthedocs.io/en/latest/pytrees.html>`__."
  },
  {
    "path": "docs/guides/converting_and_upgrading/convert_pytorch_to_flax.rst",
    "content": "Convert PyTorch models to Flax\n==============================\n\n.. testsetup::\n\n  import numpy as np\n  import jax\n  from jax import random, numpy as jnp\n  import flax\n\n  from flax import linen as nn\n\n  import torch\n\nWe will show how to convert PyTorch models to Flax. We will cover convolutions, fc layers, batch norm, and average pooling.\n\n\nFC Layers\n--------------------------------\n\nLet's start with fc layers. The only thing to be aware of here is that the PyTorch kernel has shape [outC, inC]\nand the Flax kernel has shape [inC, outC]. Transposing the kernel will do the trick.\n\n.. testcode::\n\n  t_fc = torch.nn.Linear(in_features=3, out_features=4)\n\n  kernel = t_fc.weight.detach().cpu().numpy()\n  bias = t_fc.bias.detach().cpu().numpy()\n\n  # [outC, inC] -> [inC, outC]\n  kernel = jnp.transpose(kernel, (1, 0))\n\n  key = random.key(0)\n  x = random.normal(key, (1, 3))\n\n  variables = {'params': {'kernel': kernel, 'bias': bias}}\n  j_fc = nn.Dense(features=4)\n  j_out = j_fc.apply(variables, x)\n\n  t_x = torch.from_numpy(np.array(x))\n  t_out = t_fc(t_x)\n  t_out = t_out.detach().cpu().numpy()\n\n  np.testing.assert_almost_equal(j_out, t_out, decimal=6)\n\n\nConvolutions\n--------------------------------\n\nLet's now look at 2D convolutions. PyTorch uses the NCHW format and Flax uses NHWC.\nConsequently, the kernels will have different shapes. The kernel in PyTorch has shape [outC, inC, kH, kW]\nand the Flax kernel has shape [kH, kW, inC, outC]. Transposing the kernel will do the trick.\n\n.. testcode::\n\n  t_conv = torch.nn.Conv2d(in_channels=3, out_channels=4, kernel_size=2, padding='valid')\n\n  kernel = t_conv.weight.detach().cpu().numpy()\n  bias = t_conv.bias.detach().cpu().numpy()\n\n  # [outC, inC, kH, kW] -> [kH, kW, inC, outC]\n  kernel = jnp.transpose(kernel, (2, 3, 1, 0))\n\n  key = random.key(0)\n  x = random.normal(key, (1, 6, 6, 3))\n\n  variables = {'params': {'kernel': kernel, 'bias': bias}}\n  j_conv = nn.Conv(features=4, kernel_size=(2, 2), padding='valid')\n  j_out = j_conv.apply(variables, x)\n\n  # [N, H, W, C] -> [N, C, H, W]\n  t_x = torch.from_numpy(np.transpose(np.array(x), (0, 3, 1, 2)))\n  t_out = t_conv(t_x)\n  # [N, C, H, W] -> [N, H, W, C]\n  t_out = np.transpose(t_out.detach().cpu().numpy(), (0, 2, 3, 1))\n\n  np.testing.assert_almost_equal(j_out, t_out, decimal=6)\n\n\n\nConvolutions and FC Layers\n--------------------------------\n\nWe have to be careful, when we have a model that uses convolutions followed by fc layers (ResNet, VGG, etc).\nIn PyTorch, the activations will have shape [N, C, H, W] after the convolutions and are then\nreshaped to [N, C * H * W] before being fed to the fc layers.\nWhen we port our weights from PyTorch to Flax, the activations after the convolutions will be of shape [N, H, W, C] in Flax.\nBefore we reshape the activations for the fc layers, we have to transpose them to [N, C, H, W].\n\nConsider this PyTorch model:\n\n.. testcode::\n\n  class TModel(torch.nn.Module):\n\n    def __init__(self):\n      super(TModel, self).__init__()\n      self.conv = torch.nn.Conv2d(in_channels=3, out_channels=4, kernel_size=2, padding='valid')\n      self.fc = torch.nn.Linear(in_features=100, out_features=2)\n\n    def forward(self, x):\n      x = self.conv(x)\n      x = x.reshape(x.shape[0], -1)\n      x = self.fc(x)\n      return x\n\n\n  t_model = TModel()\n\n\n\nNow, if you want to use the weights from this model in Flax, the corresponding Flax model has to look like this:\n\n\n.. testcode::\n\n  class JModel(nn.Module):\n\n    @nn.compact\n    def __call__(self, x):\n      x = nn.Conv(features=4, kernel_size=(2, 2), padding='valid', name='conv')(x)\n      # [N, H, W, C] -> [N, C, H, W]\n      x = jnp.transpose(x, (0, 3, 1, 2))\n      x = jnp.reshape(x, (x.shape[0], -1))\n      x = nn.Dense(features=2, name='fc')(x)\n      return x\n\n\n  j_model = JModel()\n\n\n\nThe model looks very similar to the PyTorch model, except that we included a transpose operation before\nreshaping our activations for the fc layer.\nWe can omit the transpose operation if we apply pooling before reshaping such that the spatial dimensions are 1x1.\n\nOther than the transpose operation before reshaping, we can convert the weights the same way as we did before:\n\n\n.. testcode::\n\n  conv_kernel = t_model.state_dict()['conv.weight'].detach().cpu().numpy()\n  conv_bias = t_model.state_dict()['conv.bias'].detach().cpu().numpy()\n  fc_kernel = t_model.state_dict()['fc.weight'].detach().cpu().numpy()\n  fc_bias = t_model.state_dict()['fc.bias'].detach().cpu().numpy()\n\n  # [outC, inC, kH, kW] -> [kH, kW, inC, outC]\n  conv_kernel = jnp.transpose(conv_kernel, (2, 3, 1, 0))\n\n  # [outC, inC] -> [inC, outC]\n  fc_kernel = jnp.transpose(fc_kernel, (1, 0))\n\n  variables = {'params': {'conv': {'kernel': conv_kernel, 'bias': conv_bias},\n                          'fc': {'kernel': fc_kernel, 'bias': fc_bias}}}\n\n  key = random.key(0)\n  x = random.normal(key, (1, 6, 6, 3))\n\n  j_out = j_model.apply(variables, x)\n\n  # [N, H, W, C] -> [N, C, H, W]\n  t_x = torch.from_numpy(np.transpose(np.array(x), (0, 3, 1, 2)))\n  t_out = t_model(t_x)\n  t_out = t_out.detach().cpu().numpy()\n\n  np.testing.assert_almost_equal(j_out, t_out, decimal=6)\n\n\n\nBatch Norm\n--------------------------------\n\n``torch.nn.BatchNorm2d`` uses ``0.1`` as the default value for the ``momentum`` parameter while\n|nn.BatchNorm|_ uses ``0.9``. However, this corresponds to the same computation, because PyTorch multiplies\nthe estimated statistic with ``(1 − momentum)`` and the new observed value with ``momentum``,\nwhile Flax multiplies the estimated statistic with ``momentum`` and the new observed value with ``(1 − momentum)``.\n\n.. |nn.BatchNorm| replace:: ``nn.BatchNorm``\n.. _nn.BatchNorm: https://flax.readthedocs.io/en/latest/api_reference/flax.linen/layers.html#flax.linen.BatchNorm\n\n.. testcode::\n\n  t_bn = torch.nn.BatchNorm2d(num_features=3, momentum=0.1)\n  t_bn.eval()\n\n  scale = t_bn.weight.detach().cpu().numpy()\n  bias = t_bn.bias.detach().cpu().numpy()\n  mean = t_bn.running_mean.detach().cpu().numpy()\n  var = t_bn.running_var.detach().cpu().numpy()\n\n  variables = {'params': {'scale': scale, 'bias': bias},\n               'batch_stats': {'mean': mean, 'var': var}}\n\n  key = random.key(0)\n  x = random.normal(key, (1, 6, 6, 3))\n\n  j_bn = nn.BatchNorm(momentum=0.9, use_running_average=True)\n\n  j_out = j_bn.apply(variables, x)\n\n  # [N, H, W, C] -> [N, C, H, W]\n  t_x = torch.from_numpy(np.transpose(np.array(x), (0, 3, 1, 2)))\n  t_out = t_bn(t_x)\n  # [N, C, H, W] -> [N, H, W, C]\n  t_out = np.transpose(t_out.detach().cpu().numpy(), (0, 2, 3, 1))\n\n  np.testing.assert_almost_equal(j_out, t_out, decimal=6)\n\n\n\nAverage Pooling\n--------------------------------\n\n``torch.nn.AvgPool2d`` and |nn.avg_pool()|_ are compatible when using default parameters.\nHowever, ``torch.nn.AvgPool2d`` has a parameter ``count_include_pad``. When ``count_include_pad=False``,\nthe zero-padding will not be considered for the average calculation. There does not exist a similar\nparameter for |nn.avg_pool()|_. However, we can easily implement a wrapper around the pooling\noperation. ``nn.pool()`` is the core function behind |nn.avg_pool()|_ and |nn.max_pool()|_.\n\n.. |nn.avg_pool()| replace:: ``nn.avg_pool()``\n.. _nn.avg_pool(): https://flax.readthedocs.io/en/latest/api_reference/flax.linen/layers.html#flax.linen.avg_pool\n\n.. |nn.max_pool()| replace:: ``nn.max_pool()``\n.. _nn.max_pool(): https://flax.readthedocs.io/en/latest/api_reference/flax.linen/layers.html#flax.linen.max_pool\n\n\n.. testcode::\n\n  def avg_pool(inputs, window_shape, strides=None, padding='VALID'):\n    \"\"\"\n    Pools the input by taking the average over a window.\n    In comparison to nn.avg_pool(), this pooling operation does not\n    consider the padded zero's for the average computation.\n    \"\"\"\n    assert len(window_shape) == 2\n\n    y = nn.pool(inputs, 0., jax.lax.add, window_shape, strides, padding)\n    counts = nn.pool(jnp.ones_like(inputs), 0., jax.lax.add, window_shape, strides, padding)\n    y = y / counts\n    return y\n\n\n  key = random.key(0)\n  x = random.normal(key, (1, 6, 6, 3))\n\n  j_out = avg_pool(x, window_shape=(2, 2), strides=(1, 1), padding=((1, 1), (1, 1)))\n  t_pool = torch.nn.AvgPool2d(kernel_size=2, stride=1, padding=1, count_include_pad=False)\n\n  # [N, H, W, C] -> [N, C, H, W]\n  t_x = torch.from_numpy(np.transpose(np.array(x), (0, 3, 1, 2)))\n  t_out = t_pool(t_x)\n  # [N, C, H, W] -> [N, H, W, C]\n  t_out = np.transpose(t_out.detach().cpu().numpy(), (0, 2, 3, 1))\n\n  np.testing.assert_almost_equal(j_out, t_out, decimal=6)\n\n\n\nTransposed Convolutions\n--------------------------------\n\n``torch.nn.ConvTranspose2d`` and |nn.ConvTranspose|_ are not compatible.\n|nn.ConvTranspose|_ is a wrapper around |jax.lax.conv_transpose|_ which computes a fractionally strided convolution,\nwhile ``torch.nn.ConvTranspose2d`` computes a gradient based transposed convolution. Currently, there is no\nimplementation of a gradient based transposed convolution is ``Jax``. However, there is a pending `pull request`_\nthat contains an implementation.\n\nTo load ``torch.nn.ConvTranspose2d`` parameters into Flax, we need to use the ``transpose_kernel`` arg in Flax's\n``nn.ConvTranspose`` layer.\n\n.. testcode::\n\n  # padding is inverted\n  torch_padding = 0\n  flax_padding = 1 - torch_padding\n\n  t_conv = torch.nn.ConvTranspose2d(in_channels=3, out_channels=4, kernel_size=2, padding=torch_padding)\n\n  kernel = t_conv.weight.detach().cpu().numpy()\n  bias = t_conv.bias.detach().cpu().numpy()\n\n  # [inC, outC, kH, kW] -> [kH, kW, outC, inC]\n  kernel = jnp.transpose(kernel, (2, 3, 1, 0))\n\n  key = random.key(0)\n  x = random.normal(key, (1, 6, 6, 3))\n\n  variables = {'params': {'kernel': kernel, 'bias': bias}}\n  # ConvTranspose expects the kernel to be [kH, kW, inC, outC],\n  # but with `transpose_kernel=True`, it expects [kH, kW, outC, inC] instead\n  j_conv = nn.ConvTranspose(features=4, kernel_size=(2, 2), padding=flax_padding, transpose_kernel=True)\n  j_out = j_conv.apply(variables, x)\n\n  # [N, H, W, C] -> [N, C, H, W]\n  t_x = torch.from_numpy(np.transpose(np.array(x), (0, 3, 1, 2)))\n  t_out = t_conv(t_x)\n  # [N, C, H, W] -> [N, H, W, C]\n  t_out = np.transpose(t_out.detach().cpu().numpy(), (0, 2, 3, 1))\n  np.testing.assert_almost_equal(j_out, t_out, decimal=6)\n\n\n.. _`pull request`: https://github.com/jax-ml/jax/pull/5772\n\n.. |nn.ConvTranspose| replace:: ``nn.ConvTranspose``\n.. _nn.ConvTranspose: https://flax.readthedocs.io/en/latest/api_reference/flax.linen/layers.html#flax.linen.ConvTranspose\n\n.. |jax.lax.conv_transpose| replace:: ``jax.lax.conv_transpose``\n.. _jax.lax.conv_transpose: https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv_transpose.html\n\n"
  },
  {
    "path": "docs/guides/converting_and_upgrading/haiku_migration_guide.rst",
    "content": "\nMigrating from Haiku to Flax\n============================\n\nThis guide will walk through the process of migrating Haiku models to Flax,\nand highlight the differences between the two libraries.\n\n.. testsetup:: Haiku, Flax\n\n  import jax\n  import jax.numpy as jnp\n  from jax import random\n  import optax\n  import flax.linen as nn\n  import haiku as hk\n\nBasic Example\n-----------------\n\nTo create custom Modules you subclass from a ``Module`` base class in\nboth Haiku and Flax. However, Haiku classes use a regular ``__init__`` method\nwhereas Flax classes are ``dataclasses``, meaning you define some class\nattributes that are used to automatically generate a constructor. Also,\nall Flax Modules accept a ``name`` argument without needing to define it,\nwhereas in Haiku ``name`` must be explicitly defined in the constructor\nsignature and passed to the superclass constructor.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  import haiku as hk\n\n  class Block(hk.Module):\n    def __init__(self, features: int, name=None):\n      super().__init__(name=name)\n      self.features = features\n\n    def __call__(self, x, training: bool):\n      x = hk.Linear(self.features)(x)\n      x = hk.dropout(hk.next_rng_key(), 0.5 if training else 0, x)\n      x = jax.nn.relu(x)\n      return x\n\n  class Model(hk.Module):\n    def __init__(self, dmid: int, dout: int, name=None):\n      super().__init__(name=name)\n      self.dmid = dmid\n      self.dout = dout\n\n    def __call__(self, x, training: bool):\n      x = Block(self.dmid)(x, training)\n      x = hk.Linear(self.dout)(x)\n      return x\n\n  ---\n\n  import flax.linen as nn\n\n  class Block(nn.Module):\n    features: int\n\n\n    @nn.compact\n    def __call__(self, x, training: bool):\n      x = nn.Dense(self.features)(x)\n      x = nn.Dropout(0.5, deterministic=not training)(x)\n      x = jax.nn.relu(x)\n      return x\n\n  class Model(nn.Module):\n    dmid: int\n    dout: int\n\n\n    @nn.compact\n    def __call__(self, x, training: bool):\n      x = Block(self.dmid)(x, training)\n      x = nn.Dense(self.dout)(x)\n      return x\n\nThe ``__call__`` method looks very similar in both libraries, however, in Flax\nyou have to use the ``@nn.compact`` decorator in order to be able to define\nsubmodules inline. In Haiku, this is the default behavior.\n\nNow, a place where Haiku and Flax differ substantially is in how you construct\nthe model. In Haiku, you use ``hk.transform`` over a function\nthat calls your Module, ``transform`` will return an object with ``init``\nand ``apply`` methods. In Flax, you simply instantiate your Module.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  def forward(x, training: bool):\n    return Model(256, 10)(x, training)\n\n  model = hk.transform(forward)\n\n  ---\n\n  ...\n\n\n  model = Model(256, 10)\n\nTo get the model parameters in both libraries you use the ``init`` method\nwith a ``random.key`` plus some inputs to run the model. The main difference here is\nthat Flax returns a mapping from collection names to nested array dictionaries,\n``params`` is just one of these possible collections. In Haiku, you get the ``params``\nstructure directly.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  sample_x = jax.numpy.ones((1, 784))\n  params = model.init(\n    random.key(0),\n    sample_x, training=False # <== inputs\n  )\n  ...\n\n  ---\n\n  sample_x = jax.numpy.ones((1, 784))\n  variables = model.init(\n    random.key(0),\n    sample_x, training=False # <== inputs\n  )\n  params = variables[\"params\"]\n\nOne very important thing to note is that in Flax the parameters structure is\nhierarchical, with one level per nested module and a final level for the\nparameter name.\nIn Haiku the parameters structure is a python dictionary with a two level hierarchy:\nthe fully qualified module name mapping to the parameter name. The module name\nconsists of a ``/`` separated string path of all the nested Modules.\n\n.. tab-set::\n\n  .. tab-item:: Haiku\n    :sync: Haiku\n\n    .. code-block:: python\n\n      ...\n      {\n        'model/block/linear': {\n          'b': (256,),\n          'w': (784, 256),\n        },\n        'model/linear': {\n          'b': (10,),\n          'w': (256, 10),\n        }\n      }\n      ...\n\n\n  .. tab-item:: Flax\n    :sync: Flax\n\n    .. code-block:: python\n\n      FrozenDict({\n        Block_0: {\n          Dense_0: {\n            bias: (256,),\n            kernel: (784, 256),\n          },\n        },\n        Dense_0: {\n          bias: (10,),\n          kernel: (256, 10),\n        },\n      })\n\nDuring training in both frameworks you pass the parameters structure to the\n``apply`` method to run the forward pass. Since we are using dropout, in\nboth cases we must provide a ``key`` to ``apply`` in order to generate\nthe random dropout masks.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  def train_step(key, params, inputs, labels):\n    def loss_fn(params):\n        logits = model.apply(\n          params,\n          key,\n          inputs, training=True # <== inputs\n        )\n        return optax.softmax_cross_entropy_with_integer_labels(logits, labels).mean()\n\n    grads = jax.grad(loss_fn)(params)\n    params = jax.tree_util.tree_map(lambda p, g: p - 0.1 * g, params, grads)\n\n    return params\n\n  ---\n\n  def train_step(key, params, inputs, labels):\n    def loss_fn(params):\n        logits = model.apply(\n          {'params': params},\n          inputs, training=True, # <== inputs\n          rngs={'dropout': key}\n        )\n        return optax.softmax_cross_entropy_with_integer_labels(logits, labels).mean()\n\n    grads = jax.grad(loss_fn)(params)\n    params = jax.tree_util.tree_map(lambda p, g: p - 0.1 * g, params, grads)\n\n    return params\n\n.. testcode:: Haiku, Flax\n  :hide:\n\n  train_step(random.key(0), params, sample_x, jnp.ones((1,), dtype=jnp.int32))\n\nThe most notable differences is that in Flax you have to\npass the parameters inside a dictionary with a ``params`` key, and the\nkey inside a dictionary with a ``dropout`` key. This is because in Flax\nyou can have many types of model state and random state. In Haiku, you\njust pass the parameters and the key directly.\n\nHandling State\n-----------------\n\nNow let's see how mutable state is handled in both libraries. We will take\nthe same model as before, but now we will replace Dropout with BatchNorm.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  class Block(hk.Module):\n    def __init__(self, features: int, name=None):\n      super().__init__(name=name)\n      self.features = features\n\n    def __call__(self, x, training: bool):\n      x = hk.Linear(self.features)(x)\n      x = hk.BatchNorm(\n        create_scale=True, create_offset=True, decay_rate=0.99\n      )(x, is_training=training)\n      x = jax.nn.relu(x)\n      return x\n\n  ---\n\n  class Block(nn.Module):\n    features: int\n\n\n    @nn.compact\n    def __call__(self, x, training: bool):\n      x = nn.Dense(self.features)(x)\n      x = nn.BatchNorm(\n        momentum=0.99\n      )(x, use_running_average=not training)\n      x = jax.nn.relu(x)\n      return x\n\nThe code is very similar in this case as both libraries provide a BatchNorm\nlayer. The most notable difference is that Haiku uses ``is_training`` to\ncontrol whether or not to update the running statistics, whereas Flax uses\n``use_running_average`` for the same purpose.\n\nTo instantiate a stateful model in Haiku you use ``hk.transform_with_state``,\nwhich changes the signature for ``init`` and ``apply`` to accept and return\nstate. As before, in Flax you construct the Module directly.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  def forward(x, training: bool):\n    return Model(256, 10)(x, training)\n\n  model = hk.transform_with_state(forward)\n\n  ---\n\n  ...\n\n\n  model = Model(256, 10)\n\n\nTo initialize both the parameters and state you just call the ``init`` method\nas before. However, in Haiku you now get ``state`` as a second return value, and\nin Flax you get a new ``batch_stats`` collection in the ``variables`` dictionary.\nNote that since ``hk.BatchNorm`` only initializes batch statistics when\n``is_training=True``, we must set ``training=True`` when initializing parameters\nof a Haiku model with an ``hk.BatchNorm`` layer. In Flax, we can set\n``training=False`` as usual.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  sample_x = jax.numpy.ones((1, 784))\n  params, state = model.init(\n    random.key(0),\n    sample_x, training=True # <== inputs #!\n  )\n  ...\n\n  ---\n\n  sample_x = jax.numpy.ones((1, 784))\n  variables = model.init(\n    random.key(0), #!\n    sample_x, training=False # <== inputs\n  )\n  params, batch_stats = variables[\"params\"], variables[\"batch_stats\"]\n\n\nIn general, in Flax you might find other state collections in the ``variables``\ndictionary such as ``cache`` for auto-regressive transformers models,\n``intermediates`` for intermediate values added using ``Module.sow``, or other\ncollection names defined by custom layers. Haiku only makes a distinction\nbetween ``params`` (variables which do not change while running ``apply``) and\n``state`` (variables which can change while running ``apply``).\n\nNow, training looks very similar in both frameworks as you use the same\n``apply`` method to run the forward pass. In Haiku, now pass the ``state``\nas the second argument to ``apply``, and get the new state as the second\nreturn value. In Flax, you instead add ``batch_stats`` as a new key to the\ninput dictionary, and get the ``updates`` variables dictionary as the second\nreturn value.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  def train_step(params, state, inputs, labels):\n    def loss_fn(params):\n      logits, new_state = model.apply(\n        params, state,\n        None, # <== rng\n        inputs, training=True # <== inputs\n      )\n      loss = optax.softmax_cross_entropy_with_integer_labels(logits, labels).mean()\n      return loss, new_state\n\n    grads, new_state = jax.grad(loss_fn, has_aux=True)(params)\n    params = jax.tree_util.tree_map(lambda p, g: p - 0.1 * g, params, grads)\n\n    return params, new_state\n  ---\n\n  def train_step(params, batch_stats, inputs, labels):\n    def loss_fn(params):\n      logits, updates = model.apply(\n        {'params': params, 'batch_stats': batch_stats},\n        inputs, training=True, # <== inputs\n        mutable='batch_stats',\n      )\n      loss = optax.softmax_cross_entropy_with_integer_labels(logits, labels).mean()\n      return loss, updates[\"batch_stats\"]\n\n    grads, batch_stats = jax.grad(loss_fn, has_aux=True)(params)\n    params = jax.tree_util.tree_map(lambda p, g: p - 0.1 * g, params, grads)\n\n    return params, batch_stats\n\n.. testcode:: Flax\n  :hide:\n\n  train_step(params, batch_stats, sample_x, jnp.ones((1,), dtype=jnp.int32))\n\nOne major difference is that in Flax a state collection can be mutable or immutable.\nDuring ``init`` all collections are mutable by default, however, during ``apply``\nyou have to explicitly specify which collections are mutable. In this example,\nwe specify that ``batch_stats`` is mutable. Here a single string is passed but a list\ncan also be given if there are more mutable collections. If this is not done an\nerror will be raised at runtime when trying to mutate ``batch_stats``.\nAlso, when ``mutable`` is anything other than ``False``, the ``updates``\ndictionary is returned as the second return value of ``apply``, else only the\nmodel output is returned.\nHaiku makes the mutable/immutable distinction through having ``params``\n(immutable) and ``state`` (mutable) and using either ``hk.transform`` or\n``hk.transform_with_state``\n\nUsing Multiple Methods\n-----------------------\n\nIn this section we will take a look at how to use multiple methods in Haiku and Flax.\nAs an example, we will implement an auto-encoder model with three methods:\n``encode``, ``decode``, and ``__call__``.\n\nIn Haiku, we can just define the submodules that ``encode`` and ``decode`` need\ndirectly in ``__init__``, in this case each will just use a ``Linear`` layer.\nIn Flax, we will define an ``encoder`` and a ``decoder`` Module ahead of time\nin ``setup``, and use them in the ``encode`` and ``decode`` respectively.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  class AutoEncoder(hk.Module):\n\n\n    def __init__(self, embed_dim: int, output_dim: int, name=None):\n      super().__init__(name=name)\n      self.encoder = hk.Linear(embed_dim, name=\"encoder\")\n      self.decoder = hk.Linear(output_dim, name=\"decoder\")\n\n    def encode(self, x):\n      return self.encoder(x)\n\n    def decode(self, x):\n      return self.decoder(x)\n\n    def __call__(self, x):\n      x = self.encode(x)\n      x = self.decode(x)\n      return x\n\n  ---\n\n  class AutoEncoder(nn.Module):\n    embed_dim: int\n    output_dim: int\n\n    def setup(self):\n      self.encoder = nn.Dense(self.embed_dim)\n      self.decoder = nn.Dense(self.output_dim)\n\n    def encode(self, x):\n      return self.encoder(x)\n\n    def decode(self, x):\n      return self.decoder(x)\n\n    def __call__(self, x):\n      x = self.encode(x)\n      x = self.decode(x)\n      return x\n\nNote that in Flax ``setup`` doesn't run after ``__init__``, instead it runs\nwhen ``init`` or ``apply`` are called.\n\nNow, we want to be able to call any method from our ``AutoEncoder`` model. In Haiku we\ncan define multiple ``apply`` methods for a module through ``hk.multi_transform``. The\nfunction passed to ``multi_transform`` defines how to initialize the module and which\ndifferent apply methods to generate.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  def forward():\n    module = AutoEncoder(256, 784)\n    init = lambda x: module(x)\n    return init, (module.encode, module.decode)\n\n  model = hk.multi_transform(forward)\n\n  ---\n\n  ...\n\n\n\n\n  model = AutoEncoder(256, 784)\n\n\nTo initialize the parameters of our model, ``init`` can be used to trigger the\n``__call__`` method, which uses both the ``encode`` and ``decode``\nmethod. This will create all the necessary parameters for the model.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  params = model.init(\n    random.key(0),\n    x=jax.numpy.ones((1, 784)),\n  )\n  ...\n\n  ---\n\n  variables = model.init(\n    random.key(0),\n    x=jax.numpy.ones((1, 784)),\n  )\n  params = variables[\"params\"]\n\nThis generates the following parameter structure.\n\n.. tab-set::\n\n  .. tab-item:: Haiku\n    :sync: Haiku\n\n    .. code-block:: python\n\n      {\n          'auto_encoder/~/decoder': {\n              'b': (784,),\n              'w': (256, 784)\n          },\n          'auto_encoder/~/encoder': {\n              'b': (256,),\n              'w': (784, 256)\n          }\n      }\n\n  .. tab-item:: Flax\n    :sync: Flax\n\n    .. code-block:: python\n\n      FrozenDict({\n          decoder: {\n              bias: (784,),\n              kernel: (256, 784),\n          },\n          encoder: {\n              bias: (256,),\n              kernel: (784, 256),\n          },\n      })\n\n\nFinally, let's explore how we can employ the ``apply`` function to invoke the ``encode`` method:\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  encode, decode = model.apply\n  z = encode(\n    params,\n    None, # <== rng\n    x=jax.numpy.ones((1, 784)),\n\n  )\n\n  ---\n\n  ...\n  z = model.apply(\n    {\"params\": params},\n\n    x=jax.numpy.ones((1, 784)),\n    method=\"encode\",\n  )\n\nBecause the Haiku ``apply`` function is generated through\n``hk.multi_transform``, it's a tuple of two functions which we can unpack into\nan ``encode`` and ``decode`` function which correspond to the methods on the\n``AutoEncoder`` module. In Flax we call the ``encode`` method through passing\nthe method name as a string.\nAnother noteworthy distinction here is that in Haiku, ``rng`` needs to be\nexplicitly passed, even though the module does not use any stochastic\noperations during ``apply``. In Flax this is not necessary (check out\n`Randomness and PRNGs in Flax <https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/rng_guide.html>`_).\nThe Haiku ``rng`` is set to ``None`` here, but you could also use\n``hk.without_apply_rng`` on the ``apply`` function to remove the ``rng`` argument.\n\n\nLifted Transforms\n-----------------\n\nBoth Flax and Haiku provide a set of transforms, which we will refer to as lifted transforms,\nthat wrap JAX transformations in such a way that they can be used with Modules and sometimes\nprovide additional functionality. In this section we will take a look at how to use the\nlifted version of ``scan`` in both Flax and Haiku to implement a simple RNN layer.\n\nTo begin, we will first define a ``RNNCell`` module that will contain the logic for a single\nstep of the RNN. We will also define a ``initial_state`` method that will be used to initialize\nthe state (a.k.a. ``carry``) of the RNN. Like with ``jax.lax.scan``, the ``RNNCell.__call__``\nmethod will be a function that takes the carry and input, and returns the new\ncarry and output. In this case, the carry and the output are the same.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  class RNNCell(hk.Module):\n    def __init__(self, hidden_size: int, name=None):\n      super().__init__(name=name)\n      self.hidden_size = hidden_size\n\n    def __call__(self, carry, x):\n      x = jnp.concatenate([carry, x], axis=-1)\n      x = hk.Linear(self.hidden_size)(x)\n      x = jax.nn.relu(x)\n      return x, x\n\n    def initial_state(self, batch_size: int):\n      return jnp.zeros((batch_size, self.hidden_size))\n\n  ---\n\n  class RNNCell(nn.Module):\n    hidden_size: int\n\n\n    @nn.compact\n    def __call__(self, carry, x):\n      x = jnp.concatenate([carry, x], axis=-1)\n      x = nn.Dense(self.hidden_size)(x)\n      x = jax.nn.relu(x)\n      return x, x\n\n    def initial_state(self, batch_size: int):\n      return jnp.zeros((batch_size, self.hidden_size))\n\nNext, we will define a ``RNN`` Module that will contain the logic for the entire RNN.\nIn Haiku, we will first initialze the ``RNNCell``, then use it to construct the ``carry``,\nand finally use ``hk.scan`` to run the ``RNNCell`` over the input sequence. In Flax its\ndone a bit differently, we will use ``nn.scan`` to define a new temporary type that wraps\n``RNNCell``. During this process we will also specify instruct ``nn.scan`` to broadcast\nthe ``params`` collection (all steps share the same parameters) and to not split the\n``params`` rng stream (so all steps intialize with the same parameters), and finally\nwe will specify that we want scan to run over the second axis of the input and stack\nthe outputs along the second axis as well. We will then use this temporary type immediately\nto create an instance of the lifted ``RNNCell`` and use it to create the ``carry`` and\nthe run the ``__call__`` method which will ``scan`` over the sequence.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  class RNN(hk.Module):\n    def __init__(self, hidden_size: int, name=None):\n      super().__init__(name=name)\n      self.hidden_size = hidden_size\n\n    def __call__(self, x):\n      cell = RNNCell(self.hidden_size)\n      carry = cell.initial_state(x.shape[0])\n      carry, y = hk.scan(cell, carry, jnp.swapaxes(x, 1, 0))\n      y = jnp.swapaxes(y, 0, 1)\n      return y\n\n  ---\n\n  class RNN(nn.Module):\n    hidden_size: int\n\n\n    @nn.compact\n    def __call__(self, x):\n      rnn = nn.scan(RNNCell, variable_broadcast='params', split_rngs={'params': False},\n                    in_axes=1, out_axes=1)(self.hidden_size)\n      carry = rnn.initial_state(x.shape[0])\n      carry, y = rnn(carry, x)\n      return y\n\nIn general, the main difference between lifted transforms between Flax and Haiku is that\nin Haiku the lifted transforms don't operate over the state, that is, Haiku will handle the\n``params`` and ``state`` in such a way that it keeps the same shape inside and outside of the\ntransform. In Flax, the lifted transforms can operate over both variable collections and rng\nstreams, the user must define how different collections are treated by each transform\naccording to the transform's semantics.\n\nFinally, let's quickly view how the ``RNN`` Module would be used in both Haiku and Flax.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  def forward(x):\n    return RNN(64)(x)\n\n  model = hk.without_apply_rng(hk.transform(forward))\n\n  params = model.init(\n    random.key(0),\n    x=jax.numpy.ones((3, 12, 32)),\n  )\n\n  y = model.apply(\n    params,\n    x=jax.numpy.ones((3, 12, 32)),\n  )\n\n  ---\n\n  ...\n\n\n  model = RNN(64)\n\n  variables = model.init(\n    random.key(0),\n    x=jax.numpy.ones((3, 12, 32)),\n  )\n  params = variables['params']\n  y = model.apply(\n    {'params': params},\n    x=jax.numpy.ones((3, 12, 32)),\n  )\n\nThe only notable change with respect to the examples in the previous sections is that\nthis time around we used ``hk.without_apply_rng`` in Haiku so we didn't have to\npass the ``rng`` argument as ``None`` to the ``apply`` method.\n\nScan over layers\n----------------\nOne very important application of ``scan`` is apply a sequence of layers iteratively\nover an input, passing the output of each layer as the input to the next layer. This\nis very useful to reduce compilation time for big models. As an example we will create\na simple ``Block`` Module, and then use it inside an ``MLP`` Module that will apply\nthe ``Block`` Module ``num_layers`` times.\n\nIn Haiku, we define the ``Block`` Module as usual, and then inside ``MLP`` we will\nuse ``hk.experimental.layer_stack`` over a ``stack_block`` function to create a stack\nof ``Block`` Modules. In Flax, the definition of ``Block`` is a little different,\n``__call__`` will accept and return a second dummy input/output that in both cases will\nbe ``None``. In ``MLP``, we will use ``nn.scan`` as in the previous example, but\nby setting ``split_rngs={'params': True}`` and ``variable_axes={'params': 0}``\nwe are telling ``nn.scan`` create different parameters for each step and slice the\n``params`` collection along the first axis, effectively implementing a stack of\n``Block`` Modules as in Haiku.\n\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  class Block(hk.Module):\n    def __init__(self, features: int, name=None):\n      super().__init__(name=name)\n      self.features = features\n\n    def __call__(self, x, training: bool):\n      x = hk.Linear(self.features)(x)\n      x = hk.dropout(hk.next_rng_key(), 0.5 if training else 0, x)\n      x = jax.nn.relu(x)\n      return x\n\n  class MLP(hk.Module):\n    def __init__(self, features: int, num_layers: int, name=None):\n        super().__init__(name=name)\n        self.features = features\n        self.num_layers = num_layers\n\n    def __call__(self, x, training: bool):\n      @hk.experimental.layer_stack(self.num_layers)\n      def stack_block(x):\n        return Block(self.features)(x, training)\n\n      stack = hk.experimental.layer_stack(self.num_layers)\n      return stack_block(x)\n\n  ---\n\n  class Block(nn.Module):\n    features: int\n    training: bool\n\n    @nn.compact\n    def __call__(self, x, _):\n      x = nn.Dense(self.features)(x)\n      x = nn.Dropout(0.5)(x, deterministic=not self.training)\n      x = jax.nn.relu(x)\n      return x, None\n\n  class MLP(nn.Module):\n    features: int\n    num_layers: int\n\n    @nn.compact\n    def __call__(self, x, training: bool):\n      ScanBlock = nn.scan(\n        Block, variable_axes={'params': 0}, split_rngs={'params': True},\n        length=self.num_layers)\n\n      y, _ = ScanBlock(self.features, training)(x, None)\n      return y\n\nNotice how in Flax we pass ``None`` as the second argument to ``ScanBlock`` and ignore\nits second output. These represent the inputs/outputs per-step but they are ``None``\nbecause in this case we don't have any.\n\nInitializing each model is the same as in previous examples. In this case,\nwe will be specifying that we want to use ``5`` layers each with ``64`` features.\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  def forward(x, training: bool):\n    return MLP(64, num_layers=5)(x, training)\n\n  model = hk.transform(forward)\n\n  sample_x = jax.numpy.ones((1, 64))\n  params = model.init(\n    random.key(0),\n    sample_x, training=False # <== inputs\n  )\n  ...\n\n  ---\n\n  ...\n\n\n  model = MLP(64, num_layers=5)\n\n  sample_x = jax.numpy.ones((1, 64))\n  variables = model.init(\n    random.key(0),\n    sample_x, training=False # <== inputs\n  )\n  params = variables['params']\n\nWhen using scan over layers the one thing you should notice is that all layers\nare fused into a single layer whose parameters have an extra \"layer\" dimension on\nthe first axis. In this case, the shape of all parameters will start with ``(5, ...)``\nas we are using ``5`` layers.\n\n.. tab-set::\n\n  .. tab-item:: Haiku\n    :sync: Haiku\n\n    .. code-block:: python\n\n      ...\n      {\n          'mlp/__layer_stack_no_per_layer/block/linear': {\n              'b': (5, 64),\n              'w': (5, 64, 64)\n          }\n      }\n      ...\n\n  .. tab-item:: Flax\n    :sync: Flax\n\n    .. code-block:: python\n\n      FrozenDict({\n          ScanBlock_0: {\n              Dense_0: {\n                  bias: (5, 64),\n                  kernel: (5, 64, 64),\n              },\n          },\n      })\n\nTop-level Haiku functions vs top-level Flax modules\n-----------------------------------\n\nIn Haiku, it is possible to write the entire model as a single function by using the raw ``hk.{get,set}_{parameter,state}`` to define/access model parameters and states. It very common to write the top-level \"Module\" as a function instead:\n\nThe Flax team recommends a more Module-centric approach that uses `__call__` to define the forward function. The corresponding accessor will be `nn.module.param` and `nn.module.variable` (go to `Handling State <#handling-state>`__ for an explanaion on collections).\n\n.. codediff::\n  :title: Haiku, Flax\n  :sync:\n\n  def forward(x):\n\n\n    counter = hk.get_state('counter', shape=[], dtype=jnp.int32, init=jnp.ones)\n    multiplier = hk.get_parameter('multiplier', shape=[1,], dtype=x.dtype, init=jnp.ones)\n    output = x + multiplier * counter\n    hk.set_state(\"counter\", counter + 1)\n\n    return output\n\n  model = hk.transform_with_state(forward)\n\n  params, state = model.init(random.key(0), jax.numpy.ones((1, 64)))\n\n  ---\n\n  class FooModule(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      counter = self.variable('counter', 'count', lambda: jnp.ones((), jnp.int32))\n      multiplier = self.param('multiplier', nn.initializers.ones_init(), [1,], x.dtype)\n      output = x + multiplier * counter.value\n      if not self.is_initializing():  # otherwise model.init() also increases it\n        counter.value += 1\n      return output\n\n  model = FooModule()\n  variables = model.init(random.key(0), jax.numpy.ones((1, 64)))\n  params, counter = variables['params'], variables['counter']"
  },
  {
    "path": "docs/guides/converting_and_upgrading/index.rst",
    "content": "Converting and upgrading\n========================\n\n.. toctree::\n   :maxdepth: 1\n\n   haiku_migration_guide\n   convert_pytorch_to_flax\n   orbax_upgrade_guide\n   optax_update_guide\n   linen_upgrade_guide\n   rnncell_upgrade_guide\n   regular_dict_upgrade_guide"
  },
  {
    "path": "docs/guides/converting_and_upgrading/linen_upgrade_guide.rst",
    "content": "Upgrading my codebase to Linen\n==============================\n\nAs of Flax v0.4.0, ``flax.nn`` no longer exists, and is replaced with the new\nLinen API at ``flax.linen``. If your codebase is still using the old API, you\ncan use this upgrade guide to upgrade it to Linen.\n\n.. testsetup:: Linen\n\n  from flax.training import train_state\n  from jax import random\n  import optax\n  import jax\n  import flax.linen as nn\n  from flax.linen import initializers\n\n  from jax import lax\n  import jax.numpy as jnp\n  import numpy as np\n  from typing import Any, Callable, Sequence, Tuple\n\n  PRNGKey = Any\n  Shape = Tuple[int, ...]\n  Dtype = Any\n  Array = Any\n\n  default_kernel_init = initializers.lecun_normal()\n\nDefining simple Flax Modules\n----------------------------\n\n.. codediff::\n  :title: Old Flax, Linen\n  :skip_test: Old Flax\n  :sync:\n\n  from flax import nn\n\n  class Dense(base.Module):\n    def apply(self,\n              inputs,\n              features,\n              use_bias=True,\n              kernel_init=default_kernel_init,\n              bias_init=initializers.zeros_init()):\n\n      kernel = self.param('kernel',\n        (inputs.shape[-1], features), kernel_init)\n      y = jnp.dot(inputs, kernel)\n      if use_bias:\n        bias = self.param(\n          'bias', (features,), bias_init)\n        y = y + bias\n      return y\n  ---\n  from flax import linen as nn  # [1] #!\n\n  class Dense(nn.Module):\n    features: int  # [2] #!\n    use_bias: bool = True\n    kernel_init: Callable[[PRNGKey, Shape, Dtype], Array] = default_kernel_init\n    bias_init: Callable[[PRNGKey, Shape, Dtype], Array] = initializers.zeros_init()\n\n    @nn.compact\n    def __call__(self, inputs):  # [3] #!\n      kernel = self.param('kernel',\n        self.kernel_init, (inputs.shape[-1], self.features))  # [4] #!\n      y = jnp.dot(inputs, kernel)\n      if self.use_bias:\n        bias = self.param(\n          'bias', self.bias_init, (self.features,))  # [5] #!\n        y = y + bias\n      return y\n\n1. Replace ``from flax import nn`` with ``from flax import linen as nn``.\n\n2. Move arguments to ``apply`` into dataclass attributes. Add type annotations\n   (or use type ``Any`` to bypass).\n\n3. Rename method ``apply`` to ``__call__`` and (optionally) wrap with\n   |@compact|_. Methods wrapped in |@compact|_ can define submodules directly\n   within the method (like in old Flax). You can only wrap a single method with\n   |@compact|_. Alternatively, you can define a ``setup`` method. For more\n   details, please see our other HOWTO `Should I use setup or nn.compact?`_.\n\n4. Access dataclass attributes values by ``self.<attr>`` inside methods, e.g.\n   ``self.features``.\n\n5. Move shape to the end of the arguments to |self.param|_ (initializer functions\n   can take arbitrary argument lists).\n\n\nUsing Flax Modules inside other Modules\n---------------------------------------\n\n.. codediff::\n  :title: Old Flax, Linen\n  :skip_test: Old Flax\n  :sync:\n\n  class Encoder(nn.Module):\n\n    def apply(self, x):\n      x = nn.Dense(x, 500)\n      x = nn.relu(x)\n      z = nn.Dense(x, 500, name=\"latents\")\n      return z\n  ---\n  class Encoder(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      x = nn.Dense(500)(x)  # [1] #!\n      x = nn.relu(x)\n      z = nn.Dense(500, name='latents')(x)  # [2] #!\n      return z\n\n1. Module constructors no longer return the outputs. Instead, they work like\n   normal constructors and return module instances. These instances can be\n   shared like in normal Python (instead of using ``.shared()`` in old Flax).\n   Since most modules implement ``__call__``, you can retain the conciseness of\n   old Flax.\n\n2. Names can be optionally passed to all module constructors.\n\nSharing submodules and defining multiple methods\n--------------------------------\n\n.. codediff::\n  :title: Old Flax, Linen\n  :skip_test: Old Flax\n  :sync:\n\n  class AutoEncoder(nn.Module):\n    def _create_submodules(self):\n      return Decoder.shared(name=\"encoder\")\n\n    def apply(self, x, z_rng, latents=20):\n      decoder = self._create_decoder()\n      z = Encoder(x, latents, name=\"encoder\")\n      return decoder(z)\n\n    @nn.module_method\n    def generate(self, z, **unused_kwargs):\n      decoder = self._create_decoder()\n      return nn.sigmoid(decoder(z))\n  ---\n  class AutoEncoder(nn.Module):\n    latents: int = 20\n\n    def setup(self):  # [1] #!\n      self.encoder = Encoder(self.latents)  # [2] #!\n      self.decoder = Decoder()\n\n    def __call__(self, x):  # [3] #!\n      z = self.encoder(x)\n      return self.decoder(z)\n\n    def generate(self, z):  # [4] #!\n      return nn.sigmoid(self.decoder(z))\n\n\n1. Use |setup|_ instead of ``__init__``, which is already defined in\n   the dataclasses library. Flax calls setup right after modules are ready to be\n   used. (You can do this for all modules if you like instead of using\n   |@compact|, but we like how |@compact| co-locates where modules are defined\n   and used, especially if you have loops or conditionals).\n\n2. Like regular Python, share submodules by assigning to self during\n   initialization. Similar to PyTorch, ``self.encoder`` automatically has the\n   name ``\"encoder\"``.\n\n3. We don't use |@compact|_ here because we're not defining any inline\n   submodules (all submodules are defined in setup).\n\n4. Define additional methods just like in regular Python.\n\n``Module.partial`` inside other modules\n---------------------------------------\n\n.. codediff::\n  :title: Old Flax, Linen\n  :skip_test: Old Flax\n  :sync:\n\n  # no import #!\n\n  class ResNet(nn.Module):\n    \"\"\"ResNetV1.\"\"\"\n\n\n    def apply(self, x,\n              stage_sizes,\n              num_filters=64,\n              train=True):\n      conv = nn.Conv.partial(bias=False)\n      norm = nn.BatchNorm.partial(\n          use_running_average=not train,\n          momentum=0.9, epsilon=1e-5)\n\n      x = conv(x, num_filters, (7, 7), (2, 2),\n              padding=[(3, 3), (3, 3)],\n              name='conv_init')\n      x = norm(x, name='bn_init')\n\n      # [...]\n      return x\n  ---\n  from functools import partial  #!\n\n  class ResNet(nn.Module):\n    \"\"\"ResNetV1.\"\"\"\n    stage_sizes: Sequence[int]\n    num_filters: int = 64\n    train: bool = True\n\n    @nn.compact\n    def __call__(self, x):\n      conv = partial(nn.Conv, use_bias=False) #!\n      norm = partial(nn.BatchNorm,  #!\n                    use_running_average=not self.train, #!\n                    momentum=0.9, epsilon=1e-5) #!\n\n      x = conv(self.num_filters, (7, 7), (2, 2),\n              padding=[(3, 3), (3, 3)],\n              name='conv_init')(x)\n      x = norm(name='bn_init')(x)\n\n      # [...]\n      return x\n\nUse normal ``functools.partial`` instead of ``Module.partial``. The rest stays\nthe same.\n\nTop-level training code patterns\n--------------------------------\n\n.. codediff::\n  :title: Old Flax, Linen\n  :skip_test: Old Flax\n  :sync:\n\n  def create_model(key):\n    _, initial_params = CNN.init_by_shape(\n      key, [((1, 28, 28, 1), jnp.float32)])\n    model = nn.Model(CNN, initial_params)\n    return model\n\n  def create_optimizer(model, learning_rate):\n    optimizer_def = optim.Momentum(learning_rate=learning_rate)\n    optimizer = optimizer_def.create(model)\n    return optimizer\n\n  def cross_entropy_loss(*, logits, labels):\n    one_hot_labels = jax.nn.one_hot(labels, num_classes=10)\n    return -jnp.mean(jnp.sum(one_hot_labels * logits, axis=-1))\n\n  def loss_fn(model):\n    logits = model(batch['image'])\n    one_hot = jax.nn.one_hot(batch['label'], num_classes=10)\n    loss = -jnp.mean(jnp.sum(one_hot_labels * batch['label'],\n                             axis=-1))\n    return loss, logits\n  ---\n  def create_train_state(rng, config):  # [1] #!\n    variables = CNN().init(rng, jnp.ones([1, 28, 28, 1]))  # [2] #!\n    params = variables['params']  # [3] #!\n    tx = optax.sgd(config.learning_rate, config.momentum)  # [4] #!\n    return train_state.TrainState.create(\n        apply_fn=CNN.apply, params=params, tx=tx)\n\n\n  def loss_fn(params):\n    logits = CNN().apply({'params': params}, batch['image'])  # [5] #!\n    one_hot = jax.nn.one_hot(batch['label'], 10)\n    loss = jnp.mean(optax.softmax_cross_entropy(logits=logits,\n                                                labels=one_hot))\n    return loss, logits\n\n\n1. We no longer use the ``Model`` abstraction -- instead we pass parameters\n   around directly, usually encapsulated in a `TrainState`_ object, which can\n   directly be passed to JAX transformations.\n\n2. To compute initial parameters, construct a module instance and call |init|_\n   or |init_with_output|_. We haven't ported over ``init_by_shape`` because this\n   function did some magic we did not like (it evaluated the function by shape.\n   but returned real values anyway). Therefore, you should now pass concrete\n   values to the initializer functions, and you can optimize the initialization\n   by wrapping it with |jax.jit|_, which is highly recommended to avoid running\n   a full forward pass.\n\n3. Linen generalizes parameters into variables. Parameters are one\n   \"collection\" of variables. Variables are nested dicts, where the top-level\n   keys reflect the different variable collections, of which \"param\" is one of.\n   See the `Variables documentation`_ for more details.\n\n4. We recommend using Optax optimizers. See our separate HOWTO called\n   `Upgrading my codebase to Optax`_ for more details.\n\n5. To make predictions with your model, make an instance at the top level (this\n   is free -- just a wrapper around constructor attributes) and call the\n   ``apply`` method (which will call ``__call__`` internally).\n\nNon-trainable variables (\"state\"): Use within Modules\n-----------------------------------------------------\n\n.. codediff::\n  :title: Old Flax, Linen\n  :skip_test: Old Flax\n  :sync:\n\n  class BatchNorm(nn.Module):\n    def apply(self, x):\n      # [...]\n      ra_mean = self.state(\n        'mean', (x.shape[-1], ), initializers.zeros_init())\n      ra_var = self.state(\n        'var', (x.shape[-1], ), initializers.ones_init())\n      # [...]\n  ---\n  class BatchNorm(nn.Module):\n    def __call__(self, x):\n      # [...]\n      ra_mean = self.variable(  #!\n        'batch_stats', 'mean', initializers.zeros_init(), (x.shape[-1], ))\n      ra_var = self.variable(\n        'batch_stats', 'var', initializers.ones_init(), (x.shape[-1], ))\n      # [...]\n\nThe first argument is the name of the variable collection (\"param\" is the only\nvariable collection that's always available). Some colllections may be treated\nas mutable, and others as immutable at top-level training code (see next section\nfor details). Flax also lets you treat each variable collection differently when\nusing JAX transformations inside modules.\n\nNon-trainable variables (\"state\"): Top-level training code patterns\n-------------------------------------------------------------------\n\n.. codediff::\n  :title: Old Flax, Linen\n  :skip_test: Old Flax\n  :sync:\n\n  # initial params and state\n  def initial_model(key, init_batch):\n    with nn.stateful() as initial_state:\n      _, initial_params = ResNet.init(key, init_batch)\n    model = nn.Model(ResNet, initial_params)\n    return model, init_state\n\n\n  # updates batch statistics during training\n  def loss_fn(model, model_state):\n    with nn.stateful(model_state) as new_model_state:\n      logits = model(batch['image'])\n    # [...]\n\n\n\n  # reads immutable batch statistics during evaluation\n  def eval_step(model, model_state, batch):\n    with nn.stateful(model_state, mutable=False):\n      logits = model(batch['image'], train=False)\n    return compute_metrics(logits, batch['label'])\n  ---\n  # initial variables ({\"param\": ..., \"batch_stats\": ...})\n  def initial_variables(key, init_batch):\n    return ResNet().init(key, init_batch)  # [1] #!\n\n\n\n  # updates batch statistics during training\n  def loss_fn(params, batch_stats):\n    variables = {'params': params, 'batch_stats': batch_stats}  # [2] #!\n    logits, new_variables = ResNet(train=true).apply(\n      variables, batch['image'], mutable=['batch_stats'])  # [3] #!\n    new_batch_stats = new_variables['batch_stats']\n    # [...]\n\n\n  # reads immutable batch statistics during evaluation\n  def eval_step(params, batch_stats, batch):\n    variables = {'params': params, 'batch_stats': batch_stats}\n    logits = ResNet(train=False).apply(\n      variables, batch['image'], mutable=False)  # [4] #!\n    return compute_metrics(logits, batch['label'])\n\n1. |init|_ returns a variable dict, e.g. ``{\"param\": ..., \"batch_stats\": ...}``\n   (see `Variables documentation`_).\n\n2. Combine the different variable collections into a variable dict.\n\n3. During training, the ``batch_stats`` variable collection changes. Since we\n   specify that in the mutable argument, the return value from ``module.apply``\n   becomes an ordered pair of ``output, new_variables``.\n\n4. During evaluation, we want to raise an error if we're accidentally applying\n   Batch Norm in training mode. By passing ``mutable=False`` into\n   ``module.apply`` we enforce that. Since no variables are mutated, the return\n   value is once again just the output.\n\nLoading pre-Linen checkpoints\n-----------------------------\n\nWhile most Linen modules should be able to use pre-Linen weights without any\nmodification, there is one catch: In pre-Linen API submodules were numbered\nincrementally, independent of the submodule class. With Linen this behavior has\nchanged to keep separate submodule counts per module class.\n\nIn pre-Linen, params have the following structure:\n\n``{'Conv_0': { ... }, 'Dense_1': { ... } }``\n\nIn Linen this is instead:\n\n``{'Conv_0': { ... }, 'Dense_0': { ... } }``\n\nTODO: Add an example here how to load a new ``TrainState`` object.\n\nRandomness\n----------\n\n.. codediff::\n  :title: Old Flax, Linen\n  :skip_test: Old Flax\n  :sync:\n\n  def dropout(inputs, rate, deterministic=False):\n    keep_prob = 1. - rate\n    if deterministic:\n      return inputs\n    else:\n      mask = random.bernoulli(\n      make_rng(), p=keep_prob, shape=inputs.shape)\n      return lax.select(\n        mask, inputs / keep_prob, jnp.zeros_like(inputs))\n\n\n  def loss_fn(model, dropout_rng):\n    with nn.stochastic(dropout_rng):\n      logits = model(inputs)\n  ---\n  class Dropout(nn.Module):\n    rate: float\n\n    @nn.compact\n    def __call__(self, inputs, deterministic=False):\n      keep_prob = 1. - self.rate\n      if deterministic:\n        return inputs\n      else:\n        mask = random.bernoulli(\n          self.make_rng('dropout'), p=keep_prob, shape=inputs.shape)  # [1] #!\n        return lax.select(\n          mask, inputs / keep_prob, jnp.zeros_like(inputs))\n\n\n  def loss_fn(params, dropout_rng):\n    logits = Transformer().apply(\n      {'params': params}, inputs, rngs={'dropout': dropout_rng})  # [2] #!\n\n1. RNGs in Linen have \"kinds\" -- in this case ``'dropout'``. Different kinds can\n   be treated different in JAX transformations (for example, do you want the\n   same dropout mask for each timestep in a sequence model or a different one?)\n\n2. Instead of using the ``nn.stochastic`` context manager, you pass in RNGs\n   explicitly to ``module.apply``. During evaluation you wouldn't pass any RNGs\n   -- then if you accidentally use dropout in non-deterministic mode,\n   ``self.make_rng('dropout')`` would raise an error.\n\n\nLifted transformations\n----------------------\n\nIn Linen, rather than using JAX transformation directly, we are using\n\"lifted transforms\", which are JAX transformations applied to Flax Modules.\n\nFor more information, please see the design note on `Lifted transformations`_.\n\nTODO: Given an example of ``jax.scan_in_dim`` (pre-Linen) vs. ``nn.scan``\n(Linen).\n\n.. _`Should I use setup or nn.compact?`: https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/setup_or_nncompact.html\n.. _`Variables documentation`: https://flax.readthedocs.io/en/latest/api_reference/flax.linen/variable.html\n.. _`TrainState`: https://flax.readthedocs.io/en/latest/flax.training.html#train-state\n.. _`Upgrading my codebase to Optax`: https://flax.readthedocs.io/en/latest/guides/converting_and_upgrading/optax_update_guide.html\n.. _`Lifted transformations`: https://flax.readthedocs.io/en/latest/developer_notes/lift.html\n\n\n.. |@compact| replace:: ``@compact``\n.. _@compact: https://flax.readthedocs.io/en/latest/api_reference/flax.linen/decorators.html#flax.linen.compact\n\n.. |init| replace:: ``init``\n.. _init: https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.init\n\n.. |init_with_output| replace:: ``init_with_output``\n.. _init_with_output: https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.init_with_output\n\n.. |jax.jit| replace:: ``jax.jit``\n.. _jax.jit: https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html#jax.jit\n\n.. |self.param| replace:: ``self.param``\n.. _self.param: https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.param\n\n.. |setup| replace:: ``setup``\n.. _setup: https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.setup\n\n.. |@flax.struct.dataclass| replace:: ``@flax.struct.dataclass``\n.. _@flax.struct.dataclass: https://flax.readthedocs.io/en/latest/flax.struct.html#flax.struct.dataclass\n\n.. |checkpoints.convert_pre_linen()| replace:: ``checkpoints.convert_pre_linen()``\n.. _checkpoints.convert_pre_linen(): https://flax.readthedocs.io/en/latest/flax.training.html#flax.training.checkpoints.convert_pre_linen\n"
  },
  {
    "path": "docs/guides/converting_and_upgrading/optax_update_guide.rst",
    "content": "Upgrading my codebase to Optax\n==============================\n\nWe have proposed to replace :py:mod:`flax.optim` with `Optax\n<https://optax.readthedocs.io>`_ in 2021 with `FLIP #1009\n<https://github.com/google/flax/blob/main/docs/flip/1009-optimizer-api.md>`_ and\nthe Flax optimizers have been removed in v0.6.0 - this guide is targeted\ntowards :py:mod:`flax.optim` users to help them update their code to Optax.\n\nSee also Optax's quick start documentation:\nhttps://optax.readthedocs.io/en/latest/getting_started.html\n\n.. testsetup:: default, flax.optim, optax\n\n  import flax\n  import jax\n  import jax.numpy as jnp\n  import flax.linen as nn\n  import optax\n\n  # Note: this is the minimal code required to make below code run. See in the\n  # Colab linked above for a more meaningful definition of datasets etc.\n  batch = {'image': jnp.ones([1, 28, 28, 1]), 'label': jnp.array([0])}\n  ds_train = [batch]\n  get_ds_train = lambda: [batch]\n  model = nn.Dense(1)\n  variables = model.init(jax.random.key(0), batch['image'])\n  learning_rate, momentum, weight_decay, grad_clip_norm = .1, .9, 1e-3, 1.\n  loss = lambda params, batch: jnp.array(0.)\n\nReplacing ``flax.optim`` with ``optax``\n---------------------------------------\n\nOptax has drop-in replacements for all of Flax's optimizers. Refer to Optax's\ndocumentation `Common Optimizers <https://optax.readthedocs.io/en/latest/api/optimizers.html>`_\nfor API details.\n\nThe usage is very similar, with the difference that ``optax`` does not keep a\ncopy of the ``params``, so they need to be passed around separately. Flax\nprovides the utility :py:class:`~flax.training.train_state.TrainState` to store\noptimizer state, parameters, and other associated data in a single dataclass\n(not used in code below).\n\n.. codediff::\n  :title: flax.optim, optax\n  :skip_test: flax.optim\n  :sync:\n\n  @jax.jit\n  def train_step(optimizer, batch):\n    grads = jax.grad(loss)(optimizer.target, batch)\n\n\n    return optimizer.apply_gradient(grads)\n\n  optimizer_def = flax.optim.Momentum(\n      learning_rate, momentum)\n  optimizer = optimizer_def.create(variables['params'])\n\n  for batch in get_ds_train():\n    optimizer = train_step(optimizer, batch)\n\n  ---\n\n  @jax.jit\n  def train_step(params, opt_state, batch):\n    grads = jax.grad(loss)(params, batch)\n    updates, opt_state = tx.update(grads, opt_state)\n    params = optax.apply_updates(params, updates)\n    return params, opt_state\n\n  tx = optax.sgd(learning_rate, momentum)\n  params = variables['params']\n  opt_state = tx.init(params)\n\n  for batch in ds_train:\n    params, opt_state = train_step(params, opt_state, batch)\n\n\nComposable Gradient Transformations\n-----------------------------------\n\nThe function |optax.sgd()|_ used in the code snippet above is simply a wrapper\nfor the sequential application of two gradient transformations. Instead of using\nthis alias, it is common to use |optax.chain()|_ to combine multiple of these\ngeneric building blocks.\n\n.. |optax.sgd()| replace:: ``optax.sgd()``\n.. _optax.sgd(): https://optax.readthedocs.io/en/latest/api/optimizers.html#optax.sgd\n.. |optax.chain()| replace:: ``optax.chain()``\n.. _optax.chain(): https://optax.readthedocs.io/en/latest/api/combining_optimizers.html#optax.chain\n\n.. codediff::\n  :title: Pre-defined alias, Combining transformations\n  :groups: default, default\n\n  # Note that the aliases follow the convention to use positive\n  # values for the learning rate by default.\n  tx = optax.sgd(learning_rate, momentum)\n\n  ---\n\n  #\n\n  tx = optax.chain(\n      # 1. Step: keep a trace of past updates and add to gradients.\n      optax.trace(decay=momentum),\n      # 2. Step: multiply result from step 1 with negative learning rate.\n      # Note that `optax.apply_updates()` simply adds the final updates to the\n      # parameters, so we must make sure to flip the sign here for gradient\n      # descent.\n      optax.scale(-learning_rate),\n  )\n\nWeight Decay\n------------\n\nSome of Flax's optimizers also include a weight decay. In Optax, some optimizers\nalso have a weight decay parameter (such as |optax.adamw()|_), and to others the\nweight decay can be added as another \"gradient transformation\"\n|optax.add_decayed_weights()|_ that adds an update derived from the parameters.\n\n.. |optax.adamw()| replace:: ``optax.adamw()``\n.. _optax.adamw(): https://optax.readthedocs.io/en/latest/api/optimizers.html#optax.adamw\n.. |optax.add_decayed_weights()| replace:: ``optax.add_decayed_weights()``\n.. _optax.add_decayed_weights(): https://optax.readthedocs.io/en/latest/api/transformations.html#optax.add_decayed_weights\n\n.. codediff::\n  :title: flax.optim, optax\n  :skip_test: flax.optim\n  :sync:\n\n  optimizer_def = flax.optim.Adam(\n      learning_rate, weight_decay=weight_decay)\n  optimizer = optimizer_def.create(variables['params'])\n\n  ---\n\n  # (Note that you could also use `optax.adamw()` in this case)\n  tx = optax.chain(\n      optax.scale_by_adam(),\n      optax.add_decayed_weights(weight_decay),\n      # params -= learning_rate * (adam(grads) + params * weight_decay)\n      optax.scale(-learning_rate),\n  )\n  # Note that you'll need to specify `params` when computing the udpates:\n  # tx.update(grads, opt_state, params)\n\nGradient Clipping\n-----------------\n\nTraining can be stabilized by clipping gradients to a global norm (`Pascanu et\nal, 2012 <https://arxiv.org/abs/1211.5063>`_). In Flax this is often done by\nprocessing the gradients before passing them to the optimizer. With Optax this\nbecomes just another gradient transformation |optax.clip_by_global_norm()|_.\n\n.. |optax.clip_by_global_norm()| replace:: ``optax.clip_by_global_norm()``\n.. _optax.clip_by_global_norm(): https://optax.readthedocs.io/en/latest/api/transformations.html#optax.clip_by_global_norm\n\n.. codediff::\n  :title: flax.optim, optax\n  :skip_test: flax.optim\n  :sync:\n\n  def train_step(optimizer, batch):\n    grads = jax.grad(loss)(optimizer.target, batch)\n    grads_flat, _ = jax.tree_util.tree_flatten(grads)\n    global_l2 = jnp.sqrt(sum([jnp.vdot(p, p) for p in grads_flat]))\n    g_factor = jnp.minimum(1.0, grad_clip_norm / global_l2)\n    grads = jax.tree_util.tree_map(lambda g: g * g_factor, grads)\n    return optimizer.apply_gradient(grads)\n\n  ---\n\n  tx = optax.chain(\n      optax.clip_by_global_norm(grad_clip_norm),\n      optax.trace(decay=momentum),\n      optax.scale(-learning_rate),\n  )\n\nLearning Rate Schedules\n-----------------------\n\nFor learning rate schedules, Flax allows overwriting hyper parameters when\napplying the gradients. Optax maintains a step counter and provides this as an\nargument to a function for scaling the updates added with\n|optax.scale_by_schedule()|_. Optax also allows specifying a functions to\ninject arbitrary scalar values for other gradient updates via\n|optax.inject_hyperparams()|_.\n\nRead more about learning rate schedules in the :doc:`lr_schedule` guide.\n\nRead more about schedules defined in Optax under `Optimizer Schedules\n<https://optax.readthedocs.io/en/latest/api/optimizer_schedules.html>`_. the\nstandard optimizers (like ``optax.adam()``, ``optax.sgd()`` etc.) also accept a\nlearning rate schedule as a parameter for ``learning_rate``.\n\n\n.. |optax.scale_by_schedule()| replace:: ``optax.scale_by_schedule()``\n.. _optax.scale_by_schedule(): https://optax.readthedocs.io/en/latest/api/transformations.html#optax.scale_by_schedule\n.. |optax.inject_hyperparams()| replace:: ``optax.inject_hyperparams()``\n.. _optax.inject_hyperparams(): https://optax.readthedocs.io/en/latest/api/optimizer_schedules.html#optax.inject_hyperparams\n\n.. codediff::\n  :title: flax.optim, optax\n  :skip_test: flax.optim\n  :sync:\n\n  def train_step(step, optimizer, batch):\n    grads = jax.grad(loss)(optimizer.target, batch)\n    return step + 1, optimizer.apply_gradient(grads, learning_rate=schedule(step))\n\n  ---\n\n  tx = optax.chain(\n      optax.trace(decay=momentum),\n      # Note that we still want a negative value for scaling the updates!\n      optax.scale_by_schedule(lambda step: -schedule(step)),\n  )\n\nMultiple Optimizers / Updating a Subset of Parameters\n-----------------------------------------------------\n\nIn Flax, traversals are used to specify which parameters should be updated by an\noptimizer. And you can combine traversals using\n:py:class:`flax.optim.MultiOptimizer` to apply different optimizers on different\nparameters. The equivalent in Optax is |optax.masked()|_ and |optax.chain()|_.\n\nNote that the example below is using :py:mod:`flax.traverse_util` to create the\nboolean masks required by |optax.masked()|_ - alternatively you could also\ncreate them manually, or use |optax.multi_transform()|_ that takes a\nmultivalent pytree to specify gradient transformations.\n\nBeware that |optax.masked()|_ flattens the pytree internally and the inner\ngradient transformations will only be called with that partial flattened view of\nthe params/gradients. This is not a problem usually, but it makes it hard to\nnest multiple levels of masked gradient transformations (because the inner\nmasks will expect the mask to be defined in terms of the partial flattened view\nthat is not readily available outside the outer mask).\n\n.. |optax.masked()| replace:: ``optax.masked()``\n.. _optax.masked(): https://optax.readthedocs.io/en/latest/api/optimizer_wrappers.html#optax.masked\n.. |optax.multi_transform()| replace:: ``optax.multi_transform()``\n.. _optax.multi_transform(): https://optax.readthedocs.io/en/latest/api/combining_optimizers.html#optax.multi_transform\n\n.. codediff::\n  :title: flax.optim, optax\n  :skip_test: flax.optim\n  :sync:\n\n  kernels = flax.traverse_util.ModelParamTraversal(lambda p, _: 'kernel' in p)\n  biases = flax.traverse_util.ModelParamTraversal(lambda p, _: 'bias' in p)\n\n  kernel_opt = flax.optim.Momentum(learning_rate, momentum)\n  bias_opt = flax.optim.Momentum(learning_rate * 0.1, momentum)\n\n\n  optimizer = flax.optim.MultiOptimizer(\n      (kernels, kernel_opt),\n      (biases, bias_opt)\n  ).create(variables['params'])\n\n  ---\n\n  kernels = flax.traverse_util.ModelParamTraversal(lambda p, _: 'kernel' in p)\n  biases = flax.traverse_util.ModelParamTraversal(lambda p, _: 'bias' in p)\n\n  all_false = jax.tree_util.tree_map(lambda _: False, params)\n  kernels_mask = kernels.update(lambda _: True, all_false)\n  biases_mask = biases.update(lambda _: True, all_false)\n\n  tx = optax.chain(\n      optax.trace(decay=momentum),\n      optax.masked(optax.scale(-learning_rate), kernels_mask),\n      optax.masked(optax.scale(-learning_rate * 0.1), biases_mask),\n  )\n\nFinal Words\n-----------\n\nAll above patterns can of course also be mixed and Optax makes it possible to\nencapsulate all these transformations into a single place outside the main\ntraining loop, which makes testing much easier.\n"
  },
  {
    "path": "docs/guides/converting_and_upgrading/orbax_upgrade_guide.rst",
    "content": "Migrate checkpointing to Orbax\n==============================\n\nThis guide shows how to convert Flax's checkpoint saving and restoring calls — `flax.training.checkpoints.save_checkpoint <https://flax.readthedocs.io/en/latest/api_reference/flax.training.html#flax.training.checkpoints.save_checkpoint>`__ and `restore_checkpoint <https://flax.readthedocs.io/en/latest/api_reference/flax.training.html#flax.training.checkpoints>`__ — to the equivalent `Orbax <https://github.com/google/orbax>`__ methods. Orbax provides a flexible and customizable API for managing checkpoints for various objects. Note that as Flax's checkpointing is being migrated to Orbax from ``flax.training.checkpoints``, all existing features in the Flax API will continue to be supported, but the API will change.\n\nYou will learn how to migrate to Orbax through the following scenarios:\n\n*  The most common use case: Saving/loading and managing checkpoints\n*  A \"lightweight\" use case: \"Pure\" saving/loading without the top-level checkpoint manager\n*  Restoring checkpoints without a target pytree\n*  Async checkpointing\n*  Saving/loading a single JAX or NumPy Array\n\nTo learn more about Orbax, check out the `quick start introductory Colab notebook <http://colab.research.google.com/github/google/orbax/blob/main/docs/guides/checkpoint/orbax_checkpoint_101.ipynb>`__ and `the official Orbax documentation <https://orbax.readthedocs.io/en/latest/>`_.\n\nYou can click on \"Open in Colab\" above to run the code from this guide.\n\nThroughout the guide, you will be able to compare code examples with and without the Orbax code.\n\n.. testsetup:: orbax.checkpoint\n\n  import flax\n  from flax.training import checkpoints, orbax_utils\n  import orbax\n  import jax\n  import jax.numpy as jnp\n  import numpy as np\n\n  # Orbax needs to have asyncio enabled in the Colab environment.\n  import nest_asyncio\n  nest_asyncio.apply()\n\n  # Set up the directory.\n  import os\n  import shutil\n  if os.path.exists('/tmp/orbax_upgrade'):\n    shutil.rmtree('/tmp/orbax_upgrade')\n  os.makedirs('/tmp/orbax_upgrade')\n\n\nSetup\n*****\n\n.. testcode:: orbax.checkpoint\n\n  # Create some dummy variables for this example.\n  MAX_STEPS = 5\n  CKPT_PYTREE = [12, {'bar': np.array((2, 3))}, [1, 4, 10]]\n  TARGET_PYTREE = [0, {'bar': np.array((0))}, [0, 0, 0]]\n\nMost common use case: Saving/loading and managing checkpoints\n*************************************************************\n\nThis section covers the following scenario:\n\n*  Your original Flax ``save_checkpoint()`` or ``save_checkpoint_multiprocess()`` call contains the following arguments: ``prefix``, ``keep``, ``keep_every_n_steps``; or\n*  You want to use some automatic management logic for your checkpoints (for example, for deleting old data, deleting data based on metrics/loss, and so on).\n\nIn this case, you need to use ``orbax.CheckpointManager``. This allows you to not only save and load your model, but also manage your checkpoints and delete outdated checkpoints *automatically*.\n\nTo upgrade your code:\n\n1. Create and keep an ``orbax.CheckpointManager`` instance at the top level, customized with ``orbax.CheckpointManagerOptions``.\n\n2. At runtime, call ``orbax.CheckpointManager.save()`` to save your data.\n\n3. Then, call ``orbax.CheckpointManager.restore()`` to restore your data.\n\n4. And, if your checkpoint includes some multi-host/multi-process array, pass the correct ``mesh`` into ``flax.training.orbax_utils.restore_args_from_target()`` to generate the correct ``restore_args`` before restoring.\n\nFor example:\n\n.. codediff::\n  :title: flax.checkpoints, orbax.checkpoint\n  :skip_test: flax.checkpoints\n  :sync:\n\n  CKPT_DIR = '/tmp/orbax_upgrade/'\n  flax.config.update('flax_use_orbax_checkpointing', False)\n\n  # Inside your training loop\n  for step in range(MAX_STEPS):\n    # do training\n    checkpoints.save_checkpoint(CKPT_DIR, CKPT_PYTREE, step=step,\n                                prefix='test_', keep=3, keep_every_n_steps=2)\n\n\n  checkpoints.restore_checkpoint(CKPT_DIR, target=TARGET_PYTREE, step=4, prefix='test_')\n\n  ---\n\n  CKPT_DIR = '/tmp/orbax_upgrade/orbax'\n\n  # At the top level\n  mgr_options = orbax.checkpoint.CheckpointManagerOptions(\n    create=True, max_to_keep=3, keep_period=2, step_prefix='test')\n  ckpt_mgr = orbax.checkpoint.CheckpointManager(\n    CKPT_DIR,\n    orbax.checkpoint.Checkpointer(orbax.checkpoint.PyTreeCheckpointHandler()), mgr_options)\n\n  # Inside your training loop\n  for step in range(MAX_STEPS):\n    # do training\n    save_args = flax.training.orbax_utils.save_args_from_target(CKPT_PYTREE)\n    ckpt_mgr.save(step, CKPT_PYTREE, save_kwargs={'save_args': save_args})\n\n\n  restore_args = flax.training.orbax_utils.restore_args_from_target(TARGET_PYTREE, mesh=None)\n  ckpt_mgr.restore(4, items=TARGET_PYTREE, restore_kwargs={'restore_args': restore_args})\n\n\nA \"lightweight\" use case: \"Pure\" saving/loading without the top-level checkpoint manager\n****************************************************************************************\n\nIf you prefer to not maintain a top-level checkpoint manager, you can still save and restore any individual checkpoint with an ``orbax.checkpoint.Checkpointer``. Note that this means you cannot use all the Orbax management features.\n\nTo migrate to Orbax code, instead of using the ``overwrite`` argument in ``flax.save_checkpoint()`` use the ``force`` argument in ``orbax.checkpoint.Checkpointer.save()``.\n\nFor example:\n\n.. codediff::\n  :title: flax.checkpoints, orbax.checkpoint\n  :skip_test: flax.checkpoints\n  :sync:\n\n  PURE_CKPT_DIR = '/tmp/orbax_upgrade/pure'\n  flax.config.update('flax_use_orbax_checkpointing', False)\n\n  checkpoints.save_checkpoint(PURE_CKPT_DIR, CKPT_PYTREE, step=0, overwrite=True)\n  checkpoints.restore_checkpoint(PURE_CKPT_DIR, target=TARGET_PYTREE)\n\n  ---\n\n  PURE_CKPT_DIR = '/tmp/orbax_upgrade/pure'\n\n  ckptr = orbax.checkpoint.Checkpointer(orbax.checkpoint.PyTreeCheckpointHandler())  # A stateless object, can be created on the fly.\n  ckptr.save(PURE_CKPT_DIR, CKPT_PYTREE,\n             save_args=flax.training.orbax_utils.save_args_from_target(CKPT_PYTREE), force=True)\n  ckptr.restore(PURE_CKPT_DIR, item=TARGET_PYTREE,\n                restore_args=flax.training.orbax_utils.restore_args_from_target(TARGET_PYTREE, mesh=None))\n\n\n\nRestoring checkpoints without a target pytree\n*********************************************\n\nIf you need to restore your checkpoints without a target pytree, pass ``item=None`` to ``orbax.checkpoint.Checkpointer`` or ``items=None`` to ``orbax.CheckpointManager``'s ``.restore()`` method, which should trigger the restoration.\n\nFor example:\n\n.. codediff::\n  :title: flax.checkpoints, orbax.checkpoint\n  :skip_test: flax.checkpoints\n  :sync:\n\n  NOTARGET_CKPT_DIR = '/tmp/orbax_upgrade/no_target'\n  flax.config.update('flax_use_orbax_checkpointing', False)\n\n  checkpoints.save_checkpoint(NOTARGET_CKPT_DIR, CKPT_PYTREE, step=0)\n  checkpoints.restore_checkpoint(NOTARGET_CKPT_DIR, target=None)\n\n  ---\n\n  NOTARGET_CKPT_DIR = '/tmp/orbax_upgrade/no_target'\n\n  # A stateless object, can be created on the fly.\n  ckptr = orbax.checkpoint.Checkpointer(orbax.checkpoint.PyTreeCheckpointHandler())\n  ckptr.save(NOTARGET_CKPT_DIR, CKPT_PYTREE,\n             save_args=flax.training.orbax_utils.save_args_from_target(CKPT_PYTREE))\n  ckptr.restore(NOTARGET_CKPT_DIR, item=None)\n\n\nAsync checkpointing\n*******************\n\nTo make your checkpoint-saving asynchronous, substitute ``orbax.checkpoint.Checkpointer`` with ``orbax.checkpoint.AsyncCheckpointer``.\n\nThen, you can call ``orbax.checkpoint.AsyncCheckpointer.wait_until_finished()`` or Orbax's ``CheckpointerManager.wait_until_finished()`` to wait for the save the complete.\n\nFor more details, read the `checkpoint guide <https://flax.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html#asynchronized-checkpointing>`_.\n\nYou can also use Orbax AsyncCheckpointer with Flax APIs through async manager. Async manager internally calls wait_until_finished(). This solution is not actively maintained and the recommedation is to use Orbax async checkpointing.\n\nFor example:\n\n.. codediff::\n  :title: flax.checkpoints, orbax.checkpoint\n  :skip_test: flax.checkpoints\n  :sync:\n\n  ASYNC_CKPT_DIR = '/tmp/orbax_upgrade/async'\n  flax.config.update('flax_use_orbax_checkpointing', True)\n  async_manager = checkpoints.AsyncManager()\n\n  checkpoints.save_checkpoint(ASYNC_CKPT_DIR, CKPT_PYTREE, step=0, overwrite=True, async_manager=async_manager)\n  checkpoints.restore_checkpoint(ASYNC_CKPT_DIR, target=TARGET_PYTREE)\n  ---\n\n  ASYNC_CKPT_DIR = '/tmp/orbax_upgrade/async'\n\n  import orbax.checkpoint as ocp\n  ckptr = ocp.AsyncCheckpointer(ocp.StandardCheckpointHandler())\n  ckptr.save(ASYNC_CKPT_DIR, args=ocp.args.StandardSave(CKPT_PYTREE))\n  # ... Continue with your work...\n  # ... Until a time when you want to wait until the save completes:\n  ckptr.wait_until_finished() # Blocks until the checkpoint saving is completed.\n  ckptr.restore(ASYNC_CKPT_DIR, args=ocp.args.StandardRestore(TARGET_PYTREE))\n\n\nSaving/loading a single JAX or NumPy Array\n******************************************\n\nThe ``orbax.checkpoint.PyTreeCheckpointHandler`` class, as the name suggests, can only be used for pytrees. Therefore, if you need to save/restore a single pytree leaf (for example, an array), use ``orbax.checkpoint.ArrayCheckpointHandler`` instead.\n\nFor example:\n\n.. codediff::\n  :title: flax.checkpoints, orbax.checkpoint\n  :skip_test: flax.checkpoints\n  :sync:\n\n  ARR_CKPT_DIR = '/tmp/orbax_upgrade/singleton'\n  flax.config.update('flax_use_orbax_checkpointing', False)\n\n  checkpoints.save_checkpoint(ARR_CKPT_DIR, jnp.arange(10), step=0)\n  checkpoints.restore_checkpoint(ARR_CKPT_DIR, target=None)\n\n  ---\n\n  ARR_CKPT_DIR = '/tmp/orbax_upgrade/singleton'\n\n  ckptr = orbax.checkpoint.Checkpointer(orbax.checkpoint.ArrayCheckpointHandler())\n  ckptr.save(ARR_CKPT_DIR, jnp.arange(10))\n  ckptr.restore(ARR_CKPT_DIR, item=None)\n\n\nFinal words\n***********\n\nThis guide provides an overview of how to migrate from the \"legacy\" Flax checkpointing API to the Orbax API. Orbax provides more functionalities and the Orbax team is actively developing new features. Stay tuned and follow the `official Orbax GitHub repository <https://github.com/google/orbax>`__ for more!\n"
  },
  {
    "path": "docs/guides/converting_and_upgrading/regular_dict_upgrade_guide.rst",
    "content": "Migrate to regular dicts\n========================\n\nFlax will migrate from returning ``FrozenDicts`` to regular dicts when calling\n:meth:`.init <flax.linen.Module.init>`, :meth:`.init_with_output <flax.linen.Module.init_with_output>` and\n:meth:`.apply <flax.linen.Module.apply>` ``Module`` methods.\n\nThe original issue is outlined `here <https://github.com/google/flax/issues/1223>`__.\n\nThis guide shows some common upgrade patterns.\n\n\nUtility functions\n-----------------\n\n``FrozenDicts`` are immutable dictionaries that implement an additional 4 methods:\n\n* :meth:`copy <flax.core.frozen_dict.FrozenDict.copy>`\n* :meth:`pop <flax.core.frozen_dict.FrozenDict.pop>`\n* :meth:`pretty_repr <flax.core.frozen_dict.FrozenDict.pretty_repr>`\n* :meth:`unfreeze <flax.core.frozen_dict.FrozenDict.unfreeze>`\n\nTo accommodate the regular dict change, replace usage of ``FrozenDict`` methods with their utility function equivalent from ``flax.core.frozen_dict``.\nThese utility functions mimic the behavior of their corresponding ``FrozenDict`` method, and can be called on either ``FrozenDicts`` or regular dicts.\nThe following are the utility functions and example upgrade patterns:\n\n.. testsetup:: default, Only ``FrozenDict``, Both ``FrozenDict`` and regular dict\n\n  import flax\n  import flax.linen as nn\n  import jax\n  import jax.numpy as jnp\n\n  x = jnp.empty((1,3))\n  variables = flax.core.freeze(nn.Dense(5).init(jax.random.key(0), x))\n\n  other_variables = jnp.array([1, 1, 1, 1, 1], dtype=jnp.float32)\n\n:meth:`copy <flax.core.frozen_dict.copy>`\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. codediff::\n  :title: Only ``FrozenDict``, Both ``FrozenDict`` and regular dict\n  :sync:\n\n  variables = variables.copy(add_or_replace={'other_variables': other_variables})\n\n  ---\n\n  variables = flax.core.copy(variables, add_or_replace={'other_variables': other_variables})\n\n:meth:`pop <flax.core.frozen_dict.pop>`\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. codediff::\n  :title: Only ``FrozenDict``, Both ``FrozenDict`` and regular dict\n  :sync:\n\n  state, params = variables.pop('params')\n\n  ---\n\n  state, params = flax.core.pop(variables, 'params')\n\n:meth:`pretty_repr <flax.core.frozen_dict.pretty_repr>`\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. codediff::\n  :title: Only ``FrozenDict``, Both ``FrozenDict`` and regular dict\n  :sync:\n\n  str_repr = variables.pretty_repr()\n\n  ---\n\n  str_repr = flax.core.pretty_repr(variables)\n\n:meth:`unfreeze <flax.core.frozen_dict.unfreeze>`\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. codediff::\n  :title: Only ``FrozenDict``, Both ``FrozenDict`` and regular dict\n  :sync:\n\n  variables = variables.unfreeze()\n\n  ---\n\n  variables = flax.core.unfreeze(variables)\n\n\nModifying config values\n-----------------------\n\nA temporary feature flag ``flax_return_frozendict`` is set up to help with the migration.\nTo toggle behavior between returning FrozenDict and regular dict variables at runtime,\nrun ``flax.config.update('flax_return_frozendict', <BOOLEAN_VALUE>)`` in your code.\n\nFor example:\n\n.. testcode::\n\n  x = jnp.empty((1,3))\n\n  flax.config.update('flax_return_frozendict', True) # set Flax to return FrozenDicts\n  variables = nn.Dense(5).init(jax.random.key(0), x)\n\n  assert isinstance(variables, flax.core.FrozenDict)\n\n  flax.config.update('flax_return_frozendict', False) # set Flax to return regular dicts\n  variables = nn.Dense(5).init(jax.random.key(0), x)\n\n  assert isinstance(variables, dict)\n\nAlternatively, the environment variable ``flax_return_frozendict``\n(found `here <https://github.com/google/flax/blob/main/flax/configurations.py>`__) can be directly modified in the Flax source code.\n\n\nMigration status\n--------------\n\nAs of July 19th, 2023, ``flax_return_frozendict`` is set to ``False`` (see\n`#3193 <https://github.com/google/flax/pull/3193>`__), meaning Flax will default to\nreturning regular dicts from version `0.7.1 <https://github.com/google/flax/releases/tag/v0.7.1>`__\nonward. This flag can be flipped to ``True`` temporarily to have Flax return\n``Frozendicts``. However this feature flag will eventually be removed in the future."
  },
  {
    "path": "docs/guides/converting_and_upgrading/rnncell_upgrade_guide.rst",
    "content": "RNNCellBase Upgrade Guide\n=========================\n\nThe ``RNNCellBase`` API has undergone some key updates aimed at enhancing usability:\n\n- The ``initialize_carry`` method has transitioned from a class method to an instance method, simplifying its application.\n- All necessary metadata is now stored directly within the cell instance, providing a streamlined method signature.\n\nThis guide will walk you through these changes, demonstrating how to update your existing code to align with these enhancements.\n\nBasic Usage\n-----------\n\n.. testsetup:: New\n\n  import flax.linen as nn\n  import jax.numpy as jnp\n  import jax\n  import functools\n\nLet's begin by defining some variables and a sample input that represents\na batch of sequences:\n\n.. testcode:: New\n\n  batch_size = 32\n  seq_len = 10\n  in_features = 64\n  out_features = 128\n\n  x = jnp.ones((batch_size, seq_len, in_features))\n\nFirst and foremost, it's important to note that all metadata, including the number of features,\ncarry initializer, and so on, is now stored within the cell instance:\n\n.. codediff::\n  :title: Legacy, New\n  :skip_test: Legacy\n  :sync:\n\n  cell = nn.LSTMCell()\n\n  ---\n\n  cell = nn.LSTMCell(features=out_features)\n\nA significant change is that ``initialize_carry`` has been transitioned into an instance method. Given that\nthe cell instance now contains all metadata, the ``initialize_carry`` method's\nsignature only requires a PRNG key and a sample input:\n\n.. codediff::\n  :title: Legacy, New\n  :skip_test: Legacy\n  :sync:\n\n  carry = nn.LSTMCell.initialize_carry(jax.random.key(0), (batch_size,), out_features)\n\n  ---\n\n  carry = cell.initialize_carry(jax.random.key(0), x[:, 0].shape)\n\nHere, ``x[:, 0].shape`` represents the input for the cell (without the time dimension).\nYou can also just create the input shape directly when its more convenient:\n\n.. testcode:: New\n\n  carry = cell.initialize_carry(jax.random.key(0), (batch_size, in_features))\n\n\nUpgrade Patterns\n-----------------\n\nThe following sections will demonstrate some useful\npatterns for updating your code to align with the new API.\n\nFirst, we will show how to upgrade a ``Module`` that wraps\na cell, applies the scan logic during ``__call__``, and\nhas a static ``initialize_carry`` method. Here, we will try\nto make the minimal amount of changes to the code to get\nit working, albeit not in the most idiomatic way:\n\n.. codediff::\n  :title: Legacy, New\n  :skip_test: Legacy\n  :sync:\n\n  class SimpleLSTM(nn.Module):\n\n    @functools.partial(\n      nn.transforms.scan,\n      variable_broadcast='params',\n      in_axes=1, out_axes=1,\n      split_rngs={'params': False})\n    @nn.compact\n    def __call__(self, carry, x):\n\n      return nn.OptimizedLSTMCell()(carry, x)\n\n    @staticmethod\n    def initialize_carry(batch_dims, hidden_size):\n      return nn.OptimizedLSTMCell.initialize_carry(\n        jax.random.key(0), batch_dims, hidden_size)\n\n  ---\n\n  class SimpleLSTM(nn.Module):\n\n    @functools.partial(\n      nn.transforms.scan,\n      variable_broadcast='params',\n      in_axes=1, out_axes=1,\n      split_rngs={'params': False})\n    @nn.compact\n    def __call__(self, carry, x):\n      features = carry[0].shape[-1]\n      return nn.OptimizedLSTMCell(features)(carry, x)\n\n    @staticmethod\n    def initialize_carry(batch_dims, hidden_size):\n      return nn.OptimizedLSTMCell(hidden_size, parent=None).initialize_carry(\n        jax.random.key(0), (*batch_dims, hidden_size))\n\nNotice how in the new version, we have to extract the number of features from the carry\nduring ``__call__``, and use ``parent=None`` during ``initialize_carry`` to avoid some potential\nside effects.\n\nNext, we will show a more idiomatic way of writing a similar LSTM module. The main change\nhere will be that we will add a ``features`` attribute to the module and use it to initialize\na ``nn.scan``-ed version of the cell in the ``setup`` method:\n\n.. codediff::\n  :title: Legacy, New\n  :skip_test: Legacy\n  :sync:\n\n  class SimpleLSTM(nn.Module):\n\n    @functools.partial(\n      nn.transforms.scan,\n      variable_broadcast='params',\n      in_axes=1, out_axes=1,\n      split_rngs={'params': False})\n    @nn.compact\n    def __call__(self, carry, x):\n      return nn.OptimizedLSTMCell()(carry, x)\n\n    @staticmethod\n    def initialize_carry(batch_dims, hidden_size):\n      return nn.OptimizedLSTMCell.initialize_carry(\n        jax.random.key(0), batch_dims, hidden_size)\n\n  model = SimpleLSTM()\n  carry = SimpleLSTM.initialize_carry((batch_size,), out_features)\n  variables = model.init(jax.random.key(0), carry, x)\n\n  ---\n\n  class SimpleLSTM(nn.Module):\n    features: int\n\n    def setup(self):\n      self.scan_cell = nn.transforms.scan(\n        nn.OptimizedLSTMCell,\n        variable_broadcast='params',\n        in_axes=1, out_axes=1,\n        split_rngs={'params': False})(self.features)\n\n\n    @nn.compact\n    def __call__(self, x):\n      carry = self.scan_cell.initialize_carry(jax.random.key(0), x[:, 0].shape)\n      return self.scan_cell(carry, x)[1]  # only return the output\n\n\n  model = SimpleLSTM(features=out_features)\n  variables = model.init(jax.random.key(0), x)\n\nBecause the ``carry`` can be easily initialized from the sample input, we can move the\ncall to ``initialize_carry`` into the ``__call__`` method, somewhat simplifying the code.\n\nDevelopment Notes\n-----------------\n\nWhen developing a new cell, consider the following:\n\n* Include necessary metadata as instance attributes.\n* The ``initialize_carry`` now only requires a PRNG key and a sample input.\n* A new ``num_feature_axes`` property is required to specify the number of\n  feature dimensions.\n\n.. code-block::\n\n  class LSTMCell(nn.RNNCellBase):\n    features: int # ← All metadata is now stored within the cell instance\n    ... #              ↓\n    carry_init: Initializer\n\n    def initialize_carry(self, rng, input_shape) -> Carry:\n      ...\n\n    @property\n    def num_feature_axes(self):\n      return 1\n\n``num_feature_axes`` is a new API feature that allows code handling arbitrary ``RNNCellBase``\ninstances, such as the ``RNN`` Module, to infer the number of batch dimensions and\ndetermine the position of the time axis."
  },
  {
    "path": "docs/guides/data_preprocessing/full_eval.rst",
    "content": "Processing the entire Dataset\n=============================\n\nFor efficiency reasons, we form batches that contain multiple examples and\nprocess them in parallel. Especially when evaluating a model, it is important\nthat we process all examples and **avoid losing the remainder** of examples that\ndoes not form a complete batch at the end.\n\n\nThe problem\n-----------\n\nWhen evaluating on a single device, one can either drop the last incomplete\nbatch, or one can form a last batch with a shape different from the preceding\nbatches. Doing the latter has the disadvantage that this will trigger a\n**recompilation** of the ``eval_step()`` because XLA is not shape polymorphic.\n\n.. code-block:: python\n\n  collections.Counter(\n      tuple(batch['image'].shape)\n      for batch in tfds.load('mnist', split='test').batch(per_device_batch_size)\n  )\n  # output:\n  # Counter({(272, 28, 28, 1): 1, (512, 28, 28, 1): 19})\n\nThe problem is accentuated when using multiple devices for data parallelism.  If\nthe batch size is not **divisible by the number devices**, then that last step\nmust be executed on a single device (or a subset of devices). Usually one would\ndrop the last batch, but this will lead to incorrect results.\n\n\n.. code-block:: python\n\n  sum(\n      np.prod(batch['label'].shape)\n      for batch in tfds.load('mnist', split='test')\n          .batch(per_device_batch_size, drop_remainder=True)\n          .batch(jax.local_device_count())\n  )\n  # output:\n  # 9728\n\nUsing multiple hosts further complicates the situation because JAX uses the SPMD\nparadigm and every host must execute the same program. We would usually form\nnon-overlapping splits for different hosts with |tfds.split_for_jax_process()|_,\nbut this can lead to **different numbers for different hosts**, resulting in\ndifferent JAX programs when all examples are to be processed.\n\n.. code-block:: python\n\n  process_count = 6\n  [\n      len(tfds.load(dataset_name, split=tfds.split_for_jax_process(\n          'test', process_index=process_index, process_count=process_count)))\n      for process_index in range(process_count)\n  ]\n  # output:\n  # [1667, 1667, 1667, 1667, 1666, 1666]\n\n\n\n.. |tfds.split_for_jax_process()| replace:: ``tfds.split_for_jax_process()``\n.. _tfds.split_for_jax_process(): https://www.tensorflow.org/datasets/api_docs/python/tfds/split_for_jax_process\n\n\nThe solution: padding\n---------------------\n\nEven though it's possible to solve this problem by cleverly adjusting the number\nof batches executed by different devices on different hosts, such a solution\nquickly becomes complicated and makes the main eval loop hard to read with a lot\nof cumbersome logic.\n\nThe more straightforward solution to this problem is to use padding at the end\nof the dataset to make sure that the last batch has the same size as the\npreceding batches.\n\n\nManual implementation\n~~~~~~~~~~~~~~~~~~~~~\n\nThe last batch is manually padded to contain the same number of examples as in\nthe preceding batches. The predictions for the padded examples are discarded\nfrom the computation.\n\n.. code-block:: python\n\n  shard = lambda x: einops.rearrange(\n      x, '(d b) ... -> d b ...', d=jax.local_device_count())\n  unshard = lambda x: einops.rearrange(x, 'd b ... -> (d b) ...')\n\n  correct = total = 0\n  for batch in ds.as_numpy_iterator():\n    images = batch['image']\n    n = len(images)\n    padding = np.zeros([per_host_batch_size - n, *images.shape[1:]], images.dtype)\n    padded_images = np.concatenate([images, padding])\n    preds = unshard(get_preds(variables, shard(padded_images)))[:n]\n    total += n\n    correct += (batch['label'] == preds.argmax(axis=-1)).sum()\n\n\nUsing ``pad_shard_unpad()``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe above pattern, namely the pad→shard→predict→unshard→unpad sequence, can be\nextracted into a utility wrapper ``pad_shard_unpad()``, which greatly simplifies\nabove evaluation loop.\n\n.. code-block:: python\n\n  correct = total = 0\n  for batch in ds.as_numpy_iterator():\n    preds = flax.jax_utils.pad_shard_unpad(get_preds)(\n        vs, batch['image'], min_device_batch=per_device_batch_size)\n    total += len(batch['image'])\n    correct += (batch['label'] == preds.argmax(axis=-1)).sum()\n\n\nComputing metrics in ``eval_step()``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nInstead of returning the predictions and computing the metrics in the main\nevaluation loop, we would often want to make the metric computation part of the\nevaluation step, especially when using libraries like |jax_metrics|_, or\n|clu.metrics|_.\n\nIn that case we would want to pass the metrics as a ``static_argnums`` (i.e. do\nnot shard/pad it), and treat the return value as ``static_return`` too (i.e. no\nun-sharding or un-padding):\n\n.. code-block:: python\n\n  def eval_step(metrics, variables, batch):\n    print('retrigger compilation', {k: v.shape for k, v in batch.items()})\n    preds = model.apply(variables, batch['image'])\n    correct = (batch['mask'] & (batch['label'] == preds.argmax(axis=-1))).sum()\n    total = batch['mask'].sum()\n    return dict(\n        correct=metrics['correct'] + jax.lax.psum(correct, axis_name='batch'),\n        total=metrics['total'] + jax.lax.psum(total, axis_name='batch'),\n    )\n\n  eval_step = jax.pmap(eval_step, axis_name='batch')\n  eval_step = flax.jax_utils.pad_shard_unpad(\n      eval_step, static_argnums=(0, 1), static_return=True)\n\n.. |jax_metrics| replace:: ``clu.metrics``\n.. _jax_metrics: https://github.com/cgarciae/jax_metrics\n\n\n.. |clu.metrics| replace:: ``clu.metrics``\n.. _clu.metrics: https://github.com/google/CommonLoopUtils/blob/main/clu/metrics.py\n\n\nAdding \"infinite padding\"\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe above solution works in most cases, but it has some limitations:\n\n1. In the rare case where even splitting of the dataset on multiple hosts leads\n   to a different number of batches. Imagine having a dataset of ``n=4097``\n   examples, and evaluating this on ``h=8``, each having ``d=8`` local devices,\n   and forming on-device batch sizes of ``b=128``. With even dataset splitting,\n   the first host would get ``4096/8+1==513`` examples, and all other hosts\n   would get ``4096/8==512`` examples. Forming per-host batches of ``d*b==512``\n   this would lead to two batches on the first host, and a single batch on all\n   other hosts, violating SPMD principles and hanging the multi-host setup in\n   the last ``psum()`` directive (which would only be executed by the first\n   host, but not the others).\n\n2. When dropping examples dynamically by using ``ds.filter()``.\n\nIn these more complicated cases we could add \"infinite padding\" to the dataset,\non each of the hosts independently, and continuing processing examples until\n*all* hosts run out of unpadded examples.\n\n.. code-block:: python\n\n  correct = total = 0\n  for batch in ds.as_numpy_iterator():\n    n = count_p(batch['mask'])[0].item()  # adds sync barrier\n    if not n: break\n\n    preds = get_preds(vs, batch['image']).argmax(axis=-1)\n    total += n\n    correct += count_correct_p(batch['label'], preds, batch['mask'])[0]\n"
  },
  {
    "path": "docs/guides/data_preprocessing/index.rst",
    "content": "Data preprocessing\n=================\n\n.. toctree::\n   :maxdepth: 1\n\n   full_eval\n   loading_datasets\n"
  },
  {
    "path": "docs/guides/data_preprocessing/loading_datasets.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Loading datasets\\n\",\n    \"\\n\",\n    \"[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google/flax/blob/main/docs/guides/data_preprocessing/loading_datasets.ipynb)\\n\",\n    \"\\n\",\n    \"A neural net written in Jax+Flax expects its input data as `jax.numpy` array instances. Therefore, loading a dataset from any source is as simple as converting it to `jax.numpy` types and reshaping it to the appropriate dimensions for your network.\\n\",\n    \"\\n\",\n    \"As an example, this guide demonstrates how to import [MNIST](http://yann.lecun.com/exdb/mnist/) using the APIs from Torchvision, Tensorflow, and Hugging Face. We'll load the whole dataset into memory. For datasets that don't fit into memory the process is analogous but should be done in a batchwise fashion.\\n\",\n    \"\\n\",\n    \"The MNIST dataset consists of greyscale images of 28x28 pixels of handwritten digits, and has a designated 60k/10k train/test split. The task is to predict the correct class (digit 0, ..., 9) of each image.\\n\",\n    \"\\n\",\n    \"Assuming a CNN-based classifier, the input data should have shape `(B, 28, 28, 1)`, where the trailing singleton dimension denotes the greyscale image channel.\\n\",\n    \"\\n\",\n    \"The labels are simply the integer denoting the digit corresponding to the image. Labels should therefore have shape `(B,)`, to enable loss computation with [`optax.softmax_cross_entropy_with_integer_labels`](https://optax.readthedocs.io/en/latest/api.html#optax.softmax_cross_entropy_with_integer_labels).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import numpy as np\\n\",\n    \"import jax.numpy as jnp\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Loading from `torchvision.datasets`\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import torchvision\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def get_dataset_torch():\\n\",\n    \"    mnist = {\\n\",\n    \"        'train': torchvision.datasets.MNIST('./data', train=True, download=True),\\n\",\n    \"        'test': torchvision.datasets.MNIST('./data', train=False, download=True)\\n\",\n    \"    }\\n\",\n    \"\\n\",\n    \"    ds = {}\\n\",\n    \"\\n\",\n    \"    for split in ['train', 'test']:\\n\",\n    \"        ds[split] = {\\n\",\n    \"            'image': mnist[split].data.numpy(),\\n\",\n    \"            'label': mnist[split].targets.numpy()\\n\",\n    \"        }\\n\",\n    \"\\n\",\n    \"        # cast from np to jnp and rescale the pixel values from [0,255] to [0,1]\\n\",\n    \"        ds[split]['image'] = jnp.float32(ds[split]['image']) / 255\\n\",\n    \"        ds[split]['label'] = jnp.int16(ds[split]['label'])\\n\",\n    \"\\n\",\n    \"        # torchvision returns shape (B, 28, 28).\\n\",\n    \"        # hence, append the trailing channel dimension.\\n\",\n    \"        ds[split]['image'] = jnp.expand_dims(ds[split]['image'], 3)\\n\",\n    \"\\n\",\n    \"    return ds['train'], ds['test']\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {\n    \"outputId\": \"be39b756-d13e-4380-b99e-a5cbf61458cc\",\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"(60000, 28, 28, 1) float32\\n\",\n      \"(60000,) int16\\n\",\n      \"(10000, 28, 28, 1) float32\\n\",\n      \"(10000,) int16\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"train, test = get_dataset_torch()\\n\",\n    \"print(train['image'].shape, train['image'].dtype)\\n\",\n    \"print(train['label'].shape, train['label'].dtype)\\n\",\n    \"print(test['image'].shape, test['image'].dtype)\\n\",\n    \"print(test['label'].shape, test['label'].dtype)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Loading from `tensorflow_datasets`\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import tensorflow_datasets as tfds\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def get_dataset_tf():\\n\",\n    \"    mnist = tfds.builder('mnist')\\n\",\n    \"    mnist.download_and_prepare()\\n\",\n    \"\\n\",\n    \"    ds = {}\\n\",\n    \"\\n\",\n    \"    for split in ['train', 'test']:\\n\",\n    \"        ds[split] = tfds.as_numpy(mnist.as_dataset(split=split, batch_size=-1))\\n\",\n    \"\\n\",\n    \"        # cast to jnp and rescale pixel values\\n\",\n    \"        ds[split]['image'] = jnp.float32(ds[split]['image']) / 255\\n\",\n    \"        ds[split]['label'] = jnp.int16(ds[split]['label'])\\n\",\n    \"\\n\",\n    \"    return ds['train'], ds['test']\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {\n    \"outputId\": \"25d2c468-cbc8-4971-a738-1295ce8c6f16\",\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"(60000, 28, 28, 1) float32\\n\",\n      \"(60000,) int16\\n\",\n      \"(10000, 28, 28, 1) float32\\n\",\n      \"(10000,) int16\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"train, test = get_dataset_tf()\\n\",\n    \"print(train['image'].shape, train['image'].dtype)\\n\",\n    \"print(train['label'].shape, train['label'].dtype)\\n\",\n    \"print(test['image'].shape, test['image'].dtype)\\n\",\n    \"print(test['label'].shape, test['label'].dtype)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Loading from 🤗 Hugging Face `datasets`\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"#!pip install datasets # datasets isn't preinstalled on Colab; uncomment to install\\n\",\n    \"from datasets import load_dataset\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def get_dataset_hf():\\n\",\n    \"    mnist = load_dataset(\\\"mnist\\\")\\n\",\n    \"\\n\",\n    \"    ds = {}\\n\",\n    \"\\n\",\n    \"    for split in ['train', 'test']:\\n\",\n    \"        ds[split] = {\\n\",\n    \"            'image': np.array([np.array(im) for im in mnist[split]['image']]),\\n\",\n    \"            'label': np.array(mnist[split]['label'])\\n\",\n    \"        }\\n\",\n    \"\\n\",\n    \"        # cast to jnp and rescale pixel values\\n\",\n    \"        ds[split]['image'] = jnp.float32(ds[split]['image']) / 255\\n\",\n    \"        ds[split]['label'] = jnp.int16(ds[split]['label'])\\n\",\n    \"\\n\",\n    \"        # append trailing channel dimension\\n\",\n    \"        ds[split]['image'] = jnp.expand_dims(ds[split]['image'], 3)\\n\",\n    \"\\n\",\n    \"    return ds['train'], ds['test']\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {\n    \"outputId\": \"b026b33f-3bdd-4d26-867c-49400fff1c96\",\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"(60000, 28, 28, 1) float32\\n\",\n      \"(60000,) int16\\n\",\n      \"(10000, 28, 28, 1) float32\\n\",\n      \"(10000,) int16\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"train, test = get_dataset_hf()\\n\",\n    \"print(train['image'].shape, train['image'].dtype)\\n\",\n    \"print(train['label'].shape, train['label'].dtype)\\n\",\n    \"print(test['image'].shape, test['image'].dtype)\\n\",\n    \"print(test['label'].shape, test['label'].dtype)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.5\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "docs/guides/data_preprocessing/loading_datasets.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Loading datasets\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google/flax/blob/main/docs/guides/data_preprocessing/loading_datasets.ipynb)\n\nA neural net written in Jax+Flax expects its input data as `jax.numpy` array instances. Therefore, loading a dataset from any source is as simple as converting it to `jax.numpy` types and reshaping it to the appropriate dimensions for your network.\n\nAs an example, this guide demonstrates how to import [MNIST](http://yann.lecun.com/exdb/mnist/) using the APIs from Torchvision, Tensorflow, and Hugging Face. We'll load the whole dataset into memory. For datasets that don't fit into memory the process is analogous but should be done in a batchwise fashion.\n\nThe MNIST dataset consists of greyscale images of 28x28 pixels of handwritten digits, and has a designated 60k/10k train/test split. The task is to predict the correct class (digit 0, ..., 9) of each image.\n\nAssuming a CNN-based classifier, the input data should have shape `(B, 28, 28, 1)`, where the trailing singleton dimension denotes the greyscale image channel.\n\nThe labels are simply the integer denoting the digit corresponding to the image. Labels should therefore have shape `(B,)`, to enable loss computation with [`optax.softmax_cross_entropy_with_integer_labels`](https://optax.readthedocs.io/en/latest/api.html#optax.softmax_cross_entropy_with_integer_labels).\n\n```{code-cell} ipython3\n:tags: [skip-execution]\n\nimport numpy as np\nimport jax.numpy as jnp\n```\n\n## Loading from `torchvision.datasets`\n\n```{code-cell} ipython3\n:tags: [skip-execution]\n\nimport torchvision\n```\n\n```{code-cell} ipython3\n:tags: [skip-execution]\n\ndef get_dataset_torch():\n    mnist = {\n        'train': torchvision.datasets.MNIST('./data', train=True, download=True),\n        'test': torchvision.datasets.MNIST('./data', train=False, download=True)\n    }\n\n    ds = {}\n\n    for split in ['train', 'test']:\n        ds[split] = {\n            'image': mnist[split].data.numpy(),\n            'label': mnist[split].targets.numpy()\n        }\n\n        # cast from np to jnp and rescale the pixel values from [0,255] to [0,1]\n        ds[split]['image'] = jnp.float32(ds[split]['image']) / 255\n        ds[split]['label'] = jnp.int16(ds[split]['label'])\n\n        # torchvision returns shape (B, 28, 28).\n        # hence, append the trailing channel dimension.\n        ds[split]['image'] = jnp.expand_dims(ds[split]['image'], 3)\n\n    return ds['train'], ds['test']\n```\n\n```{code-cell} ipython3\n:outputId: be39b756-d13e-4380-b99e-a5cbf61458cc\n:tags: [skip-execution]\n\ntrain, test = get_dataset_torch()\nprint(train['image'].shape, train['image'].dtype)\nprint(train['label'].shape, train['label'].dtype)\nprint(test['image'].shape, test['image'].dtype)\nprint(test['label'].shape, test['label'].dtype)\n```\n\n## Loading from `tensorflow_datasets`\n\n```{code-cell} ipython3\n:tags: [skip-execution]\n\nimport tensorflow_datasets as tfds\n```\n\n```{code-cell} ipython3\n:tags: [skip-execution]\n\ndef get_dataset_tf():\n    mnist = tfds.builder('mnist')\n    mnist.download_and_prepare()\n\n    ds = {}\n\n    for split in ['train', 'test']:\n        ds[split] = tfds.as_numpy(mnist.as_dataset(split=split, batch_size=-1))\n\n        # cast to jnp and rescale pixel values\n        ds[split]['image'] = jnp.float32(ds[split]['image']) / 255\n        ds[split]['label'] = jnp.int16(ds[split]['label'])\n\n    return ds['train'], ds['test']\n```\n\n```{code-cell} ipython3\n:outputId: 25d2c468-cbc8-4971-a738-1295ce8c6f16\n:tags: [skip-execution]\n\ntrain, test = get_dataset_tf()\nprint(train['image'].shape, train['image'].dtype)\nprint(train['label'].shape, train['label'].dtype)\nprint(test['image'].shape, test['image'].dtype)\nprint(test['label'].shape, test['label'].dtype)\n```\n\n## Loading from 🤗 Hugging Face `datasets`\n\n```{code-cell} ipython3\n:tags: [skip-execution]\n\n#!pip install datasets # datasets isn't preinstalled on Colab; uncomment to install\nfrom datasets import load_dataset\n```\n\n```{code-cell} ipython3\n:tags: [skip-execution]\n\ndef get_dataset_hf():\n    mnist = load_dataset(\"mnist\")\n\n    ds = {}\n\n    for split in ['train', 'test']:\n        ds[split] = {\n            'image': np.array([np.array(im) for im in mnist[split]['image']]),\n            'label': np.array(mnist[split]['label'])\n        }\n\n        # cast to jnp and rescale pixel values\n        ds[split]['image'] = jnp.float32(ds[split]['image']) / 255\n        ds[split]['label'] = jnp.int16(ds[split]['label'])\n\n        # append trailing channel dimension\n        ds[split]['image'] = jnp.expand_dims(ds[split]['image'], 3)\n\n    return ds['train'], ds['test']\n```\n\n```{code-cell} ipython3\n:outputId: b026b33f-3bdd-4d26-867c-49400fff1c96\n:tags: [skip-execution]\n\ntrain, test = get_dataset_hf()\nprint(train['image'].shape, train['image'].dtype)\nprint(train['label'].shape, train['label'].dtype)\nprint(test['image'].shape, test['image'].dtype)\nprint(test['label'].shape, test['label'].dtype)\n```\n"
  },
  {
    "path": "docs/guides/flax_fundamentals/arguments.md",
    "content": "# Dealing with Flax Module arguments\n\n## Introduction\n\nIn Flax Linen we can define `Module` arguments either as dataclass attributes or as arguments to methods (usually `__call__`).\nTypically the distinction is clear:\n* Completely fixed properties, such as the choice of kernel initializer or number of output features, are hyperparameters and should be defined as dataclass attributes. Typically two Module instances with different hyperparameters cannot share in a meaningful way.\n* Dynamic properties, such as input data and top-level \"mode switches\" like `train=True/False`, should be passed as arguments to `__call__` or another method.\n\nSome cases are however less clear cut. Take for example the `Dropout` module.\nWe have a number of clear hyperparameters:\n\n1. The dropout rate\n2. The axes for which a dropout mask is generated\n\nAnd some clear call time arguments:\n\n1. The input that should be masked using dropout\n2. The (optional) rng used to sample the random mask\n\nThere is however one property that is ambiguous -- the `deterministic` property in a Dropout module.\n\nIf `deterministic` is `True` no dropout mask is sampled. This is typically used during model evaluation.\nHowever, if we pass `eval=True` or `train=False` to a top-level Module. The `deterministic` argument needs\nto be applied everywhere and the boolean argument needs to be passed down to all the layers that might use `Dropout`.\nIf instead `deterministic` is a dataclass attribute, we might do the following:\n\n```python\nfrom functools import partial\nfrom flax import linen as nn\n\nclass ResidualModel(nn.Module):\n  drop_rate: float\n\n  @nn.compact\n  def __call__(self, x, *, train):\n    dropout = partial(nn.Dropout, rate=self.drop_rate, deterministic=not train)\n    for i in range(10):\n      x += ResidualBlock(dropout=dropout, ...)(x)\n```\n\nIt makes sense to pass `determinstic` to the constructor here because this way we can pass the dropout template to the sub-modules.\nNow the sub-module no longer needs to take care of train vs eval mode and can simply use the `dropout` argument.\nNote that because the dropout layer can only be constructed in the sub-module we can only partially apply `deterministic` to the constructor but not to `__call__`.\n\nHowever, if `deterministic` is a dataclass attribute we run into trouble when using the setup pattern. We would **want** to write our module code like this:\n\n```python\nclass SomeModule(nn.Module):\n  drop_rate: float\n\n  def setup(self):\n    self.dropout = nn.Dropout(rate=self.drop_rate)\n\n  @nn.compact\n  def __call__(self, x, *, train):\n    # ...\n    x = self.dropout(x, deterministic=not train)\n    # ...\n```\n\nBut, as defined above, `deterministic` would be an attribute, so this doesn't work.\nHere it makes sense to pass `deterministic` during `__call__` because it depends on the `train` argument.\n\n## Solution\n\nWe can support both use cases described before by allowing certain properties to be passed\nas dataclass attributes or as method argument (but not both!).\nThis can be implemented as follows:\n```python\nclass MyDropout(nn.Module):\n  drop_rate: float\n  deterministic: Optional[bool] = None\n\n  @nn.compact\n  def __call__(self, x, deterministic=None):\n    deterministic = nn.merge_param('deterministic', self.deterministic, deterministic)\n    # ...\n```\n\nIn this example `nn.merge_param` will ensure that either `self.deterministic` or `deterministic` is set but not both.\nAn error is raised if both values are `None` or both values are not `None`.\nThis avoids confusing behavior where 2 different parts of the code set the same parameter and one is overruled by the other.\nIt also avoids a default value which would probably cause either the train step or eval step of a training procedure to be broken by default.\n\n\n\n## Functional Core\n\nFunctional core defines functions rather than classes.\nTherefore, there is no clear distinction between hyperparameters and call-time arguments.\nThe only way to pre-determine the hyperparameters is by using `partial`.\nOn the upside, there are no ambiguous cases where method arguments could also be attributes.\n"
  },
  {
    "path": "docs/guides/flax_fundamentals/flax_basics.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google/flax/blob/main/docs/guides/flax_fundamentals/flax_basics.ipynb)\\n\",\n    \"[![Open On GitHub](https://img.shields.io/badge/Open-on%20GitHub-blue?logo=GitHub)](https://github.com/google/flax/blob/main/docs/guides/flax_fundamentals/flax_basics.ipynb)\\n\",\n    \"\\n\",\n    \"# Flax Basics\\n\",\n    \"\\n\",\n    \"This notebook will walk you through the following workflow:\\n\",\n    \"\\n\",\n    \"*   Instantiating a model from Flax built-in layers or third-party models.\\n\",\n    \"*   Initializing parameters of the model and manually written training.\\n\",\n    \"*   Using optimizers provided by Flax to ease training.\\n\",\n    \"*   Serialization of parameters and other objects.\\n\",\n    \"*   Creating your own models and managing state.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Setting up our environment\\n\",\n    \"\\n\",\n    \"Here we provide the code needed to set up the environment for our notebook.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"outputId\": \"e30aa464-fa52-4f35-df96-716c68a4b3ee\",\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[33mWARNING: Running pip as root will break packages and permissions. You should install packages reliably by using venv: https://pip.pypa.io/warnings/venv\\u001b[0m\\n\",\n      \"\\u001b[33mWARNING: Running pip as root will break packages and permissions. You should install packages reliably by using venv: https://pip.pypa.io/warnings/venv\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Install the latest JAXlib version.\\n\",\n    \"!pip install --upgrade -q pip jax jaxlib\\n\",\n    \"# Install Flax at head:\\n\",\n    \"!pip install --upgrade -q git+https://github.com/google/flax.git\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import jax\\n\",\n    \"from typing import Any, Callable, Sequence\\n\",\n    \"from jax import random, numpy as jnp\\n\",\n    \"import flax\\n\",\n    \"from flax import linen as nn\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Linear regression with Flax\\n\",\n    \"\\n\",\n    \"In the previous *JAX for the impatient* notebook, we finished up with a linear regression example. As we know, linear regression can also be written as a single dense neural network layer, which we will show in the following so that we can compare how it's done.\\n\",\n    \"\\n\",\n    \"A dense layer is a layer that has a kernel parameter $W\\\\in\\\\mathcal{M}_{m,n}(\\\\mathbb{R})$ where $m$ is the number of features as an output of the model, and $n$ the dimensionality of the input, and a bias parameter $b\\\\in\\\\mathbb{R}^m$. The dense layers returns $Wx+b$ from an input $x\\\\in\\\\mathbb{R}^n$.\\n\",\n    \"\\n\",\n    \"This dense layer is already provided by Flax in the `flax.linen` module (here imported as `nn`).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# We create one dense layer instance (taking 'features' parameter as input)\\n\",\n    \"model = nn.Dense(features=5)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Layers (and models in general, we'll use that word from now on) are subclasses of the `linen.Module` class.\\n\",\n    \"\\n\",\n    \"### Model parameters & initialization\\n\",\n    \"\\n\",\n    \"Parameters are not stored with the models themselves. You need to initialize parameters by calling the `init` function, using a PRNGKey and dummy input data.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"outputId\": \"06feb9d2-db50-4f41-c169-6df4336f43a5\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"FrozenDict({\\n\",\n       \"    params: {\\n\",\n       \"        bias: (5,),\\n\",\n       \"        kernel: (10, 5),\\n\",\n       \"    },\\n\",\n       \"})\"\n      ]\n     },\n     \"execution_count\": 4,\n     \"metadata\": {\n      \"tags\": []\n     },\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"key1, key2 = random.split(random.key(0))\\n\",\n    \"x = random.normal(key1, (10,)) # Dummy input data\\n\",\n    \"params = model.init(key2, x) # Initialization call\\n\",\n    \"jax.tree_util.tree_map(lambda x: x.shape, params) # Checking output shapes\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"*Note: JAX and Flax, like NumPy, are row-based systems, meaning that vectors are represented as row vectors and not column vectors. This can be seen in the shape of the kernel here.*\\n\",\n    \"\\n\",\n    \"The result is what we expect: bias and kernel parameters of the correct size. Under the hood:\\n\",\n    \"\\n\",\n    \"*   The dummy input data `x` is used to trigger shape inference: we only declared the number of features we wanted in the output of the model, not the size of the input. Flax finds out by itself the correct size of the kernel.\\n\",\n    \"*   The random PRNG key is used to trigger the initialization functions (those have default values provided by the module here).\\n\",\n    \"* Initialization functions are called to generate the initial set of parameters that the model will use. Those are functions that take as arguments `(PRNG Key, shape, dtype)` and return an Array of shape `shape`.\\n\",\n    \"* The init function returns the initialized set of parameters (you can also get the output of the forward pass on the dummy input with the same syntax by using the `init_with_output` method instead of `init`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To conduct a forward pass with the model with a given set of parameters (which are never stored with the model), we just use the `apply` method by providing it the parameters to use as well as the input:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {\n    \"outputId\": \"7bbe6bb4-94d5-4574-fbb5-aa0fcd1c84ae\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"DeviceArray([-0.7358944,  1.3583755, -0.7976872,  0.8168598,  0.6297793],            dtype=float32)\"\n      ]\n     },\n     \"execution_count\": 6,\n     \"metadata\": {\n      \"tags\": []\n     },\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"model.apply(params, x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Gradient descent\\n\",\n    \"\\n\",\n    \"If you jumped here directly without going through the JAX part, here is the linear regression formulation we're going to use: from a set of data points $\\\\{(x_i,y_i), i\\\\in \\\\{1,\\\\ldots, k\\\\}, x_i\\\\in\\\\mathbb{R}^n,y_i\\\\in\\\\mathbb{R}^m\\\\}$, we try to find a set of parameters $W\\\\in \\\\mathcal{M}_{m,n}(\\\\mathbb{R}), b\\\\in\\\\mathbb{R}^m$ such that the function $f_{W,b}(x)=Wx+b$ minimizes the mean squared error:\\n\",\n    \"\\n\",\n    \"$$\\\\mathcal{L}(W,b)\\\\rightarrow\\\\frac{1}{k}\\\\sum_{i=1}^{k} \\\\frac{1}{2}\\\\|y_i-f_{W,b}(x_i)\\\\|^2_2$$\\n\",\n    \"\\n\",\n    \"Here, we see that the tuple $(W,b)$ matches the parameters of the Dense layer. We'll perform gradient descent using those. Let's first generate the fake data we'll use. The data is exactly the same as in the JAX part's linear regression pytree example.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {\n    \"outputId\": \"6eae59dc-0632-4f53-eac8-c22a7c646a52\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"x shape: (20, 10) ; y shape: (20, 5)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Set problem dimensions.\\n\",\n    \"n_samples = 20\\n\",\n    \"x_dim = 10\\n\",\n    \"y_dim = 5\\n\",\n    \"\\n\",\n    \"# Generate random ground truth W and b.\\n\",\n    \"key = random.key(0)\\n\",\n    \"k1, k2 = random.split(key)\\n\",\n    \"W = random.normal(k1, (x_dim, y_dim))\\n\",\n    \"b = random.normal(k2, (y_dim,))\\n\",\n    \"# Store the parameters in a FrozenDict pytree.\\n\",\n    \"true_params = flax.core.freeze({'params': {'bias': b, 'kernel': W}})\\n\",\n    \"\\n\",\n    \"# Generate samples with additional noise.\\n\",\n    \"key_sample, key_noise = random.split(k1)\\n\",\n    \"x_samples = random.normal(key_sample, (n_samples, x_dim))\\n\",\n    \"y_samples = jnp.dot(x_samples, W) + b + 0.1 * random.normal(key_noise,(n_samples, y_dim))\\n\",\n    \"print('x shape:', x_samples.shape, '; y shape:', y_samples.shape)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We copy the same training loop that we used in the JAX pytree linear regression example with `jax.value_and_grad()`, but here we can use `model.apply()` instead of having to define our own feed-forward function (`predict_pytree()` in the [JAX example](https://flax.readthedocs.io/en/latest/guides/jax_for_the_impatient.html#linear-regression-with-pytrees)).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Same as JAX version but using model.apply().\\n\",\n    \"@jax.jit\\n\",\n    \"def mse(params, x_batched, y_batched):\\n\",\n    \"  # Define the squared loss for a single pair (x,y)\\n\",\n    \"  def squared_error(x, y):\\n\",\n    \"    pred = model.apply(params, x)\\n\",\n    \"    return jnp.inner(y-pred, y-pred) / 2.0\\n\",\n    \"  # Vectorize the previous to compute the average of the loss on all samples.\\n\",\n    \"  return jnp.mean(jax.vmap(squared_error)(x_batched,y_batched), axis=0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"And finally perform the gradient descent.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {\n    \"outputId\": \"50d975b3-4706-4d8a-c4b8-2629ab8e3ac4\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Loss for \\\"true\\\" W,b:  0.023639778\\n\",\n      \"Loss step 0:  38.094772\\n\",\n      \"Loss step 10:  0.44692168\\n\",\n      \"Loss step 20:  0.10053458\\n\",\n      \"Loss step 30:  0.035822745\\n\",\n      \"Loss step 40:  0.018846875\\n\",\n      \"Loss step 50:  0.013864839\\n\",\n      \"Loss step 60:  0.012312559\\n\",\n      \"Loss step 70:  0.011812928\\n\",\n      \"Loss step 80:  0.011649306\\n\",\n      \"Loss step 90:  0.011595251\\n\",\n      \"Loss step 100:  0.0115773035\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"learning_rate = 0.3  # Gradient step size.\\n\",\n    \"print('Loss for \\\"true\\\" W,b: ', mse(true_params, x_samples, y_samples))\\n\",\n    \"loss_grad_fn = jax.value_and_grad(mse)\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def update_params(params, learning_rate, grads):\\n\",\n    \"  params = jax.tree_util.tree_map(\\n\",\n    \"      lambda p, g: p - learning_rate * g, params, grads)\\n\",\n    \"  return params\\n\",\n    \"\\n\",\n    \"for i in range(101):\\n\",\n    \"  # Perform one gradient update.\\n\",\n    \"  loss_val, grads = loss_grad_fn(params, x_samples, y_samples)\\n\",\n    \"  params = update_params(params, learning_rate, grads)\\n\",\n    \"  if i % 10 == 0:\\n\",\n    \"    print(f'Loss step {i}: ', loss_val)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Optimizing with Optax\\n\",\n    \"\\n\",\n    \"Flax used to use its own `flax.optim` package for optimization, but with\\n\",\n    \"[FLIP #1009](https://github.com/google/flax/blob/main/docs/flip/1009-optimizer-api.md)\\n\",\n    \"this was deprecated in favor of\\n\",\n    \"[Optax](https://github.com/deepmind/optax).\\n\",\n    \"\\n\",\n    \"Basic usage of Optax is straightforward:\\n\",\n    \"\\n\",\n    \"1.   Choose an optimization method (e.g. `optax.adam`).\\n\",\n    \"2.   Create optimizer state from parameters (for the Adam optimizer, this state will contain the [momentum values](https://optax.readthedocs.io/en/latest/api.html#optax.adam)).\\n\",\n    \"3.   Compute the gradients of your loss with `jax.value_and_grad()`.\\n\",\n    \"4.   At every iteration, call the Optax `update` function to update the internal\\n\",\n    \"     optimizer state and create an update to the parameters. Then add the update\\n\",\n    \"     to the parameters with Optax's `apply_updates` method.\\n\",\n    \"\\n\",\n    \"Note that Optax can do a lot more: it's designed for composing simple gradient\\n\",\n    \"transformations into more complex transformations that allows to implement a\\n\",\n    \"wide range of optimizers. There is also support for changing optimizer\\n\",\n    \"hyperparameters over time (\\\"schedules\\\"), applying different updates to different\\n\",\n    \"parts of the parameter tree (\\\"masking\\\") and much more. For details please refer\\n\",\n    \"to the\\n\",\n    \"[official documentation](https://optax.readthedocs.io/en/latest/).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import optax\\n\",\n    \"tx = optax.adam(learning_rate=learning_rate)\\n\",\n    \"opt_state = tx.init(params)\\n\",\n    \"loss_grad_fn = jax.value_and_grad(mse)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {\n    \"outputId\": \"eec0c096-1d9e-4b3c-f8e5-942ee63828ec\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Loss step 0:  0.011576377\\n\",\n      \"Loss step 10:  0.0115710115\\n\",\n      \"Loss step 20:  0.011569244\\n\",\n      \"Loss step 30:  0.011568661\\n\",\n      \"Loss step 40:  0.011568454\\n\",\n      \"Loss step 50:  0.011568379\\n\",\n      \"Loss step 60:  0.011568358\\n\",\n      \"Loss step 70:  0.01156836\\n\",\n      \"Loss step 80:  0.01156835\\n\",\n      \"Loss step 90:  0.011568353\\n\",\n      \"Loss step 100:  0.011568348\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"for i in range(101):\\n\",\n    \"  loss_val, grads = loss_grad_fn(params, x_samples, y_samples)\\n\",\n    \"  updates, opt_state = tx.update(grads, opt_state)\\n\",\n    \"  params = optax.apply_updates(params, updates)\\n\",\n    \"  if i % 10 == 0:\\n\",\n    \"    print('Loss step {}: '.format(i), loss_val)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Serializing the result\\n\",\n    \"\\n\",\n    \"Now that we're happy with the result of our training, we might want to save the model parameters to load them back later. Flax provides a serialization package to enable you to do that.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {\n    \"outputId\": \"b97e7d83-3e40-4a80-b1fe-1f6ceff30a0c\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Dict output\\n\",\n      \"{'params': {'bias': DeviceArray([-1.4540135, -2.0262308,  2.0806582,  1.2201802, -0.9964547],            dtype=float32), 'kernel': DeviceArray([[ 1.0106664 ,  0.19014716,  0.04533899, -0.92722285,\\n\",\n      \"               0.34720102],\\n\",\n      \"             [ 1.7320251 ,  0.9901233 ,  1.1662225 ,  1.1027892 ,\\n\",\n      \"              -0.10574618],\\n\",\n      \"             [-1.2009128 ,  0.28837162,  1.4176372 ,  0.12073109,\\n\",\n      \"              -1.3132601 ],\\n\",\n      \"             [-1.1944956 , -0.18993308,  0.03379077,  1.3165942 ,\\n\",\n      \"               0.07996067],\\n\",\n      \"             [ 0.14103189,  1.3737966 , -1.3162128 ,  0.53401774,\\n\",\n      \"              -2.239638  ],\\n\",\n      \"             [ 0.5643044 ,  0.813604  ,  0.31888172,  0.5359193 ,\\n\",\n      \"               0.90352124],\\n\",\n      \"             [-0.37948322,  1.7408353 ,  1.0788013 , -0.5041964 ,\\n\",\n      \"               0.9286919 ],\\n\",\n      \"             [ 0.9701384 , -1.3158673 ,  0.33630812,  0.80941117,\\n\",\n      \"              -1.202457  ],\\n\",\n      \"             [ 1.0198247 , -0.6198277 ,  1.0822718 , -1.8385581 ,\\n\",\n      \"              -0.45790705],\\n\",\n      \"             [-0.64384323,  0.4564892 , -1.1331053 , -0.68556863,\\n\",\n      \"               0.17010891]], dtype=float32)}}\\n\",\n      \"Bytes output\\n\",\n      \"b'\\\\x81\\\\xa6params\\\\x82\\\\xa4bias\\\\xc7!\\\\x01\\\\x93\\\\x91\\\\x05\\\\xa7float32\\\\xc4\\\\x14\\\\x1d\\\\x1d\\\\xba\\\\xbf\\\\xc4\\\\xad\\\\x01\\\\xc0\\\\x81)\\\\x05@\\\\xdd.\\\\x9c?\\\\xa8\\\\x17\\\\x7f\\\\xbf\\\\xa6kernel\\\\xc7\\\\xd6\\\\x01\\\\x93\\\\x92\\\\n\\\\x05\\\\xa7float32\\\\xc4\\\\xc8\\\\x84]\\\\x81?\\\\xf0\\\\xb5B>`\\\\xb59=z^m\\\\xbfU\\\\xc4\\\\xb1>\\\\x00\\\\xb3\\\\xdd?\\\\xb8x}?\\\\xc7F\\\\x95?2(\\\\x8d?t\\\\x91\\\\xd8\\\\xbd\\\\x83\\\\xb7\\\\x99\\\\xbfr\\\\xa5\\\\x93>#u\\\\xb5?\\\\xdcA\\\\xf7=\\\\xe8\\\\x18\\\\xa8\\\\xbf;\\\\xe5\\\\x98\\\\xbf\\\\xd1}B\\\\xbe0h\\\\n=)\\\\x86\\\\xa8?k\\\\xc2\\\\xa3=\\\\xaaj\\\\x10>\\\\x91\\\\xd8\\\\xaf?\\\\xa9y\\\\xa8\\\\xbfc\\\\xb5\\\\x08?;V\\\\x0f\\\\xc0Av\\\\x10?ZHP?wD\\\\xa3>\\\\x022\\\\t?+Mg?\\\\xa0K\\\\xc2\\\\xbe\\\\xb1\\\\xd3\\\\xde?)\\\\x16\\\\x8a?\\\\x04\\\\x13\\\\x01\\\\xbf\\\\xc1\\\\xbem?\\\\xfdZx?Wn\\\\xa8\\\\xbf\\\\x940\\\\xac>\\\\x925O?\\\\x1c\\\\xea\\\\x99\\\\xbf\\\\x9e\\\\x89\\\\x82?\\\\x07\\\\xad\\\\x1e\\\\xbf\\\\xe2\\\\x87\\\\x8a?\\\\xdfU\\\\xeb\\\\xbf\\\\xcbr\\\\xea\\\\xbe\\\\xe9\\\\xd2$\\\\xbf\\\\xf4\\\\xb8\\\\xe9>\\\\x98\\\\t\\\\x91\\\\xbfm\\\\x81/\\\\xbf\\\\x081.>'\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from flax import serialization\\n\",\n    \"bytes_output = serialization.to_bytes(params)\\n\",\n    \"dict_output = serialization.to_state_dict(params)\\n\",\n    \"print('Dict output')\\n\",\n    \"print(dict_output)\\n\",\n    \"print('Bytes output')\\n\",\n    \"print(bytes_output)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To load the model back, you'll need to use a template of the model parameter structure, like the one you would get from the model initialization. Here, we use the previously generated `params` as a template. Note that this will produce a new variable structure, and not mutate in-place.\\n\",\n    \"\\n\",\n    \"*The point of enforcing structure through template is to avoid users issues downstream, so you need to first have the right model that generates the parameters structure.*\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {\n    \"outputId\": \"13acc4e1-8757-4554-e2c8-d594ba6e67dc\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"FrozenDict({\\n\",\n       \"    params: {\\n\",\n       \"        bias: array([-1.4540135, -2.0262308,  2.0806582,  1.2201802, -0.9964547],\\n\",\n       \"              dtype=float32),\\n\",\n       \"        kernel: array([[ 1.0106664 ,  0.19014716,  0.04533899, -0.92722285,  0.34720102],\\n\",\n       \"               [ 1.7320251 ,  0.9901233 ,  1.1662225 ,  1.1027892 , -0.10574618],\\n\",\n       \"               [-1.2009128 ,  0.28837162,  1.4176372 ,  0.12073109, -1.3132601 ],\\n\",\n       \"               [-1.1944956 , -0.18993308,  0.03379077,  1.3165942 ,  0.07996067],\\n\",\n       \"               [ 0.14103189,  1.3737966 , -1.3162128 ,  0.53401774, -2.239638  ],\\n\",\n       \"               [ 0.5643044 ,  0.813604  ,  0.31888172,  0.5359193 ,  0.90352124],\\n\",\n       \"               [-0.37948322,  1.7408353 ,  1.0788013 , -0.5041964 ,  0.9286919 ],\\n\",\n       \"               [ 0.9701384 , -1.3158673 ,  0.33630812,  0.80941117, -1.202457  ],\\n\",\n       \"               [ 1.0198247 , -0.6198277 ,  1.0822718 , -1.8385581 , -0.45790705],\\n\",\n       \"               [-0.64384323,  0.4564892 , -1.1331053 , -0.68556863,  0.17010891]],\\n\",\n       \"              dtype=float32),\\n\",\n       \"    },\\n\",\n       \"})\"\n      ]\n     },\n     \"execution_count\": 14,\n     \"metadata\": {\n      \"tags\": []\n     },\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"serialization.from_bytes(params, bytes_output)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Defining your own models\\n\",\n    \"\\n\",\n    \"Flax allows you to define your own models, which should be a bit more complicated than a linear regression. In this section, we'll show you how to build simple models. To do so, you'll need to create subclasses of the base `nn.Module` class.\\n\",\n    \"\\n\",\n    \"*Keep in mind that we imported* `linen as nn` *and this only works with the new linen API*\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Module basics\\n\",\n    \"\\n\",\n    \"The base abstraction for models is the `nn.Module` class, and every type of predefined layers in Flax (like the previous `Dense`) is a subclass of `nn.Module`. Let's take a look and start by defining a simple but custom multi-layer perceptron i.e. a sequence of Dense layers interleaved with calls to a non-linear activation function.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 17,\n   \"metadata\": {\n    \"outputId\": \"b59c679c-d164-4fd6-92db-b50f0d310ec3\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized parameter shapes:\\n\",\n      \" {'params': {'layers_0': {'bias': (3,), 'kernel': (4, 3)}, 'layers_1': {'bias': (4,), 'kernel': (3, 4)}, 'layers_2': {'bias': (5,), 'kernel': (4, 5)}}}\\n\",\n      \"output:\\n\",\n      \" [[ 4.2292815e-02 -4.3807115e-02  2.9323792e-02  6.5492536e-03\\n\",\n      \"  -1.7147182e-02]\\n\",\n      \" [ 1.2967806e-01 -1.4551792e-01  9.4432183e-02  1.2521387e-02\\n\",\n      \"  -4.5417298e-02]\\n\",\n      \" [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00\\n\",\n      \"   0.0000000e+00]\\n\",\n      \" [ 9.3024032e-04  2.7864395e-05  2.4478821e-04  8.1344310e-04\\n\",\n      \"  -1.0110770e-03]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class ExplicitMLP(nn.Module):\\n\",\n    \"  features: Sequence[int]\\n\",\n    \"\\n\",\n    \"  def setup(self):\\n\",\n    \"    # we automatically know what to do with lists, dicts of submodules\\n\",\n    \"    self.layers = [nn.Dense(feat) for feat in self.features]\\n\",\n    \"    # for single submodules, we would just write:\\n\",\n    \"    # self.layer1 = nn.Dense(feat1)\\n\",\n    \"\\n\",\n    \"  def __call__(self, inputs):\\n\",\n    \"    x = inputs\\n\",\n    \"    for i, lyr in enumerate(self.layers):\\n\",\n    \"      x = lyr(x)\\n\",\n    \"      if i != len(self.layers) - 1:\\n\",\n    \"        x = nn.relu(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"key1, key2 = random.split(random.key(0), 2)\\n\",\n    \"x = random.uniform(key1, (4,4))\\n\",\n    \"\\n\",\n    \"model = ExplicitMLP(features=[3,4,5])\\n\",\n    \"params = model.init(key2, x)\\n\",\n    \"y = model.apply(params, x)\\n\",\n    \"\\n\",\n    \"print('initialized parameter shapes:\\\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(params)))\\n\",\n    \"print('output:\\\\n', y)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"As we can see, a `nn.Module` subclass is made of:\\n\",\n    \"\\n\",\n    \"*   A collection of data fields (`nn.Module` are Python dataclasses) - here we only have the `features` field of type `Sequence[int]`.\\n\",\n    \"*   A `setup()` method that is being called at the end of the `__postinit__` where you can register submodules, variables, parameters you will need in your model.\\n\",\n    \"*   A `__call__` function that returns the output of the model from a given input.\\n\",\n    \"*   The model structure defines a pytree of parameters following the same tree structure as the model: the params tree contains one `layers_n` sub dict per layer, and each of those contain the parameters of the associated Dense layer. The layout is very explicit.\\n\",\n    \"\\n\",\n    \"*Note: lists are mostly managed as you would expect (WIP), there are corner cases you should be aware of as pointed out* [here](https://github.com/google/flax/issues/524)\\n\",\n    \"\\n\",\n    \"Since the module structure and its parameters are not tied to each other, you can't directly call `model(x)` on a given input as it will return an error. The `__call__` function is being wrapped up in the `apply` one, which is the one to call on an input:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 19,\n   \"metadata\": {\n    \"outputId\": \"4af16ec5-b52a-43b0-fc47-1f8ab25e7058\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\\"ExplicitMLP\\\" object has no attribute \\\"layers\\\"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"try:\\n\",\n    \"    y = model(x) # Returns an error\\n\",\n    \"except AttributeError as e:\\n\",\n    \"    print(e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Since here we have a very simple model, we could have used an alternative (but equivalent) way of declaring the submodules inline in the `__call__` using the `@nn.compact` annotation like so:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 20,\n   \"metadata\": {\n    \"outputId\": \"183a74ef-f54e-4848-99bf-fee4c174ba6d\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized parameter shapes:\\n\",\n      \" {'params': {'layers_0': {'bias': (3,), 'kernel': (4, 3)}, 'layers_1': {'bias': (4,), 'kernel': (3, 4)}, 'layers_2': {'bias': (5,), 'kernel': (4, 5)}}}\\n\",\n      \"output:\\n\",\n      \" [[ 4.2292815e-02 -4.3807115e-02  2.9323792e-02  6.5492536e-03\\n\",\n      \"  -1.7147182e-02]\\n\",\n      \" [ 1.2967806e-01 -1.4551792e-01  9.4432183e-02  1.2521387e-02\\n\",\n      \"  -4.5417298e-02]\\n\",\n      \" [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00\\n\",\n      \"   0.0000000e+00]\\n\",\n      \" [ 9.3024032e-04  2.7864395e-05  2.4478821e-04  8.1344310e-04\\n\",\n      \"  -1.0110770e-03]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class SimpleMLP(nn.Module):\\n\",\n    \"  features: Sequence[int]\\n\",\n    \"\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, inputs):\\n\",\n    \"    x = inputs\\n\",\n    \"    for i, feat in enumerate(self.features):\\n\",\n    \"      x = nn.Dense(feat, name=f'layers_{i}')(x)\\n\",\n    \"      if i != len(self.features) - 1:\\n\",\n    \"        x = nn.relu(x)\\n\",\n    \"      # providing a name is optional though!\\n\",\n    \"      # the default autonames would be \\\"Dense_0\\\", \\\"Dense_1\\\", ...\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"key1, key2 = random.split(random.key(0), 2)\\n\",\n    \"x = random.uniform(key1, (4,4))\\n\",\n    \"\\n\",\n    \"model = SimpleMLP(features=[3,4,5])\\n\",\n    \"params = model.init(key2, x)\\n\",\n    \"y = model.apply(params, x)\\n\",\n    \"\\n\",\n    \"print('initialized parameter shapes:\\\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(params)))\\n\",\n    \"print('output:\\\\n', y)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"There are, however, a few differences you should be aware of between the two declaration modes:\\n\",\n    \"\\n\",\n    \"*   In `setup`, you are able to name some sublayers and keep them around for further use (e.g. encoder/decoder methods in autoencoders).\\n\",\n    \"*   If you want to have multiple methods, then you **need** to declare the module using `setup`, as the `@nn.compact` annotation only allows one method to be annotated.\\n\",\n    \"*   The last initialization will be handled differently. See these notes for more details (TODO: add notes link).\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Module parameters\\n\",\n    \"\\n\",\n    \"In the previous MLP example, we relied only on predefined layers and operators (`Dense`, `relu`). Let's imagine that you didn't have a Dense layer provided by Flax and you wanted to write it on your own. Here is what it would look like using the `@nn.compact` way to declare a new modules:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 21,\n   \"metadata\": {\n    \"outputId\": \"83b5fea4-071e-4ea0-8fa8-610e69fb5fd5\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized parameters:\\n\",\n      \" FrozenDict({\\n\",\n      \"    params: {\\n\",\n      \"        kernel: DeviceArray([[ 0.6503669 ,  0.86789787,  0.4604268 ],\\n\",\n      \"                     [ 0.05673932,  0.9909285 , -0.63536596],\\n\",\n      \"                     [ 0.76134115, -0.3250529 , -0.65221626],\\n\",\n      \"                     [-0.82430327,  0.4150194 ,  0.19405058]], dtype=float32),\\n\",\n      \"        bias: DeviceArray([0., 0., 0.], dtype=float32),\\n\",\n      \"    },\\n\",\n      \"})\\n\",\n      \"output:\\n\",\n      \" [[ 0.5035518   1.8548558  -0.4270195 ]\\n\",\n      \" [ 0.0279097   0.5589246  -0.43061772]\\n\",\n      \" [ 0.3547128   1.5740999  -0.32865518]\\n\",\n      \" [ 0.5264864   1.2928858   0.10089308]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class SimpleDense(nn.Module):\\n\",\n    \"  features: int\\n\",\n    \"  kernel_init: Callable = nn.initializers.lecun_normal()\\n\",\n    \"  bias_init: Callable = nn.initializers.zeros_init()\\n\",\n    \"\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, inputs):\\n\",\n    \"    kernel = self.param('kernel',\\n\",\n    \"                        self.kernel_init, # Initialization function\\n\",\n    \"                        (inputs.shape[-1], self.features))  # shape info.\\n\",\n    \"    y = jnp.dot(inputs, kernel)\\n\",\n    \"    bias = self.param('bias', self.bias_init, (self.features,))\\n\",\n    \"    y = y + bias\\n\",\n    \"    return y\\n\",\n    \"\\n\",\n    \"key1, key2 = random.split(random.key(0), 2)\\n\",\n    \"x = random.uniform(key1, (4,4))\\n\",\n    \"\\n\",\n    \"model = SimpleDense(features=3)\\n\",\n    \"params = model.init(key2, x)\\n\",\n    \"y = model.apply(params, x)\\n\",\n    \"\\n\",\n    \"print('initialized parameters:\\\\n', params)\\n\",\n    \"print('output:\\\\n', y)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here, we see how to both declare and assign a parameter to the model using the `self.param` method. It takes as input `(name, init_fn, *init_args, **init_kwargs)` :\\n\",\n    \"\\n\",\n    \"*   `name` is simply the name of the parameter that will end up in the parameter structure.\\n\",\n    \"*   `init_fn` is a function with input `(PRNGKey, *init_args, **init_kwargs)` returning an Array, with `init_args` and `init_kwargs` being the arguments needed to call the initialisation function.\\n\",\n    \"*   `init_args` and `init_kwargs` are the arguments to provide to the initialization function.\\n\",\n    \"\\n\",\n    \"Such params can also be declared in the `setup` method; it won't be able to use shape inference because Flax is using lazy initialization at the first call site.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Variables and collections of variables\\n\",\n    \"\\n\",\n    \"As we've seen so far, working with models means working with:\\n\",\n    \"\\n\",\n    \"*   A subclass of `nn.Module`;\\n\",\n    \"*   A pytree of parameters for the model (typically from `model.init()`);\\n\",\n    \"\\n\",\n    \"However this is not enough to cover everything that we would need for machine learning, especially neural networks. In some cases, you might want your neural network to keep track of some internal state while it runs (e.g. batch normalization layers). There is a way to declare variables beyond the parameters of the model with the `variable` method.\\n\",\n    \"\\n\",\n    \"For demonstration purposes, we'll implement a simplified but similar mechanism to batch normalization: we'll store running averages and subtract those to the input at training time. For proper batchnorm, you should use (and look at) the implementation [here](https://github.com/google/flax/blob/main/flax/linen/normalization.py).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 22,\n   \"metadata\": {\n    \"outputId\": \"75465fd6-cdc8-497c-a3ec-7f709b5dde7a\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized variables:\\n\",\n      \" FrozenDict({\\n\",\n      \"    batch_stats: {\\n\",\n      \"        mean: DeviceArray([0., 0., 0., 0., 0.], dtype=float32),\\n\",\n      \"    },\\n\",\n      \"    params: {\\n\",\n      \"        bias: DeviceArray([0., 0., 0., 0., 0.], dtype=float32),\\n\",\n      \"    },\\n\",\n      \"})\\n\",\n      \"updated state:\\n\",\n      \" FrozenDict({\\n\",\n      \"    batch_stats: {\\n\",\n      \"        mean: DeviceArray([[0.01, 0.01, 0.01, 0.01, 0.01]], dtype=float32),\\n\",\n      \"    },\\n\",\n      \"})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class BiasAdderWithRunningMean(nn.Module):\\n\",\n    \"  decay: float = 0.99\\n\",\n    \"\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    # easy pattern to detect if we're initializing via empty variable tree\\n\",\n    \"    is_initialized = self.has_variable('batch_stats', 'mean')\\n\",\n    \"    ra_mean = self.variable('batch_stats', 'mean',\\n\",\n    \"                            lambda s: jnp.zeros(s),\\n\",\n    \"                            x.shape[1:])\\n\",\n    \"    bias = self.param('bias', lambda rng, shape: jnp.zeros(shape), x.shape[1:])\\n\",\n    \"    if is_initialized:\\n\",\n    \"      ra_mean.value = self.decay * ra_mean.value + (1.0 - self.decay) * jnp.mean(x, axis=0, keepdims=True)\\n\",\n    \"\\n\",\n    \"    return x - ra_mean.value + bias\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"key1, key2 = random.split(random.key(0), 2)\\n\",\n    \"x = jnp.ones((10,5))\\n\",\n    \"model = BiasAdderWithRunningMean()\\n\",\n    \"variables = model.init(key1, x)\\n\",\n    \"print('initialized variables:\\\\n', variables)\\n\",\n    \"y, updated_state = model.apply(variables, x, mutable=['batch_stats'])\\n\",\n    \"print('updated state:\\\\n', updated_state)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here, `updated_state` returns only the state variables that are being mutated by the model while applying it on data. To update the variables and get the new parameters of the model, we can use the following pattern:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 23,\n   \"metadata\": {\n    \"outputId\": \"09a8bdd1-eaf8-401a-cf7c-386a7a5aa87b\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"updated state:\\n\",\n      \" FrozenDict({\\n\",\n      \"    batch_stats: {\\n\",\n      \"        mean: DeviceArray([[0.01, 0.01, 0.01, 0.01, 0.01]], dtype=float32),\\n\",\n      \"    },\\n\",\n      \"})\\n\",\n      \"updated state:\\n\",\n      \" FrozenDict({\\n\",\n      \"    batch_stats: {\\n\",\n      \"        mean: DeviceArray([[0.0299, 0.0299, 0.0299, 0.0299, 0.0299]], dtype=float32),\\n\",\n      \"    },\\n\",\n      \"})\\n\",\n      \"updated state:\\n\",\n      \" FrozenDict({\\n\",\n      \"    batch_stats: {\\n\",\n      \"        mean: DeviceArray([[0.059601, 0.059601, 0.059601, 0.059601, 0.059601]], dtype=float32),\\n\",\n      \"    },\\n\",\n      \"})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"for val in [1.0, 2.0, 3.0]:\\n\",\n    \"  x = val * jnp.ones((10,5))\\n\",\n    \"  y, updated_state = model.apply(variables, x, mutable=['batch_stats'])\\n\",\n    \"  old_state, params = flax.core.pop(variables, 'params')\\n\",\n    \"  variables = flax.core.freeze({'params': params, **updated_state})\\n\",\n    \"  print('updated state:\\\\n', updated_state) # Shows only the mutable part\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"From this simplified example, you should be able to derive a full BatchNorm implementation, or any layer involving a state. To finish, let's add an optimizer to see how to play with both parameters updated by an optimizer and state variables.\\n\",\n    \"\\n\",\n    \"*This example isn't doing anything and is only for demonstration purposes.*\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 29,\n   \"metadata\": {\n    \"outputId\": \"0906fbab-b866-4956-d231-b1374415d448\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Updated state:  FrozenDict({\\n\",\n      \"    batch_stats: {\\n\",\n      \"        mean: DeviceArray([[0.01, 0.01, 0.01, 0.01, 0.01]], dtype=float32),\\n\",\n      \"    },\\n\",\n      \"})\\n\",\n      \"Updated state:  FrozenDict({\\n\",\n      \"    batch_stats: {\\n\",\n      \"        mean: DeviceArray([[0.0199, 0.0199, 0.0199, 0.0199, 0.0199]], dtype=float32),\\n\",\n      \"    },\\n\",\n      \"})\\n\",\n      \"Updated state:  FrozenDict({\\n\",\n      \"    batch_stats: {\\n\",\n      \"        mean: DeviceArray([[0.029701, 0.029701, 0.029701, 0.029701, 0.029701]], dtype=float32),\\n\",\n      \"    },\\n\",\n      \"})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from functools import partial\\n\",\n    \"\\n\",\n    \"@partial(jax.jit, static_argnums=(0, 1))\\n\",\n    \"def update_step(tx, apply_fn, x, opt_state, params, state):\\n\",\n    \"\\n\",\n    \"  def loss(params):\\n\",\n    \"    y, updated_state = apply_fn({'params': params, **state},\\n\",\n    \"                                x, mutable=list(state.keys()))\\n\",\n    \"    l = ((x - y) ** 2).sum()\\n\",\n    \"    return l, updated_state\\n\",\n    \"\\n\",\n    \"  (l, state), grads = jax.value_and_grad(loss, has_aux=True)(params)\\n\",\n    \"  updates, opt_state = tx.update(grads, opt_state)\\n\",\n    \"  params = optax.apply_updates(params, updates)\\n\",\n    \"  return opt_state, params, state\\n\",\n    \"\\n\",\n    \"x = jnp.ones((10,5))\\n\",\n    \"variables = model.init(random.key(0), x)\\n\",\n    \"state, params = flax.core.pop(variables, 'params')\\n\",\n    \"del variables\\n\",\n    \"tx = optax.sgd(learning_rate=0.02)\\n\",\n    \"opt_state = tx.init(params)\\n\",\n    \"\\n\",\n    \"for _ in range(3):\\n\",\n    \"  opt_state, params, state = update_step(tx, model.apply, x, opt_state, params, state)\\n\",\n    \"  print('Updated state: ', state)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that the above function has a quite verbose signature and it would not actually\\n\",\n    \"work with `jax.jit()` because the function arguments are not \\\"valid JAX types\\\".\\n\",\n    \"\\n\",\n    \"Flax provides a handy wrapper - `TrainState` - that simplifies the above code. Check out [`flax.training.train_state.TrainState`](https://flax.readthedocs.io/en/latest/api_reference/flax.training.html#flax.training.train_state.TrainState) to learn more.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Exporting to Tensorflow's SavedModel with jax2tf\\n\",\n    \"\\n\",\n    \"JAX released an experimental converter called [jax2tf](https://github.com/jax-ml/jax/tree/main/jax/jax2tf), which allows converting trained Flax models into Tensorflow's SavedModel format (so it can be used for [TF Hub](https://www.tensorflow.org/hub), [TF.lite](https://www.tensorflow.org/lite), [TF.js](https://www.tensorflow.org/js), or other downstream applications). The repository contains more documentation and has various examples for Flax.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"name\": \"python\",\n   \"version\": \"3.8.15\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "docs/guides/flax_fundamentals/flax_basics.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google/flax/blob/main/docs/guides/flax_fundamentals/flax_basics.ipynb)\n[![Open On GitHub](https://img.shields.io/badge/Open-on%20GitHub-blue?logo=GitHub)](https://github.com/google/flax/blob/main/docs/guides/flax_fundamentals/flax_basics.ipynb)\n\n# Flax Basics\n\nThis notebook will walk you through the following workflow:\n\n*   Instantiating a model from Flax built-in layers or third-party models.\n*   Initializing parameters of the model and manually written training.\n*   Using optimizers provided by Flax to ease training.\n*   Serialization of parameters and other objects.\n*   Creating your own models and managing state.\n\n+++\n\n## Setting up our environment\n\nHere we provide the code needed to set up the environment for our notebook.\n\n```{code-cell}\n:outputId: e30aa464-fa52-4f35-df96-716c68a4b3ee\n:tags: [skip-execution]\n\n# Install the latest JAXlib version.\n!pip install --upgrade -q pip jax jaxlib\n# Install Flax at head:\n!pip install --upgrade -q git+https://github.com/google/flax.git\n```\n\n```{code-cell}\nimport jax\nfrom typing import Any, Callable, Sequence\nfrom jax import random, numpy as jnp\nimport flax\nfrom flax import linen as nn\n```\n\n## Linear regression with Flax\n\nIn the previous *JAX for the impatient* notebook, we finished up with a linear regression example. As we know, linear regression can also be written as a single dense neural network layer, which we will show in the following so that we can compare how it's done.\n\nA dense layer is a layer that has a kernel parameter $W\\in\\mathcal{M}_{m,n}(\\mathbb{R})$ where $m$ is the number of features as an output of the model, and $n$ the dimensionality of the input, and a bias parameter $b\\in\\mathbb{R}^m$. The dense layers returns $Wx+b$ from an input $x\\in\\mathbb{R}^n$.\n\nThis dense layer is already provided by Flax in the `flax.linen` module (here imported as `nn`).\n\n```{code-cell}\n# We create one dense layer instance (taking 'features' parameter as input)\nmodel = nn.Dense(features=5)\n```\n\nLayers (and models in general, we'll use that word from now on) are subclasses of the `linen.Module` class.\n\n### Model parameters & initialization\n\nParameters are not stored with the models themselves. You need to initialize parameters by calling the `init` function, using a PRNGKey and dummy input data.\n\n```{code-cell}\n:outputId: 06feb9d2-db50-4f41-c169-6df4336f43a5\n\nkey1, key2 = random.split(random.key(0))\nx = random.normal(key1, (10,)) # Dummy input data\nparams = model.init(key2, x) # Initialization call\njax.tree_util.tree_map(lambda x: x.shape, params) # Checking output shapes\n```\n\n*Note: JAX and Flax, like NumPy, are row-based systems, meaning that vectors are represented as row vectors and not column vectors. This can be seen in the shape of the kernel here.*\n\nThe result is what we expect: bias and kernel parameters of the correct size. Under the hood:\n\n*   The dummy input data `x` is used to trigger shape inference: we only declared the number of features we wanted in the output of the model, not the size of the input. Flax finds out by itself the correct size of the kernel.\n*   The random PRNG key is used to trigger the initialization functions (those have default values provided by the module here).\n* Initialization functions are called to generate the initial set of parameters that the model will use. Those are functions that take as arguments `(PRNG Key, shape, dtype)` and return an Array of shape `shape`.\n* The init function returns the initialized set of parameters (you can also get the output of the forward pass on the dummy input with the same syntax by using the `init_with_output` method instead of `init`.\n\n+++\n\nTo conduct a forward pass with the model with a given set of parameters (which are never stored with the model), we just use the `apply` method by providing it the parameters to use as well as the input:\n\n```{code-cell}\n:outputId: 7bbe6bb4-94d5-4574-fbb5-aa0fcd1c84ae\n\nmodel.apply(params, x)\n```\n\n### Gradient descent\n\nIf you jumped here directly without going through the JAX part, here is the linear regression formulation we're going to use: from a set of data points $\\{(x_i,y_i), i\\in \\{1,\\ldots, k\\}, x_i\\in\\mathbb{R}^n,y_i\\in\\mathbb{R}^m\\}$, we try to find a set of parameters $W\\in \\mathcal{M}_{m,n}(\\mathbb{R}), b\\in\\mathbb{R}^m$ such that the function $f_{W,b}(x)=Wx+b$ minimizes the mean squared error:\n\n$$\\mathcal{L}(W,b)\\rightarrow\\frac{1}{k}\\sum_{i=1}^{k} \\frac{1}{2}\\|y_i-f_{W,b}(x_i)\\|^2_2$$\n\nHere, we see that the tuple $(W,b)$ matches the parameters of the Dense layer. We'll perform gradient descent using those. Let's first generate the fake data we'll use. The data is exactly the same as in the JAX part's linear regression pytree example.\n\n```{code-cell}\n:outputId: 6eae59dc-0632-4f53-eac8-c22a7c646a52\n\n# Set problem dimensions.\nn_samples = 20\nx_dim = 10\ny_dim = 5\n\n# Generate random ground truth W and b.\nkey = random.key(0)\nk1, k2 = random.split(key)\nW = random.normal(k1, (x_dim, y_dim))\nb = random.normal(k2, (y_dim,))\n# Store the parameters in a FrozenDict pytree.\ntrue_params = flax.core.freeze({'params': {'bias': b, 'kernel': W}})\n\n# Generate samples with additional noise.\nkey_sample, key_noise = random.split(k1)\nx_samples = random.normal(key_sample, (n_samples, x_dim))\ny_samples = jnp.dot(x_samples, W) + b + 0.1 * random.normal(key_noise,(n_samples, y_dim))\nprint('x shape:', x_samples.shape, '; y shape:', y_samples.shape)\n```\n\nWe copy the same training loop that we used in the JAX pytree linear regression example with `jax.value_and_grad()`, but here we can use `model.apply()` instead of having to define our own feed-forward function (`predict_pytree()` in the [JAX example](https://flax.readthedocs.io/en/latest/guides/jax_for_the_impatient.html#linear-regression-with-pytrees)).\n\n```{code-cell}\n# Same as JAX version but using model.apply().\n@jax.jit\ndef mse(params, x_batched, y_batched):\n  # Define the squared loss for a single pair (x,y)\n  def squared_error(x, y):\n    pred = model.apply(params, x)\n    return jnp.inner(y-pred, y-pred) / 2.0\n  # Vectorize the previous to compute the average of the loss on all samples.\n  return jnp.mean(jax.vmap(squared_error)(x_batched,y_batched), axis=0)\n```\n\nAnd finally perform the gradient descent.\n\n```{code-cell}\n:outputId: 50d975b3-4706-4d8a-c4b8-2629ab8e3ac4\n\nlearning_rate = 0.3  # Gradient step size.\nprint('Loss for \"true\" W,b: ', mse(true_params, x_samples, y_samples))\nloss_grad_fn = jax.value_and_grad(mse)\n\n@jax.jit\ndef update_params(params, learning_rate, grads):\n  params = jax.tree_util.tree_map(\n      lambda p, g: p - learning_rate * g, params, grads)\n  return params\n\nfor i in range(101):\n  # Perform one gradient update.\n  loss_val, grads = loss_grad_fn(params, x_samples, y_samples)\n  params = update_params(params, learning_rate, grads)\n  if i % 10 == 0:\n    print(f'Loss step {i}: ', loss_val)\n```\n\n### Optimizing with Optax\n\nFlax used to use its own `flax.optim` package for optimization, but with\n[FLIP #1009](https://github.com/google/flax/blob/main/docs/flip/1009-optimizer-api.md)\nthis was deprecated in favor of\n[Optax](https://github.com/deepmind/optax).\n\nBasic usage of Optax is straightforward:\n\n1.   Choose an optimization method (e.g. `optax.adam`).\n2.   Create optimizer state from parameters (for the Adam optimizer, this state will contain the [momentum values](https://optax.readthedocs.io/en/latest/api.html#optax.adam)).\n3.   Compute the gradients of your loss with `jax.value_and_grad()`.\n4.   At every iteration, call the Optax `update` function to update the internal\n     optimizer state and create an update to the parameters. Then add the update\n     to the parameters with Optax's `apply_updates` method.\n\nNote that Optax can do a lot more: it's designed for composing simple gradient\ntransformations into more complex transformations that allows to implement a\nwide range of optimizers. There is also support for changing optimizer\nhyperparameters over time (\"schedules\"), applying different updates to different\nparts of the parameter tree (\"masking\") and much more. For details please refer\nto the\n[official documentation](https://optax.readthedocs.io/en/latest/).\n\n```{code-cell}\nimport optax\ntx = optax.adam(learning_rate=learning_rate)\nopt_state = tx.init(params)\nloss_grad_fn = jax.value_and_grad(mse)\n```\n\n```{code-cell}\n:outputId: eec0c096-1d9e-4b3c-f8e5-942ee63828ec\n\nfor i in range(101):\n  loss_val, grads = loss_grad_fn(params, x_samples, y_samples)\n  updates, opt_state = tx.update(grads, opt_state)\n  params = optax.apply_updates(params, updates)\n  if i % 10 == 0:\n    print('Loss step {}: '.format(i), loss_val)\n```\n\n### Serializing the result\n\nNow that we're happy with the result of our training, we might want to save the model parameters to load them back later. Flax provides a serialization package to enable you to do that.\n\n```{code-cell}\n:outputId: b97e7d83-3e40-4a80-b1fe-1f6ceff30a0c\n\nfrom flax import serialization\nbytes_output = serialization.to_bytes(params)\ndict_output = serialization.to_state_dict(params)\nprint('Dict output')\nprint(dict_output)\nprint('Bytes output')\nprint(bytes_output)\n```\n\nTo load the model back, you'll need to use a template of the model parameter structure, like the one you would get from the model initialization. Here, we use the previously generated `params` as a template. Note that this will produce a new variable structure, and not mutate in-place.\n\n*The point of enforcing structure through template is to avoid users issues downstream, so you need to first have the right model that generates the parameters structure.*\n\n```{code-cell}\n:outputId: 13acc4e1-8757-4554-e2c8-d594ba6e67dc\n\nserialization.from_bytes(params, bytes_output)\n```\n\n## Defining your own models\n\nFlax allows you to define your own models, which should be a bit more complicated than a linear regression. In this section, we'll show you how to build simple models. To do so, you'll need to create subclasses of the base `nn.Module` class.\n\n*Keep in mind that we imported* `linen as nn` *and this only works with the new linen API*\n\n+++\n\n### Module basics\n\nThe base abstraction for models is the `nn.Module` class, and every type of predefined layers in Flax (like the previous `Dense`) is a subclass of `nn.Module`. Let's take a look and start by defining a simple but custom multi-layer perceptron i.e. a sequence of Dense layers interleaved with calls to a non-linear activation function.\n\n```{code-cell}\n:outputId: b59c679c-d164-4fd6-92db-b50f0d310ec3\n\nclass ExplicitMLP(nn.Module):\n  features: Sequence[int]\n\n  def setup(self):\n    # we automatically know what to do with lists, dicts of submodules\n    self.layers = [nn.Dense(feat) for feat in self.features]\n    # for single submodules, we would just write:\n    # self.layer1 = nn.Dense(feat1)\n\n  def __call__(self, inputs):\n    x = inputs\n    for i, lyr in enumerate(self.layers):\n      x = lyr(x)\n      if i != len(self.layers) - 1:\n        x = nn.relu(x)\n    return x\n\nkey1, key2 = random.split(random.key(0), 2)\nx = random.uniform(key1, (4,4))\n\nmodel = ExplicitMLP(features=[3,4,5])\nparams = model.init(key2, x)\ny = model.apply(params, x)\n\nprint('initialized parameter shapes:\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(params)))\nprint('output:\\n', y)\n```\n\nAs we can see, a `nn.Module` subclass is made of:\n\n*   A collection of data fields (`nn.Module` are Python dataclasses) - here we only have the `features` field of type `Sequence[int]`.\n*   A `setup()` method that is being called at the end of the `__postinit__` where you can register submodules, variables, parameters you will need in your model.\n*   A `__call__` function that returns the output of the model from a given input.\n*   The model structure defines a pytree of parameters following the same tree structure as the model: the params tree contains one `layers_n` sub dict per layer, and each of those contain the parameters of the associated Dense layer. The layout is very explicit.\n\n*Note: lists are mostly managed as you would expect (WIP), there are corner cases you should be aware of as pointed out* [here](https://github.com/google/flax/issues/524)\n\nSince the module structure and its parameters are not tied to each other, you can't directly call `model(x)` on a given input as it will return an error. The `__call__` function is being wrapped up in the `apply` one, which is the one to call on an input:\n\n```{code-cell}\n:outputId: 4af16ec5-b52a-43b0-fc47-1f8ab25e7058\n\ntry:\n    y = model(x) # Returns an error\nexcept AttributeError as e:\n    print(e)\n```\n\nSince here we have a very simple model, we could have used an alternative (but equivalent) way of declaring the submodules inline in the `__call__` using the `@nn.compact` annotation like so:\n\n```{code-cell}\n:outputId: 183a74ef-f54e-4848-99bf-fee4c174ba6d\n\nclass SimpleMLP(nn.Module):\n  features: Sequence[int]\n\n  @nn.compact\n  def __call__(self, inputs):\n    x = inputs\n    for i, feat in enumerate(self.features):\n      x = nn.Dense(feat, name=f'layers_{i}')(x)\n      if i != len(self.features) - 1:\n        x = nn.relu(x)\n      # providing a name is optional though!\n      # the default autonames would be \"Dense_0\", \"Dense_1\", ...\n    return x\n\nkey1, key2 = random.split(random.key(0), 2)\nx = random.uniform(key1, (4,4))\n\nmodel = SimpleMLP(features=[3,4,5])\nparams = model.init(key2, x)\ny = model.apply(params, x)\n\nprint('initialized parameter shapes:\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(params)))\nprint('output:\\n', y)\n```\n\nThere are, however, a few differences you should be aware of between the two declaration modes:\n\n*   In `setup`, you are able to name some sublayers and keep them around for further use (e.g. encoder/decoder methods in autoencoders).\n*   If you want to have multiple methods, then you **need** to declare the module using `setup`, as the `@nn.compact` annotation only allows one method to be annotated.\n*   The last initialization will be handled differently. See these notes for more details (TODO: add notes link).\n\n+++\n\n### Module parameters\n\nIn the previous MLP example, we relied only on predefined layers and operators (`Dense`, `relu`). Let's imagine that you didn't have a Dense layer provided by Flax and you wanted to write it on your own. Here is what it would look like using the `@nn.compact` way to declare a new modules:\n\n```{code-cell}\n:outputId: 83b5fea4-071e-4ea0-8fa8-610e69fb5fd5\n\nclass SimpleDense(nn.Module):\n  features: int\n  kernel_init: Callable = nn.initializers.lecun_normal()\n  bias_init: Callable = nn.initializers.zeros_init()\n\n  @nn.compact\n  def __call__(self, inputs):\n    kernel = self.param('kernel',\n                        self.kernel_init, # Initialization function\n                        (inputs.shape[-1], self.features))  # shape info.\n    y = jnp.dot(inputs, kernel)\n    bias = self.param('bias', self.bias_init, (self.features,))\n    y = y + bias\n    return y\n\nkey1, key2 = random.split(random.key(0), 2)\nx = random.uniform(key1, (4,4))\n\nmodel = SimpleDense(features=3)\nparams = model.init(key2, x)\ny = model.apply(params, x)\n\nprint('initialized parameters:\\n', params)\nprint('output:\\n', y)\n```\n\nHere, we see how to both declare and assign a parameter to the model using the `self.param` method. It takes as input `(name, init_fn, *init_args, **init_kwargs)` :\n\n*   `name` is simply the name of the parameter that will end up in the parameter structure.\n*   `init_fn` is a function with input `(PRNGKey, *init_args, **init_kwargs)` returning an Array, with `init_args` and `init_kwargs` being the arguments needed to call the initialisation function.\n*   `init_args` and `init_kwargs` are the arguments to provide to the initialization function.\n\nSuch params can also be declared in the `setup` method; it won't be able to use shape inference because Flax is using lazy initialization at the first call site.\n\n+++\n\n### Variables and collections of variables\n\nAs we've seen so far, working with models means working with:\n\n*   A subclass of `nn.Module`;\n*   A pytree of parameters for the model (typically from `model.init()`);\n\nHowever this is not enough to cover everything that we would need for machine learning, especially neural networks. In some cases, you might want your neural network to keep track of some internal state while it runs (e.g. batch normalization layers). There is a way to declare variables beyond the parameters of the model with the `variable` method.\n\nFor demonstration purposes, we'll implement a simplified but similar mechanism to batch normalization: we'll store running averages and subtract those to the input at training time. For proper batchnorm, you should use (and look at) the implementation [here](https://github.com/google/flax/blob/main/flax/linen/normalization.py).\n\n```{code-cell}\n:outputId: 75465fd6-cdc8-497c-a3ec-7f709b5dde7a\n\nclass BiasAdderWithRunningMean(nn.Module):\n  decay: float = 0.99\n\n  @nn.compact\n  def __call__(self, x):\n    # easy pattern to detect if we're initializing via empty variable tree\n    is_initialized = self.has_variable('batch_stats', 'mean')\n    ra_mean = self.variable('batch_stats', 'mean',\n                            lambda s: jnp.zeros(s),\n                            x.shape[1:])\n    bias = self.param('bias', lambda rng, shape: jnp.zeros(shape), x.shape[1:])\n    if is_initialized:\n      ra_mean.value = self.decay * ra_mean.value + (1.0 - self.decay) * jnp.mean(x, axis=0, keepdims=True)\n\n    return x - ra_mean.value + bias\n\n\nkey1, key2 = random.split(random.key(0), 2)\nx = jnp.ones((10,5))\nmodel = BiasAdderWithRunningMean()\nvariables = model.init(key1, x)\nprint('initialized variables:\\n', variables)\ny, updated_state = model.apply(variables, x, mutable=['batch_stats'])\nprint('updated state:\\n', updated_state)\n```\n\nHere, `updated_state` returns only the state variables that are being mutated by the model while applying it on data. To update the variables and get the new parameters of the model, we can use the following pattern:\n\n```{code-cell}\n:outputId: 09a8bdd1-eaf8-401a-cf7c-386a7a5aa87b\n\nfor val in [1.0, 2.0, 3.0]:\n  x = val * jnp.ones((10,5))\n  y, updated_state = model.apply(variables, x, mutable=['batch_stats'])\n  old_state, params = flax.core.pop(variables, 'params')\n  variables = flax.core.freeze({'params': params, **updated_state})\n  print('updated state:\\n', updated_state) # Shows only the mutable part\n```\n\nFrom this simplified example, you should be able to derive a full BatchNorm implementation, or any layer involving a state. To finish, let's add an optimizer to see how to play with both parameters updated by an optimizer and state variables.\n\n*This example isn't doing anything and is only for demonstration purposes.*\n\n```{code-cell}\n:outputId: 0906fbab-b866-4956-d231-b1374415d448\n\nfrom functools import partial\n\n@partial(jax.jit, static_argnums=(0, 1))\ndef update_step(tx, apply_fn, x, opt_state, params, state):\n\n  def loss(params):\n    y, updated_state = apply_fn({'params': params, **state},\n                                x, mutable=list(state.keys()))\n    l = ((x - y) ** 2).sum()\n    return l, updated_state\n\n  (l, state), grads = jax.value_and_grad(loss, has_aux=True)(params)\n  updates, opt_state = tx.update(grads, opt_state)\n  params = optax.apply_updates(params, updates)\n  return opt_state, params, state\n\nx = jnp.ones((10,5))\nvariables = model.init(random.key(0), x)\nstate, params = flax.core.pop(variables, 'params')\ndel variables\ntx = optax.sgd(learning_rate=0.02)\nopt_state = tx.init(params)\n\nfor _ in range(3):\n  opt_state, params, state = update_step(tx, model.apply, x, opt_state, params, state)\n  print('Updated state: ', state)\n```\n\nNote that the above function has a quite verbose signature and it would not actually\nwork with `jax.jit()` because the function arguments are not \"valid JAX types\".\n\nFlax provides a handy wrapper - `TrainState` - that simplifies the above code. Check out [`flax.training.train_state.TrainState`](https://flax.readthedocs.io/en/latest/api_reference/flax.training.html#flax.training.train_state.TrainState) to learn more.\n\n+++\n\n### Exporting to Tensorflow's SavedModel with jax2tf\n\nJAX released an experimental converter called [jax2tf](https://github.com/jax-ml/jax/tree/main/jax/jax2tf), which allows converting trained Flax models into Tensorflow's SavedModel format (so it can be used for [TF Hub](https://www.tensorflow.org/hub), [TF.lite](https://www.tensorflow.org/lite), [TF.js](https://www.tensorflow.org/js), or other downstream applications). The repository contains more documentation and has various examples for Flax.\n"
  },
  {
    "path": "docs/guides/flax_fundamentals/index.rst",
    "content": "Flax fundamentals\n=================\n\n.. toctree::\n   :maxdepth: 1\n\n   JAX 101 <https://jax.readthedocs.io/en/latest/jax-101/index.html>\n   flax_basics\n   state_params\n   setup_or_nncompact\n   arguments\n   rng_guide\n"
  },
  {
    "path": "docs/guides/flax_fundamentals/rng_guide.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Randomness and PRNGs in Flax\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In this guide, you will learn how Flax uses [JAX's explicit pseudorandom number generator (PRNG) keys](https://jax.readthedocs.io/en/latest/notebooks/Common_Gotchas_in_JAX.html#jax-prng) to emulate randomness, and adds some additional features to make it easier for users to thread PRNG keys through different Flax `Module`s.\\n\",\n    \"\\n\",\n    \"If you are new to JAX PRNG keys or need a refresher, check out:\\n\",\n    \"- [JAX 101: PRNGs in JAX](https://jax.readthedocs.io/en/latest/jax-101/05-random-numbers.html)\\n\",\n    \"- [🔪 JAX - The Sharp Bits 🔪: Random Numbers](https://jax.readthedocs.io/en/latest/notebooks/Common_Gotchas_in_JAX.html#random-numbers).\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Setup\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Install or upgrade Flax, and then import some necessary dependencies.\\n\",\n    \"\\n\",\n    \"**Note:** This guide uses the `--xla_force_host_platform_device_count=8` flag to emulate multiple devices in a CPU environment in a Google Colab/Jupyter Notebook. You don’t need this if you are already using a multi-device Google Cloud TPU environment, for example, on Google Cloud or in a Kaggle VM with a TPU.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"!pip install -q flax\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import os\\n\",\n    \"os.environ[\\\"XLA_FLAGS\\\"] = '--xla_force_host_platform_device_count=8'\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import flax, flax.linen as nn\\n\",\n    \"import jax, jax.numpy as jnp\\n\",\n    \"from jax.sharding import Mesh, PartitionSpec, NamedSharding\\n\",\n    \"from jax.experimental import mesh_utils\\n\",\n    \"from jax.experimental.shard_map import shard_map\\n\",\n    \"\\n\",\n    \"import hashlib\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"outputId\": \"ec904f6b-0e87-4efe-87c4-fea0f8e8ec23\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"[CpuDevice(id=0),\\n\",\n       \" CpuDevice(id=1),\\n\",\n       \" CpuDevice(id=2),\\n\",\n       \" CpuDevice(id=3),\\n\",\n       \" CpuDevice(id=4),\\n\",\n       \" CpuDevice(id=5),\\n\",\n       \" CpuDevice(id=6),\\n\",\n       \" CpuDevice(id=7)]\"\n      ]\n     },\n     \"execution_count\": 4,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"jax.devices()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Set the JAX config variable `jax_threefry_partitionable` to `True`. This will be the default value in the future and makes the PRNG more efficiently auto-parallelizable under `jax.jit`. Refer to [JAX discussion](https://github.com/jax-ml/jax/discussions/18480) for more details.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"jax.config.update('jax_threefry_partitionable', True)\\n\",\n    \"assert jax.config.jax_threefry_partitionable == True\\n\",\n    \"assert jax.config.jax_default_prng_impl == 'threefry2x32'\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Receiving, manipulating and creating PRNG keys with `Module.make_rng`\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The primary method Flax uses to receive, manipulate and create PRNG keys is via the `Module` method [`self.make_rng`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.make_rng). It is a method that accepts a string name that represents an \\\"RNG stream\\\". Each RNG stream has an initial starting seed PRNG key, which the user passes in as a dictionary argument (i.e. into an [`.init`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.init) or [`.apply`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.apply) function), and the starting seed is used by `self.make_rng` to generate more PRNG keys for that stream. If `self.make_rng` is called on a string name that does not have an initial starting seed PRNG key (i.e. the user did not pass a key with the corresponding name into `.init` or `.apply`), then `self.make_rng` will use the `'params'` key as the initial starting seed by default.\\n\",\n    \"\\n\",\n    \"Note that this method can only be called with bounded modules (see [The Flax Module lifecycle](https://flax.readthedocs.io/en/latest/developer_notes/module_lifecycle.html#top-level-modules)).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {\n    \"outputId\": \"2a16435b-e92a-480a-f9fb-e6effc42c4c2\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[1428664606 3351135085]\\n\",\n      \"Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3456700291 3873160899]\\n\",\n      \"Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[2411773124 4124888837]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class RNGModule(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self):\\n\",\n    \"    print(self.make_rng('rng_stream'))\\n\",\n    \"    print(self.make_rng('rng_stream'))\\n\",\n    \"    print(self.make_rng('rng_stream'))\\n\",\n    \"\\n\",\n    \"rng_module = RNGModule()\\n\",\n    \"variables = rng_module.init({'rng_stream': jax.random.key(0)})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now if we use a different starting seed PRNG key, we will generate different values (as intended).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {\n    \"outputId\": \"985b0f62-dfde-4f0f-fad4-a31927fc9f59\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3077990774 2166202870]\\n\",\n      \"Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3825832496 2886313970]\\n\",\n      \"Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[ 791337683 1373966058]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"variables = rng_module.init({'rng_stream': jax.random.key(1)})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Calling `self.make_rng` for one stream will not affect the random values generated from another stream; i.e. the call order doesn't matter.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {\n    \"outputId\": \"7e8ce538-e380-4db9-db23-bc4a8da577da\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"rng_stream1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[1428664606 3351135085]\\n\",\n      \"rng_stream2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3077990774 2166202870]\\n\",\n      \"rng_stream1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3456700291 3873160899]\\n\",\n      \"rng_stream2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3825832496 2886313970]\\n\",\n      \"rng_stream1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[2411773124 4124888837]\\n\",\n      \"rng_stream2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[ 791337683 1373966058]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class RNGModuleTwoStreams(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self):\\n\",\n    \"    # same value as first code snippet above\\n\",\n    \"    print(f\\\"rng_stream1: {self.make_rng('rng_stream1')}\\\")\\n\",\n    \"    # same value as second code snippet above\\n\",\n    \"    print(f\\\"rng_stream2: {self.make_rng('rng_stream2')}\\\")\\n\",\n    \"    # same value as first code snippet above\\n\",\n    \"    print(f\\\"rng_stream1: {self.make_rng('rng_stream1')}\\\")\\n\",\n    \"    # same value as second code snippet above\\n\",\n    \"    print(f\\\"rng_stream2: {self.make_rng('rng_stream2')}\\\")\\n\",\n    \"    # same value as first code snippet above\\n\",\n    \"    print(f\\\"rng_stream1: {self.make_rng('rng_stream1')}\\\")\\n\",\n    \"    # same value as second code snippet above\\n\",\n    \"    print(f\\\"rng_stream2: {self.make_rng('rng_stream2')}\\\")\\n\",\n    \"\\n\",\n    \"rng_module_two_streams = RNGModuleTwoStreams()\\n\",\n    \"variables = rng_module_two_streams.init(\\n\",\n    \"  {'rng_stream1': jax.random.key(0), 'rng_stream2': jax.random.key(1)}\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Providing the same seed PRNG key will result in the same values being generated (provided that the same operations are used for those keys).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {\n    \"outputId\": \"b70be039-589a-48f7-dc54-65e78c449c65\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"rng_stream1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[1428664606 3351135085]\\n\",\n      \"rng_stream2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[1428664606 3351135085]\\n\",\n      \"rng_stream1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3456700291 3873160899]\\n\",\n      \"rng_stream2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3456700291 3873160899]\\n\",\n      \"rng_stream1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[2411773124 4124888837]\\n\",\n      \"rng_stream2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[2411773124 4124888837]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"variables = rng_module_two_streams.init(\\n\",\n    \"  {'rng_stream1': jax.random.key(0), 'rng_stream2': jax.random.key(0)}\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### How `self.make_rng` works under the hood\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This is what happens when `self.make_rng` (`flax.linen.Module.make_rng`) is called:\\n\",\n    \"* The following data is collected:\\n\",\n    \"  * The path of the `Module` as provided by `self.scope.path` (the top-level root module has an empty path `()`).\\n\",\n    \"  * The `self.make_rng` call count. That is, the number of times `self.make_rng` has been called for this specific stream (including this call).\\n\",\n    \"    * **Note:** Each sub-`Module` will have its own individual call count that's separate from other `Module`s. For example, a `Module` that has called `self.make_rng('params')` twice and contains a sub-`Module` that has called `self.make_rng('params')` once, will have a call count of 2 and 1 for each of the RNG stream `'params'`, respectively.\\n\",\n    \"* The data is bundled into a tuple and fed into a hash function and produces an integer.\\n\",\n    \"* The generated integer is folded into the RNG stream's starting seed PRNG key to generate a new, unique PRNG key.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Below is a slightly simplified version of the hash function that Flax uses for `self.make_rng`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"def produce_hash(data):\\n\",\n    \"  m = hashlib.sha1()\\n\",\n    \"  for x in data:\\n\",\n    \"    if isinstance(x, str):\\n\",\n    \"      m.update(x.encode('utf-8'))\\n\",\n    \"    elif isinstance(x, int):\\n\",\n    \"      m.update(x.to_bytes((x.bit_length() + 7) // 8, byteorder='big'))\\n\",\n    \"    else:\\n\",\n    \"      raise ValueError(f'Expected int or string, got: {x}')\\n\",\n    \"  d = m.digest()\\n\",\n    \"  hash_int = int.from_bytes(d[:4], byteorder='big')\\n\",\n    \"  return hash_int\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"And now you can manually reproduce the PRNG keys generated from `self.make_rng`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {\n    \"outputId\": \"d26b7355-9e8b-4954-b2f4-cf7520d5c5a3\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[1428664606 3351135085]\\n\",\n      \"Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3456700291 3873160899]\\n\",\n      \"Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[2411773124 4124888837]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"stream_seed = jax.random.key(0)\\n\",\n    \"for call_count in range(1, 4):\\n\",\n    \"  hash_int = produce_hash(data=(call_count,))\\n\",\n    \"  print(jax.random.fold_in(stream_seed, jnp.uint32(hash_int)))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {\n    \"outputId\": \"dec627a6-4c5a-4e3e-ce11-ce4f72775261\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[1428664606 3351135085]\\n\",\n      \"Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3456700291 3873160899]\\n\",\n      \"Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[2411773124 4124888837]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"variables = rng_module.init({'rng_stream': jax.random.key(0)})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Sub-`Module`s and `self.make_rng`\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This section explores how `self.make_rng` (`flax.linen.Module.make_rng`) behaves with sub-`Module`s.\\n\",\n    \"\\n\",\n    \"Consider the following example:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {\n    \"outputId\": \"5b7a9ae9-ca49-4ac0-d007-5caeee739ff0\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"RNGModule, count 1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[1428664606 3351135085]\\n\",\n      \"RNGModule, count 2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3456700291 3873160899]\\n\",\n      \"RNGSubModule_0, count 1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3858825717 2323087578]\\n\",\n      \"RNGSubModule_0, count 2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[ 601859108 3782857444]\\n\",\n      \"RNGSubSubModule_0, count 1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[ 234240654 1028548813]\\n\",\n      \"RNGSubSubModule_0, count 2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3650462303 2124609379]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class RNGSubSubModule(nn.Module):\\n\",\n    \"  def __call__(self):\\n\",\n    \"    print(f\\\"{self.name}, count 1: {self.make_rng('rng_stream')}\\\")\\n\",\n    \"    print(f\\\"{self.name}, count 2: {self.make_rng('rng_stream')}\\\")\\n\",\n    \"\\n\",\n    \"class RNGSubModule(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self):\\n\",\n    \"    print(f\\\"{self.name}, count 1: {self.make_rng('rng_stream')}\\\")\\n\",\n    \"    print(f\\\"{self.name}, count 2: {self.make_rng('rng_stream')}\\\")\\n\",\n    \"    RNGSubSubModule()()\\n\",\n    \"\\n\",\n    \"class RNGModule(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self):\\n\",\n    \"    print(f\\\"RNGModule, count 1: {self.make_rng('rng_stream')}\\\")\\n\",\n    \"    print(f\\\"RNGModule, count 2: {self.make_rng('rng_stream')}\\\")\\n\",\n    \"    RNGSubModule()()\\n\",\n    \"\\n\",\n    \"rng_module = RNGModule()\\n\",\n    \"variables = rng_module.init({'rng_stream': jax.random.key(0)})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"As previously discussed, the data that is fed into the Flax hash function consists of:\\n\",\n    \"\\n\",\n    \"  * The path of the `Module`, provided by `self.scope.path` (the top-level root module has an empty path `()`); and\\n\",\n    \"  * The call count for the specific RNG stream.\\n\",\n    \"\\n\",\n    \"In addition, note that each Flax `Module` and sub-`Module` have their own individual call counts, even for the same RNG stream. The convention for sub-`Module` names is: `f'{module_name}_{module_number}'`. For example, the first `Dense` sub-`Module` will be called `Dense_0`, the second one will be called `Dense_1`, and so on.\\n\",\n    \"\\n\",\n    \"Therefore, the following data will be fed into the hash function:\\n\",\n    \"\\n\",\n    \"  * For `RNGModule`: The data is just the call count, such as `(1,)` and `(2,)`, since the root `Module` has an empty path.\\n\",\n    \"  * For `RNGSubModule`: The data is `('RNGSubModule_0', 1)` and `('RNGSubModule_0', 2)`.\\n\",\n    \"  * For `RNGSubSubModule`: The data is `('RNGSubModule_0', 'RNGSubSubModule_0', 1)` and `('RNGSubModule_0', 'RNGSubSubModule_0', 2)`.\\n\",\n    \"\\n\",\n    \"With this data, you can manually reproduce the PRNG keys generated from the `Module` and sub-`Module`s using `self.make_rng`.\\n\",\n    \"\\n\",\n    \"For example:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {\n    \"outputId\": \"c0de4d37-0f00-4e58-bdfd-e8a6454ed681\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"RNGModule, count 1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[1428664606 3351135085]\\n\",\n      \"RNGModule, count 2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3456700291 3873160899]\\n\",\n      \"RNGSubModule_0, count 1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3858825717 2323087578]\\n\",\n      \"RNGSubModule_0, count 2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[ 601859108 3782857444]\\n\",\n      \"RNGSubSubModule_0, count 1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[ 234240654 1028548813]\\n\",\n      \"RNGSubSubModule_0, count 2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3650462303 2124609379]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"stream_seed = jax.random.key(0)\\n\",\n    \"for initial_data in ((), ('RNGSubModule_0',), ('RNGSubModule_0', 'RNGSubSubModule_0')):\\n\",\n    \"  if initial_data:\\n\",\n    \"    module_name = initial_data[-1]\\n\",\n    \"  else:\\n\",\n    \"    module_name = 'RNGModule'\\n\",\n    \"  for call_count in (1, 2):\\n\",\n    \"    hash_int = produce_hash(data=initial_data+(call_count,))\\n\",\n    \"    rng_key = jax.random.fold_in(stream_seed, jnp.uint32(hash_int))\\n\",\n    \"    print(f\\\"{module_name}, count {call_count}: {rng_key}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If the same sub-`Module` class is used multiple times, you can increment the suffix of the sub-`Module` name accordingly. For example: `RNGSubModule_0`, `RNGSubModule_1`, and so on.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"metadata\": {\n    \"outputId\": \"0b77a038-7000-407b-c5b8-a28dea7951d1\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"RNGModule, count 1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[1428664606 3351135085]\\n\",\n      \"RNGModule, count 2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3456700291 3873160899]\\n\",\n      \"RNGSubModule_0, count 1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3858825717 2323087578]\\n\",\n      \"RNGSubModule_0, count 2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[ 601859108 3782857444]\\n\",\n      \"RNGSubModule_1, count 1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[ 426957352 2006350344]\\n\",\n      \"RNGSubModule_1, count 2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[4006253729 4205356731]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class RNGSubModule(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self):\\n\",\n    \"    print(f\\\"{self.name}, count 1: {self.make_rng('rng_stream')}\\\")\\n\",\n    \"    print(f\\\"{self.name}, count 2: {self.make_rng('rng_stream')}\\\")\\n\",\n    \"\\n\",\n    \"class RNGModule(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self):\\n\",\n    \"    print(f\\\"RNGModule, count 1: {self.make_rng('rng_stream')}\\\")\\n\",\n    \"    print(f\\\"RNGModule, count 2: {self.make_rng('rng_stream')}\\\")\\n\",\n    \"    RNGSubModule()()\\n\",\n    \"    RNGSubModule()()\\n\",\n    \"\\n\",\n    \"rng_module = RNGModule()\\n\",\n    \"variables = rng_module.init({'rng_stream': jax.random.key(0)})\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"metadata\": {\n    \"outputId\": \"d189d25e-425d-4fd7-fe18-2dfd63f28b87\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"RNGModule, count 1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[1428664606 3351135085]\\n\",\n      \"RNGModule, count 2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3456700291 3873160899]\\n\",\n      \"RNGSubModule_0, count 1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[3858825717 2323087578]\\n\",\n      \"RNGSubModule_0, count 2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[ 601859108 3782857444]\\n\",\n      \"RNGSubModule_1, count 1: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[ 426957352 2006350344]\\n\",\n      \"RNGSubModule_1, count 2: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[4006253729 4205356731]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"stream_seed = jax.random.key(0)\\n\",\n    \"for initial_data in ((), ('RNGSubModule_0',), ('RNGSubModule_1',)):\\n\",\n    \"  if initial_data:\\n\",\n    \"    module_name = initial_data[-1]\\n\",\n    \"  else:\\n\",\n    \"    module_name = 'RNGModule'\\n\",\n    \"  for call_count in (1, 2):\\n\",\n    \"    hash_int = produce_hash(data=initial_data+(call_count,))\\n\",\n    \"    rng_key = jax.random.fold_in(stream_seed, jnp.uint32(hash_int))\\n\",\n    \"    print(f\\\"{module_name}, count {call_count}: {rng_key}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Using `self.param` and `self.variable`\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Flax users have the option of creating additional parameters and variables in their modules by using the [`self.param`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.param) and [`self.variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.variable) `Module` methods. An `init_fn` argument must be passed to these methods so that it can generate the initial value of the parameter/variable. `self.make_rng` is commonly used implicitly or explicitly in this `init_fn`, since many initializer functions are stochastic in nature and require a PRNG key. See the full list of Flax initializers [here](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/initializers.html).\\n\",\n    \"\\n\",\n    \"There are a couple of differences between the two methods that the user should take note of:\\n\",\n    \"* `self.param` always creates a parameter in the `'params'` [collection](https://flax.readthedocs.io/en/latest/glossary.html#term-Params-parameters), whereas `self.variable` creates a variable in any [collection](https://flax.readthedocs.io/en/latest/glossary.html#term-Variable-collections) the user specifies\\n\",\n    \"* `self.param` will automatically call `self.make_rng('params')` and pass in the generated PRNG key implicitly to the `init_fn` of the parameter you instantiated (it will be passed in as the first argument), whereas users will have to manually specify what RNG stream to call `self.make_rng` on in the `init_fn` of `self.variable` (it could be `'params'` or something different).\\n\",\n    \"\\n\",\n    \"Below is an example using both `self.param` and `self.variable`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 17,\n   \"metadata\": {\n    \"outputId\": \"a7816385-0e08-48e2-dc51-055d7bcd0bab\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"[[-1.6185919   0.700908  ]\\n\",\n      \" [-1.3146383  -0.79342234]]\\n\",\n      \"[[ 0.0761425 -1.6157459]\\n\",\n      \" [-1.6857724  0.7126891]]\\n\",\n      \"[[ 0.60175574  0.2553228 ]\\n\",\n      \" [ 0.27367848 -2.1975214 ]]\\n\",\n      \"[[1.6249592  0.30813068]\\n\",\n      \" [1.6613585  1.0404155 ]]\\n\",\n      \"[[ 0.0030665   0.29551846]\\n\",\n      \" [ 0.16670242 -0.78252524]]\\n\",\n      \"[1.582462   0.15216611]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Model(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    # kernel will use 'params' seed, initial data will include 'Dense_0', call count 1\\n\",\n    \"    x = nn.Dense(2, kernel_init=jax.random.normal, use_bias=False)(x)\\n\",\n    \"    # model_param will use 'params' seed, call count 1\\n\",\n    \"    model_param = self.param('model_param', jax.random.normal, x.shape)\\n\",\n    \"    # model_variable1 will use 'params' seed, call count 2\\n\",\n    \"    model_variable1 = self.variable(\\n\",\n    \"      'other_collection',\\n\",\n    \"      'model_variable1',\\n\",\n    \"      lambda: jax.random.normal(self.make_rng('params'), x.shape),\\n\",\n    \"    )\\n\",\n    \"    # model_variable2 will use 'other' seed, call count 1\\n\",\n    \"    model_variable2 = self.variable(\\n\",\n    \"      'other_collection',\\n\",\n    \"      'model_variable2',\\n\",\n    \"      lambda: jax.random.normal(self.make_rng('other'), x.shape),\\n\",\n    \"    )\\n\",\n    \"    # kernel will use 'params' seed, initial data will include 'Dense_1', call count 1\\n\",\n    \"    # bias will use 'params' seed, initial data will include 'Dense_1', call count 2\\n\",\n    \"    x = nn.Dense(2, kernel_init=jax.random.normal, bias_init=jax.random.normal)(\\n\",\n    \"      x\\n\",\n    \"    )\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"model = Model()\\n\",\n    \"variables = model.init(\\n\",\n    \"  {'params': jax.random.key(0), 'other': jax.random.key(1)}, jnp.ones((2, 2))\\n\",\n    \")\\n\",\n    \"print(variables['params']['Dense_0']['kernel'])\\n\",\n    \"print(variables['params']['model_param'])\\n\",\n    \"print(variables['other_collection']['model_variable1'])\\n\",\n    \"print(variables['other_collection']['model_variable2'])\\n\",\n    \"print(variables['params']['Dense_1']['kernel'])\\n\",\n    \"print(variables['params']['Dense_1']['bias'])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Remember:\\n\",\n    \"* there is a separate count for each RNG stream; this is why the count for `self.make_rng('other')` starts at 1 even though there were earlier calls of `self.make_rng('params')`\\n\",\n    \"* each submodule has their own separate count for each rng stream; this is why each `Dense` layer has their own separate count for `self.make_rng('params')` and why `model_param` and `model_variable1` share the same count (since they are defined within the same top-level parent module)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 18,\n   \"metadata\": {\n    \"outputId\": \"ccec9d64-9a27-47f7-adaf-b36a5ea655db\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"[[-1.6185919   0.700908  ]\\n\",\n      \" [-1.3146383  -0.79342234]]\\n\",\n      \"[[ 0.0761425 -1.6157459]\\n\",\n      \" [-1.6857724  0.7126891]]\\n\",\n      \"[[ 0.60175574  0.2553228 ]\\n\",\n      \" [ 0.27367848 -2.1975214 ]]\\n\",\n      \"[[1.6249592  0.30813068]\\n\",\n      \" [1.6613585  1.0404155 ]]\\n\",\n      \"[[ 0.0030665   0.29551846]\\n\",\n      \" [ 0.16670242 -0.78252524]]\\n\",\n      \"[[1.582462   0.15216611]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"params_seed = jax.random.key(0)\\n\",\n    \"other_seed = jax.random.key(1)\\n\",\n    \"for initial_data, count, seed, shape in (\\n\",\n    \"  (('Dense_0',), 1, params_seed, (2, 2)),\\n\",\n    \"  ((), 1, params_seed, (2, 2)),\\n\",\n    \"  ((), 2, params_seed, (2, 2)),\\n\",\n    \"  ((), 1, other_seed, (2, 2)),\\n\",\n    \"  (('Dense_1',), 1, params_seed, (2, 2)),\\n\",\n    \"  (('Dense_1',), 2, params_seed, (1, 2)),\\n\",\n    \"):\\n\",\n    \"  hash_int = produce_hash(data=(*initial_data, count))\\n\",\n    \"  rng_key = jax.random.fold_in(seed, jnp.uint32(hash_int))\\n\",\n    \"  print(jax.random.normal(rng_key, shape))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Managing RNG streams inside a training loop\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Below is an example of managing RNG streams from `self.make_rng`, `self.param`, `self.variable` and `nn.Dropout` in a training loop (note: `nn.Dropout` requires a seed PRNG key to be passed in the `'dropout'` RNG stream, since it implicitly calls `self.make_rng('dropout')`):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 19,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class SubModule(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x, train):\\n\",\n    \"    # variables created using `self.param` will use `self.make_rng('params')`\\n\",\n    \"    kernel = self.param('submodule_kernel', jax.random.normal, x.shape)\\n\",\n    \"    x = x + kernel\\n\",\n    \"    # `nn.Dropout` will use self.make_rng('dropout')\\n\",\n    \"    x = nn.Dropout(0.2)(x, deterministic=not train)\\n\",\n    \"    # `nn.Dense` will use self.make_rng('params')\\n\",\n    \"    x = nn.Dense(3)(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"class Model(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x, train):\\n\",\n    \"    # make kernel use `self.make_rng('other')`\\n\",\n    \"    kernel = self.variable(\\n\",\n    \"      'other_collection',\\n\",\n    \"      'module_kernel',\\n\",\n    \"      lambda: jax.random.normal(self.make_rng('other'), x.shape),\\n\",\n    \"    )\\n\",\n    \"    x = (\\n\",\n    \"      x + kernel.value\\n\",\n    \"    )  # `.value` will extract the underlying value of the variable\\n\",\n    \"    x = SubModule()(x, train)\\n\",\n    \"    # `nn.Dropout` will use self.make_rng('dropout')\\n\",\n    \"    x = nn.Dropout(0.2)(x, deterministic=not train)\\n\",\n    \"    # `nn.Dense` will use self.make_rng('params')\\n\",\n    \"    x = nn.Dense(2)(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"params_rng, other_rng, train_rng = jax.random.split(jax.random.key(0), 3)\\n\",\n    \"init_rngs = {'params': params_rng, 'other': other_rng}\\n\",\n    \"\\n\",\n    \"x = jnp.ones((1, 3))\\n\",\n    \"y = jnp.ones((1, 2))\\n\",\n    \"\\n\",\n    \"module = Model()\\n\",\n    \"variables = module.init(init_rngs, x, train=False)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 20,\n   \"metadata\": {\n    \"outputId\": \"e9da8228-acba-403d-bcb5-33a39d4d530d\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"2.518454\\n\",\n      \"2.4859657\\n\",\n      \"2.4171872\\n\",\n      \"2.412684\\n\",\n      \"2.3435805\\n\",\n      \"2.2773488\\n\",\n      \"2.2592616\\n\",\n      \"2.2009292\\n\",\n      \"2.1839895\\n\",\n      \"2.1707344\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"def update(variables, rng):\\n\",\n    \"  # we don't need to provide a 'params' or 'other' rng, as only 'dropout' rng will be used during training\\n\",\n    \"  # split the rng to get a dropout_rng to be used for this training iteration,\\n\",\n    \"  # and to get another rng key to be used for the next training iteration\\n\",\n    \"  dropout_rng, next_rng = jax.random.split(rng)\\n\",\n    \"  def loss(params):\\n\",\n    \"    out = module.apply(\\n\",\n    \"      {'params': params, 'other_collection': variables['other_collection']},\\n\",\n    \"      x,\\n\",\n    \"      train=True,\\n\",\n    \"      rngs={'dropout': dropout_rng},\\n\",\n    \"    )\\n\",\n    \"    return jnp.mean((y - out) ** 2)\\n\",\n    \"  grads = jax.grad(loss)(variables['params'])\\n\",\n    \"  params = jax.tree_util.tree_map(lambda p, g: p - 1e-3 * g, variables['params'], grads)\\n\",\n    \"  return {\\n\",\n    \"    'params': params,\\n\",\n    \"    'other_collection': variables['other_collection'],\\n\",\n    \"  }, next_rng\\n\",\n    \"\\n\",\n    \"for _ in range(10):\\n\",\n    \"  variables, train_rng = update(variables, train_rng)\\n\",\n    \"  out = module.apply(variables, x, train=False)\\n\",\n    \"  print(jnp.mean((y - out)**2))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### 🔪 Sharp edge 🔪 - unintentionally generating the same values\\n\",\n    \"\\n\",\n    \"There is an edge case where the same value can be unintentionally generated.\\n\",\n    \"See the [Flax issue](https://github.com/google/flax/issues/2157) for more details.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 21,\n   \"metadata\": {\n    \"outputId\": \"887142ff-c9ca-4aae-d9fa-cc9993d809c5\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Array(True, dtype=bool)\"\n      ]\n     },\n     \"execution_count\": 21,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"class Leaf(nn.Module):\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    return x + jax.random.randint(self.make_rng(\\\"rng\\\"), (), 0, 100)\\n\",\n    \"\\n\",\n    \"class Node(nn.Module):\\n\",\n    \"  leaf_name: str\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    return Leaf(name=self.leaf_name)(x)\\n\",\n    \"\\n\",\n    \"class Model(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    return (Node(name=\\\"ab\\\", leaf_name=\\\"cdef\\\")(x),\\n\",\n    \"            Node(name=\\\"abc\\\", leaf_name=\\\"def\\\")(x),\\n\",\n    \"    )\\n\",\n    \"\\n\",\n    \"out1, out2 = Model().apply({}, 0, rngs={\\\"rng\\\": jax.random.key(33)})\\n\",\n    \"out1 == out2 # same output, despite having different submodule names\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This occurs because the hash function [concatenates strings together](https://docs.python.org/3/library/hashlib.html#hashlib.hash.update), so the data `('AB', 'C')` is equivalent to data `('A', 'BC')` when fed into the hash function, therefore producing the same hash int.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 22,\n   \"metadata\": {\n    \"outputId\": \"001cbd49-129b-4474-c6a1-3255a4ee3dfe\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"947574064\\n\",\n      \"947574064\\n\",\n      \"947574064\\n\",\n      \"947574064\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(produce_hash(data=('A', 'B', 'C', 1)))\\n\",\n    \"print(produce_hash(data=('AB', 'C', 1)))\\n\",\n    \"print(produce_hash(data=('A', 'BC', 1)))\\n\",\n    \"print(produce_hash(data=('ABC', 1)))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To avoid this edge case, users can flip the `flax_fix_rng_separator` [configuration flag](https://flax.readthedocs.io/en/latest/api_reference/flax.config.html#flax.configurations.Config.flax_fix_rng_separator) to `True`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 23,\n   \"metadata\": {\n    \"outputId\": \"35a2b204-bdfd-4f83-8e98-ba723963cb0c\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Array(False, dtype=bool)\"\n      ]\n     },\n     \"execution_count\": 23,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"flax.config.update('flax_fix_rng_separator', True)\\n\",\n    \"out1, out2 = Model().apply({}, 0, rngs={\\\"rng\\\": jax.random.key(33)})\\n\",\n    \"out1 == out2 # different output\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Managing RNG's on multiple devices\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This section will show examples on how to use `jit` and `shard_map` to use RNG's in multi-device settings.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Using `jax.jit`\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"When using [`jax.jit`](https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html), we can use RNG's as we did before, but we now include `in_shardings` and `out_shardings` arguments to specify how to shard input and output data. The RNG key itself gets replicated (not sharded); `jax.jit` makes each device use it as appropriate for its shard of the data.\\n\",\n    \"\\n\",\n    \"For more details on training on multiple devices in Flax using `jax.jit`, see our [Scale up Flax Modules on multiple devices guide](https://flax.readthedocs.io/en/latest/guides/parallel_training/flax_on_pjit.html#) and [lm1b example](https://github.com/google/flax/tree/main/examples/lm1b).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 24,\n   \"metadata\": {\n    \"outputId\": \"6c280522-4b43-4b82-f40a-b73986659b2c\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"[CpuDevice(id=0) CpuDevice(id=1) CpuDevice(id=2) CpuDevice(id=3)\\n\",\n      \" CpuDevice(id=4) CpuDevice(id=5) CpuDevice(id=6) CpuDevice(id=7)]\\n\",\n      \"Mesh(device_ids=array([0, 1, 2, 3, 4, 5, 6, 7]), axis_names=('data',))\\n\",\n      \"NamedSharding(mesh=Mesh('data': 8), spec=PartitionSpec('data',))\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Create a mesh and annotate the axis with a name.\\n\",\n    \"device_mesh = mesh_utils.create_device_mesh((8,))\\n\",\n    \"print(device_mesh)\\n\",\n    \"\\n\",\n    \"mesh = Mesh(devices=device_mesh, axis_names=('data',))\\n\",\n    \"print(mesh)\\n\",\n    \"\\n\",\n    \"data_sharding = NamedSharding(mesh, PartitionSpec('data',))\\n\",\n    \"print(data_sharding)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 25,\n   \"metadata\": {\n    \"outputId\": \"d1bfbcad-e28a-4fae-8136-98bd1efb9332\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Array([[-2.2187614 ],\\n\",\n       \"       [-2.8055234 ],\\n\",\n       \"       [-2.5464187 ],\\n\",\n       \"       [ 1.0270392 ],\\n\",\n       \"       [-3.5243359 ],\\n\",\n       \"       [-2.2795477 ],\\n\",\n       \"       [-0.6504516 ],\\n\",\n       \"       [ 0.17373264]], dtype=float32)\"\n      ]\n     },\n     \"execution_count\": 25,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"class Model(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x, add_noise):\\n\",\n    \"    x = nn.Dense(1)(x)\\n\",\n    \"    # use jnp.where for control flow; for more details see: https://jax.readthedocs.io/en/latest/errors.html#jax.errors.TracerBoolConversionError\\n\",\n    \"    return jnp.where(\\n\",\n    \"      add_noise, x + jax.random.normal(self.make_rng('params'), x.shape), x\\n\",\n    \"    )\\n\",\n    \"\\n\",\n    \"module = Model()\\n\",\n    \"init_rng, apply_rng = jax.random.split(jax.random.key(0))\\n\",\n    \"x = jnp.ones((8, 1))\\n\",\n    \"variables = module.init(init_rng, x, False)\\n\",\n    \"\\n\",\n    \"# create custom forward function, since jit does not support kwargs when in_shardings is specified\\n\",\n    \"def forward(variables, x, add_noise, rng):\\n\",\n    \"  return module.apply(variables, x, add_noise, rngs={'params': rng})\\n\",\n    \"\\n\",\n    \"# shard the inputs x across devices\\n\",\n    \"# replicate the variables, add_noise boolean and rng key across devices\\n\",\n    \"# shard the output across devices\\n\",\n    \"jit_forward = jax.jit(\\n\",\n    \"  forward,\\n\",\n    \"  in_shardings=(None, data_sharding, None, None),\\n\",\n    \"  out_shardings=data_sharding,\\n\",\n    \")\\n\",\n    \"out = jit_forward(variables, x, True, apply_rng)\\n\",\n    \"out\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The output is different given the same input, meaning the RNG key was used to add noise to the output.\\n\",\n    \"\\n\",\n    \"We can also confirm that the output is sharded across devices:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 26,\n   \"metadata\": {\n    \"outputId\": \"b672b85f-7a2d-44b5-afc1-bbf9426655ed\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"[Shard(device=CpuDevice(id=0), index=(slice(0, 1, None), slice(None, None, None)), replica_id=0, data=[[-2.2187614]]),\\n\",\n       \" Shard(device=CpuDevice(id=1), index=(slice(1, 2, None), slice(None, None, None)), replica_id=0, data=[[-2.8055234]]),\\n\",\n       \" Shard(device=CpuDevice(id=2), index=(slice(2, 3, None), slice(None, None, None)), replica_id=0, data=[[-2.5464187]]),\\n\",\n       \" Shard(device=CpuDevice(id=3), index=(slice(3, 4, None), slice(None, None, None)), replica_id=0, data=[[1.0270392]]),\\n\",\n       \" Shard(device=CpuDevice(id=4), index=(slice(4, 5, None), slice(None, None, None)), replica_id=0, data=[[-3.5243359]]),\\n\",\n       \" Shard(device=CpuDevice(id=5), index=(slice(5, 6, None), slice(None, None, None)), replica_id=0, data=[[-2.2795477]]),\\n\",\n       \" Shard(device=CpuDevice(id=6), index=(slice(6, 7, None), slice(None, None, None)), replica_id=0, data=[[-0.6504516]]),\\n\",\n       \" Shard(device=CpuDevice(id=7), index=(slice(7, 8, None), slice(None, None, None)), replica_id=0, data=[[0.17373264]])]\"\n      ]\n     },\n     \"execution_count\": 26,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"out.addressable_shards\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Another way to visualize the output sharding:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 27,\n   \"metadata\": {\n    \"outputId\": \"1c0a16ce-fa3f-4b95-d794-58464bbaa9ae\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\"><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">  CPU 0  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">  CPU 1  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">  CPU 2  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">  CPU 3  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span>\\n\",\n       \"<span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">  CPU 4  </span>\\n\",\n       \"<span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">  CPU 5  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">  CPU 6  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">  CPU 7  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;57;59;121mCPU 0\\u001b[0m\\u001b[38;2;255;255;255;48;2;57;59;121m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;214;97;107m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107mCPU 1\\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;140;162;82m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82mCPU 2\\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;222;158;214m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214mCPU 3\\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;0;0;0;48;2;231;203;148m  \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148mCPU 4\\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;107;110;207m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207mCPU 5\\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;165;81;148m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148mCPU 6\\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;140;109;49m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49mCPU 7\\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"jax.debug.visualize_array_sharding(out)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If we choose not to add noise, then the output is the same across all batches (as expected, since the input is the same for all batches):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 28,\n   \"metadata\": {\n    \"outputId\": \"fe9ec875-3e7f-4861-babc-f07064737276\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Array([[-1.2839764],\\n\",\n       \"       [-1.2839764],\\n\",\n       \"       [-1.2839764],\\n\",\n       \"       [-1.2839764],\\n\",\n       \"       [-1.2839764],\\n\",\n       \"       [-1.2839764],\\n\",\n       \"       [-1.2839764],\\n\",\n       \"       [-1.2839764]], dtype=float32)\"\n      ]\n     },\n     \"execution_count\": 28,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"out = jit_forward(variables, x, False, apply_rng)\\n\",\n    \"out\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can confirm the un-jitted function produces the same values, albeit unsharded (note there may be small numerical differences due to compiler optimizations from jitting):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 29,\n   \"metadata\": {\n    \"outputId\": \"0a9e5f2c-d4bf-4051-bf71-f32a9c32dc06\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Array([[-2.2187614 ],\\n\",\n       \"       [-2.8055234 ],\\n\",\n       \"       [-2.5464187 ],\\n\",\n       \"       [ 1.0270392 ],\\n\",\n       \"       [-3.5243359 ],\\n\",\n       \"       [-2.2795477 ],\\n\",\n       \"       [-0.6504516 ],\\n\",\n       \"       [ 0.17373264]], dtype=float32)\"\n      ]\n     },\n     \"execution_count\": 29,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"out = forward(variables, x, True, apply_rng)\\n\",\n    \"out\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 30,\n   \"metadata\": {\n    \"outputId\": \"772a5063-1bd5-46b4-f6f6-cae9b4b81a26\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Array([[-1.2839764],\\n\",\n       \"       [-1.2839764],\\n\",\n       \"       [-1.2839764],\\n\",\n       \"       [-1.2839764],\\n\",\n       \"       [-1.2839764],\\n\",\n       \"       [-1.2839764],\\n\",\n       \"       [-1.2839764],\\n\",\n       \"       [-1.2839764]], dtype=float32)\"\n      ]\n     },\n     \"execution_count\": 30,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"out = forward(variables, x, False, apply_rng)\\n\",\n    \"out\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Using `shard_map`\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"When using [`jax.experimental.shard_map.shard_map`](https://jax.readthedocs.io/en/latest/jep/14273-shard-map.html), the important parts to remember are to:\\n\",\n    \"* split your PRNG key to produce a different key for each device\\n\",\n    \"* the PRNG keys will be sharded automatically to each device (provided you use the correct partition specification), but the [**rank of the original batched PRNG key array will not be reduced**](https://jax.readthedocs.io/en/latest/jep/14273-shard-map.html#rank-reducing-vs-rank-preserving-maps-over-array-axes); e.g.\\n\",\n    \"with a batch of 8 PRNG keys and 8 devices, each device will see a PRNG key batch of size 1 within the `shard_map`-ed function\\n\",\n    \"  * therefore to access the PRNG key itself, we need to index slice into it (see the example below)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 31,\n   \"metadata\": {\n    \"outputId\": \"aa00b9a3-24ba-4048-ed8c-afbb9070f039\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Array([[-1.2605132 ],\\n\",\n       \"       [-1.2405176 ],\\n\",\n       \"       [-0.99350417],\\n\",\n       \"       [-1.0277128 ],\\n\",\n       \"       [-1.4154483 ],\\n\",\n       \"       [-0.3905797 ],\\n\",\n       \"       [-2.417677  ],\\n\",\n       \"       [ 0.9023453 ]], dtype=float32)\"\n      ]\n     },\n     \"execution_count\": 31,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"def forward(variables, x, add_noise, rng_key_batch):\\n\",\n    \"  # rng_key_batch is a batch of size 1 containing 1 PRNG key\\n\",\n    \"  # index slice into the rng_key_batch to access the PRNG key\\n\",\n    \"  return module.apply(\\n\",\n    \"    variables, x, add_noise, rngs={'params': rng_key_batch[0]}\\n\",\n    \"  )\\n\",\n    \"\\n\",\n    \"# define partition specifications\\n\",\n    \"data_pspec = PartitionSpec('data')\\n\",\n    \"no_pspec = PartitionSpec()\\n\",\n    \"\\n\",\n    \"# shard the inputs x and rng keys across devices\\n\",\n    \"# replicate the variables and add_noise boolean across devices\\n\",\n    \"# shard the output across devices\\n\",\n    \"shmap_forward = shard_map(\\n\",\n    \"  forward,\\n\",\n    \"  mesh=mesh,\\n\",\n    \"  in_specs=(no_pspec, data_pspec, no_pspec, data_pspec),\\n\",\n    \"  out_specs=data_pspec,\\n\",\n    \")\\n\",\n    \"# get 8 different rng's that will be used by the 8 devices when doing forward inference\\n\",\n    \"apply_rngs = jax.random.split(apply_rng, 8)\\n\",\n    \"out = shmap_forward(variables, x, True, apply_rngs)\\n\",\n    \"out\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Confirm that the output is sharded across devices:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 32,\n   \"metadata\": {\n    \"outputId\": \"e304289b-ef1c-4e4a-d4c1-4c41613bfa62\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"[Shard(device=CpuDevice(id=0), index=(slice(0, 1, None), slice(None, None, None)), replica_id=0, data=[[-1.2605132]]),\\n\",\n       \" Shard(device=CpuDevice(id=1), index=(slice(1, 2, None), slice(None, None, None)), replica_id=0, data=[[-1.2405176]]),\\n\",\n       \" Shard(device=CpuDevice(id=2), index=(slice(2, 3, None), slice(None, None, None)), replica_id=0, data=[[-0.99350417]]),\\n\",\n       \" Shard(device=CpuDevice(id=3), index=(slice(3, 4, None), slice(None, None, None)), replica_id=0, data=[[-1.0277128]]),\\n\",\n       \" Shard(device=CpuDevice(id=4), index=(slice(4, 5, None), slice(None, None, None)), replica_id=0, data=[[-1.4154483]]),\\n\",\n       \" Shard(device=CpuDevice(id=5), index=(slice(5, 6, None), slice(None, None, None)), replica_id=0, data=[[-0.3905797]]),\\n\",\n       \" Shard(device=CpuDevice(id=6), index=(slice(6, 7, None), slice(None, None, None)), replica_id=0, data=[[-2.417677]]),\\n\",\n       \" Shard(device=CpuDevice(id=7), index=(slice(7, 8, None), slice(None, None, None)), replica_id=0, data=[[0.9023453]])]\"\n      ]\n     },\n     \"execution_count\": 32,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"out.addressable_shards\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 33,\n   \"metadata\": {\n    \"outputId\": \"52fdb6d2-4c4f-44b3-feee-4bc5363c8f2f\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\"><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">  CPU 0  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">  CPU 1  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">  CPU 2  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">  CPU 3  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span>\\n\",\n       \"<span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">  CPU 4  </span>\\n\",\n       \"<span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">  CPU 5  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">  CPU 6  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">  CPU 7  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;57;59;121mCPU 0\\u001b[0m\\u001b[38;2;255;255;255;48;2;57;59;121m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;214;97;107m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107mCPU 1\\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;140;162;82m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82mCPU 2\\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;222;158;214m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214mCPU 3\\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;0;0;0;48;2;231;203;148m  \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148mCPU 4\\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;107;110;207m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207mCPU 5\\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;165;81;148m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148mCPU 6\\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;140;109;49m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49mCPU 7\\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"jax.debug.visualize_array_sharding(out)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Lifted transforms\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"[Flax lifted transforms](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/transformations.html) allow you to use [JAX transforms](https://github.com/jax-ml/jax#transformations) with `Module` arguments. This section will show you how to control how PRNG keys are split in Flax lifted transforms.\\n\",\n    \"\\n\",\n    \"Refer to [Lifted transformations](https://flax.readthedocs.io/en/latest/developer_notes/lift.html) for more detail.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### `nn.vmap`\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can use [`nn.vmap`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/_autosummary/flax.linen.vmap.html) to create a batched `Dense` layer:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 34,\n   \"metadata\": {\n    \"outputId\": \"f0830f6b-659c-446f-c933-7b2a430f8004\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'params': {'bias': Array([0., 0.], dtype=float32),\\n\",\n       \"  'kernel': Array([[-1.2488099 , -0.6127134 ],\\n\",\n       \"         [-0.07084481,  0.60130936]], dtype=float32)}}\"\n      ]\n     },\n     \"execution_count\": 34,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"x = jnp.ones((3, 2))\\n\",\n    \"\\n\",\n    \"BatchDense = nn.vmap(\\n\",\n    \"    nn.Dense,\\n\",\n    \"    in_axes=0, out_axes=0,\\n\",\n    \"    variable_axes={'params': None},\\n\",\n    \"    split_rngs={'params': False})\\n\",\n    \"\\n\",\n    \"BatchDense(2).init(jax.random.key(0), x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"By denoting `variable_axes={'params': 0}'`, we vectorize the `params` Arrays on the first axis. However the parameter values generated are all identical to each other:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 35,\n   \"metadata\": {\n    \"outputId\": \"eef5c0ca-f8d5-4f25-8ce6-9f2f60622daf\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'params': {'bias': Array([[0., 0.],\\n\",\n       \"         [0., 0.],\\n\",\n       \"         [0., 0.]], dtype=float32),\\n\",\n       \"  'kernel': Array([[[-1.2488099 , -0.6127134 ],\\n\",\n       \"          [-0.07084481,  0.60130936]],\\n\",\n       \"  \\n\",\n       \"         [[-1.2488099 , -0.6127134 ],\\n\",\n       \"          [-0.07084481,  0.60130936]],\\n\",\n       \"  \\n\",\n       \"         [[-1.2488099 , -0.6127134 ],\\n\",\n       \"          [-0.07084481,  0.60130936]]], dtype=float32)}}\"\n      ]\n     },\n     \"execution_count\": 35,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"BatchDense = nn.vmap(\\n\",\n    \"    nn.Dense,\\n\",\n    \"    in_axes=0, out_axes=0,\\n\",\n    \"    variable_axes={'params': 0},\\n\",\n    \"    split_rngs={'params': False})\\n\",\n    \"\\n\",\n    \"BatchDense(2).init(jax.random.key(0), x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If we also make `split_rngs={'params': True}`, then the PRNG key we provide is split across the variable axis (in this case, the batch axis 0), and we can generate different parameters for each batch input:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 36,\n   \"metadata\": {\n    \"outputId\": \"275699c3-ba48-403e-877d-07b65981cff5\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'params': {'bias': Array([[0., 0.],\\n\",\n       \"         [0., 0.],\\n\",\n       \"         [0., 0.]], dtype=float32),\\n\",\n       \"  'kernel': Array([[[-0.2526208 , -0.15088455],\\n\",\n       \"          [-1.1987205 , -0.40843305]],\\n\",\n       \"  \\n\",\n       \"         [[-0.7064888 , -1.108805  ],\\n\",\n       \"          [-0.938775  ,  1.4812315 ]],\\n\",\n       \"  \\n\",\n       \"         [[-0.59468937, -0.2502723 ],\\n\",\n       \"          [-1.33515   ,  0.5067442 ]]], dtype=float32)}}\"\n      ]\n     },\n     \"execution_count\": 36,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"BatchDense = nn.vmap(\\n\",\n    \"    nn.Dense,\\n\",\n    \"    in_axes=0, out_axes=0,\\n\",\n    \"    variable_axes={'params': 0},\\n\",\n    \"    split_rngs={'params': True})\\n\",\n    \"\\n\",\n    \"BatchDense(2).init(jax.random.key(0), x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Adding a variable via `self.variable` is straightforward:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 37,\n   \"metadata\": {\n    \"outputId\": \"c11a80bc-d865-4e2e-e059-4d6bcea79e09\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'params': {'Dense_0': {'bias': Array([[0., 0.],\\n\",\n       \"          [0., 0.],\\n\",\n       \"          [0., 0.]], dtype=float32),\\n\",\n       \"   'kernel': Array([[[-0.9079084 ,  0.76390624],\\n\",\n       \"           [-0.01285526,  0.4320353 ]],\\n\",\n       \"   \\n\",\n       \"          [[ 0.12398645,  0.7884565 ],\\n\",\n       \"           [ 1.5344163 ,  1.3186085 ]],\\n\",\n       \"   \\n\",\n       \"          [[-0.44171348,  0.43430036],\\n\",\n       \"           [-0.40732604,  0.29774475]]], dtype=float32)}},\\n\",\n       \" 'other_collection': {'kernel': Array([[-0.8193048 ,  0.711106  ],\\n\",\n       \"         [-0.37802765, -0.66705877],\\n\",\n       \"         [-0.44808003,  0.93031347]], dtype=float32)}}\"\n      ]\n     },\n     \"execution_count\": 37,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"class Model(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x = nn.Dense(2)(x)\\n\",\n    \"    kernel = self.variable(\\n\",\n    \"      'other_collection',\\n\",\n    \"      'kernel',\\n\",\n    \"      lambda: jax.random.normal(self.make_rng('other'), x.shape),\\n\",\n    \"    )\\n\",\n    \"    return x + kernel.value\\n\",\n    \"\\n\",\n    \"BatchModel = nn.vmap(\\n\",\n    \"  Model,\\n\",\n    \"  in_axes=0,\\n\",\n    \"  out_axes=0,\\n\",\n    \"  variable_axes={'params': 0, 'other_collection': 0},\\n\",\n    \"  split_rngs={'params': True, 'other': True},\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"BatchModel().init({'params': jax.random.key(0), 'other': jax.random.key(1)}, x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can control which RNG stream to split, for example, if we only wanted to split the `'params'` RNG stream, then the variables generated from `self.variable` will be the same for each batch input:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 38,\n   \"metadata\": {\n    \"outputId\": \"fb16619c-c975-497d-c867-6fd5143b4507\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'params': {'Dense_0': {'bias': Array([[0., 0.],\\n\",\n       \"          [0., 0.],\\n\",\n       \"          [0., 0.]], dtype=float32),\\n\",\n       \"   'kernel': Array([[[-0.9079084 ,  0.76390624],\\n\",\n       \"           [-0.01285526,  0.4320353 ]],\\n\",\n       \"   \\n\",\n       \"          [[ 0.12398645,  0.7884565 ],\\n\",\n       \"           [ 1.5344163 ,  1.3186085 ]],\\n\",\n       \"   \\n\",\n       \"          [[-0.44171348,  0.43430036],\\n\",\n       \"           [-0.40732604,  0.29774475]]], dtype=float32)}},\\n\",\n       \" 'other_collection': {'kernel': Array([[ 0.44956833, -1.1854612 ],\\n\",\n       \"         [ 0.44956833, -1.1854612 ],\\n\",\n       \"         [ 0.44956833, -1.1854612 ]], dtype=float32)}}\"\n      ]\n     },\n     \"execution_count\": 38,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"BatchModel = nn.vmap(\\n\",\n    \"    Model,\\n\",\n    \"    in_axes=0, out_axes=0,\\n\",\n    \"    variable_axes={'params': 0, 'other_collection': 0},\\n\",\n    \"    split_rngs={'params': True, 'other': False})\\n\",\n    \"\\n\",\n    \"BatchModel().init({'params': jax.random.key(0), 'other': jax.random.key(1)}, x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can also control which parameters / variables should be generated for each batch input, for example, if we only wanted `'params'` to generate separate parameters for each batch input:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 39,\n   \"metadata\": {\n    \"outputId\": \"f3a17d59-6f75-4408-caba-5769d4589263\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'params': {'Dense_0': {'bias': Array([[0., 0.],\\n\",\n       \"          [0., 0.],\\n\",\n       \"          [0., 0.]], dtype=float32),\\n\",\n       \"   'kernel': Array([[[-0.9079084 ,  0.76390624],\\n\",\n       \"           [-0.01285526,  0.4320353 ]],\\n\",\n       \"   \\n\",\n       \"          [[ 0.12398645,  0.7884565 ],\\n\",\n       \"           [ 1.5344163 ,  1.3186085 ]],\\n\",\n       \"   \\n\",\n       \"          [[-0.44171348,  0.43430036],\\n\",\n       \"           [-0.40732604,  0.29774475]]], dtype=float32)}},\\n\",\n       \" 'other_collection': {'kernel': Array([ 0.44956833, -1.1854612 ], dtype=float32)}}\"\n      ]\n     },\n     \"execution_count\": 39,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"BatchModel = nn.vmap(\\n\",\n    \"    Model,\\n\",\n    \"    in_axes=0, out_axes=0,\\n\",\n    \"    variable_axes={'params': 0, 'other_collection': None},\\n\",\n    \"    split_rngs={'params': True, 'other': False})\\n\",\n    \"\\n\",\n    \"BatchModel().init({'params': jax.random.key(0), 'other': jax.random.key(1)}, x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### `nn.scan`\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can use [`nn.scan`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/_autosummary/flax.linen.scan.html) to create a scanned `Module` layer (this is useful for simplifying repetitively stacked submodules):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 40,\n   \"metadata\": {\n    \"outputId\": \"29d1863b-809f-42ce-894c-1b0810faa41e\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'params': {'Dense_0': {'bias': Array([[0., 0.],\\n\",\n       \"          [0., 0.],\\n\",\n       \"          [0., 0.]], dtype=float32),\\n\",\n       \"   'kernel': Array([[[-0.07838312, -0.7422982 ],\\n\",\n       \"           [ 0.87488323,  0.13773395]],\\n\",\n       \"   \\n\",\n       \"          [[ 0.97309333,  0.9087693 ],\\n\",\n       \"           [-0.12564984, -1.0920651 ]],\\n\",\n       \"   \\n\",\n       \"          [[-0.99055105,  1.1499453 ],\\n\",\n       \"           [-0.15721127, -0.62520015]]], dtype=float32)}}}\"\n      ]\n     },\n     \"execution_count\": 40,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"x = jnp.ones((3, 2))\\n\",\n    \"\\n\",\n    \"class ResidualMLPBlock(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x, _):\\n\",\n    \"    h = nn.Dense(features=2)(x)\\n\",\n    \"    h = nn.relu(h)\\n\",\n    \"    return x + h, None # return an empty carry\\n\",\n    \"\\n\",\n    \"ScanMLP = nn.scan(\\n\",\n    \"      ResidualMLPBlock, variable_axes={'params': 0},\\n\",\n    \"      variable_broadcast=False, split_rngs={'params': True},\\n\",\n    \"      length=3)\\n\",\n    \"\\n\",\n    \"ScanMLP().init(jax.random.key(0), x, None) # pass in an empty carry\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Similar to before, we can control whether to split the RNG stream or not, for example, if we wanted all the stacked modules to be initialized to the same parameter values, we can pass in `split_rngs={'params': False}`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 41,\n   \"metadata\": {\n    \"outputId\": \"6a825bcd-9c3b-43c2-afd2-42500d89fb26\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'params': {'Dense_0': {'bias': Array([[0., 0.],\\n\",\n       \"          [0., 0.],\\n\",\n       \"          [0., 0.]], dtype=float32),\\n\",\n       \"   'kernel': Array([[[-0.66715515, -0.0484313 ],\\n\",\n       \"           [ 0.9867164 ,  0.75408363]],\\n\",\n       \"   \\n\",\n       \"          [[-0.66715515, -0.0484313 ],\\n\",\n       \"           [ 0.9867164 ,  0.75408363]],\\n\",\n       \"   \\n\",\n       \"          [[-0.66715515, -0.0484313 ],\\n\",\n       \"           [ 0.9867164 ,  0.75408363]]], dtype=float32)}}}\"\n      ]\n     },\n     \"execution_count\": 41,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"ScanMLP = nn.scan(\\n\",\n    \"      ResidualMLPBlock, variable_axes={'params': 0},\\n\",\n    \"      variable_broadcast=False, split_rngs={'params': False},\\n\",\n    \"      length=3)\\n\",\n    \"\\n\",\n    \"ScanMLP().init(jax.random.key(0), x, None)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"name\": \"python\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "docs/guides/flax_fundamentals/rng_guide.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Randomness and PRNGs in Flax\n\n+++\n\nIn this guide, you will learn how Flax uses [JAX's explicit pseudorandom number generator (PRNG) keys](https://jax.readthedocs.io/en/latest/notebooks/Common_Gotchas_in_JAX.html#jax-prng) to emulate randomness, and adds some additional features to make it easier for users to thread PRNG keys through different Flax `Module`s.\n\nIf you are new to JAX PRNG keys or need a refresher, check out:\n- [JAX 101: PRNGs in JAX](https://jax.readthedocs.io/en/latest/jax-101/05-random-numbers.html)\n- [🔪 JAX - The Sharp Bits 🔪: Random Numbers](https://jax.readthedocs.io/en/latest/notebooks/Common_Gotchas_in_JAX.html#random-numbers).\n\n+++\n\n## Setup\n\n+++\n\nInstall or upgrade Flax, and then import some necessary dependencies.\n\n**Note:** This guide uses the `--xla_force_host_platform_device_count=8` flag to emulate multiple devices in a CPU environment in a Google Colab/Jupyter Notebook. You don’t need this if you are already using a multi-device Google Cloud TPU environment, for example, on Google Cloud or in a Kaggle VM with a TPU.\n\n```{code-cell}\n:tags: [skip-execution]\n\n!pip install -q flax\n```\n\n```{code-cell}\nimport os\nos.environ[\"XLA_FLAGS\"] = '--xla_force_host_platform_device_count=8'\n```\n\n```{code-cell}\nimport flax, flax.linen as nn\nimport jax, jax.numpy as jnp\nfrom jax.sharding import Mesh, PartitionSpec, NamedSharding\nfrom jax.experimental import mesh_utils\nfrom jax.experimental.shard_map import shard_map\n\nimport hashlib\n```\n\n```{code-cell}\n:outputId: ec904f6b-0e87-4efe-87c4-fea0f8e8ec23\n\njax.devices()\n```\n\nSet the JAX config variable `jax_threefry_partitionable` to `True`. This will be the default value in the future and makes the PRNG more efficiently auto-parallelizable under `jax.jit`. Refer to [JAX discussion](https://github.com/jax-ml/jax/discussions/18480) for more details.\n\n```{code-cell}\njax.config.update('jax_threefry_partitionable', True)\nassert jax.config.jax_threefry_partitionable == True\nassert jax.config.jax_default_prng_impl == 'threefry2x32'\n```\n\n## Receiving, manipulating and creating PRNG keys with `Module.make_rng`\n\n+++\n\nThe primary method Flax uses to receive, manipulate and create PRNG keys is via the `Module` method [`self.make_rng`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.make_rng). It is a method that accepts a string name that represents an \"RNG stream\". Each RNG stream has an initial starting seed PRNG key, which the user passes in as a dictionary argument (i.e. into an [`.init`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.init) or [`.apply`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.apply) function), and the starting seed is used by `self.make_rng` to generate more PRNG keys for that stream. If `self.make_rng` is called on a string name that does not have an initial starting seed PRNG key (i.e. the user did not pass a key with the corresponding name into `.init` or `.apply`), then `self.make_rng` will use the `'params'` key as the initial starting seed by default.\n\nNote that this method can only be called with bounded modules (see [The Flax Module lifecycle](https://flax.readthedocs.io/en/latest/developer_notes/module_lifecycle.html#top-level-modules)).\n\n```{code-cell}\n:outputId: 2a16435b-e92a-480a-f9fb-e6effc42c4c2\n\nclass RNGModule(nn.Module):\n  @nn.compact\n  def __call__(self):\n    print(self.make_rng('rng_stream'))\n    print(self.make_rng('rng_stream'))\n    print(self.make_rng('rng_stream'))\n\nrng_module = RNGModule()\nvariables = rng_module.init({'rng_stream': jax.random.key(0)})\n```\n\nNow if we use a different starting seed PRNG key, we will generate different values (as intended).\n\n```{code-cell}\n:outputId: 985b0f62-dfde-4f0f-fad4-a31927fc9f59\n\nvariables = rng_module.init({'rng_stream': jax.random.key(1)})\n```\n\nCalling `self.make_rng` for one stream will not affect the random values generated from another stream; i.e. the call order doesn't matter.\n\n```{code-cell}\n:outputId: 7e8ce538-e380-4db9-db23-bc4a8da577da\n\nclass RNGModuleTwoStreams(nn.Module):\n  @nn.compact\n  def __call__(self):\n    # same value as first code snippet above\n    print(f\"rng_stream1: {self.make_rng('rng_stream1')}\")\n    # same value as second code snippet above\n    print(f\"rng_stream2: {self.make_rng('rng_stream2')}\")\n    # same value as first code snippet above\n    print(f\"rng_stream1: {self.make_rng('rng_stream1')}\")\n    # same value as second code snippet above\n    print(f\"rng_stream2: {self.make_rng('rng_stream2')}\")\n    # same value as first code snippet above\n    print(f\"rng_stream1: {self.make_rng('rng_stream1')}\")\n    # same value as second code snippet above\n    print(f\"rng_stream2: {self.make_rng('rng_stream2')}\")\n\nrng_module_two_streams = RNGModuleTwoStreams()\nvariables = rng_module_two_streams.init(\n  {'rng_stream1': jax.random.key(0), 'rng_stream2': jax.random.key(1)}\n)\n```\n\nProviding the same seed PRNG key will result in the same values being generated (provided that the same operations are used for those keys).\n\n```{code-cell}\n:outputId: b70be039-589a-48f7-dc54-65e78c449c65\n\nvariables = rng_module_two_streams.init(\n  {'rng_stream1': jax.random.key(0), 'rng_stream2': jax.random.key(0)}\n)\n```\n\n### How `self.make_rng` works under the hood\n\n+++\n\nThis is what happens when `self.make_rng` (`flax.linen.Module.make_rng`) is called:\n* The following data is collected:\n  * The path of the `Module` as provided by `self.scope.path` (the top-level root module has an empty path `()`).\n  * The `self.make_rng` call count. That is, the number of times `self.make_rng` has been called for this specific stream (including this call).\n    * **Note:** Each sub-`Module` will have its own individual call count that's separate from other `Module`s. For example, a `Module` that has called `self.make_rng('params')` twice and contains a sub-`Module` that has called `self.make_rng('params')` once, will have a call count of 2 and 1 for each of the RNG stream `'params'`, respectively.\n* The data is bundled into a tuple and fed into a hash function and produces an integer.\n* The generated integer is folded into the RNG stream's starting seed PRNG key to generate a new, unique PRNG key.\n\n+++\n\nBelow is a slightly simplified version of the hash function that Flax uses for `self.make_rng`:\n\n```{code-cell}\ndef produce_hash(data):\n  m = hashlib.sha1()\n  for x in data:\n    if isinstance(x, str):\n      m.update(x.encode('utf-8'))\n    elif isinstance(x, int):\n      m.update(x.to_bytes((x.bit_length() + 7) // 8, byteorder='big'))\n    else:\n      raise ValueError(f'Expected int or string, got: {x}')\n  d = m.digest()\n  hash_int = int.from_bytes(d[:4], byteorder='big')\n  return hash_int\n```\n\nAnd now you can manually reproduce the PRNG keys generated from `self.make_rng`:\n\n```{code-cell}\n:outputId: d26b7355-9e8b-4954-b2f4-cf7520d5c5a3\n\nstream_seed = jax.random.key(0)\nfor call_count in range(1, 4):\n  hash_int = produce_hash(data=(call_count,))\n  print(jax.random.fold_in(stream_seed, jnp.uint32(hash_int)))\n```\n\n```{code-cell}\n:outputId: dec627a6-4c5a-4e3e-ce11-ce4f72775261\n\nvariables = rng_module.init({'rng_stream': jax.random.key(0)})\n```\n\n### Sub-`Module`s and `self.make_rng`\n\n+++\n\nThis section explores how `self.make_rng` (`flax.linen.Module.make_rng`) behaves with sub-`Module`s.\n\nConsider the following example:\n\n```{code-cell}\n:outputId: 5b7a9ae9-ca49-4ac0-d007-5caeee739ff0\n\nclass RNGSubSubModule(nn.Module):\n  def __call__(self):\n    print(f\"{self.name}, count 1: {self.make_rng('rng_stream')}\")\n    print(f\"{self.name}, count 2: {self.make_rng('rng_stream')}\")\n\nclass RNGSubModule(nn.Module):\n  @nn.compact\n  def __call__(self):\n    print(f\"{self.name}, count 1: {self.make_rng('rng_stream')}\")\n    print(f\"{self.name}, count 2: {self.make_rng('rng_stream')}\")\n    RNGSubSubModule()()\n\nclass RNGModule(nn.Module):\n  @nn.compact\n  def __call__(self):\n    print(f\"RNGModule, count 1: {self.make_rng('rng_stream')}\")\n    print(f\"RNGModule, count 2: {self.make_rng('rng_stream')}\")\n    RNGSubModule()()\n\nrng_module = RNGModule()\nvariables = rng_module.init({'rng_stream': jax.random.key(0)})\n```\n\nAs previously discussed, the data that is fed into the Flax hash function consists of:\n\n  * The path of the `Module`, provided by `self.scope.path` (the top-level root module has an empty path `()`); and\n  * The call count for the specific RNG stream.\n\nIn addition, note that each Flax `Module` and sub-`Module` have their own individual call counts, even for the same RNG stream. The convention for sub-`Module` names is: `f'{module_name}_{module_number}'`. For example, the first `Dense` sub-`Module` will be called `Dense_0`, the second one will be called `Dense_1`, and so on.\n\nTherefore, the following data will be fed into the hash function:\n\n  * For `RNGModule`: The data is just the call count, such as `(1,)` and `(2,)`, since the root `Module` has an empty path.\n  * For `RNGSubModule`: The data is `('RNGSubModule_0', 1)` and `('RNGSubModule_0', 2)`.\n  * For `RNGSubSubModule`: The data is `('RNGSubModule_0', 'RNGSubSubModule_0', 1)` and `('RNGSubModule_0', 'RNGSubSubModule_0', 2)`.\n\nWith this data, you can manually reproduce the PRNG keys generated from the `Module` and sub-`Module`s using `self.make_rng`.\n\nFor example:\n\n```{code-cell}\n:outputId: c0de4d37-0f00-4e58-bdfd-e8a6454ed681\n\nstream_seed = jax.random.key(0)\nfor initial_data in ((), ('RNGSubModule_0',), ('RNGSubModule_0', 'RNGSubSubModule_0')):\n  if initial_data:\n    module_name = initial_data[-1]\n  else:\n    module_name = 'RNGModule'\n  for call_count in (1, 2):\n    hash_int = produce_hash(data=initial_data+(call_count,))\n    rng_key = jax.random.fold_in(stream_seed, jnp.uint32(hash_int))\n    print(f\"{module_name}, count {call_count}: {rng_key}\")\n```\n\nIf the same sub-`Module` class is used multiple times, you can increment the suffix of the sub-`Module` name accordingly. For example: `RNGSubModule_0`, `RNGSubModule_1`, and so on.\n\n```{code-cell}\n:outputId: 0b77a038-7000-407b-c5b8-a28dea7951d1\n\nclass RNGSubModule(nn.Module):\n  @nn.compact\n  def __call__(self):\n    print(f\"{self.name}, count 1: {self.make_rng('rng_stream')}\")\n    print(f\"{self.name}, count 2: {self.make_rng('rng_stream')}\")\n\nclass RNGModule(nn.Module):\n  @nn.compact\n  def __call__(self):\n    print(f\"RNGModule, count 1: {self.make_rng('rng_stream')}\")\n    print(f\"RNGModule, count 2: {self.make_rng('rng_stream')}\")\n    RNGSubModule()()\n    RNGSubModule()()\n\nrng_module = RNGModule()\nvariables = rng_module.init({'rng_stream': jax.random.key(0)})\n```\n\n```{code-cell}\n:outputId: d189d25e-425d-4fd7-fe18-2dfd63f28b87\n\nstream_seed = jax.random.key(0)\nfor initial_data in ((), ('RNGSubModule_0',), ('RNGSubModule_1',)):\n  if initial_data:\n    module_name = initial_data[-1]\n  else:\n    module_name = 'RNGModule'\n  for call_count in (1, 2):\n    hash_int = produce_hash(data=initial_data+(call_count,))\n    rng_key = jax.random.fold_in(stream_seed, jnp.uint32(hash_int))\n    print(f\"{module_name}, count {call_count}: {rng_key}\")\n```\n\n### Using `self.param` and `self.variable`\n\n+++\n\nFlax users have the option of creating additional parameters and variables in their modules by using the [`self.param`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.param) and [`self.variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.variable) `Module` methods. An `init_fn` argument must be passed to these methods so that it can generate the initial value of the parameter/variable. `self.make_rng` is commonly used implicitly or explicitly in this `init_fn`, since many initializer functions are stochastic in nature and require a PRNG key. See the full list of Flax initializers [here](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/initializers.html).\n\nThere are a couple of differences between the two methods that the user should take note of:\n* `self.param` always creates a parameter in the `'params'` [collection](https://flax.readthedocs.io/en/latest/glossary.html#term-Params-parameters), whereas `self.variable` creates a variable in any [collection](https://flax.readthedocs.io/en/latest/glossary.html#term-Variable-collections) the user specifies\n* `self.param` will automatically call `self.make_rng('params')` and pass in the generated PRNG key implicitly to the `init_fn` of the parameter you instantiated (it will be passed in as the first argument), whereas users will have to manually specify what RNG stream to call `self.make_rng` on in the `init_fn` of `self.variable` (it could be `'params'` or something different).\n\nBelow is an example using both `self.param` and `self.variable`:\n\n```{code-cell}\n:outputId: a7816385-0e08-48e2-dc51-055d7bcd0bab\n\nclass Model(nn.Module):\n  @nn.compact\n  def __call__(self, x):\n    # kernel will use 'params' seed, initial data will include 'Dense_0', call count 1\n    x = nn.Dense(2, kernel_init=jax.random.normal, use_bias=False)(x)\n    # model_param will use 'params' seed, call count 1\n    model_param = self.param('model_param', jax.random.normal, x.shape)\n    # model_variable1 will use 'params' seed, call count 2\n    model_variable1 = self.variable(\n      'other_collection',\n      'model_variable1',\n      lambda: jax.random.normal(self.make_rng('params'), x.shape),\n    )\n    # model_variable2 will use 'other' seed, call count 1\n    model_variable2 = self.variable(\n      'other_collection',\n      'model_variable2',\n      lambda: jax.random.normal(self.make_rng('other'), x.shape),\n    )\n    # kernel will use 'params' seed, initial data will include 'Dense_1', call count 1\n    # bias will use 'params' seed, initial data will include 'Dense_1', call count 2\n    x = nn.Dense(2, kernel_init=jax.random.normal, bias_init=jax.random.normal)(\n      x\n    )\n    return x\n\nmodel = Model()\nvariables = model.init(\n  {'params': jax.random.key(0), 'other': jax.random.key(1)}, jnp.ones((2, 2))\n)\nprint(variables['params']['Dense_0']['kernel'])\nprint(variables['params']['model_param'])\nprint(variables['other_collection']['model_variable1'])\nprint(variables['other_collection']['model_variable2'])\nprint(variables['params']['Dense_1']['kernel'])\nprint(variables['params']['Dense_1']['bias'])\n```\n\nRemember:\n* there is a separate count for each RNG stream; this is why the count for `self.make_rng('other')` starts at 1 even though there were earlier calls of `self.make_rng('params')`\n* each submodule has their own separate count for each rng stream; this is why each `Dense` layer has their own separate count for `self.make_rng('params')` and why `model_param` and `model_variable1` share the same count (since they are defined within the same top-level parent module)\n\n```{code-cell}\n:outputId: ccec9d64-9a27-47f7-adaf-b36a5ea655db\n\nparams_seed = jax.random.key(0)\nother_seed = jax.random.key(1)\nfor initial_data, count, seed, shape in (\n  (('Dense_0',), 1, params_seed, (2, 2)),\n  ((), 1, params_seed, (2, 2)),\n  ((), 2, params_seed, (2, 2)),\n  ((), 1, other_seed, (2, 2)),\n  (('Dense_1',), 1, params_seed, (2, 2)),\n  (('Dense_1',), 2, params_seed, (1, 2)),\n):\n  hash_int = produce_hash(data=(*initial_data, count))\n  rng_key = jax.random.fold_in(seed, jnp.uint32(hash_int))\n  print(jax.random.normal(rng_key, shape))\n```\n\n### Managing RNG streams inside a training loop\n\n+++\n\nBelow is an example of managing RNG streams from `self.make_rng`, `self.param`, `self.variable` and `nn.Dropout` in a training loop (note: `nn.Dropout` requires a seed PRNG key to be passed in the `'dropout'` RNG stream, since it implicitly calls `self.make_rng('dropout')`):\n\n```{code-cell}\nclass SubModule(nn.Module):\n  @nn.compact\n  def __call__(self, x, train):\n    # variables created using `self.param` will use `self.make_rng('params')`\n    kernel = self.param('submodule_kernel', jax.random.normal, x.shape)\n    x = x + kernel\n    # `nn.Dropout` will use self.make_rng('dropout')\n    x = nn.Dropout(0.2)(x, deterministic=not train)\n    # `nn.Dense` will use self.make_rng('params')\n    x = nn.Dense(3)(x)\n    return x\n\nclass Model(nn.Module):\n  @nn.compact\n  def __call__(self, x, train):\n    # make kernel use `self.make_rng('other')`\n    kernel = self.variable(\n      'other_collection',\n      'module_kernel',\n      lambda: jax.random.normal(self.make_rng('other'), x.shape),\n    )\n    x = (\n      x + kernel.value\n    )  # `.value` will extract the underlying value of the variable\n    x = SubModule()(x, train)\n    # `nn.Dropout` will use self.make_rng('dropout')\n    x = nn.Dropout(0.2)(x, deterministic=not train)\n    # `nn.Dense` will use self.make_rng('params')\n    x = nn.Dense(2)(x)\n    return x\n\nparams_rng, other_rng, train_rng = jax.random.split(jax.random.key(0), 3)\ninit_rngs = {'params': params_rng, 'other': other_rng}\n\nx = jnp.ones((1, 3))\ny = jnp.ones((1, 2))\n\nmodule = Model()\nvariables = module.init(init_rngs, x, train=False)\n```\n\n```{code-cell}\n:outputId: e9da8228-acba-403d-bcb5-33a39d4d530d\n\ndef update(variables, rng):\n  # we don't need to provide a 'params' or 'other' rng, as only 'dropout' rng will be used during training\n  # split the rng to get a dropout_rng to be used for this training iteration,\n  # and to get another rng key to be used for the next training iteration\n  dropout_rng, next_rng = jax.random.split(rng)\n  def loss(params):\n    out = module.apply(\n      {'params': params, 'other_collection': variables['other_collection']},\n      x,\n      train=True,\n      rngs={'dropout': dropout_rng},\n    )\n    return jnp.mean((y - out) ** 2)\n  grads = jax.grad(loss)(variables['params'])\n  params = jax.tree_util.tree_map(lambda p, g: p - 1e-3 * g, variables['params'], grads)\n  return {\n    'params': params,\n    'other_collection': variables['other_collection'],\n  }, next_rng\n\nfor _ in range(10):\n  variables, train_rng = update(variables, train_rng)\n  out = module.apply(variables, x, train=False)\n  print(jnp.mean((y - out)**2))\n```\n\n### 🔪 Sharp edge 🔪 - unintentionally generating the same values\n\nThere is an edge case where the same value can be unintentionally generated.\nSee the [Flax issue](https://github.com/google/flax/issues/2157) for more details.\n\n```{code-cell}\n:outputId: 887142ff-c9ca-4aae-d9fa-cc9993d809c5\n\nclass Leaf(nn.Module):\n  def __call__(self, x):\n    return x + jax.random.randint(self.make_rng(\"rng\"), (), 0, 100)\n\nclass Node(nn.Module):\n  leaf_name: str\n  @nn.compact\n  def __call__(self, x):\n    return Leaf(name=self.leaf_name)(x)\n\nclass Model(nn.Module):\n  @nn.compact\n  def __call__(self, x):\n    return (Node(name=\"ab\", leaf_name=\"cdef\")(x),\n            Node(name=\"abc\", leaf_name=\"def\")(x),\n    )\n\nout1, out2 = Model().apply({}, 0, rngs={\"rng\": jax.random.key(33)})\nout1 == out2 # same output, despite having different submodule names\n```\n\nThis occurs because the hash function [concatenates strings together](https://docs.python.org/3/library/hashlib.html#hashlib.hash.update), so the data `('AB', 'C')` is equivalent to data `('A', 'BC')` when fed into the hash function, therefore producing the same hash int.\n\n```{code-cell}\n:outputId: 001cbd49-129b-4474-c6a1-3255a4ee3dfe\n\nprint(produce_hash(data=('A', 'B', 'C', 1)))\nprint(produce_hash(data=('AB', 'C', 1)))\nprint(produce_hash(data=('A', 'BC', 1)))\nprint(produce_hash(data=('ABC', 1)))\n```\n\nTo avoid this edge case, users can flip the `flax_fix_rng_separator` [configuration flag](https://flax.readthedocs.io/en/latest/api_reference/flax.config.html#flax.configurations.Config.flax_fix_rng_separator) to `True`.\n\n```{code-cell}\n:outputId: 35a2b204-bdfd-4f83-8e98-ba723963cb0c\n\nflax.config.update('flax_fix_rng_separator', True)\nout1, out2 = Model().apply({}, 0, rngs={\"rng\": jax.random.key(33)})\nout1 == out2 # different output\n```\n\n## Managing RNG's on multiple devices\n\n+++\n\nThis section will show examples on how to use `jit` and `shard_map` to use RNG's in multi-device settings.\n\n+++\n\n### Using `jax.jit`\n\n+++\n\nWhen using [`jax.jit`](https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html), we can use RNG's as we did before, but we now include `in_shardings` and `out_shardings` arguments to specify how to shard input and output data. The RNG key itself gets replicated (not sharded); `jax.jit` makes each device use it as appropriate for its shard of the data.\n\nFor more details on training on multiple devices in Flax using `jax.jit`, see our [Scale up Flax Modules on multiple devices guide](https://flax.readthedocs.io/en/latest/guides/parallel_training/flax_on_pjit.html#) and [lm1b example](https://github.com/google/flax/tree/main/examples/lm1b).\n\n```{code-cell}\n:outputId: 6c280522-4b43-4b82-f40a-b73986659b2c\n\n# Create a mesh and annotate the axis with a name.\ndevice_mesh = mesh_utils.create_device_mesh((8,))\nprint(device_mesh)\n\nmesh = Mesh(devices=device_mesh, axis_names=('data',))\nprint(mesh)\n\ndata_sharding = NamedSharding(mesh, PartitionSpec('data',))\nprint(data_sharding)\n```\n\n```{code-cell}\n:outputId: d1bfbcad-e28a-4fae-8136-98bd1efb9332\n\nclass Model(nn.Module):\n  @nn.compact\n  def __call__(self, x, add_noise):\n    x = nn.Dense(1)(x)\n    # use jnp.where for control flow; for more details see: https://jax.readthedocs.io/en/latest/errors.html#jax.errors.TracerBoolConversionError\n    return jnp.where(\n      add_noise, x + jax.random.normal(self.make_rng('params'), x.shape), x\n    )\n\nmodule = Model()\ninit_rng, apply_rng = jax.random.split(jax.random.key(0))\nx = jnp.ones((8, 1))\nvariables = module.init(init_rng, x, False)\n\n# create custom forward function, since jit does not support kwargs when in_shardings is specified\ndef forward(variables, x, add_noise, rng):\n  return module.apply(variables, x, add_noise, rngs={'params': rng})\n\n# shard the inputs x across devices\n# replicate the variables, add_noise boolean and rng key across devices\n# shard the output across devices\njit_forward = jax.jit(\n  forward,\n  in_shardings=(None, data_sharding, None, None),\n  out_shardings=data_sharding,\n)\nout = jit_forward(variables, x, True, apply_rng)\nout\n```\n\nThe output is different given the same input, meaning the RNG key was used to add noise to the output.\n\nWe can also confirm that the output is sharded across devices:\n\n```{code-cell}\n:outputId: b672b85f-7a2d-44b5-afc1-bbf9426655ed\n\nout.addressable_shards\n```\n\nAnother way to visualize the output sharding:\n\n```{code-cell}\n:outputId: 1c0a16ce-fa3f-4b95-d794-58464bbaa9ae\n\njax.debug.visualize_array_sharding(out)\n```\n\nIf we choose not to add noise, then the output is the same across all batches (as expected, since the input is the same for all batches):\n\n```{code-cell}\n:outputId: fe9ec875-3e7f-4861-babc-f07064737276\n\nout = jit_forward(variables, x, False, apply_rng)\nout\n```\n\nWe can confirm the un-jitted function produces the same values, albeit unsharded (note there may be small numerical differences due to compiler optimizations from jitting):\n\n```{code-cell}\n:outputId: 0a9e5f2c-d4bf-4051-bf71-f32a9c32dc06\n\nout = forward(variables, x, True, apply_rng)\nout\n```\n\n```{code-cell}\n:outputId: 772a5063-1bd5-46b4-f6f6-cae9b4b81a26\n\nout = forward(variables, x, False, apply_rng)\nout\n```\n\n### Using `shard_map`\n\n+++\n\nWhen using [`jax.experimental.shard_map.shard_map`](https://jax.readthedocs.io/en/latest/jep/14273-shard-map.html), the important parts to remember are to:\n* split your PRNG key to produce a different key for each device\n* the PRNG keys will be sharded automatically to each device (provided you use the correct partition specification), but the [**rank of the original batched PRNG key array will not be reduced**](https://jax.readthedocs.io/en/latest/jep/14273-shard-map.html#rank-reducing-vs-rank-preserving-maps-over-array-axes); e.g.\nwith a batch of 8 PRNG keys and 8 devices, each device will see a PRNG key batch of size 1 within the `shard_map`-ed function\n  * therefore to access the PRNG key itself, we need to index slice into it (see the example below)\n\n```{code-cell}\n:outputId: aa00b9a3-24ba-4048-ed8c-afbb9070f039\n\ndef forward(variables, x, add_noise, rng_key_batch):\n  # rng_key_batch is a batch of size 1 containing 1 PRNG key\n  # index slice into the rng_key_batch to access the PRNG key\n  return module.apply(\n    variables, x, add_noise, rngs={'params': rng_key_batch[0]}\n  )\n\n# define partition specifications\ndata_pspec = PartitionSpec('data')\nno_pspec = PartitionSpec()\n\n# shard the inputs x and rng keys across devices\n# replicate the variables and add_noise boolean across devices\n# shard the output across devices\nshmap_forward = shard_map(\n  forward,\n  mesh=mesh,\n  in_specs=(no_pspec, data_pspec, no_pspec, data_pspec),\n  out_specs=data_pspec,\n)\n# get 8 different rng's that will be used by the 8 devices when doing forward inference\napply_rngs = jax.random.split(apply_rng, 8)\nout = shmap_forward(variables, x, True, apply_rngs)\nout\n```\n\nConfirm that the output is sharded across devices:\n\n```{code-cell}\n:outputId: e304289b-ef1c-4e4a-d4c1-4c41613bfa62\n\nout.addressable_shards\n```\n\n```{code-cell}\n:outputId: 52fdb6d2-4c4f-44b3-feee-4bc5363c8f2f\n\njax.debug.visualize_array_sharding(out)\n```\n\n## Lifted transforms\n\n+++\n\n[Flax lifted transforms](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/transformations.html) allow you to use [JAX transforms](https://github.com/jax-ml/jax#transformations) with `Module` arguments. This section will show you how to control how PRNG keys are split in Flax lifted transforms.\n\nRefer to [Lifted transformations](https://flax.readthedocs.io/en/latest/developer_notes/lift.html) for more detail.\n\n+++\n\n### `nn.vmap`\n\n+++\n\nWe can use [`nn.vmap`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/_autosummary/flax.linen.vmap.html) to create a batched `Dense` layer:\n\n```{code-cell}\n:outputId: f0830f6b-659c-446f-c933-7b2a430f8004\n\nx = jnp.ones((3, 2))\n\nBatchDense = nn.vmap(\n    nn.Dense,\n    in_axes=0, out_axes=0,\n    variable_axes={'params': None},\n    split_rngs={'params': False})\n\nBatchDense(2).init(jax.random.key(0), x)\n```\n\nBy denoting `variable_axes={'params': 0}'`, we vectorize the `params` Arrays on the first axis. However the parameter values generated are all identical to each other:\n\n```{code-cell}\n:outputId: eef5c0ca-f8d5-4f25-8ce6-9f2f60622daf\n\nBatchDense = nn.vmap(\n    nn.Dense,\n    in_axes=0, out_axes=0,\n    variable_axes={'params': 0},\n    split_rngs={'params': False})\n\nBatchDense(2).init(jax.random.key(0), x)\n```\n\nIf we also make `split_rngs={'params': True}`, then the PRNG key we provide is split across the variable axis (in this case, the batch axis 0), and we can generate different parameters for each batch input:\n\n```{code-cell}\n:outputId: 275699c3-ba48-403e-877d-07b65981cff5\n\nBatchDense = nn.vmap(\n    nn.Dense,\n    in_axes=0, out_axes=0,\n    variable_axes={'params': 0},\n    split_rngs={'params': True})\n\nBatchDense(2).init(jax.random.key(0), x)\n```\n\nAdding a variable via `self.variable` is straightforward:\n\n```{code-cell}\n:outputId: c11a80bc-d865-4e2e-e059-4d6bcea79e09\n\nclass Model(nn.Module):\n  @nn.compact\n  def __call__(self, x):\n    x = nn.Dense(2)(x)\n    kernel = self.variable(\n      'other_collection',\n      'kernel',\n      lambda: jax.random.normal(self.make_rng('other'), x.shape),\n    )\n    return x + kernel.value\n\nBatchModel = nn.vmap(\n  Model,\n  in_axes=0,\n  out_axes=0,\n  variable_axes={'params': 0, 'other_collection': 0},\n  split_rngs={'params': True, 'other': True},\n)\n\nBatchModel().init({'params': jax.random.key(0), 'other': jax.random.key(1)}, x)\n```\n\nWe can control which RNG stream to split, for example, if we only wanted to split the `'params'` RNG stream, then the variables generated from `self.variable` will be the same for each batch input:\n\n```{code-cell}\n:outputId: fb16619c-c975-497d-c867-6fd5143b4507\n\nBatchModel = nn.vmap(\n    Model,\n    in_axes=0, out_axes=0,\n    variable_axes={'params': 0, 'other_collection': 0},\n    split_rngs={'params': True, 'other': False})\n\nBatchModel().init({'params': jax.random.key(0), 'other': jax.random.key(1)}, x)\n```\n\nWe can also control which parameters / variables should be generated for each batch input, for example, if we only wanted `'params'` to generate separate parameters for each batch input:\n\n```{code-cell}\n:outputId: f3a17d59-6f75-4408-caba-5769d4589263\n\nBatchModel = nn.vmap(\n    Model,\n    in_axes=0, out_axes=0,\n    variable_axes={'params': 0, 'other_collection': None},\n    split_rngs={'params': True, 'other': False})\n\nBatchModel().init({'params': jax.random.key(0), 'other': jax.random.key(1)}, x)\n```\n\n### `nn.scan`\n\n+++\n\nWe can use [`nn.scan`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/_autosummary/flax.linen.scan.html) to create a scanned `Module` layer (this is useful for simplifying repetitively stacked submodules):\n\n```{code-cell}\n:outputId: 29d1863b-809f-42ce-894c-1b0810faa41e\n\nx = jnp.ones((3, 2))\n\nclass ResidualMLPBlock(nn.Module):\n  @nn.compact\n  def __call__(self, x, _):\n    h = nn.Dense(features=2)(x)\n    h = nn.relu(h)\n    return x + h, None # return an empty carry\n\nScanMLP = nn.scan(\n      ResidualMLPBlock, variable_axes={'params': 0},\n      variable_broadcast=False, split_rngs={'params': True},\n      length=3)\n\nScanMLP().init(jax.random.key(0), x, None) # pass in an empty carry\n```\n\nSimilar to before, we can control whether to split the RNG stream or not, for example, if we wanted all the stacked modules to be initialized to the same parameter values, we can pass in `split_rngs={'params': False}`:\n\n```{code-cell}\n:outputId: 6a825bcd-9c3b-43c2-afd2-42500d89fb26\n\nScanMLP = nn.scan(\n      ResidualMLPBlock, variable_axes={'params': 0},\n      variable_broadcast=False, split_rngs={'params': False},\n      length=3)\n\nScanMLP().init(jax.random.key(0), x, None)\n```\n"
  },
  {
    "path": "docs/guides/flax_fundamentals/setup_or_nncompact.rst",
    "content": "``setup`` vs ``compact``\n=========================================\n\nIn Flax's module system (named `Linen`_), submodules and variables (parameters or others)\ncan be defined in two ways:\n\n1. **Explicitly** (using ``setup``):\n\n   Assign submodules or variables to ``self.<attr>`` inside a\n   :meth:`setup <flax.linen.Module.setup>` method. Then use the submodules\n   and variables assigned to ``self.<attr>`` in ``setup`` from\n   any \"forward pass\" method defined on the class.\n   This resembles how modules are defined in PyTorch.\n\n2. **In-line** (using ``nn.compact``):\n\n   Write your network's logic directly within a single \"forward pass\" method annotated\n   with :meth:`nn.compact <flax.linen.compact>`. This allows you to define your whole module\n   in a single method, and \"co-locate\" submodules and variables next to\n   where they are used.\n\n**Both of these approaches are perfectly valid, behave the same way, and interoperate with all of Flax**.\n\nHere is a short example of a module defined in both ways, with exactly\nthe same functionality.\n\n.. testsetup:: Using ``setup``, Using ``nn.compact``\n\n  import flax.linen as nn\n\n.. codediff::\n  :title: Using ``setup``, Using ``nn.compact``\n\n  class MLP(nn.Module):\n    def setup(self):\n      # Submodule names are derived by the attributes you assign to. In this\n      # case, \"dense1\" and \"dense2\". This follows the logic in PyTorch.\n      self.dense1 = nn.Dense(32)\n      self.dense2 = nn.Dense(32)\n\n    def __call__(self, x):\n      x = self.dense1(x)\n      x = nn.relu(x)\n      x = self.dense2(x)\n      return x\n  ---\n  class MLP(nn.Module):\n\n    @nn.compact #!\n    def __call__(self, x):\n      x = nn.Dense(32, name=\"dense1\")(x) #!\n      x = nn.relu(x)\n      x = nn.Dense(32, name=\"dense2\")(x) #!\n      return x\n\nSo, how would you decide which style to use? It can be a matter of taste, but here are some pros and cons:\n\nReasons to prefer using ``nn.compact``:\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n1. Allows defining submodules, parameters and other variables next to where they are used: less\n   scrolling up/down to see how everything is defined.\n2. Reduces code duplication when there are conditionals or for loops that conditionally define\n   submodules, parameters or variables.\n3. Code typically looks more like mathematical notation: ``y = self.param('W', ...) @ x + self.param('b', ...)``\n   looks similar to :math:`y=Wx+b``)\n4. If you are using shape inference, i.e. using parameters whose shape/value depend on shapes of\n   the inputs (which are unknown at initialization), this is not possible using ``setup``.\n\nReasons to prefer using ``setup``:\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n1. Closer to the PyTorch convention, thus easier when porting models\n   from PyTorch\n2. Some people find it more natural to explicitly separate the definition\n   of submodules and variables from where they are used\n3. Allows defining more than one \"forward pass\" method\n   (see :class:`MultipleMethodsCompactError <flax.errors.MultipleMethodsCompactError>`)\n\n\n\n\n.. _`Linen`: https://jax.readthedocs.io/en/latest/notebooks/thinking_in_jax.html#JIT-mechanics:-tracing-and-static-variables\n"
  },
  {
    "path": "docs/guides/flax_fundamentals/state_params.rst",
    "content": "Managing Parameters and State\n=============================\n\nWe will show you how to...\n\n* manage the variables from initialization to updates.\n* split and re-assemble parameters and state.\n* use :code:`vmap` with batch-dependant state.\n\n.. testsetup::\n\n  import flax\n  from flax import linen as nn\n  from jax import random\n  import jax.numpy as jnp\n  import jax\n  import optax\n\n  # Create some fake data and run only for one epoch for testing.\n  dummy_input = jnp.ones((3, 4))\n  num_epochs = 1\n\n.. testcode::\n\n  class BiasAdderWithRunningMean(nn.Module):\n    momentum: float = 0.9\n\n    @nn.compact\n    def __call__(self, x):\n      is_initialized = self.has_variable('batch_stats', 'mean')\n      mean = self.variable('batch_stats', 'mean', jnp.zeros, x.shape[1:])\n      bias = self.param('bias', lambda rng, shape: jnp.zeros(shape), x.shape[1:])\n      if is_initialized:\n        mean.value = (self.momentum * mean.value +\n                      (1.0 - self.momentum) * jnp.mean(x, axis=0, keepdims=True))\n      return mean.value + bias\n\nThis example model is a minimal example that contains both parameters (declared\nwith :code:`self.param`) and state variables (declared with\n:code:`self.variable`).\n\nThe tricky part with initialization here is that we need to split the state\nvariables and the parameters we're going to optimize for.\n\nFirst we define ``update_step`` as follows (with a dummy loss that should be\nreplaced with yours):\n\n.. testcode::\n\n  def update_step(apply_fn, x, opt_state, params, state):\n    def loss(params):\n      y, updated_state = apply_fn({'params': params, **state},\n                                  x, mutable=list(state.keys()))\n      l = ((x - y) ** 2).sum() # Replace with your loss here.\n      return l, updated_state\n\n    (l, updated_state), grads = jax.value_and_grad(\n        loss, has_aux=True)(params)\n    updates, opt_state = tx.update(grads, opt_state)  # Defined below.\n    params = optax.apply_updates(params, updates)\n    return opt_state, params, updated_state\n\nThen we can write the actual training code.\n\n.. testcode::\n\n  model = BiasAdderWithRunningMean()\n  variables = model.init(random.key(0), dummy_input)\n  # Split state and params (which are updated by optimizer).\n  state, params = flax.core.pop(variables, 'params')\n  del variables  # Delete variables to avoid wasting resources\n  tx = optax.sgd(learning_rate=0.02)\n  opt_state = tx.init(params)\n\n  for _ in range(num_epochs):\n    opt_state, params, state = update_step(\n        model.apply, dummy_input, opt_state, params, state)\n\n\n:code:`vmap` accross the batch dimension\n----------------------------------------\nWhen using :code:`vmap` and managing state that depends on the batch dimension,\nfor example when using :code:`BatchNorm`,  the setup above must be modified\nslightly. This is because any layer whose state depends on the batch dimension\nis not strictly vectorizable. In the case of :code:`BatchNorm`,\n:code:`lax.pmean()` must be used to average the statistics over the batch\ndimension so that the state is in sync for each item in the batch.\n\nThis requires two small changes. Firstly, we need to name the batch axis in our\nmodel definition. Here, this is done by specifying the :code:`axis_name`\nargument of :code:`BatchNorm`. In your own code this might require specifying\nthe :code:`axis_name` argument of :code:`lax.pmean()` directly.\n\n.. testsetup::\n\n  from functools import partial\n  from flax import linen as nn\n  from jax import random\n  import jax.numpy as jnp\n  import jax\n  import optax\n\n  # Create some fake data and run only for one epoch for testing.\n  dummy_input = jnp.ones((100,))\n  key1, key2 = random.split(random.key(0), num=2)\n  batch_size = 64\n  X = random.normal(key1, (batch_size, 100))\n  Y = random.normal(key2, (batch_size, 1))\n  num_epochs = 1\n\n.. testcode::\n\n  class MLP(nn.Module):\n    hidden_size: int\n    out_size: int\n\n    @nn.compact\n    def __call__(self, x, train=False):\n      norm = partial(\n          nn.BatchNorm,\n          use_running_average=not train,\n          momentum=0.9,\n          epsilon=1e-5,\n          axis_name=\"batch\", # Name batch dim\n      )\n\n      x = nn.Dense(self.hidden_size)(x)\n      x = norm()(x)\n      x = nn.relu(x)\n      x = nn.Dense(self.hidden_size)(x)\n      x = norm()(x)\n      x = nn.relu(x)\n      y = nn.Dense(self.out_size)(x)\n\n      return y\n\nSecondly, we need to specify the same name when calling :code:`vmap` in our training code:\n\n.. testcode::\n\n  def update_step(apply_fn, x_batch, y_batch, opt_state, params, state):\n\n    def batch_loss(params):\n      def loss_fn(x, y):\n        pred, updated_state = apply_fn(\n          {'params': params, **state},\n          x, mutable=list(state.keys())\n        )\n        return (pred - y) ** 2, updated_state\n\n      loss, updated_state = jax.vmap(\n        loss_fn, out_axes=(0, None),  # Do not vmap `updated_state`.\n        axis_name='batch'  # Name batch dim\n      )(x_batch, y_batch)  # vmap only `x`, `y`, but not `state`.\n      return jnp.mean(loss), updated_state\n\n    (loss, updated_state), grads = jax.value_and_grad(\n      batch_loss, has_aux=True\n    )(params)\n\n    updates, opt_state = tx.update(grads, opt_state)  # Defined below.\n    params = optax.apply_updates(params, updates)\n    return opt_state, params, updated_state, loss\n\nNote that we also need to specify that the model state does not have a batch\ndimension. Now we are able to train the model:\n\n.. testcode::\n\n  model = MLP(hidden_size=10, out_size=1)\n  variables = model.init(random.key(0), dummy_input)\n  # Split state and params (which are updated by optimizer).\n  state, params = flax.core.pop(variables, 'params')\n  del variables  # Delete variables to avoid wasting resources\n  tx = optax.sgd(learning_rate=0.02)\n  opt_state = tx.init(params)\n\n  for _ in range(num_epochs):\n    opt_state, params, state, loss = update_step(\n        model.apply, X, Y, opt_state, params, state)\n"
  },
  {
    "path": "docs/guides/flax_sharp_bits.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# 🔪 Flax - The Sharp Bits 🔪\\n\",\n    \"\\n\",\n    \"Flax exposes the full power of JAX. And just like when using JAX, there are certain _[\\\"sharp bits\\\"](https://jax.readthedocs.io/en/latest/notebooks/Common_Gotchas_in_JAX.html)_ you may experience when working with Flax. This evolving document is designed to assist you with them.\\n\",\n    \"\\n\",\n    \"First, install and/or update Flax:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"! pip install -qq flax\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 🔪 `flax.linen.Dropout` layer and randomness\\n\",\n    \"\\n\",\n    \"### TL;DR\\n\",\n    \"\\n\",\n    \"When working on a model with dropout (subclassed from [Flax `Module`](https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/flax_basics.html#module-basics)), add the `'dropout'` PRNGkey only during the forward pass.\\n\",\n    \"\\n\",\n    \"1. Start with [`jax.random.split()`](https://jax.readthedocs.io/en/latest/_autosummary/jax.random.split.html#jax-random-split) to explicitly create PRNG keys for `'params'` and `'dropout'`.\\n\",\n    \"2. Add the [`flax.linen.Dropout`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/layers.html#flax.linen.Dropout) layer(s) to your model (subclassed from Flax [`Module`](https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/flax_basics.html#module-basics)).\\n\",\n    \"3. When initializing the model ([`flax.linen.init()`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/init_apply.html)), there's no need to pass in an extra `'dropout'` PRNG key—just the `'params'` key like in a \\\"simpler\\\" model.\\n\",\n    \"4. During the forward pass with [`flax.linen.apply()`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/init_apply.html), pass in `rngs={'dropout': dropout_key}`.\\n\",\n    \"\\n\",\n    \"Check out a full example below.\\n\",\n    \"\\n\",\n    \"### Why this works\\n\",\n    \"\\n\",\n    \"- Internally, `flax.linen.Dropout` makes use of [`flax.linen.Module.make_rng`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.make_rng) to create a key for dropout (check out the [source code](https://github.com/google/flax/blob/5714e57a0dc8146eb58a7a06ed768ed3a17672f9/flax/linen/stochastic.py#L72)).\\n\",\n    \"- Every time `make_rng` is called (in this case, it's done implicitly in `Dropout`), you get a new PRNG key split from the main/root PRNG key.\\n\",\n    \"- `make_rng` still _guarantees full reproducibility_.\\n\",\n    \"\\n\",\n    \"### Background \\n\",\n    \"\\n\",\n    \"The [dropout](https://jmlr.org/papers/volume15/srivastava14a/srivastava14a.pdf) stochastic regularization technique randomly removes hidden and visible units in a network. Dropout is a random operation, requiring a PRNG state, and Flax (like JAX) uses [Threefry](https://github.com/jax-ml/jax/blob/main/docs/jep/263-prng.md) PRNG that is splittable. \\n\",\n    \"\\n\",\n    \"> Note: Recall that JAX has an explicit way of giving you PRNG keys: you can fork the main PRNG state (such as `key = jax.random.key(seed=0)`) into multiple new PRNG keys with `key, subkey = jax.random.split(key)`. Refresh your memory in [🔪 JAX - The Sharp Bits 🔪 Randomness and PRNG keys](https://jax.readthedocs.io/en/latest/notebooks/Common_Gotchas_in_JAX.html#random-numbers).\\n\",\n    \"\\n\",\n    \"Flax provides an _implicit_ way of handling PRNG key streams via [Flax `Module`](https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/flax_basics.html#module-basics)'s [`flax.linen.Module.make_rng`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.make_rng) helper function. It allows the code in Flax `Module`s (or its sub-`Module`s) to \\\"pull PRNG keys\\\". `make_rng` guarantees to provide a unique key each time you call it. See the [RNG guide](https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/rng_guide.html) for more details.\\n\",\n    \"\\n\",\n    \"> Note: Recall that [`flax.linen.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html) is the base class for all neural network modules. All layers and models are subclassed from it.\\n\",\n    \"\\n\",\n    \"### Example\\n\",\n    \"\\n\",\n    \"Remember that each of the Flax PRNG streams has a name. The example below uses the `'params'` stream for initializing parameters, as well as the `'dropout'` stream. The PRNG key provided to [`flax.linen.init()`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/init_apply.html) is the one that seeds the `'params'` PRNG key stream. To draw PRNG keys during the forward pass (with dropout), provide a PRNG key to seed that stream (`'dropout'`) when you call `Module.apply()`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Setup.\\n\",\n    \"import jax\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"import flax.linen as nn\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Randomness.\\n\",\n    \"seed = 0\\n\",\n    \"root_key = jax.random.key(seed=seed)\\n\",\n    \"main_key, params_key, dropout_key = jax.random.split(key=root_key, num=3)\\n\",\n    \"\\n\",\n    \"# A simple network.\\n\",\n    \"class MyModel(nn.Module):\\n\",\n    \"  num_neurons: int\\n\",\n    \"  training: bool\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x = nn.Dense(self.num_neurons)(x)\\n\",\n    \"    # Set the dropout layer with a rate of 50% .\\n\",\n    \"    # When the `deterministic` flag is `True`, dropout is turned off.\\n\",\n    \"    x = nn.Dropout(rate=0.5, deterministic=not self.training)(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"# Instantiate `MyModel` (you don't need to set `training=True` to\\n\",\n    \"# avoid performing the forward pass computation).\\n\",\n    \"my_model = MyModel(num_neurons=3, training=False)\\n\",\n    \"\\n\",\n    \"x = jax.random.uniform(key=main_key, shape=(3, 4, 4))\\n\",\n    \"\\n\",\n    \"# Initialize with `flax.linen.init()`.\\n\",\n    \"# The `params_key` is equivalent to a dictionary of PRNGs.\\n\",\n    \"# (Here, you are providing only one PRNG key.) \\n\",\n    \"variables = my_model.init(params_key, x)\\n\",\n    \"\\n\",\n    \"# Perform the forward pass with `flax.linen.apply()`.\\n\",\n    \"y = my_model.apply(variables, x, rngs={'dropout': dropout_key})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Real-life examples:\\n\",\n    \"\\n\",\n    \"* Applying word dropout to a batch of input IDs (in a [text classification](https://github.com/google/flax/blob/main/examples/sst2/models.py) context).\\n\",\n    \"* Defining a prediction token in a decoder of a [sequence-to-sequence model](https://github.com/google/flax/blob/main/examples/seq2seq/models.py).\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"accelerator\": \"GPU\",\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.8.10\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "docs/guides/flax_sharp_bits.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# 🔪 Flax - The Sharp Bits 🔪\n\nFlax exposes the full power of JAX. And just like when using JAX, there are certain _[\"sharp bits\"](https://jax.readthedocs.io/en/latest/notebooks/Common_Gotchas_in_JAX.html)_ you may experience when working with Flax. This evolving document is designed to assist you with them.\n\nFirst, install and/or update Flax:\n\n```{code-cell} ipython3\n:tags: [skip-execution]\n\n! pip install -qq flax\n```\n\n## 🔪 `flax.linen.Dropout` layer and randomness\n\n### TL;DR\n\nWhen working on a model with dropout (subclassed from [Flax `Module`](https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/flax_basics.html#module-basics)), add the `'dropout'` PRNGkey only during the forward pass.\n\n1. Start with [`jax.random.split()`](https://jax.readthedocs.io/en/latest/_autosummary/jax.random.split.html#jax-random-split) to explicitly create PRNG keys for `'params'` and `'dropout'`.\n2. Add the [`flax.linen.Dropout`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/layers.html#flax.linen.Dropout) layer(s) to your model (subclassed from Flax [`Module`](https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/flax_basics.html#module-basics)).\n3. When initializing the model ([`flax.linen.init()`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/init_apply.html)), there's no need to pass in an extra `'dropout'` PRNG key—just the `'params'` key like in a \"simpler\" model.\n4. During the forward pass with [`flax.linen.apply()`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/init_apply.html), pass in `rngs={'dropout': dropout_key}`.\n\nCheck out a full example below.\n\n### Why this works\n\n- Internally, `flax.linen.Dropout` makes use of [`flax.linen.Module.make_rng`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.make_rng) to create a key for dropout (check out the [source code](https://github.com/google/flax/blob/5714e57a0dc8146eb58a7a06ed768ed3a17672f9/flax/linen/stochastic.py#L72)).\n- Every time `make_rng` is called (in this case, it's done implicitly in `Dropout`), you get a new PRNG key split from the main/root PRNG key.\n- `make_rng` still _guarantees full reproducibility_.\n\n### Background \n\nThe [dropout](https://jmlr.org/papers/volume15/srivastava14a/srivastava14a.pdf) stochastic regularization technique randomly removes hidden and visible units in a network. Dropout is a random operation, requiring a PRNG state, and Flax (like JAX) uses [Threefry](https://github.com/jax-ml/jax/blob/main/docs/jep/263-prng.md) PRNG that is splittable. \n\n> Note: Recall that JAX has an explicit way of giving you PRNG keys: you can fork the main PRNG state (such as `key = jax.random.key(seed=0)`) into multiple new PRNG keys with `key, subkey = jax.random.split(key)`. Refresh your memory in [🔪 JAX - The Sharp Bits 🔪 Randomness and PRNG keys](https://jax.readthedocs.io/en/latest/notebooks/Common_Gotchas_in_JAX.html#random-numbers).\n\nFlax provides an _implicit_ way of handling PRNG key streams via [Flax `Module`](https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/flax_basics.html#module-basics)'s [`flax.linen.Module.make_rng`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.make_rng) helper function. It allows the code in Flax `Module`s (or its sub-`Module`s) to \"pull PRNG keys\". `make_rng` guarantees to provide a unique key each time you call it. See the [RNG guide](https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/rng_guide.html) for more details.\n\n> Note: Recall that [`flax.linen.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html) is the base class for all neural network modules. All layers and models are subclassed from it.\n\n### Example\n\nRemember that each of the Flax PRNG streams has a name. The example below uses the `'params'` stream for initializing parameters, as well as the `'dropout'` stream. The PRNG key provided to [`flax.linen.init()`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/init_apply.html) is the one that seeds the `'params'` PRNG key stream. To draw PRNG keys during the forward pass (with dropout), provide a PRNG key to seed that stream (`'dropout'`) when you call `Module.apply()`.\n\n```{code-cell} ipython3\n# Setup.\nimport jax\nimport jax.numpy as jnp\nimport flax.linen as nn\n```\n\n```{code-cell} ipython3\n# Randomness.\nseed = 0\nroot_key = jax.random.key(seed=seed)\nmain_key, params_key, dropout_key = jax.random.split(key=root_key, num=3)\n\n# A simple network.\nclass MyModel(nn.Module):\n  num_neurons: int\n  training: bool\n  @nn.compact\n  def __call__(self, x):\n    x = nn.Dense(self.num_neurons)(x)\n    # Set the dropout layer with a rate of 50% .\n    # When the `deterministic` flag is `True`, dropout is turned off.\n    x = nn.Dropout(rate=0.5, deterministic=not self.training)(x)\n    return x\n\n# Instantiate `MyModel` (you don't need to set `training=True` to\n# avoid performing the forward pass computation).\nmy_model = MyModel(num_neurons=3, training=False)\n\nx = jax.random.uniform(key=main_key, shape=(3, 4, 4))\n\n# Initialize with `flax.linen.init()`.\n# The `params_key` is equivalent to a dictionary of PRNGs.\n# (Here, you are providing only one PRNG key.) \nvariables = my_model.init(params_key, x)\n\n# Perform the forward pass with `flax.linen.apply()`.\ny = my_model.apply(variables, x, rngs={'dropout': dropout_key})\n```\n\nReal-life examples:\n\n* Applying word dropout to a batch of input IDs (in a [text classification](https://github.com/google/flax/blob/main/examples/sst2/models.py) context).\n* Defining a prediction token in a decoder of a [sequence-to-sequence model](https://github.com/google/flax/blob/main/examples/seq2seq/models.py).\n"
  },
  {
    "path": "docs/guides/index.rst",
    "content": "Guides\n======\n\n.. toctree::\n   :maxdepth: 2\n\n   flax_fundamentals/index\n   data_preprocessing/index\n   training_techniques/index\n   parallel_training/index\n   model_inspection/index\n   converting_and_upgrading/index\n   quantization/index\n   The Sharp Bits <flax_sharp_bits>\n"
  },
  {
    "path": "docs/guides/model_inspection/extracting_intermediates.rst",
    "content": "Extracting intermediate values\n==============================\n\nThis guide will show you how to extract intermediate values from a module.\nLet's start with this simple CNN that uses :code:`nn.compact`.\n\n.. testsetup:: default, sow\n\n  import flax\n  import flax.linen as nn\n  import jax\n  import jax.numpy as jnp\n  from flax.core import FrozenDict\n  from typing import Sequence\n\n  batch = jnp.ones((4, 32, 32, 3))\n\n.. testcode::\n\n  from flax import linen as nn\n  import jax\n  import jax.numpy as jnp\n  from typing import Sequence\n\n  class CNN(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      x = nn.Conv(features=32, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = nn.Conv(features=64, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = x.reshape((x.shape[0], -1))  # flatten\n      x = nn.Dense(features=256)(x)\n      x = nn.relu(x)\n      x = nn.Dense(features=10)(x)\n      x = nn.log_softmax(x)\n      return x\n\n\nBecause this module uses ``nn.compact``, we don't have direct access to\nintermediate values. There are a few ways to expose them:\n\n\nStore intermediate values in a new variable collection\n------------------------------------------------------\n\nThe CNN can be augmented with calls to ``sow`` to store intermediates as following:\n\n\n.. codediff::\n  :title: Default CNN, CNN using sow API\n  :groups: default, sow\n\n  class CNN(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      x = nn.Conv(features=32, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = nn.Conv(features=64, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = x.reshape((x.shape[0], -1))  # flatten\n\n      x = nn.Dense(features=256)(x)\n      x = nn.relu(x)\n      x = nn.Dense(features=10)(x)\n      x = nn.log_softmax(x)\n      return x\n  ---\n  class SowCNN(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      x = nn.Conv(features=32, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = nn.Conv(features=64, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = x.reshape((x.shape[0], -1))  # flatten\n      self.sow('intermediates', 'features', x) #!\n      x = nn.Dense(features=256)(x)\n      x = nn.relu(x)\n      x = nn.Dense(features=10)(x)\n      x = nn.log_softmax(x)\n      return x\n\n``sow`` acts as a no-op when the variable collection is not mutable.\nTherefore, it works perfectly for debugging and optional tracking of intermediates.\nThe 'intermediates' collection is also used by the ``capture_intermediates`` API (see the :ref:`Use ``capture_intermediates``` section).\n\nNote that, by default ``sow`` appends values every time it is called:\n\n* This is necessary because once instantiated, a module could be called multiple\n  times in its parent module, and we want to catch all the sowed values.\n* Therefore you want to make sure that you **do not** feed intermediate values back\n  into ``variables``. Otherwise every call will increase the length of that tuple\n  and trigger a recompile.\n* To override the default append behavior, specify ``init_fn`` and ``reduce_fn``\n  - see :meth:`Module.sow() <flax.linen.Module.sow>`.\n\n.. testcode:: sow\n\n  class SowCNN2(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      mod = SowCNN(name='SowCNN')\n      return mod(x) + mod(x)  # Calling same module instance twice.\n\n  @jax.jit\n  def init(key, x):\n    variables = SowCNN2().init(key, x)\n    # By default the 'intermediates' collection is not mutable during init.\n    # So variables will only contain 'params' here.\n    return variables\n\n  @jax.jit\n  def predict(variables, x):\n    # If mutable='intermediates' is not specified, then .sow() acts as a noop.\n    output, mod_vars = SowCNN2().apply(variables, x, mutable='intermediates')\n    features = mod_vars['intermediates']['SowCNN']['features']\n    return output, features\n\n  batch = jnp.ones((1,28,28,1))\n  variables = init(jax.random.key(0), batch)\n  preds, feats = predict(variables, batch)\n\n  assert len(feats) == 2  # Tuple with two values since module was called twice.\n\nRefactor module into submodules\n-------------------------------\n\nThis is a useful pattern for cases where it's clear in what particular\nway you want to split your submodules. Any submodule you expose in ``setup`` can\nbe used directly. In the limit, you can define all submodules in ``setup`` and\navoid using ``nn.compact`` altogether.\n\n.. testcode::\n\n  class RefactoredCNN(nn.Module):\n    def setup(self):\n      self.features = Features()\n      self.classifier = Classifier()\n\n    def __call__(self, x):\n      x = self.features(x)\n      x = self.classifier(x)\n      return x\n\n  class Features(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      x = nn.Conv(features=32, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = nn.Conv(features=64, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = x.reshape((x.shape[0], -1))  # flatten\n      return x\n\n  class Classifier(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      x = nn.Dense(features=256)(x)\n      x = nn.relu(x)\n      x = nn.Dense(features=10)(x)\n      x = nn.log_softmax(x)\n      return x\n\n  @jax.jit\n  def init(key, x):\n    variables = RefactoredCNN().init(key, x)\n    return variables['params']\n\n  @jax.jit\n  def features(params, x):\n    return RefactoredCNN().apply({\"params\": params}, x,\n      method=lambda module, x: module.features(x))\n\n  params = init(jax.random.key(0), batch)\n\n  features(params, batch)\n\n\nUse ``capture_intermediates``\n-----------------------------\n\nLinen supports the capture of intermediate return values from submodules automatically without any code changes.\nThis pattern should be considered the \"sledge hammer\" approach to capturing intermediates.\nAs a debugging and inspection tool it is very useful, but using the other patterns described in this guide\nwill give you more fine-grained control over what intermediates you want to extract.\n\nIn the following code example we check if any intermediate activations are non-finite (NaN or infinite):\n\n.. testcode::\n\n  @jax.jit\n  def init(key, x):\n    variables = CNN().init(key, x)\n    return variables\n\n  @jax.jit\n  def predict(variables, x):\n    y, state = CNN().apply(variables, x, capture_intermediates=True, mutable=[\"intermediates\"])\n    intermediates = state['intermediates']\n    fin = jax.tree_util.tree_map(lambda xs: jnp.all(jnp.isfinite(xs)), intermediates)\n    return y, fin\n\n  variables = init(jax.random.key(0), batch)\n  y, is_finite = predict(variables, batch)\n  all_finite = all(jax.tree_util.tree_leaves(is_finite))\n  assert all_finite, \"non-finite intermediate detected!\"\n\nBy default only the intermediates of ``__call__`` methods are collected.\nAlternatively, you can pass a custom filter function based on the ``Module`` instance and the method name.\n\n.. testcode::\n\n  filter_Dense = lambda mdl, method_name: isinstance(mdl, nn.Dense)\n  filter_encodings = lambda mdl, method_name: method_name == \"encode\"\n\n  y, state = CNN().apply(variables, batch, capture_intermediates=filter_Dense, mutable=[\"intermediates\"])\n  dense_intermediates = state['intermediates']\n\nNote that ``capture_intermediates`` will only apply to layers. You can use ``self.sow`` to manually store\nnon-layer intermediates, but the filter function won't be applied to it.\n\n.. codediff::\n  :title: Capturing all layer intermediates, Using filter function and ``self.sow()``\n  :groups: default, sow\n\n  class Model(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      a = nn.Dense(4)(x) # Dense_0\n      b = nn.Dense(4)(x) # Dense_1\n      c = a + b # not a Flax layer, so won't be stored as an intermediate\n      d = nn.Dense(4)(c) # Dense_2\n      return d\n\n  @jax.jit\n  def init(key, x):\n    variables = Model().init(key, x)\n    return variables['params']\n\n  @jax.jit\n  def predict(params, x):\n    return Model().apply({\"params\": params}, x, capture_intermediates=True)\n\n  batch = jax.random.uniform(jax.random.key(1), (1,3))\n  params = init(jax.random.key(0), batch)\n  preds, feats = predict(params, batch)\n  feats # intermediate c in Model was not stored because it's not a Flax layer\n  ---\n  class Model(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      a = nn.Dense(4)(x) # Dense_0\n      b = nn.Dense(4)(x) # Dense_1\n      c = a + b\n      self.sow('intermediates', 'c', c) # store intermediate c #!\n      d = nn.Dense(4)(c) # Dense_2\n      return d\n\n  @jax.jit\n  def init(key, x):\n    variables = Model().init(key, x)\n    return variables['params']\n\n  @jax.jit\n  def predict(params, x):\n    # filter specifically for only the Dense_0 and Dense_2 layer #!\n    filter_fn = lambda mdl, method_name: isinstance(mdl.name, str) and (mdl.name in {'Dense_0', 'Dense_2'}) #!\n    return Model().apply({\"params\": params}, x, capture_intermediates=filter_fn) #!\n\n  batch = jax.random.uniform(jax.random.key(1), (1,3))\n  params = init(jax.random.key(0), batch)\n  preds, feats = predict(params, batch)\n  feats # intermediate c in Model is stored and isn't filtered out by the filter function #!\n\nTo separate the intermediates extracted from ``self.sow`` from the intermediates extracted from ``capture_intermediates``,\nwe can either define a separate collection like ``self.sow('sow_intermediates', 'c', c)``, or manually filter out\nthe intermediates after calling ``.apply()``. For example:\n\n.. testcode:: sow\n\n  flattened_dict = flax.traverse_util.flatten_dict(feats['intermediates'], sep='/')\n  flattened_dict['c']\n\nIn terms of efficiency, as long as everything is jitted, then any intermediates you don't end up using\nshould be optimized away by XLA.\n\nUse ``Sequential``\n---------------------\n\nYou could also define ``CNN`` using a simple implementation of a ``Sequential`` combinator (this is quite common in more stateful approaches). This may be useful\nfor very simple models and gives you arbitrary model\nsurgery. But it can be very limiting -- if you even want to add one conditional, you are\nforced to refactor away from ``Sequential`` and structure\nyour model more explicitly.\n\n.. testcode::\n\n  class Sequential(nn.Module):\n    layers: Sequence[nn.Module]\n\n    def __call__(self, x):\n      for layer in self.layers:\n        x = layer(x)\n      return x\n\n  def SeqCNN():\n    return Sequential([\n      nn.Conv(features=32, kernel_size=(3, 3)),\n      nn.relu,\n      lambda x: nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2)),\n      nn.Conv(features=64, kernel_size=(3, 3)),\n      nn.relu,\n      lambda x: nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2)),\n      lambda x: x.reshape((x.shape[0], -1)),  # flatten\n      nn.Dense(features=256),\n      nn.relu,\n      nn.Dense(features=10),\n      nn.log_softmax,\n    ])\n\n  @jax.jit\n  def init(key, x):\n    variables = SeqCNN().init(key, x)\n    return variables['params']\n\n  @jax.jit\n  def features(params, x):\n    return Sequential(SeqCNN().layers[0:7]).apply({\"params\": params}, x)\n\n  batch = jnp.ones((1,28,28,1))\n  params = init(jax.random.key(0), batch)\n  features(params, batch)\n\nExtracting gradients of intermediate values\n===========================================\nFor debugging purposes, it can be useful to extract the gradients of intermediate values.\nThis can be done by using the :meth:`Module.perturb() <flax.linen.Module.perturb>` method over the desired values.\n\n.. testcode::\n\n  class Model(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      x = nn.relu(nn.Dense(8)(x))\n      x = self.perturb('hidden', x)\n      x = nn.Dense(2)(x)\n      x = self.perturb('logits', x)\n      return x\n\n``perturb`` adds a variable to a ``perturbations`` collection by default,\nit behaves like an identity function and the gradient of the perturbation\nmatches the gradient of the input. To get the perturbations just initialize\nthe model:\n\n.. testcode::\n\n  x = jnp.empty((1, 4)) # random data\n  y = jnp.empty((1, 2)) # random data\n\n  model = Model()\n  variables = model.init(jax.random.key(1), x)\n  params, perturbations = variables['params'], variables['perturbations']\n\nFinally compute the gradients of the loss with respect to the perturbations,\nthese will match the gradients of the intermediates:\n\n.. testcode::\n\n  def loss_fn(params, perturbations, x, y):\n    y_pred = model.apply({'params': params, 'perturbations': perturbations}, x)\n    return jnp.mean((y_pred - y) ** 2)\n\n  intermediate_grads = jax.grad(loss_fn, argnums=1)(params, perturbations, x, y)"
  },
  {
    "path": "docs/guides/model_inspection/index.rst",
    "content": "Model inspection\n================\n\n.. toctree::\n   :maxdepth: 1\n\n   model_surgery\n   extracting_intermediates\n"
  },
  {
    "path": "docs/guides/model_inspection/model_surgery.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"120e57f5\",\n   \"metadata\": {},\n   \"source\": [\n    \"Model surgery\\n\",\n    \"==============================\\n\",\n    \"\\n\",\n    \"Usually, Flax modules and optimizers track and update the params for you. But there may be some time when you want to do some model surgery and tweak the param tensors yourself. This guide shows you how to do the trick.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"9c3bfb0e\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Setup\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"413f8b2d\",\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"!pip install --upgrade -q pip jax jaxlib flax\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"5b002c8d\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import functools\\n\",\n    \"\\n\",\n    \"import jax\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"from flax import traverse_util\\n\",\n    \"from flax import linen as nn\\n\",\n    \"from flax.core import freeze\\n\",\n    \"import jax\\n\",\n    \"import optax\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1060b519\",\n   \"metadata\": {},\n   \"source\": [\n    \"Surgery with Flax Modules\\n\",\n    \"--------------------------------\\n\",\n    \"\\n\",\n    \"Let's create a small convolutional neural network model for our demo.\\n\",\n    \"\\n\",\n    \"As usual, you can run `CNN.init(...)['params']` to get the `params` to pass and modify it in every step of your training.\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"755ae323\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class CNN(nn.Module):\\n\",\n    \"    @nn.compact\\n\",\n    \"    def __call__(self, x):\\n\",\n    \"      x = nn.Conv(features=32, kernel_size=(3, 3))(x)\\n\",\n    \"      x = nn.relu(x)\\n\",\n    \"      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\\n\",\n    \"      x = nn.Conv(features=64, kernel_size=(3, 3))(x)\\n\",\n    \"      x = nn.relu(x)\\n\",\n    \"      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\\n\",\n    \"      x = x.reshape((x.shape[0], -1))\\n\",\n    \"      x = nn.Dense(features=256)(x)\\n\",\n    \"      x = nn.relu(x)\\n\",\n    \"      x = nn.Dense(features=10)(x)\\n\",\n    \"      x = nn.log_softmax(x)\\n\",\n    \"      return x\\n\",\n    \"\\n\",\n    \"def get_initial_params(key):\\n\",\n    \"    init_shape = jnp.ones((1, 28, 28, 1), jnp.float32)\\n\",\n    \"    initial_params = CNN().init(key, init_shape)['params']\\n\",\n    \"    return initial_params\\n\",\n    \"\\n\",\n    \"key = jax.random.key(0)\\n\",\n    \"params = get_initial_params(key)\\n\",\n    \"\\n\",\n    \"jax.tree_util.tree_map(jnp.shape, params)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"170273f8\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that what returned as `params` is a `FrozenDict`, which contains a few JAX arrays as kernel and bias. \\n\",\n    \"\\n\",\n    \"A `FrozenDict` is nothing more than a read-only dict, and Flax made it read-only because of the functional nature of JAX: JAX arrays are immutable, and the new `params` need to replace the old `params`. Making the dict read-only ensures that no in-place mutation of the dict can happen accidentally during the training and updating.\\n\",\n    \"\\n\",\n    \"One way to actually modify the params outside of a Flax module is to explicitly flatten it and creates a mutable dict. Note that you can use a separator `sep` to join all nested keys. If no `sep` is given, the key will be a tuple of all nested keys.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"c7ec7741\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Get a flattened key-value list.\\n\",\n    \"flat_params = traverse_util.flatten_dict(params, sep='/')\\n\",\n    \"\\n\",\n    \"jax.tree_util.tree_map(jnp.shape, flat_params)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"2adda656\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now you can do whatever you want with the params. When you are done, unflatten it back and use it in future training.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"bb975feb\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Somehow modify a layer\\n\",\n    \"dense_kernel = flat_params['Dense_1/kernel']\\n\",\n    \"flat_params['Dense_1/kernel'] = dense_kernel / jnp.linalg.norm(dense_kernel)\\n\",\n    \"\\n\",\n    \"# Unflatten.\\n\",\n    \"unflat_params = traverse_util.unflatten_dict(flat_params, sep='/')\\n\",\n    \"# Refreeze.\\n\",\n    \"unflat_params = freeze(unflat_params)\\n\",\n    \"jax.tree_util.tree_map(jnp.shape, unflat_params)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"f3462cd8\",\n   \"metadata\": {},\n   \"source\": [\n    \"Surgery with Optimizers\\n\",\n    \"--------------------------------\\n\",\n    \"\\n\",\n    \"When using `Optax` as an optimizer, the ``opt_state`` is actually a nested tuple\\n\",\n    \"of the states of individual gradient transformations that compose the optimizer.\\n\",\n    \"These states contain pytrees that mirror the parameter tree, and can be modified\\n\",\n    \"the same way: flattening, modifying, unflattening, and then recreating a new\\n\",\n    \"optimizer state that mirrors the original state.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"3cbecb63\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"tx = optax.adam(1.0)\\n\",\n    \"opt_state = tx.init(params)\\n\",\n    \"\\n\",\n    \"# The optimizer state is a tuple of gradient transformation states.\\n\",\n    \"jax.tree_util.tree_map(jnp.shape, opt_state)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"18f1cebb\",\n   \"metadata\": {},\n   \"source\": [\n    \"The pytrees inside the optimizer state follow the same structure as the\\n\",\n    \"parameters and can be flattened / modified exactly the same way.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"13b5e25f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"flat_mu = traverse_util.flatten_dict(opt_state[0].mu, sep='/')\\n\",\n    \"flat_nu = traverse_util.flatten_dict(opt_state[0].nu, sep='/')\\n\",\n    \"\\n\",\n    \"jax.tree_util.tree_map(jnp.shape, flat_mu)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"e5c4479e\",\n   \"metadata\": {},\n   \"source\": [\n    \"After modification, re-create optimizer state. Use this for future training.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"9dcac8cd\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"opt_state = (\\n\",\n    \"    opt_state[0]._replace(\\n\",\n    \"        mu=traverse_util.unflatten_dict(flat_mu, sep='/'),\\n\",\n    \"        nu=traverse_util.unflatten_dict(flat_nu, sep='/'),\\n\",\n    \"    ),\\n\",\n    \") + opt_state[1:]\\n\",\n    \"jax.tree_util.tree_map(jnp.shape, opt_state)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"md,ipynb\",\n   \"main_language\": \"python\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.9.15\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs/guides/model_inspection/model_surgery.md",
    "content": "---\njupyter:\n  jupytext:\n    formats: md,ipynb\n    main_language: python\n    text_representation:\n      extension: .md\n      format_name: markdown\n      format_version: '1.3'\n      jupytext_version: 1.13.8\n---\n\nModel surgery\n==============================\n\nUsually, Flax modules and optimizers track and update the params for you. But there may be some time when you want to do some model surgery and tweak the param tensors yourself. This guide shows you how to do the trick.\n\n\n## Setup\n\n```python tags=[\"skip-execution\"]\n!pip install --upgrade -q pip jax jaxlib flax\n```\n\n```python\nimport functools\n\nimport jax\nimport jax.numpy as jnp\nfrom flax import traverse_util\nfrom flax import linen as nn\nfrom flax.core import freeze\nimport jax\nimport optax\n```\n\nSurgery with Flax Modules\n--------------------------------\n\nLet's create a small convolutional neural network model for our demo.\n\nAs usual, you can run `CNN.init(...)['params']` to get the `params` to pass and modify it in every step of your training.\n\n\n```python\nclass CNN(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      x = nn.Conv(features=32, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = nn.Conv(features=64, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = x.reshape((x.shape[0], -1))\n      x = nn.Dense(features=256)(x)\n      x = nn.relu(x)\n      x = nn.Dense(features=10)(x)\n      x = nn.log_softmax(x)\n      return x\n\ndef get_initial_params(key):\n    init_shape = jnp.ones((1, 28, 28, 1), jnp.float32)\n    initial_params = CNN().init(key, init_shape)['params']\n    return initial_params\n\nkey = jax.random.key(0)\nparams = get_initial_params(key)\n\njax.tree_util.tree_map(jnp.shape, params)\n```\n\nNote that what returned as `params` is a `FrozenDict`, which contains a few JAX arrays as kernel and bias. \n\nA `FrozenDict` is nothing more than a read-only dict, and Flax made it read-only because of the functional nature of JAX: JAX arrays are immutable, and the new `params` need to replace the old `params`. Making the dict read-only ensures that no in-place mutation of the dict can happen accidentally during the training and updating.\n\nOne way to actually modify the params outside of a Flax module is to explicitly flatten it and creates a mutable dict. Note that you can use a separator `sep` to join all nested keys. If no `sep` is given, the key will be a tuple of all nested keys.\n\n```python\n# Get a flattened key-value list.\nflat_params = traverse_util.flatten_dict(params, sep='/')\n\njax.tree_util.tree_map(jnp.shape, flat_params)\n```\n\nNow you can do whatever you want with the params. When you are done, unflatten it back and use it in future training.\n\n```python\n# Somehow modify a layer\ndense_kernel = flat_params['Dense_1/kernel']\nflat_params['Dense_1/kernel'] = dense_kernel / jnp.linalg.norm(dense_kernel)\n\n# Unflatten.\nunflat_params = traverse_util.unflatten_dict(flat_params, sep='/')\n# Refreeze.\nunflat_params = freeze(unflat_params)\njax.tree_util.tree_map(jnp.shape, unflat_params)\n```\n\nSurgery with Optimizers\n--------------------------------\n\nWhen using `Optax` as an optimizer, the ``opt_state`` is actually a nested tuple\nof the states of individual gradient transformations that compose the optimizer.\nThese states contain pytrees that mirror the parameter tree, and can be modified\nthe same way: flattening, modifying, unflattening, and then recreating a new\noptimizer state that mirrors the original state.\n\n```python\ntx = optax.adam(1.0)\nopt_state = tx.init(params)\n\n# The optimizer state is a tuple of gradient transformation states.\njax.tree_util.tree_map(jnp.shape, opt_state)\n```\n\nThe pytrees inside the optimizer state follow the same structure as the\nparameters and can be flattened / modified exactly the same way.\n\n```python\nflat_mu = traverse_util.flatten_dict(opt_state[0].mu, sep='/')\nflat_nu = traverse_util.flatten_dict(opt_state[0].nu, sep='/')\n\njax.tree_util.tree_map(jnp.shape, flat_mu)\n```\n\nAfter modification, re-create optimizer state. Use this for future training.\n\n```python\nopt_state = (\n    opt_state[0]._replace(\n        mu=traverse_util.unflatten_dict(flat_mu, sep='/'),\n        nu=traverse_util.unflatten_dict(flat_nu, sep='/'),\n    ),\n) + opt_state[1:]\njax.tree_util.tree_map(jnp.shape, opt_state)\n```\n"
  },
  {
    "path": "docs/guides/parallel_training/ensembling.rst",
    "content": "Ensembling on multiple devices\n==============================\n\nWe show how to train an ensemble of CNNs on the MNIST dataset, where the size of\nthe ensemble is equal to the number of available devices. In short, this change\nbe described as:\n\n* make a number of functions parallel using |jax.pmap()|_,\n* split the random seed to obtain different parameter initialization,\n* replicate the inputs and unreplicate the outputs where necessary,\n* average probabilities across devices to compute the predictions.\n\nIn this HOWTO we omit some of the code such as imports, the CNN module, and\nmetrics computation, but they can be found in the `MNIST example`_.\n\n.. testsetup:: Single-model, Ensemble\n\n  import functools\n  from flax import jax_utils\n\n  # Copied from examples/mnist/train.py\n  from absl import logging\n  from flax import linen as nn\n  from flax.training import train_state\n  import jax\n  import jax.numpy as jnp\n  import numpy as np\n  import optax\n\n  class CNN(nn.Module):\n    \"\"\"A simple CNN model.\"\"\"\n\n    @nn.compact\n    def __call__(self, x):\n      x = nn.Conv(features=32, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = nn.Conv(features=64, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = x.reshape((x.shape[0], -1))  # flatten\n      x = nn.Dense(features=256)(x)\n      x = nn.relu(x)\n      x = nn.Dense(features=10)(x)\n      return x\n\n  # Fake data for faster execution.\n  def get_datasets():\n    train_ds = test_ds = {\n      'image': jnp.zeros([64, 28, 28, 1]),\n      'label': jnp.zeros([64], jnp.int32),\n    }\n    return train_ds, test_ds\n\n  # Modified from examples/mnist/configs.default.py\n  learning_rate = 0.1\n  momentum = 0.9\n  batch_size = 32\n  num_epochs = 1\n\n\nParallel functions\n------------------\n\nWe start by creating a parallel version of ``create_train_state()``, which\nretrieves the initial parameters of the models. We do this using |jax.pmap()|_.\nThe effect of \"pmapping\" a function is that it will compile the function with\nXLA (similar to |jax.jit()|_), but execute it in parallel on XLA devices (e.g.,\nGPUs/TPUs).\n\n.. codediff::\n  :title: Single-model, Ensemble\n  :sync:\n\n  #!\n  def create_train_state(rng, learning_rate, momentum):\n    cnn = CNN()\n    params = cnn.init(rng, jnp.ones([1, 28, 28, 1]))['params']\n    tx = optax.sgd(learning_rate, momentum)\n    return train_state.TrainState.create(\n        apply_fn=cnn.apply, params=params, tx=tx)\n  ---\n  @functools.partial(jax.pmap, static_broadcasted_argnums=(1, 2))  #!\n  def create_train_state(rng, learning_rate, momentum):\n    cnn = CNN()\n    params = cnn.init(rng, jnp.ones([1, 28, 28, 1]))['params']\n    tx = optax.sgd(learning_rate, momentum)\n    return train_state.TrainState.create(\n        apply_fn=cnn.apply, params=params, tx=tx)\n\nNote that for the single-model code above, we use |jax.jit()|_ to lazily\ninitialize the model (see `Module.init`_'s documentation for more details).\nFor the ensembling case, |jax.pmap()|_ will map over the first axis of the\nprovided argument ``rng`` by default, so we should make sure that we provide\na different value for each device when we call this function later on.\n\nNote also how we specify that ``learning_rate`` and ``momentum`` are static\narguments, which means the concrete values of these arguments will be used,\nrather than abstract shapes. This is necessary because the provided arguments\nwill be scalar values. For more details see `JIT mechanics: tracing and static\nvariables`_.\n\nNext we simply do the same for the functions ``apply_model()`` and\n``update_model()``. To compute the predictions from the ensemble, we take the\naverage of the individual probabilities. We use |jax.lax.pmean()|_ to compute\nthe average *across devices*. This also requires us to specify the\n``axis_name`` to both |jax.pmap()|_ and |jax.lax.pmean()|_.\n\n.. codediff::\n  :title: Single-model, Ensemble\n  :sync:\n\n  @jax.jit  #!\n  def apply_model(state, images, labels):\n    def loss_fn(params):\n      logits = CNN().apply({'params': params}, images)\n      one_hot = jax.nn.one_hot(labels, 10)\n      loss = optax.softmax_cross_entropy(logits=logits, labels=one_hot).mean()\n      return loss, logits\n\n    grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n    (loss, logits), grads = grad_fn(state.params)\n    #!\n    accuracy = jnp.mean(jnp.argmax(logits, -1) == labels)  #!\n    return grads, loss, accuracy\n\n  @jax.jit  #!\n  def update_model(state, grads):\n    return state.apply_gradients(grads=grads)\n  ---\n  @functools.partial(jax.pmap, axis_name='ensemble')  #!\n  def apply_model(state, images, labels):\n    def loss_fn(params):\n      logits = CNN().apply({'params': params}, images)\n      one_hot = jax.nn.one_hot(labels, 10)\n      loss = optax.softmax_cross_entropy(logits=logits, labels=one_hot).mean()\n      return loss, logits\n\n    grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n    (loss, logits), grads = grad_fn(state.params)\n    probs = jax.lax.pmean(jax.nn.softmax(logits), axis_name='ensemble')  #!\n    accuracy = jnp.mean(jnp.argmax(probs, -1) == labels)  #!\n    return grads, loss, accuracy\n\n  @jax.pmap  #!\n  def update_model(state, grads):\n    return state.apply_gradients(grads=grads)\n\nTraining the Ensemble\n---------------------\n\nNext we transform the ``train_epoch()`` function. When calling the pmapped\nfunctions from above, we mainly need to take care of duplicating the arguments\nfor all devices where necessary, and de-duplicating the return values.\n\n.. codediff::\n  :title: Single-model, Ensemble\n  :sync:\n\n  def train_epoch(state, train_ds, batch_size, rng):\n    train_ds_size = len(train_ds['image'])\n    steps_per_epoch = train_ds_size // batch_size\n\n    perms = jax.random.permutation(rng, len(train_ds['image']))\n    perms = perms[:steps_per_epoch * batch_size]\n    perms = perms.reshape((steps_per_epoch, batch_size))\n\n    epoch_loss = []\n    epoch_accuracy = []\n\n    for perm in perms:\n      batch_images = train_ds['image'][perm, ...]  #!\n      batch_labels = train_ds['label'][perm, ...]  #!\n      grads, loss, accuracy = apply_model(state, batch_images, batch_labels)\n      state = update_model(state, grads)\n      epoch_loss.append(loss)  #!\n      epoch_accuracy.append(accuracy)  #!\n    train_loss = np.mean(epoch_loss)\n    train_accuracy = np.mean(epoch_accuracy)\n    return state, train_loss, train_accuracy\n  ---\n  def train_epoch(state, train_ds, batch_size, rng):\n    train_ds_size = len(train_ds['image'])\n    steps_per_epoch = train_ds_size // batch_size\n\n    perms = jax.random.permutation(rng, len(train_ds['image']))\n    perms = perms[:steps_per_epoch * batch_size]\n    perms = perms.reshape((steps_per_epoch, batch_size))\n\n    epoch_loss = []\n    epoch_accuracy = []\n\n    for perm in perms:\n      batch_images = jax_utils.replicate(train_ds['image'][perm, ...])  #!\n      batch_labels = jax_utils.replicate(train_ds['label'][perm, ...])  #!\n      grads, loss, accuracy = apply_model(state, batch_images, batch_labels)\n      state = update_model(state, grads)\n      epoch_loss.append(jax_utils.unreplicate(loss))  #!\n      epoch_accuracy.append(jax_utils.unreplicate(accuracy))  #!\n    train_loss = np.mean(epoch_loss)\n    train_accuracy = np.mean(epoch_accuracy)\n    return state, train_loss, train_accuracy\n\nAs can be seen, we do not have to make any changes to the logic around the\n``state``. This is because, as we will see below in our training code,\nthe train state is replicated already, so when we pass it to ``train_step()``,\nthings will just work fine since ``train_step()`` is pmapped. However,\nthe train dataset is not yet replicated, so we do that here. Since replicating\nthe entire train dataset is too memory intensive we do it at the batch level.\n\nWe can now rewrite the actual training logic. This consists of two simple\nchanges: making sure the RNGs are replicated when we pass them to\n``create_train_state()``, and replicating the test dataset, which is much\nsmaller than the train dataset so we can do this for the entire dataset\ndirectly.\n\n.. codediff::\n  :title: Single-model, Ensemble\n  :sync:\n\n  train_ds, test_ds = get_datasets()\n  #!\n  rng = jax.random.key(0)\n\n  rng, init_rng = jax.random.split(rng)\n  state = create_train_state(init_rng, learning_rate, momentum)  #!\n  #!\n\n  for epoch in range(1, num_epochs + 1):\n    rng, input_rng = jax.random.split(rng)\n    state, train_loss, train_accuracy = train_epoch(\n        state, train_ds, batch_size, input_rng)\n\n    _, test_loss, test_accuracy = apply_model(  #!\n        state, test_ds['image'], test_ds['label'])  #!\n\n    logging.info(\n        'epoch:% 3d, train_loss: %.4f, train_accuracy: %.2f, '\n        'test_loss: %.4f, test_accuracy: %.2f'\n        % (epoch, train_loss, train_accuracy * 100, test_loss,\n           test_accuracy * 100))\n  ---\n  train_ds, test_ds = get_datasets()\n  test_ds = jax_utils.replicate(test_ds)  #!\n  rng = jax.random.key(0)\n\n  rng, init_rng = jax.random.split(rng)\n  state = create_train_state(jax.random.split(init_rng, jax.device_count()), #!\n                             learning_rate, momentum)  #!\n\n  for epoch in range(1, num_epochs + 1):\n    rng, input_rng = jax.random.split(rng)\n    state, train_loss, train_accuracy = train_epoch(\n        state, train_ds, batch_size, input_rng)\n\n    _, test_loss, test_accuracy = jax_utils.unreplicate(  #!\n        apply_model(state, test_ds['image'], test_ds['label']))  #!\n\n    logging.info(\n        'epoch:% 3d, train_loss: %.4f, train_accuracy: %.2f, '\n        'test_loss: %.4f, test_accuracy: %.2f'\n        % (epoch, train_loss, train_accuracy * 100, test_loss,\n           test_accuracy * 100))\n\n\n.. |jax.jit()| replace:: ``jax.jit()``\n.. _jax.jit(): https://jax.readthedocs.io/en/latest/notebooks/thinking_in_jax.html#To-JIT-or-not-to-JIT\n.. |jax.pmap()| replace:: ``jax.pmap()``\n.. _jax.pmap(): https://jax.readthedocs.io/en/latest/jax.html#jax.pmap\n.. |jax.lax.pmean()| replace:: ``jax.lax.pmean()``\n.. _jax.lax.pmean(): https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.pmean.html\n.. _Module.init: https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.init\n.. _`JIT mechanics: tracing and static variables`: https://jax.readthedocs.io/en/latest/notebooks/thinking_in_jax.html#JIT-mechanics:-tracing-and-static-variables\n.. _`MNIST example`: https://github.com/google/flax/blob/main/examples/mnist/train.py\n"
  },
  {
    "path": "docs/guides/parallel_training/flax_on_pjit.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Scale up Flax Modules on multiple devices\\n\",\n    \"\\n\",\n    \"This guide shows how to scale up [Flax Modules](https://flax.readthedocs.io/en/latest/developer_notes/module_lifecycle.html) on multiple devices and hosts using [`jax.jit`](https://jax.readthedocs.io/en/latest/jax-101/02-jitting.html) (formerly [`experimental.pjit`](https://jax.readthedocs.io/en/latest/jax.experimental.pjit.html#module-jax.experimental.pjit)) and [`flax.linen`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/index.html).\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Flax and `jax.jit` scaled up\\n\",\n    \"\\n\",\n    \"[`jax.jit`](https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html) follows the [Single Program Multi Data (SPMD)](https://jax.readthedocs.io/en/latest/glossary.html#term-SPMD) paradigm and automatically compiles your code to run it on multiple devices. You need to only specify how you want the input and output of your code to be partitioned, and the compiler will figure out how to: 1) partition everything inside; and 2) compile inter-device communications.\\n\",\n    \"\\n\",\n    \"Flax provides several functionalities that can help you use auto-SPMD on [Flax Modules](https://flax.readthedocs.io/en/latest/developer_notes/module_lifecycle.html), including:\\n\",\n    \"\\n\",\n    \"1. An interface to specify partitions of your data when defining [`flax.linen.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html).\\n\",\n    \"2. Utility functions to generate the sharding information that `jax.jit` requires to run.\\n\",\n    \"3. An interface to customize your axis names called \\\"logical axis annotations\\\" to decouple both your Module code and partition plan to experiment with different partition layouts more easily.\\n\",\n    \"\\n\",\n    \"You can learn more about `jax.jit` APIs for scaling up in [JAX in multi-process environments](https://jax.readthedocs.io/en/latest/multi_process.html) and [Distributed arrays and automatic parallelization](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html) on JAX's documentation site.\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Setup\\n\",\n    \"\\n\",\n    \"Import some necessary dependencies.\\n\",\n    \"\\n\",\n    \"**Note:** This guide uses the `--xla_force_host_platform_device_count=8` flag to emulate multiple devices in a CPU environment in a Google Colab/Jupyter Notebook. You don't need this if you are already using a multi-device TPU environment.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import os\\n\",\n    \"os.environ[\\\"XLA_FLAGS\\\"] = '--xla_force_host_platform_device_count=8'\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:absl:Tensorflow library not found, tensorflow.io.gfile operations will use native shim calls. GCS paths (i.e. 'gs://...') cannot be accessed.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"import functools\\n\",\n    \"from typing import Optional, Callable\\n\",\n    \"\\n\",\n    \"import numpy as np\\n\",\n    \"import jax\\n\",\n    \"from jax import lax, random, numpy as jnp\\n\",\n    \"\\n\",\n    \"import flax\\n\",\n    \"from flax import struct, traverse_util, linen as nn\\n\",\n    \"from flax.core import freeze, unfreeze\\n\",\n    \"from flax.training import train_state, checkpoints\\n\",\n    \"\\n\",\n    \"import optax # Optax for common losses and optimizers.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"We have 8 fake JAX devices now: [CpuDevice(id=0), CpuDevice(id=1), CpuDevice(id=2), CpuDevice(id=3), CpuDevice(id=4), CpuDevice(id=5), CpuDevice(id=6), CpuDevice(id=7)]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(f'We have 8 fake JAX devices now: {jax.devices()}')\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The code below shows how to import and set up the JAX-level device API, following JAX's [Distributed arrays and automatic parallelization](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html) guide:\\n\",\n    \"\\n\",\n    \"1. Start a 2x4 device `mesh` (8 devices) using JAX's `mesh_utils.create_device_mesh`. This layout is the same as the one of a [TPU v3-8](https://cloud.google.com/tpu/docs/system-architecture-tpu-vm#single_tpu_board).\\n\",\n    \"\\n\",\n    \"2. Annotate each axis with a name using the `axis_names` parameter in `jax.sharding.Mesh`. A typical way to annotate axis names is `axis_name=('data', 'model')`, where:\\n\",\n    \"  * `'data'`: the mesh dimension used for data-parallel sharding of the batch dimension of inputs and activations.\\n\",\n    \"  * `'model'`: the mesh dimension used for sharding parameters of the model across devices.\\n\",\n    \"\\n\",\n    \"3. Make a simple utility function `mesh_sharding` for generating a sharding object from the mesh and any layout.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from jax.sharding import Mesh, PartitionSpec, NamedSharding\\n\",\n    \"from jax.lax import with_sharding_constraint\\n\",\n    \"from jax.experimental import mesh_utils\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"[[CpuDevice(id=0) CpuDevice(id=1) CpuDevice(id=2) CpuDevice(id=3)]\\n\",\n      \" [CpuDevice(id=4) CpuDevice(id=5) CpuDevice(id=6) CpuDevice(id=7)]]\\n\",\n      \"Mesh('data': 2, 'model': 4)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Create a mesh and annotate each axis with a name.\\n\",\n    \"device_mesh = mesh_utils.create_device_mesh((2, 4))\\n\",\n    \"print(device_mesh)\\n\",\n    \"\\n\",\n    \"mesh = Mesh(devices=device_mesh, axis_names=('data', 'model'))\\n\",\n    \"print(mesh)\\n\",\n    \"\\n\",\n    \"def mesh_sharding(pspec: PartitionSpec) -> NamedSharding:\\n\",\n    \"  return NamedSharding(mesh, pspec)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Define a layer\\n\",\n    \"\\n\",\n    \"Before defining a simple model, create an example layer called `DotReluDot` (by subclassing `flax.linen.Module`). The layer creates two parameters `W1` and `W2` for dot product multiplication, and uses the `jax.nn.relu` (ReLU) activation function in-between.\\n\",\n    \"\\n\",\n    \"To shard the parameters efficiently, apply the following APIs to annotate the parameters and intermediate variables:\\n\",\n    \"\\n\",\n    \"1. Use [`flax.linen.with_partitioning`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.with_partitioning) to decorate the initializer function when creating sub-layers or raw parameters.\\n\",\n    \"\\n\",\n    \"2. Apply [`jax.lax.with_sharding_constraint`](https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.with_sharding_constraint.html) (formerly, `pjit.with_sharding_constraint`) to annotate intermediate variables like `y` and `z` to force a particular sharding pattern when the ideal constraint is known.\\n\",\n    \"\\n\",\n    \"  * This step is optional, but can sometimes help auto-SPMD to partition efficiently. In the example below, the call is not required, because XLA will figure out the same sharding layout for `y` and `z` regardless.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class DotReluDot(nn.Module):\\n\",\n    \"  depth: int\\n\",\n    \"  dense_init: Callable = nn.initializers.xavier_normal()\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"\\n\",\n    \"    y = nn.Dense(self.depth,\\n\",\n    \"                 kernel_init=nn.with_partitioning(self.dense_init, (None, 'model')),\\n\",\n    \"                 use_bias=False,  # or overwrite with `bias_init`\\n\",\n    \"                 )(x)\\n\",\n    \"\\n\",\n    \"    y = jax.nn.relu(y)\\n\",\n    \"    # Force a local sharding annotation.\\n\",\n    \"    y = with_sharding_constraint(y, mesh_sharding(PartitionSpec('data', 'model')))\\n\",\n    \"\\n\",\n    \"    W2 = self.param(\\n\",\n    \"        'W2',\\n\",\n    \"        nn.with_partitioning(self.dense_init, ('model', None)),\\n\",\n    \"        (self.depth, x.shape[-1]))\\n\",\n    \"\\n\",\n    \"    z = jnp.dot(y, W2)\\n\",\n    \"    # Force a local sharding annotation.\\n\",\n    \"    z = with_sharding_constraint(z, mesh_sharding(PartitionSpec('data', None)))\\n\",\n    \"\\n\",\n    \"    # Return a tuple to conform with the API `flax.linen.scan` as shown in the cell below.\\n\",\n    \"    return z, None\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that device axis names like `'data'`, `'model'` or `None` are passed into both [`flax.linen.with_partitioning`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.with_partitioning) and [`jax.lax.with_sharding_constraint`](https://github.com/jax-ml/jax/blob/main/jax/_src/pjit.py#L1516) API calls. This refers to how each dimension of this data should be sharded — either across one of the device mesh dimensions, or not sharded at all.\\n\",\n    \"\\n\",\n    \"For example:\\n\",\n    \"\\n\",\n    \"* When you define `W1` with shape `(x.shape[-1], self.depth)` and annotate as `(None, 'model')`:\\n\",\n    \"\\n\",\n    \"  * The first dimension (of length `x.shape[-1]`) will be replicated across all devices.\\n\",\n    \"  * The second dimension (of length `self.depth`) will be sharded over the `'model'` axis of the device mesh. This means `W1` will be sharded 4-way on devices `(0, 4)`, `(1, 5)`, `(2, 6)` and `(3, 7)`, on this dimension.\\n\",\n    \"\\n\",\n    \"* When you annotate the output `z` as `('data', None)`:\\n\",\n    \"\\n\",\n    \"  * The first dimension — the batch dimension — will be sharded over the `'data'` axis. This means half of the batch will be processed on devices `0-3` (first four devices), and another half on devices `4-7` (the remaining four devices).\\n\",\n    \"  * The second dimension — the data depth dimension — will be replicated across all devices.\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Define a model with `flax.linen.scan` lifted transformation\\n\",\n    \"\\n\",\n    \"Having created `DotReluDot`, you can now define the `MLP` model (by subclassing [`flax.linen.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module)) as multiple layers of `DotReluDot`.\\n\",\n    \"\\n\",\n    \"To replicate identical layers, you can either use [`flax.linen.scan`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/transformations.html#flax.linen.scan), or a for-loop:\\n\",\n    \"\\n\",\n    \"* `flax.linen.scan` can provide faster compilation times.\\n\",\n    \"* The for-loop can be faster on runtime.\\n\",\n    \"\\n\",\n    \"The code below shows how to apply both methods, and default with the for-loop, so that all the parameters are two-dimensional and you can visualize their sharding.\\n\",\n    \"\\n\",\n    \"The `flax.linen.scan` code is just to show that this API works with [Flax lifted transforms](https://flax.readthedocs.io/en/latest/developer_notes/lift.html#supported-transformations).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class MLP(nn.Module):\\n\",\n    \"  num_layers: int\\n\",\n    \"  depth: int\\n\",\n    \"  use_scan: bool\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    if self.use_scan:\\n\",\n    \"      x, _ = nn.scan(DotReluDot, length=self.num_layers,\\n\",\n    \"                     variable_axes={\\\"params\\\": 0},\\n\",\n    \"                     split_rngs={\\\"params\\\": True},\\n\",\n    \"                     metadata_params={nn.PARTITION_NAME: None}\\n\",\n    \"                     )(self.depth)(x)\\n\",\n    \"    else:\\n\",\n    \"      for i in range(self.num_layers):\\n\",\n    \"        x, _ = DotReluDot(self.depth)(x)\\n\",\n    \"    return x\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, create a `model` instance, and a sample input `x`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# MLP hyperparameters.\\n\",\n    \"BATCH, LAYERS, DEPTH, USE_SCAN = 8, 4, 1024, False\\n\",\n    \"# Create fake inputs.\\n\",\n    \"x = jnp.ones((BATCH, DEPTH))\\n\",\n    \"# Initialize a PRNG key.\\n\",\n    \"k = random.key(0)\\n\",\n    \"\\n\",\n    \"# Create an Optax optimizer.\\n\",\n    \"optimizer = optax.adam(learning_rate=0.001)\\n\",\n    \"# Instantiate the model.\\n\",\n    \"model = MLP(LAYERS, DEPTH, USE_SCAN)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Specify sharding\\n\",\n    \"\\n\",\n    \"Next, you need to tell `jax.jit` how to shard our data across devices.\\n\",\n    \"\\n\",\n    \"### The input's sharding\\n\",\n    \"\\n\",\n    \"For data parallelism, you can shard the batched _input_ `x` across the `data` axis by denoting the batch axis as `'data'`. Then, use [`jax.device_put`](https://jax.readthedocs.io/en/latest/_autosummary/jax.device_put.html) to place it onto the correct `device`s.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\">┌──────────────────────────────────────────────────────────────────────────────┐\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                 CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">0</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">1</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">2</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">3</span>                                  │\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                                                              │\\n\",\n       \"├──────────────────────────────────────────────────────────────────────────────┤\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                 CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">4</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">5</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">6</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">7</span>                                  │\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                                                              │\\n\",\n       \"└──────────────────────────────────────────────────────────────────────────────┘\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"┌──────────────────────────────────────────────────────────────────────────────┐\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                 CPU \\u001b[1;36m0\\u001b[0m,\\u001b[1;36m1\\u001b[0m,\\u001b[1;36m2\\u001b[0m,\\u001b[1;36m3\\u001b[0m                                  │\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                                                              │\\n\",\n       \"├──────────────────────────────────────────────────────────────────────────────┤\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                 CPU \\u001b[1;36m4\\u001b[0m,\\u001b[1;36m5\\u001b[0m,\\u001b[1;36m6\\u001b[0m,\\u001b[1;36m7\\u001b[0m                                  │\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                                                              │\\n\",\n       \"└──────────────────────────────────────────────────────────────────────────────┘\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"x_sharding = mesh_sharding(PartitionSpec('data', None)) # dimensions: (batch, length)\\n\",\n    \"x = jax.device_put(x, x_sharding)\\n\",\n    \"jax.debug.visualize_array_sharding(x)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### The output's sharding\\n\",\n    \"\\n\",\n    \"You need to compile `model.init()` (that is, [`flax.linen.Module.init()`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.init)), and its output as a pytree of parameters. Additionally, you may sometimes need wrap it with a [`flax.training.train_state`](https://flax.readthedocs.io/en/latest/api_reference/flax.training.html#flax.training.train_state.TrainState) to track other variables, such as optimizer states, and that would make the output an even more complex pytree.\\n\",\n    \"\\n\",\n    \"To achieve this, luckily, you don't have to hardcode the output's sharding by hand. Instead, you can:\\n\",\n    \"\\n\",\n    \"1. Evaluate `model.init` (in this case, a wrapper of it) abstractly using [`jax.eval_shape`](https://jax.readthedocs.io/en/latest/_autosummary/jax.eval_shape.html).\\n\",\n    \"\\n\",\n    \"1. Use [`flax.linen.get_sharding`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.get_sharding) to automatically generate the `jax.sharding.NamedSharding`.\\n\",\n    \"   * This step utilizes the [`flax.linen.with_partitioning`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.with_partitioning) annotations in the earlier definition to generate the correct sharding for the parameters.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"def init_fn(k, x, model, optimizer):\\n\",\n    \"  variables = model.init(k, x) # Initialize the model.\\n\",\n    \"  state = train_state.TrainState.create( # Create a `TrainState`.\\n\",\n    \"    apply_fn=model.apply,\\n\",\n    \"    params=variables['params'],\\n\",\n    \"    tx=optimizer)\\n\",\n    \"  return state\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"TrainState(step=NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec()), apply_fn=<bound method Module.apply of MLP(\\n\",\n       \"    # attributes\\n\",\n       \"    num_layers = 4\\n\",\n       \"    depth = 1024\\n\",\n       \"    use_scan = False\\n\",\n       \")>, params={'DotReluDot_0': {'Dense_0': {'kernel': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))}, 'W2': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec('model', None))}, 'DotReluDot_1': {'Dense_0': {'kernel': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))}, 'W2': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec('model', None))}, 'DotReluDot_2': {'Dense_0': {'kernel': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))}, 'W2': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec('model', None))}, 'DotReluDot_3': {'Dense_0': {'kernel': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))}, 'W2': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec('model', None))}}, tx=GradientTransformationExtraArgs(init=<function chain.<locals>.init_fn at 0x33e134280>, update=<function chain.<locals>.update_fn at 0x33e134430>), opt_state=(ScaleByAdamState(count=NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec()), mu={'DotReluDot_0': {'Dense_0': {'kernel': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))}, 'W2': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec('model', None))}, 'DotReluDot_1': {'Dense_0': {'kernel': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))}, 'W2': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec('model', None))}, 'DotReluDot_2': {'Dense_0': {'kernel': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))}, 'W2': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec('model', None))}, 'DotReluDot_3': {'Dense_0': {'kernel': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))}, 'W2': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec('model', None))}}, nu={'DotReluDot_0': {'Dense_0': {'kernel': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))}, 'W2': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec('model', None))}, 'DotReluDot_1': {'Dense_0': {'kernel': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))}, 'W2': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec('model', None))}, 'DotReluDot_2': {'Dense_0': {'kernel': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))}, 'W2': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec('model', None))}, 'DotReluDot_3': {'Dense_0': {'kernel': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))}, 'W2': NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec('model', None))}}), EmptyState()))\"\n      ]\n     },\n     \"execution_count\": 12,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# Create an abstract closure to wrap the function before feeding it in\\n\",\n    \"# because `jax.eval_shape` only takes pytrees as arguments.\\n\",\n    \"abstract_variables = jax.eval_shape(\\n\",\n    \"    functools.partial(init_fn, model=model, optimizer=optimizer), k, x)\\n\",\n    \"\\n\",\n    \"# This `state_sharding` has the same pytree structure as `state`, the output\\n\",\n    \"# of the `init_fn`.\\n\",\n    \"state_sharding = nn.get_sharding(abstract_variables, mesh)\\n\",\n    \"state_sharding\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Compile the code\\n\",\n    \"\\n\",\n    \"Now you can apply [`jax.jit`](https://jax.readthedocs.io/en/latest/jax-101/02-jitting.html) to your `init_fn`, but with two extra arguments: `in_shardings` and `out_shardings`.\\n\",\n    \"\\n\",\n    \"Run it to get the `initialized_state`, in which parameters are sharded exactly as instructed:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\">┌───────┬───────┬───────┬───────┐\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">0</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">4</span>│CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">1</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">5</span>│CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">2</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">6</span>│CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">3</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">7</span>│\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"└───────┴───────┴───────┴───────┘\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"┌───────┬───────┬───────┬───────┐\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│CPU \\u001b[1;36m0\\u001b[0m,\\u001b[1;36m4\\u001b[0m│CPU \\u001b[1;36m1\\u001b[0m,\\u001b[1;36m5\\u001b[0m│CPU \\u001b[1;36m2\\u001b[0m,\\u001b[1;36m6\\u001b[0m│CPU \\u001b[1;36m3\\u001b[0m,\\u001b[1;36m7\\u001b[0m│\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"└───────┴───────┴───────┴───────┘\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\">┌───────────────────────┐\\n\",\n       \"│        CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">0</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">4</span>        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">1</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">5</span>        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">2</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">6</span>        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">3</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">7</span>        │\\n\",\n       \"└───────────────────────┘\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"┌───────────────────────┐\\n\",\n       \"│        CPU \\u001b[1;36m0\\u001b[0m,\\u001b[1;36m4\\u001b[0m        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU \\u001b[1;36m1\\u001b[0m,\\u001b[1;36m5\\u001b[0m        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU \\u001b[1;36m2\\u001b[0m,\\u001b[1;36m6\\u001b[0m        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU \\u001b[1;36m3\\u001b[0m,\\u001b[1;36m7\\u001b[0m        │\\n\",\n       \"└───────────────────────┘\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"jit_init_fn = jax.jit(init_fn, static_argnums=(2, 3),\\n\",\n    \"                      in_shardings=(mesh_sharding(PartitionSpec()), x_sharding),  # PRNG key and x\\n\",\n    \"                      out_shardings=state_sharding)\\n\",\n    \"\\n\",\n    \"initialized_state = jit_init_fn(k, x, model, optimizer)\\n\",\n    \"\\n\",\n    \"# for weight, partitioned in initialized_state.params['DotReluDot_0'].items():\\n\",\n    \"#     print(f'Sharding of {weight}: {partitioned.names}')\\n\",\n    \"jax.debug.visualize_array_sharding(initialized_state.params['DotReluDot_0']['Dense_0']['kernel'].value)\\n\",\n    \"jax.debug.visualize_array_sharding(initialized_state.params['DotReluDot_0']['W2'].value)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Inspect the Module output\\n\",\n    \"\\n\",\n    \"Note that in the output of `initialized_state`, the `params` `W1` and `W2` are of type [`flax.linen.Partitioned`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.Partitioned). This is a wrapper around the actual `jax.Array` that allows Flax to record the axis names associated with it.\\n\",\n    \"\\n\",\n    \"You can access the raw `jax.Array`s by calling `flax.linen.meta.unbox()` upon the dictionary, or call `.value` upon individual variable. You can also use `flax.linen.meta.replace_boxed()` to change the underlying `jax.Array` without modifying the sharding annotations.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 18,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"<class 'flax.core.meta.Partitioned'>\\n\",\n      \"<class 'jaxlib.xla_extension.ArrayImpl'>\\n\",\n      \"(None, 'model')\\n\",\n      \"(1024, 1024)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(type(initialized_state.params['DotReluDot_0']['Dense_0']['kernel']))\\n\",\n    \"print(type(initialized_state.params['DotReluDot_0']['Dense_0']['kernel'].value))\\n\",\n    \"print(initialized_state.params['DotReluDot_0']['Dense_0']['kernel'].names)\\n\",\n    \"print(initialized_state.params['DotReluDot_0']['Dense_0']['kernel'].value.shape)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 19,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Say for some unknown reason you want to make the whole param tree all-zero\\n\",\n    \"unboxed_params = nn.meta.unbox(initialized_state.params)\\n\",\n    \"all_zero = jax.tree.map(jnp.zeros_like, unboxed_params)\\n\",\n    \"all_zero_params = nn.meta.replace_boxed(initialized_state.params, all_zero)\\n\",\n    \"assert jnp.sum(nn.meta.unbox(all_zero_params['DotReluDot_0']['Dense_0']['kernel'])) == 0\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"You can also check the underlying [`jax.sharding`](https://jax.readthedocs.io/en/latest/jax.sharding.html) of each parameter, which is now more internal than `NamedSharding`. Note that numbers like `initialized_state.step` are replicated across all devices.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 20,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec(None, 'model'))\"\n      ]\n     },\n     \"execution_count\": 20,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"initialized_state.params['DotReluDot_0']['Dense_0']['kernel'].value.sharding\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 21,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"0\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"NamedSharding(mesh=Mesh('data': 2, 'model': 4), spec=PartitionSpec())\"\n      ]\n     },\n     \"execution_count\": 21,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"print(initialized_state.step)\\n\",\n    \"initialized_state.step.sharding\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"You can use [`jax.tree_util.tree_map`](https://jax.readthedocs.io/en/latest/_autosummary/jax.tree_util.tree_map.html) to perform mass computation on a dict of boxed params, in the same way as on a dict of JAX arrays.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 22,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"{'Dense_0': {'kernel': Partitioned(value=(1024, 1024), names=(None, 'model'), mesh=None)}, 'W2': Partitioned(value=(1024, 1024), names=('model', None), mesh=None)}\\n\",\n      \"<class 'jaxlib.xla_extension.ArrayImpl'>\\n\",\n      \"(1024, 1024)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"diff = jax.tree_util.tree_map(\\n\",\n    \"    lambda a, b: a - b,\\n\",\n    \"    initialized_state.params['DotReluDot_0'], initialized_state.params['DotReluDot_0'])\\n\",\n    \"print(jax.tree_util.tree_map(jnp.shape, diff))\\n\",\n    \"diff_array = diff['Dense_0']['kernel'].value\\n\",\n    \"print(type(diff_array))\\n\",\n    \"print(diff_array.shape)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Compile the train step and inference\\n\",\n    \"\\n\",\n    \"Create a `jit`ted training step as follows:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 23,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"@functools.partial(jax.jit, in_shardings=(state_sharding, x_sharding),\\n\",\n    \"                   out_shardings=state_sharding)\\n\",\n    \"def train_step(state, x):\\n\",\n    \"  # A fake loss function.\\n\",\n    \"  def loss_unrolled(params):\\n\",\n    \"    y = model.apply({'params': params}, x)\\n\",\n    \"    return y.sum()\\n\",\n    \"  grad_fn = jax.grad(loss_unrolled)\\n\",\n    \"  grads = grad_fn(state.params)\\n\",\n    \"  state = state.apply_gradients(grads=grads)\\n\",\n    \"  return state\\n\",\n    \"\\n\",\n    \"with jax.set_mesh(mesh):\\n\",\n    \"  new_state = train_step(initialized_state, x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 24,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Sharding of Weight 1:\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\">┌───────┬───────┬───────┬───────┐\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">0</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">4</span>│CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">1</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">5</span>│CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">2</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">6</span>│CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">3</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">7</span>│\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"└───────┴───────┴───────┴───────┘\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"┌───────┬───────┬───────┬───────┐\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│CPU \\u001b[1;36m0\\u001b[0m,\\u001b[1;36m4\\u001b[0m│CPU \\u001b[1;36m1\\u001b[0m,\\u001b[1;36m5\\u001b[0m│CPU \\u001b[1;36m2\\u001b[0m,\\u001b[1;36m6\\u001b[0m│CPU \\u001b[1;36m3\\u001b[0m,\\u001b[1;36m7\\u001b[0m│\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"└───────┴───────┴───────┴───────┘\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Sharding of Weight 2:\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\">┌───────────────────────┐\\n\",\n       \"│        CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">0</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">4</span>        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">1</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">5</span>        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">2</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">6</span>        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">3</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">7</span>        │\\n\",\n       \"└───────────────────────┘\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"┌───────────────────────┐\\n\",\n       \"│        CPU \\u001b[1;36m0\\u001b[0m,\\u001b[1;36m4\\u001b[0m        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU \\u001b[1;36m1\\u001b[0m,\\u001b[1;36m5\\u001b[0m        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU \\u001b[1;36m2\\u001b[0m,\\u001b[1;36m6\\u001b[0m        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU \\u001b[1;36m3\\u001b[0m,\\u001b[1;36m7\\u001b[0m        │\\n\",\n       \"└───────────────────────┘\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"print(f'Sharding of Weight 1:')\\n\",\n    \"jax.debug.visualize_array_sharding(initialized_state.params['DotReluDot_0']['Dense_0']['kernel'].value)\\n\",\n    \"print(f'Sharding of Weight 2:')\\n\",\n    \"jax.debug.visualize_array_sharding(initialized_state.params['DotReluDot_0']['W2'].value)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Then, create a compiled inference step. Note that the output is also sharded along `(data, None)`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 25,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"<class 'jaxlib.xla_extension.ArrayImpl'>\\n\",\n      \"float32\\n\",\n      \"(8, 1024)\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\">┌──────────────────────────────────────────────────────────────────────────────┐\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                 CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">0</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">1</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">2</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">3</span>                                  │\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                                                              │\\n\",\n       \"├──────────────────────────────────────────────────────────────────────────────┤\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                 CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">4</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">5</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">6</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">7</span>                                  │\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                                                              │\\n\",\n       \"└──────────────────────────────────────────────────────────────────────────────┘\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"┌──────────────────────────────────────────────────────────────────────────────┐\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                 CPU \\u001b[1;36m0\\u001b[0m,\\u001b[1;36m1\\u001b[0m,\\u001b[1;36m2\\u001b[0m,\\u001b[1;36m3\\u001b[0m                                  │\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                                                              │\\n\",\n       \"├──────────────────────────────────────────────────────────────────────────────┤\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                 CPU \\u001b[1;36m4\\u001b[0m,\\u001b[1;36m5\\u001b[0m,\\u001b[1;36m6\\u001b[0m,\\u001b[1;36m7\\u001b[0m                                  │\\n\",\n       \"│                                                                              │\\n\",\n       \"│                                                                              │\\n\",\n       \"└──────────────────────────────────────────────────────────────────────────────┘\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"@functools.partial(jax.jit, in_shardings=(state_sharding, x_sharding),\\n\",\n    \"                   out_shardings=x_sharding)\\n\",\n    \"def apply_fn(state, x):\\n\",\n    \"  return state.apply_fn({'params': state.params}, x)\\n\",\n    \"\\n\",\n    \"with jax.set_mesh(mesh):\\n\",\n    \"  y = apply_fn(new_state, x)\\n\",\n    \"print(type(y))\\n\",\n    \"print(y.dtype)\\n\",\n    \"print(y.shape)\\n\",\n    \"jax.debug.visualize_array_sharding(y)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Profiling\\n\",\n    \"\\n\",\n    \"If you are running on a TPU pod or a pod slice, you can use a custom `block_all` utility function, as defined below, to measure the performance:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 26,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"20.9 ms ± 319 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"%%timeit\\n\",\n    \"\\n\",\n    \"def block_all(xs):\\n\",\n    \"  jax.tree_util.tree_map(lambda x: x.block_until_ready(), xs)\\n\",\n    \"  return xs\\n\",\n    \"\\n\",\n    \"with jax.set_mesh(mesh):\\n\",\n    \"  new_state = block_all(train_step(initialized_state, x))\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Logical axis annotation\\n\",\n    \"\\n\",\n    \"JAX's automatic SPMD encourages users to explore different sharding layouts to find the optimal one. To this end, in Flax you actually can annotate the dimensions of any data with more descriptive axis names (not just device mesh axis names like `'data'` and `'model'`).\\n\",\n    \"\\n\",\n    \"The `LogicalDotReluDot` and `LogicalMLP` Module definition below are similar to the Modules you created earlier, except for the following:\\n\",\n    \"\\n\",\n    \"1. All axes are annotated with more concrete, meaningful names, such as `'embed'`, `'hidden'`, `'batch'` and `'layer'`. These names are referred to as _logical axis names_ in Flax. They make the dimensional changes inside model definitions more readable.\\n\",\n    \"\\n\",\n    \"2. [`flax.linen.with_logical_partitioning`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.with_logical_partitioning) replaces `flax.linen.with_partitioning`; and [`flax.linen.with_logical_constraint`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.with_logical_constraint) replaces `jax.lax.with_sharding_constraint`, to recognize the logical axis names.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 27,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class LogicalDotReluDot(nn.Module):\\n\",\n    \"  depth: int\\n\",\n    \"  dense_init: Callable = nn.initializers.xavier_normal()\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    y = nn.Dense(self.depth,\\n\",\n    \"                 kernel_init=nn.with_logical_partitioning(self.dense_init, ('embed', 'hidden')),\\n\",\n    \"                 use_bias=False,  # or overwrite with `bias_init`\\n\",\n    \"                 )(x)\\n\",\n    \"\\n\",\n    \"    y = jax.nn.relu(y)\\n\",\n    \"    # Force a local sharding annotation.\\n\",\n    \"    y = with_sharding_constraint(y, mesh_sharding(PartitionSpec('data', 'model')))\\n\",\n    \"\\n\",\n    \"    W2 = self.param(\\n\",\n    \"        'W2',\\n\",\n    \"        nn.with_logical_partitioning(self.dense_init, ('hidden', 'embed')),\\n\",\n    \"        (self.depth, x.shape[-1]))\\n\",\n    \"\\n\",\n    \"    z = jnp.dot(y, W2)\\n\",\n    \"    # Force a local sharding annotation.\\n\",\n    \"    z = nn.with_logical_constraint(z, ('batch', 'embed'))\\n\",\n    \"    return z, None\\n\",\n    \"\\n\",\n    \"class LogicalMLP(nn.Module):\\n\",\n    \"  num_layers: int\\n\",\n    \"  depth: int\\n\",\n    \"  use_scan: bool\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    if self.use_scan:\\n\",\n    \"      x, _ = nn.scan(LogicalDotReluDot, length=self.num_layers,\\n\",\n    \"                    variable_axes={\\\"params\\\": 0},\\n\",\n    \"                    split_rngs={\\\"params\\\": True},\\n\",\n    \"                    metadata_params={nn.PARTITION_NAME: 'layer'}\\n\",\n    \"                    )(self.depth)(x)\\n\",\n    \"    else:\\n\",\n    \"      for i in range(self.num_layers):\\n\",\n    \"        x, _ = LogicalDotReluDot(self.depth)(x)\\n\",\n    \"    return x\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, initiate a model and try to figure out what sharding its `state` should have.\\n\",\n    \"\\n\",\n    \"To allow the device mesh to take your model correctly, you need to decide which of these logical axis names are mapped to the device axis `'data'` or `'model'`. This rule is a list of (`logical_axis_name`, `device_axis_name`) tuples, and [`flax.linen.logical_to_mesh_sharding`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.logical_to_mesh_sharding) will convert them to the kind of sharding that the device mesh can understand.\\n\",\n    \"\\n\",\n    \"This allows you to change the rules and try out new partition layouts without modifying the model definition.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 28,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"annotations are logical, not mesh-specific:  PartitionSpec('embed', 'hidden')\\n\",\n      \"sharding annotations are mesh-specific:  PartitionSpec(None, 'model')\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Unspecified rule means unsharded by default, so no need to specify `('embed', None)` and `('layer', None)`.\\n\",\n    \"rules = (('batch', 'data'),\\n\",\n    \"         ('hidden', 'model'))\\n\",\n    \"\\n\",\n    \"logical_model = LogicalMLP(LAYERS, DEPTH, USE_SCAN)\\n\",\n    \"\\n\",\n    \"logical_abstract_variables = jax.eval_shape(\\n\",\n    \"    functools.partial(init_fn, model=logical_model, optimizer=optimizer), k, x)\\n\",\n    \"logical_state_spec = nn.get_partition_spec(logical_abstract_variables)\\n\",\n    \"print('annotations are logical, not mesh-specific: ',\\n\",\n    \"      logical_state_spec.params['LogicalDotReluDot_0']['Dense_0']['kernel'])\\n\",\n    \"\\n\",\n    \"logical_state_sharding = nn.logical_to_mesh_sharding(logical_state_spec, mesh, rules)\\n\",\n    \"print('sharding annotations are mesh-specific: ',\\n\",\n    \"      logical_state_sharding.params['LogicalDotReluDot_0']['Dense_0']['kernel'].spec)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"You can verify that the `logical_state_spec` here has the same content as `state_spec` in the previous (\\\"non-logical\\\") example. This allows you to `jax.jit` your Module's [`flax.linen.Module.init`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.init) and [`flax.linen.Module.apply`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.apply) the same way in the above above.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 29,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"True\"\n      ]\n     },\n     \"execution_count\": 29,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"state_sharding.params['DotReluDot_0'] == logical_state_sharding.params['LogicalDotReluDot_0']\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 31,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"logical_jit_init_fn = jax.jit(init_fn, static_argnums=(2, 3),\\n\",\n    \"                      in_shardings=(mesh_sharding(PartitionSpec()), x_sharding),  # PRNG key and x\\n\",\n    \"                      out_shardings=logical_state_sharding)\\n\",\n    \"\\n\",\n    \"logical_initialized_state = logical_jit_init_fn(k, x, logical_model, optimizer)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 32,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Sharding of Weight 1:\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\">┌───────┬───────┬───────┬───────┐\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">0</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">4</span>│CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">1</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">5</span>│CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">2</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">6</span>│CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">3</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">7</span>│\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"└───────┴───────┴───────┴───────┘\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"┌───────┬───────┬───────┬───────┐\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│CPU \\u001b[1;36m0\\u001b[0m,\\u001b[1;36m4\\u001b[0m│CPU \\u001b[1;36m1\\u001b[0m,\\u001b[1;36m5\\u001b[0m│CPU \\u001b[1;36m2\\u001b[0m,\\u001b[1;36m6\\u001b[0m│CPU \\u001b[1;36m3\\u001b[0m,\\u001b[1;36m7\\u001b[0m│\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"│       │       │       │       │\\n\",\n       \"└───────┴───────┴───────┴───────┘\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Sharding of Weight 2:\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\">┌───────────────────────┐\\n\",\n       \"│        CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">0</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">4</span>        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">1</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">5</span>        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">2</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">6</span>        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU <span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">3</span>,<span style=\\\"color: #008080; text-decoration-color: #008080; font-weight: bold\\\">7</span>        │\\n\",\n       \"└───────────────────────┘\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"┌───────────────────────┐\\n\",\n       \"│        CPU \\u001b[1;36m0\\u001b[0m,\\u001b[1;36m4\\u001b[0m        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU \\u001b[1;36m1\\u001b[0m,\\u001b[1;36m5\\u001b[0m        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU \\u001b[1;36m2\\u001b[0m,\\u001b[1;36m6\\u001b[0m        │\\n\",\n       \"├───────────────────────┤\\n\",\n       \"│        CPU \\u001b[1;36m3\\u001b[0m,\\u001b[1;36m7\\u001b[0m        │\\n\",\n       \"└───────────────────────┘\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"print(f'Sharding of Weight 1:')\\n\",\n    \"jax.debug.visualize_array_sharding(logical_initialized_state.params['LogicalDotReluDot_0']['Dense_0']['kernel'].value)\\n\",\n    \"print(f'Sharding of Weight 2:')\\n\",\n    \"jax.debug.visualize_array_sharding(logical_initialized_state.params['LogicalDotReluDot_0']['W2'].value)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## When to use device axis / logical axis\\n\",\n    \"\\n\",\n    \"Choosing when to use a device or logical axis depends on how much you want to control the partitioning of your model:\\n\",\n    \"\\n\",\n    \"* **Device mesh axis**: If you want a very simple model, or you are very confident of your way of partitioning, defining it with __device mesh axis__ can potentially save you a few extra lines of code of converting the logical naming back to the device naming.\\n\",\n    \"\\n\",\n    \"* **Logical naming**: On the other hand, the __logical naming__ helpers can be useful for exploring different sharding layouts. Use this if you want to experiment around and find the most optimal partition layout for your model.\\n\",\n    \"\\n\",\n    \"* **Device axis names**: In really advanced use cases, you may have more complicated sharding patterns that require annotating *activation* dimension names differently from *parameter* dimension names. If you wish to have more fine-grained control on manual mesh assignments, directly using __device axis names__ could be more helpful.\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Save the data\\n\",\n    \"\\n\",\n    \"To save the cross-device array, you can use Orbax as shown in the [Save and load checkpoints guide - Multi-host/multi-process checkpointing](https://flax.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html#multi-host-multi-process-checkpointing). This is especially required if you are running on a multi-host environment (for example, a TPU pod).\\n\",\n    \"\\n\",\n    \"In practice, you might want to save the raw `jax.Array` pytree as checkpoint, instead of the wrapped `Partitioned` values, to reduce complexity. You can restore it as-is and put it back into an annotated pytree with `flax.linen.meta.replace_boxed()`.\\n\",\n    \"\\n\",\n    \"Keep in mind that to restore the arrays to the desired partition, you need to provide a sample `target` pytree that has the same structure and has the desired [`jax.sharding.Sharding`](https://jax.readthedocs.io/en/latest/jax.sharding.html#jax.sharding.Sharding) in place for each JAX array. The sharding you use to restore the array doesn't necessarily need to be the same as the ones you used to store the array.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.10.13\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "docs/guides/parallel_training/flax_on_pjit.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Scale up Flax Modules on multiple devices\n\nThis guide shows how to scale up [Flax Modules](https://flax.readthedocs.io/en/latest/developer_notes/module_lifecycle.html) on multiple devices and hosts using [`jax.jit`](https://jax.readthedocs.io/en/latest/jax-101/02-jitting.html) (formerly [`experimental.pjit`](https://jax.readthedocs.io/en/latest/jax.experimental.pjit.html#module-jax.experimental.pjit)) and [`flax.linen`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/index.html).\n\n+++\n\n## Flax and `jax.jit` scaled up\n\n[`jax.jit`](https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html) follows the [Single Program Multi Data (SPMD)](https://jax.readthedocs.io/en/latest/glossary.html#term-SPMD) paradigm and automatically compiles your code to run it on multiple devices. You need to only specify how you want the input and output of your code to be partitioned, and the compiler will figure out how to: 1) partition everything inside; and 2) compile inter-device communications.\n\nFlax provides several functionalities that can help you use auto-SPMD on [Flax Modules](https://flax.readthedocs.io/en/latest/developer_notes/module_lifecycle.html), including:\n\n1. An interface to specify partitions of your data when defining [`flax.linen.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html).\n2. Utility functions to generate the sharding information that `jax.jit` requires to run.\n3. An interface to customize your axis names called \"logical axis annotations\" to decouple both your Module code and partition plan to experiment with different partition layouts more easily.\n\nYou can learn more about `jax.jit` APIs for scaling up in [JAX in multi-process environments](https://jax.readthedocs.io/en/latest/multi_process.html) and [Distributed arrays and automatic parallelization](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html) on JAX's documentation site.\n\n+++\n\n## Setup\n\nImport some necessary dependencies.\n\n**Note:** This guide uses the `--xla_force_host_platform_device_count=8` flag to emulate multiple devices in a CPU environment in a Google Colab/Jupyter Notebook. You don't need this if you are already using a multi-device TPU environment.\n\n```{code-cell} ipython3\nimport os\nos.environ[\"XLA_FLAGS\"] = '--xla_force_host_platform_device_count=8'\n```\n\n```{code-cell} ipython3\nimport functools\nfrom typing import Optional, Callable\n\nimport numpy as np\nimport jax\nfrom jax import lax, random, numpy as jnp\n\nimport flax\nfrom flax import struct, traverse_util, linen as nn\nfrom flax.core import freeze, unfreeze\nfrom flax.training import train_state, checkpoints\n\nimport optax # Optax for common losses and optimizers.\n```\n\n```{code-cell} ipython3\nprint(f'We have 8 fake JAX devices now: {jax.devices()}')\n```\n\nThe code below shows how to import and set up the JAX-level device API, following JAX's [Distributed arrays and automatic parallelization](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html) guide:\n\n1. Start a 2x4 device `mesh` (8 devices) using JAX's `mesh_utils.create_device_mesh`. This layout is the same as the one of a [TPU v3-8](https://cloud.google.com/tpu/docs/system-architecture-tpu-vm#single_tpu_board).\n\n2. Annotate each axis with a name using the `axis_names` parameter in `jax.sharding.Mesh`. A typical way to annotate axis names is `axis_name=('data', 'model')`, where:\n  * `'data'`: the mesh dimension used for data-parallel sharding of the batch dimension of inputs and activations.\n  * `'model'`: the mesh dimension used for sharding parameters of the model across devices.\n\n3. Make a simple utility function `mesh_sharding` for generating a sharding object from the mesh and any layout.\n\n```{code-cell} ipython3\nfrom jax.sharding import Mesh, PartitionSpec, NamedSharding\nfrom jax.lax import with_sharding_constraint\nfrom jax.experimental import mesh_utils\n```\n\n```{code-cell} ipython3\n# Create a mesh and annotate each axis with a name.\ndevice_mesh = mesh_utils.create_device_mesh((2, 4))\nprint(device_mesh)\n\nmesh = Mesh(devices=device_mesh, axis_names=('data', 'model'))\nprint(mesh)\n\ndef mesh_sharding(pspec: PartitionSpec) -> NamedSharding:\n  return NamedSharding(mesh, pspec)\n```\n\n## Define a layer\n\nBefore defining a simple model, create an example layer called `DotReluDot` (by subclassing `flax.linen.Module`). The layer creates two parameters `W1` and `W2` for dot product multiplication, and uses the `jax.nn.relu` (ReLU) activation function in-between.\n\nTo shard the parameters efficiently, apply the following APIs to annotate the parameters and intermediate variables:\n\n1. Use [`flax.linen.with_partitioning`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.with_partitioning) to decorate the initializer function when creating sub-layers or raw parameters.\n\n2. Apply [`jax.lax.with_sharding_constraint`](https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.with_sharding_constraint.html) (formerly, `pjit.with_sharding_constraint`) to annotate intermediate variables like `y` and `z` to force a particular sharding pattern when the ideal constraint is known.\n\n  * This step is optional, but can sometimes help auto-SPMD to partition efficiently. In the example below, the call is not required, because XLA will figure out the same sharding layout for `y` and `z` regardless.\n\n```{code-cell} ipython3\nclass DotReluDot(nn.Module):\n  depth: int\n  dense_init: Callable = nn.initializers.xavier_normal()\n  @nn.compact\n  def __call__(self, x):\n\n    y = nn.Dense(self.depth,\n                 kernel_init=nn.with_partitioning(self.dense_init, (None, 'model')),\n                 use_bias=False,  # or overwrite with `bias_init`\n                 )(x)\n\n    y = jax.nn.relu(y)\n    # Force a local sharding annotation.\n    y = with_sharding_constraint(y, mesh_sharding(PartitionSpec('data', 'model')))\n\n    W2 = self.param(\n        'W2',\n        nn.with_partitioning(self.dense_init, ('model', None)),\n        (self.depth, x.shape[-1]))\n\n    z = jnp.dot(y, W2)\n    # Force a local sharding annotation.\n    z = with_sharding_constraint(z, mesh_sharding(PartitionSpec('data', None)))\n\n    # Return a tuple to conform with the API `flax.linen.scan` as shown in the cell below.\n    return z, None\n```\n\nNote that device axis names like `'data'`, `'model'` or `None` are passed into both [`flax.linen.with_partitioning`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.with_partitioning) and [`jax.lax.with_sharding_constraint`](https://github.com/jax-ml/jax/blob/main/jax/_src/pjit.py#L1516) API calls. This refers to how each dimension of this data should be sharded — either across one of the device mesh dimensions, or not sharded at all.\n\nFor example:\n\n* When you define `W1` with shape `(x.shape[-1], self.depth)` and annotate as `(None, 'model')`:\n\n  * The first dimension (of length `x.shape[-1]`) will be replicated across all devices.\n  * The second dimension (of length `self.depth`) will be sharded over the `'model'` axis of the device mesh. This means `W1` will be sharded 4-way on devices `(0, 4)`, `(1, 5)`, `(2, 6)` and `(3, 7)`, on this dimension.\n\n* When you annotate the output `z` as `('data', None)`:\n\n  * The first dimension — the batch dimension — will be sharded over the `'data'` axis. This means half of the batch will be processed on devices `0-3` (first four devices), and another half on devices `4-7` (the remaining four devices).\n  * The second dimension — the data depth dimension — will be replicated across all devices.\n\n+++\n\n## Define a model with `flax.linen.scan` lifted transformation\n\nHaving created `DotReluDot`, you can now define the `MLP` model (by subclassing [`flax.linen.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module)) as multiple layers of `DotReluDot`.\n\nTo replicate identical layers, you can either use [`flax.linen.scan`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/transformations.html#flax.linen.scan), or a for-loop:\n\n* `flax.linen.scan` can provide faster compilation times.\n* The for-loop can be faster on runtime.\n\nThe code below shows how to apply both methods, and default with the for-loop, so that all the parameters are two-dimensional and you can visualize their sharding.\n\nThe `flax.linen.scan` code is just to show that this API works with [Flax lifted transforms](https://flax.readthedocs.io/en/latest/developer_notes/lift.html#supported-transformations).\n\n```{code-cell} ipython3\nclass MLP(nn.Module):\n  num_layers: int\n  depth: int\n  use_scan: bool\n  @nn.compact\n  def __call__(self, x):\n    if self.use_scan:\n      x, _ = nn.scan(DotReluDot, length=self.num_layers,\n                     variable_axes={\"params\": 0},\n                     split_rngs={\"params\": True},\n                     metadata_params={nn.PARTITION_NAME: None}\n                     )(self.depth)(x)\n    else:\n      for i in range(self.num_layers):\n        x, _ = DotReluDot(self.depth)(x)\n    return x\n```\n\nNow, create a `model` instance, and a sample input `x`.\n\n```{code-cell} ipython3\n# MLP hyperparameters.\nBATCH, LAYERS, DEPTH, USE_SCAN = 8, 4, 1024, False\n# Create fake inputs.\nx = jnp.ones((BATCH, DEPTH))\n# Initialize a PRNG key.\nk = random.key(0)\n\n# Create an Optax optimizer.\noptimizer = optax.adam(learning_rate=0.001)\n# Instantiate the model.\nmodel = MLP(LAYERS, DEPTH, USE_SCAN)\n```\n\n## Specify sharding\n\nNext, you need to tell `jax.jit` how to shard our data across devices.\n\n### The input's sharding\n\nFor data parallelism, you can shard the batched _input_ `x` across the `data` axis by denoting the batch axis as `'data'`. Then, use [`jax.device_put`](https://jax.readthedocs.io/en/latest/_autosummary/jax.device_put.html) to place it onto the correct `device`s.\n\n```{code-cell} ipython3\nx_sharding = mesh_sharding(PartitionSpec('data', None)) # dimensions: (batch, length)\nx = jax.device_put(x, x_sharding)\njax.debug.visualize_array_sharding(x)\n```\n\n### The output's sharding\n\nYou need to compile `model.init()` (that is, [`flax.linen.Module.init()`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.init)), and its output as a pytree of parameters. Additionally, you may sometimes need wrap it with a [`flax.training.train_state`](https://flax.readthedocs.io/en/latest/api_reference/flax.training.html#flax.training.train_state.TrainState) to track other variables, such as optimizer states, and that would make the output an even more complex pytree.\n\nTo achieve this, luckily, you don't have to hardcode the output's sharding by hand. Instead, you can:\n\n1. Evaluate `model.init` (in this case, a wrapper of it) abstractly using [`jax.eval_shape`](https://jax.readthedocs.io/en/latest/_autosummary/jax.eval_shape.html).\n\n1. Use [`flax.linen.get_sharding`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.get_sharding) to automatically generate the `jax.sharding.NamedSharding`.\n   * This step utilizes the [`flax.linen.with_partitioning`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.with_partitioning) annotations in the earlier definition to generate the correct sharding for the parameters.\n\n```{code-cell} ipython3\ndef init_fn(k, x, model, optimizer):\n  variables = model.init(k, x) # Initialize the model.\n  state = train_state.TrainState.create( # Create a `TrainState`.\n    apply_fn=model.apply,\n    params=variables['params'],\n    tx=optimizer)\n  return state\n```\n\n```{code-cell} ipython3\n# Create an abstract closure to wrap the function before feeding it in\n# because `jax.eval_shape` only takes pytrees as arguments.\nabstract_variables = jax.eval_shape(\n    functools.partial(init_fn, model=model, optimizer=optimizer), k, x)\n\n# This `state_sharding` has the same pytree structure as `state`, the output\n# of the `init_fn`.\nstate_sharding = nn.get_sharding(abstract_variables, mesh)\nstate_sharding\n```\n\n## Compile the code\n\nNow you can apply [`jax.jit`](https://jax.readthedocs.io/en/latest/jax-101/02-jitting.html) to your `init_fn`, but with two extra arguments: `in_shardings` and `out_shardings`.\n\nRun it to get the `initialized_state`, in which parameters are sharded exactly as instructed:\n\n```{code-cell} ipython3\njit_init_fn = jax.jit(init_fn, static_argnums=(2, 3),\n                      in_shardings=(mesh_sharding(PartitionSpec()), x_sharding),  # PRNG key and x\n                      out_shardings=state_sharding)\n\ninitialized_state = jit_init_fn(k, x, model, optimizer)\n\n# for weight, partitioned in initialized_state.params['DotReluDot_0'].items():\n#     print(f'Sharding of {weight}: {partitioned.names}')\njax.debug.visualize_array_sharding(initialized_state.params['DotReluDot_0']['Dense_0']['kernel'].value)\njax.debug.visualize_array_sharding(initialized_state.params['DotReluDot_0']['W2'].value)\n```\n\n## Inspect the Module output\n\nNote that in the output of `initialized_state`, the `params` `W1` and `W2` are of type [`flax.linen.Partitioned`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.Partitioned). This is a wrapper around the actual `jax.Array` that allows Flax to record the axis names associated with it.\n\nYou can access the raw `jax.Array`s by calling `flax.linen.meta.unbox()` upon the dictionary, or call `.value` upon individual variable. You can also use `flax.linen.meta.replace_boxed()` to change the underlying `jax.Array` without modifying the sharding annotations.\n\n```{code-cell} ipython3\nprint(type(initialized_state.params['DotReluDot_0']['Dense_0']['kernel']))\nprint(type(initialized_state.params['DotReluDot_0']['Dense_0']['kernel'].value))\nprint(initialized_state.params['DotReluDot_0']['Dense_0']['kernel'].names)\nprint(initialized_state.params['DotReluDot_0']['Dense_0']['kernel'].value.shape)\n```\n\n```{code-cell} ipython3\n# Say for some unknown reason you want to make the whole param tree all-zero\nunboxed_params = nn.meta.unbox(initialized_state.params)\nall_zero = jax.tree.map(jnp.zeros_like, unboxed_params)\nall_zero_params = nn.meta.replace_boxed(initialized_state.params, all_zero)\nassert jnp.sum(nn.meta.unbox(all_zero_params['DotReluDot_0']['Dense_0']['kernel'])) == 0\n```\n\nYou can also check the underlying [`jax.sharding`](https://jax.readthedocs.io/en/latest/jax.sharding.html) of each parameter, which is now more internal than `NamedSharding`. Note that numbers like `initialized_state.step` are replicated across all devices.\n\n```{code-cell} ipython3\ninitialized_state.params['DotReluDot_0']['Dense_0']['kernel'].value.sharding\n```\n\n```{code-cell} ipython3\nprint(initialized_state.step)\ninitialized_state.step.sharding\n```\n\nYou can use [`jax.tree_util.tree_map`](https://jax.readthedocs.io/en/latest/_autosummary/jax.tree_util.tree_map.html) to perform mass computation on a dict of boxed params, in the same way as on a dict of JAX arrays.\n\n```{code-cell} ipython3\ndiff = jax.tree_util.tree_map(\n    lambda a, b: a - b,\n    initialized_state.params['DotReluDot_0'], initialized_state.params['DotReluDot_0'])\nprint(jax.tree_util.tree_map(jnp.shape, diff))\ndiff_array = diff['Dense_0']['kernel'].value\nprint(type(diff_array))\nprint(diff_array.shape)\n```\n\n## Compile the train step and inference\n\nCreate a `jit`ted training step as follows:\n\n```{code-cell} ipython3\n@functools.partial(jax.jit, in_shardings=(state_sharding, x_sharding),\n                   out_shardings=state_sharding)\ndef train_step(state, x):\n  # A fake loss function.\n  def loss_unrolled(params):\n    y = model.apply({'params': params}, x)\n    return y.sum()\n  grad_fn = jax.grad(loss_unrolled)\n  grads = grad_fn(state.params)\n  state = state.apply_gradients(grads=grads)\n  return state\n\nwith jax.set_mesh(mesh):\n  new_state = train_step(initialized_state, x)\n```\n\n```{code-cell} ipython3\nprint(f'Sharding of Weight 1:')\njax.debug.visualize_array_sharding(initialized_state.params['DotReluDot_0']['Dense_0']['kernel'].value)\nprint(f'Sharding of Weight 2:')\njax.debug.visualize_array_sharding(initialized_state.params['DotReluDot_0']['W2'].value)\n```\n\nThen, create a compiled inference step. Note that the output is also sharded along `(data, None)`.\n\n```{code-cell} ipython3\n@functools.partial(jax.jit, in_shardings=(state_sharding, x_sharding),\n                   out_shardings=x_sharding)\ndef apply_fn(state, x):\n  return state.apply_fn({'params': state.params}, x)\n\nwith jax.set_mesh(mesh):\n  y = apply_fn(new_state, x)\nprint(type(y))\nprint(y.dtype)\nprint(y.shape)\njax.debug.visualize_array_sharding(y)\n```\n\n## Profiling\n\nIf you are running on a TPU pod or a pod slice, you can use a custom `block_all` utility function, as defined below, to measure the performance:\n\n```{code-cell} ipython3\n%%timeit\n\ndef block_all(xs):\n  jax.tree_util.tree_map(lambda x: x.block_until_ready(), xs)\n  return xs\n\nwith jax.set_mesh(mesh):\n  new_state = block_all(train_step(initialized_state, x))\n```\n\n## Logical axis annotation\n\nJAX's automatic SPMD encourages users to explore different sharding layouts to find the optimal one. To this end, in Flax you actually can annotate the dimensions of any data with more descriptive axis names (not just device mesh axis names like `'data'` and `'model'`).\n\nThe `LogicalDotReluDot` and `LogicalMLP` Module definition below are similar to the Modules you created earlier, except for the following:\n\n1. All axes are annotated with more concrete, meaningful names, such as `'embed'`, `'hidden'`, `'batch'` and `'layer'`. These names are referred to as _logical axis names_ in Flax. They make the dimensional changes inside model definitions more readable.\n\n2. [`flax.linen.with_logical_partitioning`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.with_logical_partitioning) replaces `flax.linen.with_partitioning`; and [`flax.linen.with_logical_constraint`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.with_logical_constraint) replaces `jax.lax.with_sharding_constraint`, to recognize the logical axis names.\n\n```{code-cell} ipython3\nclass LogicalDotReluDot(nn.Module):\n  depth: int\n  dense_init: Callable = nn.initializers.xavier_normal()\n  @nn.compact\n  def __call__(self, x):\n    y = nn.Dense(self.depth,\n                 kernel_init=nn.with_logical_partitioning(self.dense_init, ('embed', 'hidden')),\n                 use_bias=False,  # or overwrite with `bias_init`\n                 )(x)\n\n    y = jax.nn.relu(y)\n    # Force a local sharding annotation.\n    y = with_sharding_constraint(y, mesh_sharding(PartitionSpec('data', 'model')))\n\n    W2 = self.param(\n        'W2',\n        nn.with_logical_partitioning(self.dense_init, ('hidden', 'embed')),\n        (self.depth, x.shape[-1]))\n\n    z = jnp.dot(y, W2)\n    # Force a local sharding annotation.\n    z = nn.with_logical_constraint(z, ('batch', 'embed'))\n    return z, None\n\nclass LogicalMLP(nn.Module):\n  num_layers: int\n  depth: int\n  use_scan: bool\n  @nn.compact\n  def __call__(self, x):\n    if self.use_scan:\n      x, _ = nn.scan(LogicalDotReluDot, length=self.num_layers,\n                    variable_axes={\"params\": 0},\n                    split_rngs={\"params\": True},\n                    metadata_params={nn.PARTITION_NAME: 'layer'}\n                    )(self.depth)(x)\n    else:\n      for i in range(self.num_layers):\n        x, _ = LogicalDotReluDot(self.depth)(x)\n    return x\n```\n\nNow, initiate a model and try to figure out what sharding its `state` should have.\n\nTo allow the device mesh to take your model correctly, you need to decide which of these logical axis names are mapped to the device axis `'data'` or `'model'`. This rule is a list of (`logical_axis_name`, `device_axis_name`) tuples, and [`flax.linen.logical_to_mesh_sharding`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.logical_to_mesh_sharding) will convert them to the kind of sharding that the device mesh can understand.\n\nThis allows you to change the rules and try out new partition layouts without modifying the model definition.\n\n```{code-cell} ipython3\n# Unspecified rule means unsharded by default, so no need to specify `('embed', None)` and `('layer', None)`.\nrules = (('batch', 'data'),\n         ('hidden', 'model'))\n\nlogical_model = LogicalMLP(LAYERS, DEPTH, USE_SCAN)\n\nlogical_abstract_variables = jax.eval_shape(\n    functools.partial(init_fn, model=logical_model, optimizer=optimizer), k, x)\nlogical_state_spec = nn.get_partition_spec(logical_abstract_variables)\nprint('annotations are logical, not mesh-specific: ',\n      logical_state_spec.params['LogicalDotReluDot_0']['Dense_0']['kernel'])\n\nlogical_state_sharding = nn.logical_to_mesh_sharding(logical_state_spec, mesh, rules)\nprint('sharding annotations are mesh-specific: ',\n      logical_state_sharding.params['LogicalDotReluDot_0']['Dense_0']['kernel'].spec)\n```\n\nYou can verify that the `logical_state_spec` here has the same content as `state_spec` in the previous (\"non-logical\") example. This allows you to `jax.jit` your Module's [`flax.linen.Module.init`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.init) and [`flax.linen.Module.apply`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.apply) the same way in the above above.\n\n```{code-cell} ipython3\nstate_sharding.params['DotReluDot_0'] == logical_state_sharding.params['LogicalDotReluDot_0']\n```\n\n```{code-cell} ipython3\nlogical_jit_init_fn = jax.jit(init_fn, static_argnums=(2, 3),\n                      in_shardings=(mesh_sharding(PartitionSpec()), x_sharding),  # PRNG key and x\n                      out_shardings=logical_state_sharding)\n\nlogical_initialized_state = logical_jit_init_fn(k, x, logical_model, optimizer)\n```\n\n```{code-cell} ipython3\nprint(f'Sharding of Weight 1:')\njax.debug.visualize_array_sharding(logical_initialized_state.params['LogicalDotReluDot_0']['Dense_0']['kernel'].value)\nprint(f'Sharding of Weight 2:')\njax.debug.visualize_array_sharding(logical_initialized_state.params['LogicalDotReluDot_0']['W2'].value)\n```\n\n## When to use device axis / logical axis\n\nChoosing when to use a device or logical axis depends on how much you want to control the partitioning of your model:\n\n* **Device mesh axis**: If you want a very simple model, or you are very confident of your way of partitioning, defining it with __device mesh axis__ can potentially save you a few extra lines of code of converting the logical naming back to the device naming.\n\n* **Logical naming**: On the other hand, the __logical naming__ helpers can be useful for exploring different sharding layouts. Use this if you want to experiment around and find the most optimal partition layout for your model.\n\n* **Device axis names**: In really advanced use cases, you may have more complicated sharding patterns that require annotating *activation* dimension names differently from *parameter* dimension names. If you wish to have more fine-grained control on manual mesh assignments, directly using __device axis names__ could be more helpful.\n\n+++\n\n## Save the data\n\nTo save the cross-device array, you can use Orbax as shown in the [Save and load checkpoints guide - Multi-host/multi-process checkpointing](https://flax.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html#multi-host-multi-process-checkpointing). This is especially required if you are running on a multi-host environment (for example, a TPU pod).\n\nIn practice, you might want to save the raw `jax.Array` pytree as checkpoint, instead of the wrapped `Partitioned` values, to reduce complexity. You can restore it as-is and put it back into an annotated pytree with `flax.linen.meta.replace_boxed()`.\n\nKeep in mind that to restore the arrays to the desired partition, you need to provide a sample `target` pytree that has the same structure and has the desired [`jax.sharding.Sharding`](https://jax.readthedocs.io/en/latest/jax.sharding.html#jax.sharding.Sharding) in place for each JAX array. The sharding you use to restore the array doesn't necessarily need to be the same as the ones you used to store the array.\n"
  },
  {
    "path": "docs/guides/parallel_training/index.rst",
    "content": "Parallel training\n=================\n\n.. toctree::\n   :maxdepth: 1\n\n   ensembling\n   flax_on_pjit\n"
  },
  {
    "path": "docs/guides/quantization/fp8_basics.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"ca360491\",\n   \"metadata\": {},\n   \"source\": [\n    \"# User Guide on Using FP8\\n\",\n    \"\\n\",\n    \"JAX supports various FP8 formats, including E4M3 (jnp.float8_e4m3fn) and E5M2\\n\",\n    \"(jnp.float8_e5m2). Due to the limited range of FP8 data types, higher-precision\\n\",\n    \"data must be scaled to fit within the FP8 representable range, a process known\\n\",\n    \"as quantization (Q). Conversely, de-quantization (DQ) rescales the FP8 data back\\n\",\n    \"to its original type.\\n\",\n    \"\\n\",\n    \"While jnp.dot supports FP8 inputs directly, proper quantization and\\n\",\n    \"dequantization is needed for optimal performance. Flax provides\\n\",\n    \"nn.fp8_ops.Fp8DotGeneral and nn.fp8_ops.Fp8Einsum modules that handle\\n\",\n    \"this automatically and can be used with existing layers like nn.Dense.\\n\",\n    \"\\n\",\n    \"This tutorial will walk you through the basics of how to use it.\\n\",\n    \"\\n\",\n    \"## Setting up our environment\\n\",\n    \"\\n\",\n    \"Here, we provide the code necessary to set up the environment for our notebook.\\n\",\n    \"Additionally, we define a function to check if the XLA-optimized HLO will indeed\\n\",\n    \"call an FP8 dot operation under the hood.\\n\",\n    \"\\n\",\n    \"*Note: This tutorial relies on the XLA-FP8 feature, which is only supported on\\n\",\n    \"NVIDIA Hopper GPUs or later.*\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"177b91c4\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import flax\\n\",\n    \"import jax\\n\",\n    \"import re\\n\",\n    \"import pprint\\n\",\n    \"from jax import random\\n\",\n    \"from jax import numpy as jnp\\n\",\n    \"from jax._src import test_util as jtu\\n\",\n    \"from flax import linen as nn\\n\",\n    \"from flax.linen import fp8_ops\\n\",\n    \"\\n\",\n    \"e4m3 = jnp.float8_e4m3fn\\n\",\n    \"f32 = jnp.float32\\n\",\n    \"E4M3_MAX = jnp.finfo(e4m3).max.astype(f32)\\n\",\n    \"\\n\",\n    \"assert jtu.is_cuda_compute_capability_at_least(\\\"9.0\\\")\\n\",\n    \"\\n\",\n    \"def check_fp8_call(lowered):\\n\",\n    \"  hlo = lowered.compile()\\n\",\n    \"  if re.search(r\\\"custom-call\\\\(f8e4m3fn.*, f8e4m3fn.*\\\", hlo.as_text()):\\n\",\n    \"    print(\\\"Fp8 call detected!\\\")\\n\",\n    \"  else:\\n\",\n    \"    print(\\\"No Fp8 call!\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"4adc021f\",\n   \"metadata\": {},\n   \"source\": [\n    \"## FLAX Low Level API\\n\",\n    \"\\n\",\n    \"The JAX dot operations (e.g. `jnp.dot`) support the FP8 dtype inputs. So it is\\n\",\n    \"legal to do the following call:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"c54c374e\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"k0, k1 = random.split(random.key(0), 2)\\n\",\n    \"a = random.uniform(k0, (16, 32))\\n\",\n    \"b = random.uniform(k1, (32, 64))\\n\",\n    \"@jax.jit\\n\",\n    \"def dot_fp8(a, b):\\n\",\n    \"  return jnp.dot(a.astype(e4m3), b.astype(e4m3), preferred_element_type=f32)\\n\",\n    \"check_fp8_call(dot_fp8.lower(a, b))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"adb22878\",\n   \"metadata\": {},\n   \"source\": [\n    \"However, this approach has two key limitations:\\n\",\n    \"\\n\",\n    \"1. `jnp.dot` does not support custom scaling factors for operands, defaulting to\\n\",\n    \"   a scale of 1.0\\n\",\n    \"2. The autodiff does not automatically use E5M2 for gradients and E4M3 for\\n\",\n    \"   activations/weights during training, which is the recommended practice\\n\",\n    \"\\n\",\n    \"To overcome these limitations and implement proper FP8 matrix multiplication, we\\n\",\n    \"recommend using the Flax FP8 APIs. Let's start with a basic scaling approach.\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"### Current Scaling\\n\",\n    \"\\n\",\n    \"Scaling factors are usually defined as `scale = amax(x) / MAX`, where `amax` is\\n\",\n    \"an operation to find the absolute maximum value of the tensor, and `MAX` is the\\n\",\n    \"maximum value of the representable range of the target dtype. This scaling\\n\",\n    \"approach allows us to derive the scaling factors directly from the current\\n\",\n    \"operand tensors of the dot product.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"f0e746e3\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"@jax.jit\\n\",\n    \"def dot_fp8(a, b):\\n\",\n    \"  a_scale = jnp.max(jnp.abs(A)) / E4M3_MAX\\n\",\n    \"  b_scale = jnp.max(jnp.abs(B)) / E4M3_MAX\\n\",\n    \"  a = fp8_ops.quantize(a, e4m3, a_scale, f32)\\n\",\n    \"  b = fp8_ops.quantize(b, e4m3, b_scale, f32)\\n\",\n    \"\\n\",\n    \"  c = jnp.dot(a, b, preferred_element_type=f32)\\n\",\n    \"  c = fp8_ops.dequantize(c, f32, a_scale * b_scale)\\n\",\n    \"  return c\\n\",\n    \"\\n\",\n    \"c = dot_fp8(a, b)\\n\",\n    \"check_fp8_call(dot_fp8.lower(a, b))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"59aca6fe\",\n   \"metadata\": {},\n   \"source\": [\n    \"As shown in the code, we perform quantization (`fp8_ops.quantize`) on the\\n\",\n    \"tensors to get the lower precision operands. The `jnp.dot` processes them and\\n\",\n    \"accumulates the output in high precision (i.e., the `preferred_element_type`).\\n\",\n    \"After that, we multiply the result by the scaling factors to dequantize back to\\n\",\n    \"the original range (`fp8_ops.dequantize`). Note that while this example uses\\n\",\n    \"E4M3 for both inputs, it is possible to use different FP8 dtypes like E4M3 and\\n\",\n    \"E5M2 for the inputs. The quantization method and the scaling factors can also be\\n\",\n    \"customized based on application needs.\\n\",\n    \"\\n\",\n    \"One major issue with the current scaling method is the performance overhead\\n\",\n    \"introduced by computing `a_scale` and `b_scale`, which requires additional\\n\",\n    \"loading of the operand tensors. To overcome this issue, we recommend the delayed\\n\",\n    \"scaling.\\n\",\n    \"\\n\",\n    \"### Delayed Scaling\\n\",\n    \"\\n\",\n    \"In delayed scaling, we use a scaling factor associated with an amax history. The\\n\",\n    \"scaling factor remains a scalar, but the amax history is a list that stores amax\\n\",\n    \"values from recent steps (e.g., 1024 steps). Both tensors are computed from\\n\",\n    \"previous steps and maintained in the model parameters.\\n\",\n    \"\\n\",\n    \"The quantization and dequantization operations for delayed scaling are provided\\n\",\n    \"by `fp8_ops.in_q` and `fp8_ops.out_dq` respectively. `fp8_ops.in_q` handles\\n\",\n    \"input quantization and update the amax history and scaling factor, while\\n\",\n    \"`fp8_ops.out_dq` performs output dequantization.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"cf466308\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"a_scale = jnp.array(1.0)\\n\",\n    \"b_scale = jnp.array(1.0)\\n\",\n    \"a_amax_hist = jnp.zeros((1024,))\\n\",\n    \"b_amax_hist = jnp.zeros((1024,))\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def dot_fp8(a, a_scale, a_amax_hist, b, b_scale, b_amax_hist):\\n\",\n    \"  a, a_scale = fp8_ops.in_q(f32, e4m3, a, a_scale, a_amax_hist)\\n\",\n    \"  b, b_scale = fp8_ops.in_q(f32, e4m3, b, b_scale, b_amax_hist)\\n\",\n    \"  \\n\",\n    \"  c = jnp.dot(a, b, preferred_element_type=f32)\\n\",\n    \"  c = fp8_ops.out_dq(f32, a_scale, b_scale, c)\\n\",\n    \"  return c\\n\",\n    \"\\n\",\n    \"c = dot_fp8(a, a_scale, a_amax_hist, b, b_scale, b_amax_hist)\\n\",\n    \"check_fp8_call(dot_fp8.lower(a, a_scale, a_amax_hist, b, b_scale, b_amax_hist))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"b3bdc038\",\n   \"metadata\": {},\n   \"source\": [\n    \"In this example, we first prepare three pairs of scaling factors and amax\\n\",\n    \"histories, treating them as results computed from previous steps. Then, we apply\\n\",\n    \"`fp8_ops.in_q` to the input operands of `jnp.dot`, followed by `fp8_ops.out_dq`\\n\",\n    \"to the output of `jnp.dot`.\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"## FLAX High Level API\\n\",\n    \"Flax provides high-level operations to seamlessly integrate FP8 quantization\\n\",\n    \"into existing layers. Instead of manually handling quantization of the delayed\\n\",\n    \"scaling (e.g., the maintanence of the amax history and scaling factors), users\\n\",\n    \"can simply use these drop-in replacements:\\n\",\n    \"\\n\",\n    \"* `fp8_ops.Fp8DotGeneral` for `lax.dot_general` operations\\n\",\n    \"* `fp8_ops.Fp8Einsum` for `jnp.einsum` operations \\n\",\n    \"\\n\",\n    \"These operations automatically handle all FP8-related functionality, including\\n\",\n    \"quantization/dequantization, scale factor updates, and FP8 dtype selection for\\n\",\n    \"both forward and backward passes.\\n\",\n    \"\\n\",\n    \"Consider the following example:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"bd8d9dba\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"model = nn.Dense(features=64, dot_general_cls=fp8_ops.Fp8DotGeneral)\\n\",\n    \"params = model.init(k0, A)\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def train_step(var, a): \\n\",\n    \"  c = model.apply(var, a)\\n\",\n    \"  return jnp.sum(c)\\n\",\n    \"\\n\",\n    \"check_fp8_call(train_step.lower(params, A))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"ba280e79\",\n   \"metadata\": {},\n   \"source\": [\n    \"By setting `dot_general_cls=fp8_ops.Fp8DotGeneral`, we replace the\\n\",\n    \"default `lax.dot_general` operation in `nn.Dense` with an FP8-enabled version.\\n\",\n    \"The model usage remains similar, but now includes additional parameters for FP8\\n\",\n    \"quantization: scaling factors and amax history values. The next section explains\\n\",\n    \"how to update these FP8-specific parameters.\\n\",\n    \"\\n\",\n    \"For models that use `jnp.einsum` operations, such as Mixture of Experts (MoE)\\n\",\n    \"layers, users can replace them with `fp8_ops.Fp8Einsum` to enable FP8\\n\",\n    \"quantization. Here's an example:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"961b4549\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from typing import Any\\n\",\n    \"class FooModule(nn.Module):\\n\",\n    \"  einsum: Any = None\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, a, b):\\n\",\n    \"    if self.einsum is not None:\\n\",\n    \"      einsum_fn = self.einsum()\\n\",\n    \"    elif self.einsum is None:\\n\",\n    \"      einsum_fn = jnp.einsum\\n\",\n    \"    c = einsum_fn(\\\"mk,kn->mn\\\", a, b)\\n\",\n    \"    return c\\n\",\n    \"\\n\",\n    \"model = FooModule(einsum=fp8_ops.Fp8Einsum)\\n\",\n    \"params = model.init(k0, a, b)\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def train_step(var, a, b):\\n\",\n    \"  c = model.apply(var, a, b)\\n\",\n    \"  return jnp.sum(c)\\n\",\n    \"\\n\",\n    \"check_fp8_call(train_step.lower(params, a, b))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a83b0851\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Manipulate FP8 params\\n\",\n    \"\\n\",\n    \"The following sections explain the internal FP8 parameters managed by\\n\",\n    \"`fp8_ops.Fp8DotGeneral` and `fp8_ops.Fp8Einsum`. These parameters\\n\",\n    \"include scaling factors and amax history values that control the FP8\\n\",\n    \"quantization process. While most users don't need to interact with these\\n\",\n    \"directly, understanding them can be valuable for advanced optimization and\\n\",\n    \"debugging.\\n\",\n    \"\\n\",\n    \"Let's first examine the data structure of `params`. In the code below, we redact\\n\",\n    \"the parameter values and then display the PyTree structure.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"873799fe\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"params_structure = flax.core.unfreeze(params).copy()\\n\",\n    \"params_structure = flax.traverse_util.flatten_dict(params_structure, sep='/')\\n\",\n    \"for key, value in params_structure.items():\\n\",\n    \"    params_structure[key] = '*'\\n\",\n    \"params_structure = flax.traverse_util.unflatten_dict(params_structure, sep='/')\\n\",\n    \"pprint.pprint(params_structure)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"031894dc\",\n   \"metadata\": {},\n   \"source\": [\n    \"The output is as follows:\\n\",\n    \"\\n\",\n    \"```plaintext\\n\",\n    \"{'_overwrite_with_gradient': {'Fp8Einsum_0': {'input_amax_history': '*',\\n\",\n    \"                                              'input_scale': '*',\\n\",\n    \"                                              'kernel_amax_history': '*',\\n\",\n    \"                                              'kernel_scale': '*',\\n\",\n    \"                                              'output_grad_amax_history': '*',\\n\",\n    \"                                              'output_grad_scale': '*'}}}\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"In addition to the expected `params`, there is an additional category called\\n\",\n    \"`_overwrite_with_gradient`. This category includes three pairs of `amax_history`\\n\",\n    \"and `scale` for the activation, kernel, and dot gradient, respectively.\\n\",\n    \"\\n\",\n    \"### Update gradient of FP8 params\\n\",\n    \"Now, we perform one training step to obtain the gradients and see how to use\\n\",\n    \"them to update the parameters.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"593fc35f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"step_fn = jax.jit(jax.grad(train_step, (0, 1)))\\n\",\n    \"\\n\",\n    \"grads = step_fn(params, A)\\n\",\n    \"\\n\",\n    \"params = flax.core.unfreeze(params)\\n\",\n    \"params = flax.traverse_util.flatten_dict(params, sep='/')\\n\",\n    \"grads = flax.traverse_util.flatten_dict(grads[0], sep='/')\\n\",\n    \"\\n\",\n    \"for key, value in params.items():\\n\",\n    \"  if key.startswith('params'):\\n\",\n    \"    params[key] = value + 0.01 * grads[key]\\n\",\n    \"  if key.startswith('_overwrite_with_gradient'):\\n\",\n    \"    params[key] = grads[key]\\n\",\n    \"\\n\",\n    \"params = flax.traverse_util.unflatten_dict(params, sep='/')\\n\",\n    \"params = flax.core.freeze(params)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1a8e2153\",\n   \"metadata\": {},\n   \"source\": [\n    \"The above code demonstrates how to update both `params` and\\n\",\n    \"`_overwrite_with_gradient`. For `params`, we use the formula `new_param =\\n\",\n    \"old_param + 0.01 * grads`, where `0.01` is the learning rate (or users can use\\n\",\n    \"whatever optimizers from `optax`). For `_overwrite_with_gradient`, we simply use\\n\",\n    \"the gradient to overwrite the old values.\\n\",\n    \"\\n\",\n    \"Note that `flax.training.train_state.TrainState` conveniently supports the\\n\",\n    \"category of `_overwrite_with_gradient`, so users do not need to modify their\\n\",\n    \"scripts if they don't use custom `TrainState`.\\n\",\n    \"\\n\",\n    \"## Accumulate gradient of FP8 params\\n\",\n    \"When the same parameter is used in a branched manner, the autograd mechanism\\n\",\n    \"will add their gradients from these branches. This is common in scenarios like\\n\",\n    \"pipeline parallelism, where each microbatch shares the same set of parameters\\n\",\n    \"for the minibatch. However, for the `_overwrite_with_gradient` parameters, this\\n\",\n    \"accumulation by addition is not meaningful. Instead, we prefer custom\\n\",\n    \"accumulation by taking the maximum value.\\n\",\n    \"\\n\",\n    \"To address this, we introduce a custom dtype `fp8_ops.fp32_max_grad`. The basic\\n\",\n    \"usage is demonstrated below:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"2d3a86e9\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fmax32 = fp8_ops.fp32_max_grad\\n\",\n    \"\\n\",\n    \"def reuse_fp8_param(x, y, scale, amax_history):\\n\",\n    \"  scale = scale.astype(fmax32)\\n\",\n    \"  amax_history = amax_history.astype(fmax32)\\n\",\n    \"\\n\",\n    \"  x = fp8_ops.in_qdq(f32, e4m3, x, scale, amax_history)\\n\",\n    \"  y = fp8_ops.in_qdq(f32, e4m3, y, scale, amax_history)\\n\",\n    \"  return x + y\\n\",\n    \"\\n\",\n    \"reuse_fp8_param_fn = jax.grad(reuse_fp8_param, (0, 1, 2, 3))\\n\",\n    \"reuse_fp8_param_fn = jax.jit(reuse_fp8_param_fn)\\n\",\n    \"\\n\",\n    \"_, _, new_ah, new_sf = reuse_fp8_param_fn(2.0, 3.0, a_scale, a_amax_hist)\\n\",\n    \"print(new_ah, new_sf)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"2321a9bb\",\n   \"metadata\": {},\n   \"source\": [\n    \"In this example, we first cast the `scale` and `amax_history` to\\n\",\n    \"`fp8_ops.fp32_max_grad` and then call `fp8_ops.in_qdq` twice using the same pair\\n\",\n    \"of `scale` and `amax_history`. During autograd, their gradients from each branch\\n\",\n    \"will be taken as the maximum, giving us the correct results of:\\n\",\n    \"\\n\",\n    \"```plaintext\\n\",\n    \"1.0 [3. 0. 0. ... 0. 0. 0.]\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"If we do not perform the type casting, we get the following result, meaning the\\n\",\n    \"gradients of the two branches are added:\\n\",\n    \"\\n\",\n    \"```plaintext\\n\",\n    \"2.0 [5. 0. 0. ... 0. 0. 0.]\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"This casting is already included if users choose to use the high-level APIs.\\n\",\n    \"\\n\",\n    \"## Deprecated APIs\\n\",\n    \"Previously, we provided APIs like `fp8_ops.quantize_dequantize` for current\\n\",\n    \"scaling and `fp8_ops.[in|out]_qdq` for delayed scaling. These were used with\\n\",\n    \"high precision dot operations, leveraging an XLA-FP8 feature that\\n\",\n    \"pattern-matched QDQ->dot sequences to Q->fp8_cublas_gemm. The corresponding\\n\",\n    \"high-level API was called `fp8_ops.Fp8DotGeneralOp`. However, this pattern\\n\",\n    \"matching-based solution proved brittle, as the patterns could be easily broken\\n\",\n    \"by other XLA optimizations. We recommend users migrate from these deprecated\\n\",\n    \"APIs to the newer ones described above.\\n\",\n    \"\\n\",\n    \"For migration, users should replace:\\n\",\n    \"* `fp8_ops.quantize_dequantize -> jnp.dot` with `fp8_ops.quantize -> jnp.dot ->\\n\",\n    \"  fp8_ops.dequantize`\\n\",\n    \"* `fp8_ops.in_qdq -> jnp.dot -> fp8_ops.out_qdq` with `fp8_ops.in_q -> jnp.dot\\n\",\n    \"  -> fp8_ops.out_dq`\\n\",\n    \"* `fp8_ops.Fp8DotGeneralOp` with `fp8_ops.Fp8DotGeneral`\\n\",\n    \"\\n\",\n    \"Additionally, we provide an einsum variant through `fp8_ops.Fp8Einsum`.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs/guides/quantization/fp8_basics.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# User Guide on Using FP8\n\nJAX supports various FP8 formats, including E4M3 (jnp.float8_e4m3fn) and E5M2\n(jnp.float8_e5m2). Due to the limited range of FP8 data types, higher-precision\ndata must be scaled to fit within the FP8 representable range, a process known\nas quantization (Q). Conversely, de-quantization (DQ) rescales the FP8 data back\nto its original type.\n\nWhile jnp.dot supports FP8 inputs directly, proper quantization and\ndequantization is needed for optimal performance. Flax provides\nnn.fp8_ops.Fp8DotGeneral and nn.fp8_ops.Fp8Einsum modules that handle\nthis automatically and can be used with existing layers like nn.Dense.\n\nThis tutorial will walk you through the basics of how to use it.\n\n## Setting up our environment\n\nHere, we provide the code necessary to set up the environment for our notebook.\nAdditionally, we define a function to check if the XLA-optimized HLO will indeed\ncall an FP8 dot operation under the hood.\n\n*Note: This tutorial relies on the XLA-FP8 feature, which is only supported on\nNVIDIA Hopper GPUs or later.*\n\n```{code-cell}\nimport flax\nimport jax\nimport re\nimport pprint\nfrom jax import random\nfrom jax import numpy as jnp\nfrom jax._src import test_util as jtu\nfrom flax import linen as nn\nfrom flax.linen import fp8_ops\n\ne4m3 = jnp.float8_e4m3fn\nf32 = jnp.float32\nE4M3_MAX = jnp.finfo(e4m3).max.astype(f32)\n\nassert jtu.is_cuda_compute_capability_at_least(\"9.0\")\n\ndef check_fp8_call(lowered):\n  hlo = lowered.compile()\n  if re.search(r\"custom-call\\(f8e4m3fn.*, f8e4m3fn.*\", hlo.as_text()):\n    print(\"Fp8 call detected!\")\n  else:\n    print(\"No Fp8 call!\")\n```\n\n## FLAX Low Level API\n\nThe JAX dot operations (e.g. `jnp.dot`) support the FP8 dtype inputs. So it is\nlegal to do the following call:\n\n```{code-cell}\nk0, k1 = random.split(random.key(0), 2)\na = random.uniform(k0, (16, 32))\nb = random.uniform(k1, (32, 64))\n@jax.jit\ndef dot_fp8(a, b):\n  return jnp.dot(a.astype(e4m3), b.astype(e4m3), preferred_element_type=f32)\ncheck_fp8_call(dot_fp8.lower(a, b))\n```\n\nHowever, this approach has two key limitations:\n\n1. `jnp.dot` does not support custom scaling factors for operands, defaulting to\n   a scale of 1.0\n2. The autodiff does not automatically use E5M2 for gradients and E4M3 for\n   activations/weights during training, which is the recommended practice\n\nTo overcome these limitations and implement proper FP8 matrix multiplication, we\nrecommend using the Flax FP8 APIs. Let's start with a basic scaling approach.\n\n\n### Current Scaling\n\nScaling factors are usually defined as `scale = amax(x) / MAX`, where `amax` is\nan operation to find the absolute maximum value of the tensor, and `MAX` is the\nmaximum value of the representable range of the target dtype. This scaling\napproach allows us to derive the scaling factors directly from the current\noperand tensors of the dot product.\n\n```{code-cell}\n@jax.jit\ndef dot_fp8(a, b):\n  a_scale = jnp.max(jnp.abs(A)) / E4M3_MAX\n  b_scale = jnp.max(jnp.abs(B)) / E4M3_MAX\n  a = fp8_ops.quantize(a, e4m3, a_scale, f32)\n  b = fp8_ops.quantize(b, e4m3, b_scale, f32)\n\n  c = jnp.dot(a, b, preferred_element_type=f32)\n  c = fp8_ops.dequantize(c, f32, a_scale * b_scale)\n  return c\n\nc = dot_fp8(a, b)\ncheck_fp8_call(dot_fp8.lower(a, b))\n```\n\nAs shown in the code, we perform quantization (`fp8_ops.quantize`) on the\ntensors to get the lower precision operands. The `jnp.dot` processes them and\naccumulates the output in high precision (i.e., the `preferred_element_type`).\nAfter that, we multiply the result by the scaling factors to dequantize back to\nthe original range (`fp8_ops.dequantize`). Note that while this example uses\nE4M3 for both inputs, it is possible to use different FP8 dtypes like E4M3 and\nE5M2 for the inputs. The quantization method and the scaling factors can also be\ncustomized based on application needs.\n\nOne major issue with the current scaling method is the performance overhead\nintroduced by computing `a_scale` and `b_scale`, which requires additional\nloading of the operand tensors. To overcome this issue, we recommend the delayed\nscaling.\n\n### Delayed Scaling\n\nIn delayed scaling, we use a scaling factor associated with an amax history. The\nscaling factor remains a scalar, but the amax history is a list that stores amax\nvalues from recent steps (e.g., 1024 steps). Both tensors are computed from\nprevious steps and maintained in the model parameters.\n\nThe quantization and dequantization operations for delayed scaling are provided\nby `fp8_ops.in_q` and `fp8_ops.out_dq` respectively. `fp8_ops.in_q` handles\ninput quantization and update the amax history and scaling factor, while\n`fp8_ops.out_dq` performs output dequantization.\n\n```{code-cell}\na_scale = jnp.array(1.0)\nb_scale = jnp.array(1.0)\na_amax_hist = jnp.zeros((1024,))\nb_amax_hist = jnp.zeros((1024,))\n\n@jax.jit\ndef dot_fp8(a, a_scale, a_amax_hist, b, b_scale, b_amax_hist):\n  a, a_scale = fp8_ops.in_q(f32, e4m3, a, a_scale, a_amax_hist)\n  b, b_scale = fp8_ops.in_q(f32, e4m3, b, b_scale, b_amax_hist)\n  \n  c = jnp.dot(a, b, preferred_element_type=f32)\n  c = fp8_ops.out_dq(f32, a_scale, b_scale, c)\n  return c\n\nc = dot_fp8(a, a_scale, a_amax_hist, b, b_scale, b_amax_hist)\ncheck_fp8_call(dot_fp8.lower(a, a_scale, a_amax_hist, b, b_scale, b_amax_hist))\n```\n\nIn this example, we first prepare three pairs of scaling factors and amax\nhistories, treating them as results computed from previous steps. Then, we apply\n`fp8_ops.in_q` to the input operands of `jnp.dot`, followed by `fp8_ops.out_dq`\nto the output of `jnp.dot`.\n\n\n## FLAX High Level API\nFlax provides high-level operations to seamlessly integrate FP8 quantization\ninto existing layers. Instead of manually handling quantization of the delayed\nscaling (e.g., the maintanence of the amax history and scaling factors), users\ncan simply use these drop-in replacements:\n\n* `fp8_ops.Fp8DotGeneral` for `lax.dot_general` operations\n* `fp8_ops.Fp8Einsum` for `jnp.einsum` operations \n\nThese operations automatically handle all FP8-related functionality, including\nquantization/dequantization, scale factor updates, and FP8 dtype selection for\nboth forward and backward passes.\n\nConsider the following example:\n\n```{code-cell}\nmodel = nn.Dense(features=64, dot_general_cls=fp8_ops.Fp8DotGeneral)\nparams = model.init(k0, A)\n\n@jax.jit\ndef train_step(var, a): \n  c = model.apply(var, a)\n  return jnp.sum(c)\n\ncheck_fp8_call(train_step.lower(params, A))\n```\n\nBy setting `dot_general_cls=fp8_ops.Fp8DotGeneral`, we replace the\ndefault `lax.dot_general` operation in `nn.Dense` with an FP8-enabled version.\nThe model usage remains similar, but now includes additional parameters for FP8\nquantization: scaling factors and amax history values. The next section explains\nhow to update these FP8-specific parameters.\n\nFor models that use `jnp.einsum` operations, such as Mixture of Experts (MoE)\nlayers, users can replace them with `fp8_ops.Fp8Einsum` to enable FP8\nquantization. Here's an example:\n\n```{code-cell}\nfrom typing import Any\nclass FooModule(nn.Module):\n  einsum: Any = None\n  @nn.compact\n  def __call__(self, a, b):\n    if self.einsum is not None:\n      einsum_fn = self.einsum()\n    elif self.einsum is None:\n      einsum_fn = jnp.einsum\n    c = einsum_fn(\"mk,kn->mn\", a, b)\n    return c\n\nmodel = FooModule(einsum=fp8_ops.Fp8Einsum)\nparams = model.init(k0, a, b)\n\n@jax.jit\ndef train_step(var, a, b):\n  c = model.apply(var, a, b)\n  return jnp.sum(c)\n\ncheck_fp8_call(train_step.lower(params, a, b))\n```\n\n## Manipulate FP8 params\n\nThe following sections explain the internal FP8 parameters managed by\n`fp8_ops.Fp8DotGeneral` and `fp8_ops.Fp8Einsum`. These parameters\ninclude scaling factors and amax history values that control the FP8\nquantization process. While most users don't need to interact with these\ndirectly, understanding them can be valuable for advanced optimization and\ndebugging.\n\nLet's first examine the data structure of `params`. In the code below, we redact\nthe parameter values and then display the PyTree structure.\n\n```{code-cell}\nparams_structure = flax.core.unfreeze(params).copy()\nparams_structure = flax.traverse_util.flatten_dict(params_structure, sep='/')\nfor key, value in params_structure.items():\n    params_structure[key] = '*'\nparams_structure = flax.traverse_util.unflatten_dict(params_structure, sep='/')\npprint.pprint(params_structure)\n```\n\nThe output is as follows:\n\n```plaintext\n{'_overwrite_with_gradient': {'Fp8Einsum_0': {'input_amax_history': '*',\n                                              'input_scale': '*',\n                                              'kernel_amax_history': '*',\n                                              'kernel_scale': '*',\n                                              'output_grad_amax_history': '*',\n                                              'output_grad_scale': '*'}}}\n```\n\nIn addition to the expected `params`, there is an additional category called\n`_overwrite_with_gradient`. This category includes three pairs of `amax_history`\nand `scale` for the activation, kernel, and dot gradient, respectively.\n\n### Update gradient of FP8 params\nNow, we perform one training step to obtain the gradients and see how to use\nthem to update the parameters.\n\n```{code-cell}\nstep_fn = jax.jit(jax.grad(train_step, (0, 1)))\n\ngrads = step_fn(params, A)\n\nparams = flax.core.unfreeze(params)\nparams = flax.traverse_util.flatten_dict(params, sep='/')\ngrads = flax.traverse_util.flatten_dict(grads[0], sep='/')\n\nfor key, value in params.items():\n  if key.startswith('params'):\n    params[key] = value + 0.01 * grads[key]\n  if key.startswith('_overwrite_with_gradient'):\n    params[key] = grads[key]\n\nparams = flax.traverse_util.unflatten_dict(params, sep='/')\nparams = flax.core.freeze(params)\n```\n\nThe above code demonstrates how to update both `params` and\n`_overwrite_with_gradient`. For `params`, we use the formula `new_param =\nold_param + 0.01 * grads`, where `0.01` is the learning rate (or users can use\nwhatever optimizers from `optax`). For `_overwrite_with_gradient`, we simply use\nthe gradient to overwrite the old values.\n\nNote that `flax.training.train_state.TrainState` conveniently supports the\ncategory of `_overwrite_with_gradient`, so users do not need to modify their\nscripts if they don't use custom `TrainState`.\n\n## Accumulate gradient of FP8 params\nWhen the same parameter is used in a branched manner, the autograd mechanism\nwill add their gradients from these branches. This is common in scenarios like\npipeline parallelism, where each microbatch shares the same set of parameters\nfor the minibatch. However, for the `_overwrite_with_gradient` parameters, this\naccumulation by addition is not meaningful. Instead, we prefer custom\naccumulation by taking the maximum value.\n\nTo address this, we introduce a custom dtype `fp8_ops.fp32_max_grad`. The basic\nusage is demonstrated below:\n\n```{code-cell}\nfmax32 = fp8_ops.fp32_max_grad\n\ndef reuse_fp8_param(x, y, scale, amax_history):\n  scale = scale.astype(fmax32)\n  amax_history = amax_history.astype(fmax32)\n\n  x = fp8_ops.in_qdq(f32, e4m3, x, scale, amax_history)\n  y = fp8_ops.in_qdq(f32, e4m3, y, scale, amax_history)\n  return x + y\n\nreuse_fp8_param_fn = jax.grad(reuse_fp8_param, (0, 1, 2, 3))\nreuse_fp8_param_fn = jax.jit(reuse_fp8_param_fn)\n\n_, _, new_ah, new_sf = reuse_fp8_param_fn(2.0, 3.0, a_scale, a_amax_hist)\nprint(new_ah, new_sf)\n```\n\nIn this example, we first cast the `scale` and `amax_history` to\n`fp8_ops.fp32_max_grad` and then call `fp8_ops.in_qdq` twice using the same pair\nof `scale` and `amax_history`. During autograd, their gradients from each branch\nwill be taken as the maximum, giving us the correct results of:\n\n```plaintext\n1.0 [3. 0. 0. ... 0. 0. 0.]\n```\n\nIf we do not perform the type casting, we get the following result, meaning the\ngradients of the two branches are added:\n\n```plaintext\n2.0 [5. 0. 0. ... 0. 0. 0.]\n```\n\nThis casting is already included if users choose to use the high-level APIs.\n\n## Deprecated APIs\nPreviously, we provided APIs like `fp8_ops.quantize_dequantize` for current\nscaling and `fp8_ops.[in|out]_qdq` for delayed scaling. These were used with\nhigh precision dot operations, leveraging an XLA-FP8 feature that\npattern-matched QDQ->dot sequences to Q->fp8_cublas_gemm. The corresponding\nhigh-level API was called `fp8_ops.Fp8DotGeneralOp`. However, this pattern\nmatching-based solution proved brittle, as the patterns could be easily broken\nby other XLA optimizations. We recommend users migrate from these deprecated\nAPIs to the newer ones described above.\n\nFor migration, users should replace:\n* `fp8_ops.quantize_dequantize -> jnp.dot` with `fp8_ops.quantize -> jnp.dot ->\n  fp8_ops.dequantize`\n* `fp8_ops.in_qdq -> jnp.dot -> fp8_ops.out_qdq` with `fp8_ops.in_q -> jnp.dot\n  -> fp8_ops.out_dq`\n* `fp8_ops.Fp8DotGeneralOp` with `fp8_ops.Fp8DotGeneral`\n\nAdditionally, we provide an einsum variant through `fp8_ops.Fp8Einsum`.\n"
  },
  {
    "path": "docs/guides/quantization/index.rst",
    "content": "Quantization\n============\n\n.. toctree::\n   :maxdepth: 1\n\n   fp8_basics\n"
  },
  {
    "path": "docs/guides/training_techniques/batch_norm.rst",
    "content": "Batch normalization\n===================\n\nIn this guide, you will learn how to apply `batch normalization <https://arxiv.org/abs/1502.03167>`__\nusing :meth:`flax.linen.BatchNorm <flax.linen.BatchNorm>`.\n\nBatch normalization is a regularization technique used to speed up training and improve convergence.\nDuring training, it computes running averages over feature dimensions. This adds a new form\nof non-differentiable state that must be handled appropriately.\n\nThroughout the guide, you will be able to compare code examples with and without Flax ``BatchNorm``.\n\n.. testsetup:: No BatchNorm, With BatchNorm\n\n  import flax.linen as nn\n  import jax.numpy as jnp\n  import jax\n  import optax\n  from typing import Any\n  from flax.core import FrozenDict\n\nDefining the model with ``BatchNorm``\n*************************************\n\nIn Flax, ``BatchNorm`` is a :meth:`flax.linen.Module <flax.linen.Module>` that exhibits different runtime\nbehavior between training and inference. You explicitly specify it via the ``use_running_average`` argument,\nas demonstrated below.\n\nA common pattern is to accept a ``train`` (``training``) argument in the parent Flax ``Module``, and use\nit to define ``BatchNorm``'s ``use_running_average`` argument.\n\nNote: In other machine learning frameworks, like PyTorch or\nTensorFlow (Keras), this is specified via a mutable state or a call flag (for example, in\n`torch.nn.Module.eval <https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.eval>`__\nor ``tf.keras.Model`` by setting the\n`training <https://www.tensorflow.org/api_docs/python/tf/keras/Model#call>`__ flag).\n\n.. codediff::\n  :title: No BatchNorm, With BatchNorm\n  :sync:\n\n  class MLP(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      x = nn.Dense(features=4)(x)\n\n      x = nn.relu(x)\n      x = nn.Dense(features=1)(x)\n      return x\n\n  ---\n  class MLP(nn.Module):\n    @nn.compact\n    def __call__(self, x, train: bool): #!\n      x = nn.Dense(features=4)(x)\n      x = nn.BatchNorm(use_running_average=not train)(x) #!\n      x = nn.relu(x)\n      x = nn.Dense(features=1)(x)\n      return x\n\nOnce you create your model, initialize it by calling :meth:`flax.linen.init() <flax.linen.init>` to\nget the ``variables`` structure. Here, the main difference between the code without ``BatchNorm``\nand with ``BatchNorm`` is that the ``train`` argument must be provided.\n\nThe ``batch_stats`` collection\n******************************\n\nIn addition to the ``params`` collection, ``BatchNorm`` also adds a ``batch_stats`` collection\nthat contains the running average of the batch statistics.\n\nNote: You can learn more in the ``flax.linen`` `variables <https://flax.readthedocs.io/en/latest/api_reference/flax.linen/variable.html>`__\nAPI documentation.\n\nThe ``batch_stats`` collection must be extracted from the ``variables`` for later use.\n\n.. codediff::\n  :title: No BatchNorm, With BatchNorm\n  :sync:\n\n  mlp = MLP()\n  x = jnp.ones((1, 3))\n  variables = mlp.init(jax.random.key(0), x)\n  params = variables['params']\n\n\n  jax.tree_util.tree_map(jnp.shape, variables)\n  ---\n  mlp = MLP()\n  x = jnp.ones((1, 3))\n  variables = mlp.init(jax.random.key(0), x, train=False) #!\n  params = variables['params']\n  batch_stats = variables['batch_stats'] #!\n\n  jax.tree_util.tree_map(jnp.shape, variables)\n\n\nFlax ``BatchNorm`` adds a total of 4 variables: ``mean`` and ``var`` that live in the\n``batch_stats`` collection, and ``scale`` and ``bias`` that live in the ``params``\ncollection.\n\n.. codediff::\n  :title: No BatchNorm, With BatchNorm\n  :sync:\n\n  FrozenDict({\n    'params': {\n      'Dense_0': {\n          'bias': (4,),\n          'kernel': (3, 4),\n      },\n      'Dense_1': {\n          'bias': (1,),\n          'kernel': (4, 1),\n      },\n    },\n  })\n  ---\n  FrozenDict({\n    'batch_stats': {     #!\n      'BatchNorm_0': {   #!\n          'mean': (4,),  #!\n          'var': (4,),   #!\n      },                 #!\n    },                   #!\n    'params': {\n      'BatchNorm_0': {   #!\n          'bias': (4,),  #!\n          'scale': (4,), #!\n      },                 #!\n      'Dense_0': {\n          'bias': (4,),\n          'kernel': (3, 4),\n      },\n      'Dense_1': {\n          'bias': (1,),\n          'kernel': (4, 1),\n      },\n    },\n  })\n\nModifying ``flax.linen.apply``\n******************************\n\nWhen using :meth:`flax.linen.apply <flax.linen.apply>` to run your model with the ``train=True``\nargument (that is, you have ``use_running_average=False`` in the call to ``BatchNorm``), you\nneed to consider the following:\n\n* ``batch_stats`` must be passed as an input variable.\n* The ``batch_stats`` collection needs to be marked as mutable by setting ``mutable=['batch_stats']``.\n* The mutated variables are returned as a second output.\n  The updated ``batch_stats`` must be extracted from here.\n\n.. codediff::\n  :title: No BatchNorm, With BatchNorm\n  :sync:\n\n  y = mlp.apply(\n    {'params': params},\n    x,\n  )\n  ...\n\n  ---\n  y, updates = mlp.apply( #!\n    {'params': params, 'batch_stats': batch_stats}, #!\n    x,\n    train=True, mutable=['batch_stats'] #!\n  )\n  batch_stats = updates['batch_stats'] #!\n\nTraining and evaluation\n***********************\n\nWhen integrating models that use ``BatchNorm`` into a training loop, the main challenge\nis handling the additional ``batch_stats`` state. To do this, you need to:\n\n* Add a ``batch_stats`` field to a custom :meth:`flax.training.train_state.TrainState <flax.training.train_state.TrainState>` class.\n* Pass the ``batch_stats`` values to the :meth:`train_state.TrainState.create <train_state.TrainState.create>` method.\n\n.. codediff::\n  :title: No BatchNorm, With BatchNorm\n  :sync:\n\n  from flax.training import train_state\n\n\n  state = train_state.TrainState.create(\n    apply_fn=mlp.apply,\n    params=params,\n\n    tx=optax.adam(1e-3),\n  )\n  ---\n  from flax.training import train_state\n\n  class TrainState(train_state.TrainState):  #!\n    batch_stats: Any  #!\n\n  state = TrainState.create( #!\n    apply_fn=mlp.apply,\n    params=params,\n    batch_stats=batch_stats, #!\n    tx=optax.adam(1e-3),\n  )\n\nIn addition, update your ``train_step`` function to reflect these changes:\n\n* Pass all new parameters to ``flax.linen.apply`` (as previously discussed).\n* The ``updates`` to the ``batch_stats`` must be propagated out of the ``loss_fn``.\n* The ``batch_stats`` from the ``TrainState`` must be updated.\n\n.. codediff::\n  :title: No BatchNorm, With BatchNorm\n  :sync:\n\n  @jax.jit\n  def train_step(state: train_state.TrainState, batch):\n    \"\"\"Train for a single step.\"\"\"\n    def loss_fn(params):\n      logits = state.apply_fn(\n        {'params': params},\n        x=batch['image'])\n      loss = optax.softmax_cross_entropy_with_integer_labels(\n        logits=logits, labels=batch['label']).mean()\n      return loss, logits\n    grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n    (loss, logits), grads = grad_fn(state.params)\n    state = state.apply_gradients(grads=grads)\n\n    metrics = {\n      'loss': loss,\n        'accuracy': jnp.mean(jnp.argmax(logits, -1) == batch['label']),\n    }\n    return state, metrics\n  ---\n  @jax.jit\n  def train_step(state: TrainState, batch):\n    \"\"\"Train for a single step.\"\"\"\n    def loss_fn(params):\n      logits, updates = state.apply_fn(  #!\n        {'params': params, 'batch_stats': state.batch_stats},  #!\n        x=batch['image'], train=True, mutable=['batch_stats']) #!\n      loss = optax.softmax_cross_entropy_with_integer_labels(\n        logits=logits, labels=batch['label']).mean()\n      return loss, (logits, updates) #!\n    grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n    (loss, (logits, updates)), grads = grad_fn(state.params) #!\n    state = state.apply_gradients(grads=grads)\n    state = state.replace(batch_stats=updates['batch_stats']) #!\n    metrics = {\n      'loss': loss,\n        'accuracy': jnp.mean(jnp.argmax(logits, -1) == batch['label']),\n    }\n    return state, metrics\n\nThe ``eval_step`` is much simpler. Because ``batch_stats`` is not mutable, no\nupdates\nneed to be propagated. Make sure you pass the ``batch_stats`` to ``flax.linen.apply``,\nand the ``train`` argument is set to ``False``:\n\n.. codediff::\n  :title: No BatchNorm, With BatchNorm\n  :sync:\n\n  @jax.jit\n  def eval_step(state: train_state.TrainState, batch):\n    \"\"\"Train for a single step.\"\"\"\n    logits = state.apply_fn(\n      {'params': params},\n      x=batch['image'])\n    loss = optax.softmax_cross_entropy_with_integer_labels(\n      logits=logits, labels=batch['label']).mean()\n    metrics = {\n      'loss': loss,\n        'accuracy': jnp.mean(jnp.argmax(logits, -1) == batch['label']),\n    }\n    return state, metrics\n  ---\n  @jax.jit\n  def eval_step(state: TrainState, batch):\n    \"\"\"Evaluate for a single step.\"\"\"\n    logits = state.apply_fn(\n      {'params': state.params, 'batch_stats': state.batch_stats}, #!\n      x=batch['image'], train=False) #!\n    loss = optax.softmax_cross_entropy_with_integer_labels(\n      logits=logits, labels=batch['label']).mean()\n    metrics = {\n      'loss': loss,\n        'accuracy': jnp.mean(jnp.argmax(logits, -1) == batch['label']),\n    }\n    return state, metrics\n"
  },
  {
    "path": "docs/guides/training_techniques/dropout.rst",
    "content": "Dropout\n=======\n\nThis guide provides an overview of how to apply\n`dropout <https://jmlr.org/papers/volume15/srivastava14a/srivastava14a.pdf>`__\nusing :meth:`flax.linen.Dropout`.\n\nDropout is a stochastic regularization technique that randomly removes hidden\nand visible units in a network.\n\nThroughout the guide, you will be able to compare code examples with and without\nFlax ``Dropout``.\n\n.. testsetup:: No Dropout, With Dropout\n\n  import flax.linen as nn\n  import jax.numpy as jnp\n  import jax\n  import optax\n\nSplit the PRNG key\n******************\n\nSince dropout is a random operation, it requires a pseudorandom number generator\n(PRNG) state. Flax uses JAX's (splittable) PRNG keys, which have a number of\ndesirable properties for neural networks. To learn more, refer to the\n`Pseudorandom numbers in JAX tutorial <https://jax.readthedocs.io/en/latest/jax-101/05-random-numbers.html>`__.\n\n**Note:** Recall that JAX has an explicit way of giving you PRNG keys:\nyou can fork the main PRNG state (such as ``key = jax.random.key(seed=0)``)\ninto multiple new PRNG keys with ``key, subkey = jax.random.split(key)``. You\ncan refresh your memory in\n`🔪 JAX - The Sharp Bits 🔪 Randomness and PRNG keys <https://jax.readthedocs.io/en/latest/notebooks/Common_Gotchas_in_JAX.html#jax-prng>`__.\n\nBegin by splitting the PRNG key using\n`jax.random.split() <https://jax.readthedocs.io/en/latest/_autosummary/jax.random.split.html>`__\ninto three keys, including one for Flax Linen ``Dropout``.\n\n.. codediff::\n  :title: No Dropout, With Dropout\n  :sync:\n\n  root_key = jax.random.key(seed=0)\n  main_key, params_key = jax.random.split(key=root_key)\n  ---\n  root_key = jax.random.key(seed=0)\n  main_key, params_key, dropout_key = jax.random.split(key=root_key, num=3) #!\n\n**Note:** In Flax, you provide *PRNG streams* with *names*, so that you can use them later\nin your :meth:`flax.linen.Module`. For example, you pass the stream ``'params'``\nfor initializing parameters, and ``'dropout'`` for applying\n:meth:`flax.linen.Dropout`.\n\nDefine your model with ``Dropout``\n**********************************\n\nTo create a model with dropout:\n\n* Subclass :meth:`flax.linen.Module`, and then use\n  :meth:`flax.linen.Dropout` to add a dropout layer. Recall that\n  :meth:`flax.linen.Module` is the\n  `base class for all neural network Modules <https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html>`__,\n  and all layers and models are subclassed from it.\n\n* In :meth:`flax.linen.Dropout`, the ``deterministic`` argument is required to\n  be passed as a keyword argument, either:\n\n  * When constructing the :meth:`flax.linen.Module`; or\n  * When calling :meth:`flax.linen.init()` or :meth:`flax.linen.apply()` on a constructed ``Module``. (Refer to :meth:`flax.linen.module.merge_param` for more details.)\n\n* Because ``deterministic`` is a boolean:\n\n  * If it's set to ``False``, the inputs are masked (that is, set to zero) with\n    a probability set by ``rate``. And the remaining inputs are scaled by\n    ``1 / (1 - rate)``, which ensures that the means of the inputs are\n    preserved.\n  * If it's set to ``True``, no mask is applied (the dropout is turned off),\n    and the inputs are returned as-is.\n\nA common pattern is to accept a ``training`` (or ``train``) argument (a boolean)\nin the parent Flax ``Module``, and use it to enable or disable dropout (as\ndemonstrated in later sections of this guide). In other machine learning\nframeworks, like PyTorch or TensorFlow (Keras), this is specified via a\nmutable state or a call flag (for example, in\n`torch.nn.Module.eval <https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.eval>`__\nor ``tf.keras.Model`` by setting the\n`training <https://www.tensorflow.org/api_docs/python/tf/keras/Model#call>`__ flag).\n\n**Note:** Flax provides an implicit way of handling PRNG key streams via Flax\n:meth:`flax.linen.Module`'s :meth:`flax.linen.Module.make_rng` method.\nThis allows you to split off a fresh PRNG key inside Flax Modules (or their\nsub-Modules) from the PRNG stream. The ``make_rng`` method guarantees to provide a\nunique key each time you call it. Internally, :meth:`flax.linen.Dropout` makes\nuse of :meth:`flax.linen.Module.make_rng` to create a key for dropout. You can\ncheck out the\n`source code <https://github.com/google/flax/blob/5714e57a0dc8146eb58a7a06ed768ed3a17672f9/flax/linen/stochastic.py#L72>`__.\nIn short, :meth:`flax.linen.Module.make_rng` *guarantees full reproducibility*.\n\n.. codediff::\n  :title: No Dropout, With Dropout\n  :sync:\n\n  class MyModel(nn.Module):\n    num_neurons: int\n\n    @nn.compact\n    def __call__(self, x):\n      x = nn.Dense(self.num_neurons)(x)\n\n      return x\n  ---\n  class MyModel(nn.Module):\n    num_neurons: int\n\n    @nn.compact\n    def __call__(self, x, training: bool): #!\n      x = nn.Dense(self.num_neurons)(x)\n      # Set the dropout layer with a `rate` of 50%. #!\n      # When the `deterministic` flag is `True`, dropout is turned off. #!\n      x = nn.Dropout(rate=0.5, deterministic=not training)(x) #!\n      return x\n\nInitialize the model\n********************\n\nAfter creating your model:\n\n* Instantiate the model.\n* Then, in the :meth:`flax.linen.init()` call, set ``training=False``.\n* Finally, extract the ``params`` from the\n  `variable dictionary <https://flax.readthedocs.io/en/latest/api_reference/flax.linen/variable.html>`__.\n\nHere, the main difference between the code without Flax ``Dropout``\nand with ``Dropout`` is that the ``training`` (or ``train``) argument must be\nprovided if you need dropout enabled.\n\n.. codediff::\n  :title: No Dropout, With Dropout\n  :sync:\n\n  my_model = MyModel(num_neurons=3)\n  x = jnp.empty((3, 4, 4))\n\n  variables = my_model.init(params_key, x)\n  params = variables['params']\n  ---\n  my_model = MyModel(num_neurons=3)\n  x = jnp.empty((3, 4, 4))\n  # Dropout is disabled with `training=False` (that is, `deterministic=True`). #!\n  variables = my_model.init(params_key, x, training=False) #!\n  params = variables['params']\n\nPerform the forward pass during training\n****************************************\n\nWhen using :meth:`flax.linen.apply()` to run your model:\n\n* Pass ``training=True`` to :meth:`flax.linen.apply()`.\n* Then, to draw PRNG keys during the forward pass (with dropout), provide a PRNG key\n  to seed the ``'dropout'`` stream when you call :meth:`flax.linen.apply()`.\n\n.. codediff::\n  :title: No Dropout, With Dropout\n  :sync:\n\n  # No need to pass the `training` and `rngs` flags.\n  y = my_model.apply({'params': params}, x)\n  ---\n  # Dropout is enabled with `training=True` (that is, `deterministic=False`). #!\n  y = my_model.apply({'params': params}, x, training=True, rngs={'dropout': dropout_key}) #!\n\nHere, the main difference between the code without Flax ``Dropout``\nand with ``Dropout`` is that the ``training`` (or ``train``) and ``rngs``\narguments must be provided if you need dropout enabled.\n\nDuring evaluation, use the above code with no dropout enabled (this means you do\nnot have to pass a RNG either).\n\n``TrainState`` and the training step\n************************************\n\nThis section explains how to amend your code inside the training step function if\nyou have dropout enabled.\n\n**Note:** Recall that Flax has a common pattern where you create a dataclass\nthat represents the whole training state, including parameters and the optimizer\nstate. Then, you can pass a single parameter, ``state: TrainState``, to\nthe training step function. Refer to the\n:meth:`flax.training.train_state.TrainState` API docs to learn more.\n\n* First, add a ``key`` field to a custom :meth:`flax.training.train_state.TrainState` class.\n* Then, pass the ``key`` value—in this case, the ``dropout_key``—to the :meth:`train_state.TrainState.create` method.\n\n.. codediff::\n  :title: No Dropout, With Dropout\n  :sync:\n\n  from flax.training import train_state\n\n  state = train_state.TrainState.create(\n    apply_fn=my_model.apply,\n    params=params,\n\n    tx=optax.adam(1e-3)\n  )\n  ---\n  from flax.training import train_state\n\n  class TrainState(train_state.TrainState): #!\n    key: jax.Array #!\n\n  state = TrainState.create( #!\n    apply_fn=my_model.apply,\n    params=params,\n    key=dropout_key, #!\n    tx=optax.adam(1e-3)\n  )\n\n* Next, in the Flax training step function, ``train_step``, generate a new PRNG\n  key from the ``dropout_key`` to apply dropout at each step. This can be done with one of the following:\n\n  * `jax.random.split() <https://jax.readthedocs.io/en/latest/_autosummary/jax.random.split.html>`__; or\n  * `jax.random.fold_in() <https://jax.readthedocs.io/en/latest/_autosummary/jax.random.fold_in.html>`__\n\n  Using ``jax.random.fold_in()`` is generally faster. When you use\n  ``jax.random.split()`` you split off a PRNG key that can be reused\n  afterwards. However, using ``jax.random.fold_in()`` makes sure to 1) fold in\n  unique data; and 2) can result in longer sequences of PRNG streams.\n\n* Finally, when performing the forward pass, pass the new PRNG key to ``state.apply_fn()``\n  as an extra parameter.\n\n.. codediff::\n  :title: No Dropout, With Dropout\n  :sync:\n\n  @jax.jit\n  def train_step(state: train_state.TrainState, batch):\n\n    def loss_fn(params):\n      logits = state.apply_fn(\n        {'params': params},\n        x=batch['image'],\n\n\n        )\n      loss = optax.softmax_cross_entropy_with_integer_labels(\n        logits=logits, labels=batch['label'])\n      return loss, logits\n    grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n    (loss, logits), grads = grad_fn(state.params)\n    state = state.apply_gradients(grads=grads)\n    return state\n\n  ---\n  @jax.jit\n  def train_step(state: TrainState, batch, dropout_key): #!\n    dropout_train_key = jax.random.fold_in(key=dropout_key, data=state.step) #!\n    def loss_fn(params):\n      logits = state.apply_fn(\n        {'params': params},\n        x=batch['image'],\n        training=True, #!\n        rngs={'dropout': dropout_train_key} #!\n        )\n      loss = optax.softmax_cross_entropy_with_integer_labels(\n        logits=logits, labels=batch['label'])\n      return loss, logits\n    grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n    (loss, logits), grads = grad_fn(state.params)\n    state = state.apply_gradients(grads=grads)\n    return state\n\nFlax examples with dropout\n**************************\n\n* A `Transformer-based model <https://github.com/google/flax/blob/main/examples/wmt/models.py>`__\n  trained on the WMT Machine Translation dataset. This example uses dropout and attention dropout.\n\n* Applying word dropout to a batch of input IDs in a\n  `text classification <https://github.com/google/flax/blob/main/examples/sst2/models.py>`__\n  context. This example uses a custom :meth:`flax.linen.Dropout` layer.\n\nMore Flax examples that use Module ``make_rng()``\n*************************************************\n\n* Defining a prediction token in a decoder of a\n  `sequence-to-sequence model <https://github.com/google/flax/blob/main/examples/seq2seq/models.py>`__.\n"
  },
  {
    "path": "docs/guides/training_techniques/index.rst",
    "content": "Training techniques\n===================\n\n.. toctree::\n   :maxdepth: 1\n\n   batch_norm\n   dropout\n   lr_schedule\n   transfer_learning\n   use_checkpointing"
  },
  {
    "path": "docs/guides/training_techniques/lr_schedule.rst",
    "content": "Learning rate scheduling\n=============================\n\nThe learning rate is considered one of the most important hyperparameters for\ntraining deep neural networks, but choosing it can be quite hard.\nRather than simply using a fixed learning rate, it is common to use a learning rate scheduler.\nIn this example, we will use the *cosine scheduler*.\nBefore the cosine scheduler comes into play, we start with a so-called *warmup* period in which the\nlearning rate increases linearly for ``warmup_epochs`` epochs.\nFor more information about the cosine scheduler, check out the paper\n`\"SGDR: Stochastic Gradient Descent with Warm Restarts\" <https://arxiv.org/abs/1608.03983>`_.\n\nWe will show you how to...\n\n* define a learning rate schedule\n* train a simple model using that schedule\n\n\n.. testsetup:: Default learning rate, Learning rate schedule\n\n  import jax\n  import jax.numpy as jnp\n  import flax.linen as nn\n  from flax.training import train_state\n  import optax\n  import numpy as np\n  import tensorflow_datasets as tfds\n  import functools\n  import ml_collections\n  from absl import logging\n\n\n  class CNN(nn.Module):\n    \"\"\"A simple CNN model.\"\"\"\n\n    @nn.compact\n    def __call__(self, x):\n      x = nn.Conv(features=32, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = nn.Conv(features=64, kernel_size=(3, 3))(x)\n      x = nn.relu(x)\n      x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n      x = x.reshape((x.shape[0], -1))  # flatten\n      x = nn.Dense(features=256)(x)\n      x = nn.relu(x)\n      x = nn.Dense(features=10)(x)\n      return x\n\n  def get_dummy_data(ds_size):\n    image = np.random.rand(ds_size, 28, 28, 1)\n    label = np.random.randint(low=0, high=10, size=(ds_size,))\n    return {'image': image, 'label': label}\n\n  def get_config():\n    \"\"\"Get the default hyperparameter configuration.\"\"\"\n    config = ml_collections.ConfigDict()\n\n    config.learning_rate = 0.001\n    config.momentum = 0.9\n    config.batch_size = 128\n    config.num_epochs = 10\n    config.warmup_epochs = 2\n    config.train_ds_size = 128\n    return config\n\n  def compute_metrics(logits, labels):\n    one_hot = jax.nn.one_hot(labels, 10)\n    loss = jnp.mean(optax.softmax_cross_entropy(logits, one_hot))\n    accuracy = jnp.mean(jnp.argmax(logits, -1) == labels)\n    metrics = {\n        'loss': loss,\n        'accuracy': accuracy,\n    }\n    return metrics\n\n\n.. testcode:: Default learning rate, Learning rate schedule\n\n  def create_learning_rate_fn(config, base_learning_rate, steps_per_epoch):\n    \"\"\"Creates learning rate schedule.\"\"\"\n    warmup_fn = optax.linear_schedule(\n        init_value=0., end_value=base_learning_rate,\n        transition_steps=config.warmup_epochs * steps_per_epoch)\n    cosine_epochs = max(config.num_epochs - config.warmup_epochs, 1)\n    cosine_fn = optax.cosine_decay_schedule(\n        init_value=base_learning_rate,\n        decay_steps=cosine_epochs * steps_per_epoch)\n    schedule_fn = optax.join_schedules(\n        schedules=[warmup_fn, cosine_fn],\n        boundaries=[config.warmup_epochs * steps_per_epoch])\n    return schedule_fn\n\nTo use the schedule, we must create a learning rate function by passing the hyperparameters to the\n``create_learning_rate_fn`` function and then pass the function to your |Optax|_ optimizer.\nFor example using this schedule on MNIST would require changing the ``train_step`` function:\n\n.. |Optax| replace:: ``Optax``\n.. _Optax: https://optax.readthedocs.io/en/latest/api.html#optimizer-schedules\n\n.. codediff::\n  :title: Default learning rate, Learning rate schedule\n  :sync:\n\n  @jax.jit\n  def train_step(state, batch):\n    def loss_fn(params):\n      logits = CNN().apply({'params': params}, batch['image'])\n      one_hot = jax.nn.one_hot(batch['label'], 10)\n      loss = jnp.mean(optax.softmax_cross_entropy(logits, one_hot))\n      return loss, logits\n    grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n    (_, logits), grads = grad_fn(state.params)\n    new_state = state.apply_gradients(grads=grads)\n    metrics = compute_metrics(logits, batch['label'])\n\n\n    return new_state, metrics\n  ---\n  @functools.partial(jax.jit, static_argnums=2) #!\n  def train_step(state, batch, learning_rate_fn): #!\n    def loss_fn(params):\n      logits = CNN().apply({'params': params}, batch['image'])\n      one_hot = jax.nn.one_hot(batch['label'], 10)\n      loss = jnp.mean(optax.softmax_cross_entropy(logits, one_hot))\n      return loss, logits\n    grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n    (_, logits), grads = grad_fn(state.params)\n    new_state = state.apply_gradients(grads=grads)\n    metrics = compute_metrics(logits, batch['label'])\n    lr = learning_rate_fn(state.step) #!\n    metrics['learning_rate'] = lr #!\n    return new_state, metrics\n\nAnd the ``train_epoch`` function:\n\n.. codediff::\n  :title: Default learning rate, Learning rate schedule\n  :sync:\n\n  def train_epoch(state, train_ds, batch_size, epoch, rng):\n    \"\"\"Trains for a single epoch.\"\"\"\n    train_ds_size = len(train_ds['image'])\n    steps_per_epoch = train_ds_size // batch_size\n    perms = jax.random.permutation(rng, len(train_ds['image']))\n    perms = perms[:steps_per_epoch * batch_size]\n    perms = perms.reshape((steps_per_epoch, batch_size))\n    batch_metrics = []\n    for perm in perms:\n      batch = {k: v[perm, ...] for k, v in train_ds.items()}\n      state, metrics = train_step(state, batch)\n      batch_metrics.append(metrics)\n\n    # compute mean of metrics across each batch in epoch.\n    batch_metrics = jax.device_get(batch_metrics)\n    epoch_metrics = {\n        k: np.mean([metrics[k] for metrics in batch_metrics])\n        for k in batch_metrics[0]}\n\n    logging.info('train epoch: %d, loss: %.4f, accuracy: %.2f', epoch,\n                 epoch_metrics['loss'], epoch_metrics['accuracy'] * 100)\n\n    return state, epoch_metrics\n  ---\n  def train_epoch(state, train_ds, batch_size, epoch, learning_rate_fn, rng): #!\n    \"\"\"Trains for a single epoch.\"\"\"\n    train_ds_size = len(train_ds['image'])\n    steps_per_epoch = train_ds_size // batch_size\n    perms = jax.random.permutation(rng, len(train_ds['image']))\n    perms = perms[:steps_per_epoch * batch_size]\n    perms = perms.reshape((steps_per_epoch, batch_size))\n    batch_metrics = []\n    for perm in perms:\n      batch = {k: v[perm, ...] for k, v in train_ds.items()}\n      state, metrics = train_step(state, batch, learning_rate_fn) #!\n      batch_metrics.append(metrics)\n\n    # compute mean of metrics across each batch in epoch.\n    batch_metrics = jax.device_get(batch_metrics)\n    epoch_metrics = {\n        k: np.mean([metrics[k] for metrics in batch_metrics])\n        for k in batch_metrics[0]}\n\n    logging.info('train epoch: %d, loss: %.4f, accuracy: %.2f', epoch,\n                 epoch_metrics['loss'], epoch_metrics['accuracy'] * 100)\n\n    return state, epoch_metrics\n\n\nAnd the ``create_train_state`` function:\n\n\n.. codediff::\n  :title: Default learning rate, Learning rate schedule\n  :sync:\n\n  def create_train_state(rng, config):\n    \"\"\"Creates initial `TrainState`.\"\"\"\n    cnn = CNN()\n    params = cnn.init(rng, jnp.ones([1, 28, 28, 1]))['params']\n    tx = optax.sgd(config.learning_rate, config.momentum)\n    return train_state.TrainState.create(\n        apply_fn=cnn.apply, params=params, tx=tx)\n  ---\n  def create_train_state(rng, config, learning_rate_fn): #!\n    \"\"\"Creates initial `TrainState`.\"\"\"\n    cnn = CNN()\n    params = cnn.init(rng, jnp.ones([1, 28, 28, 1]))['params']\n    tx = optax.sgd(learning_rate_fn, config.momentum) #!\n    return train_state.TrainState.create(\n        apply_fn=cnn.apply, params=params, tx=tx)\n\n\n.. testcleanup:: Learning rate schedule\n\n  config = get_config()\n\n  train_ds_size = config.train_ds_size\n  steps_per_epoch = train_ds_size // config.batch_size\n  learning_rate_fn = create_learning_rate_fn(config, config.learning_rate, steps_per_epoch)\n\n  rng = jax.random.key(0)\n  state = create_train_state(rng, config, learning_rate_fn)\n\n  train_ds = get_dummy_data(config.train_ds_size)\n  rng, _ = jax.random.split(rng)\n  state, epoch_metrics = train_epoch(state, train_ds, config.batch_size, 0, learning_rate_fn, rng)\n\n  assert 'accuracy' in epoch_metrics and 'learning_rate' in epoch_metrics\n\n\n\n"
  },
  {
    "path": "docs/guides/training_techniques/transfer_learning.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Transfer learning\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This guide demonstrates various parts of the transfer learning workflow with Flax. Depending on the task, a pretrained model can be used just as a feature extractor or it can be fine-tuned as part of a larger model.\\n\",\n    \"\\n\",\n    \"This guide demonstrates how to:\\n\",\n    \"\\n\",\n    \"* Load a pretrained model from HuggingFace [Transformers](https://huggingface.co/docs/transformers/index) and extract a specific sub-module from that pretrained model.\\n\",\n    \"* Create a classifier model.\\n\",\n    \"* Transfer the pretrained parameters to the new model structure.\\n\",\n    \"* Create an optimizer for training different parts of the model separately with [Optax](https://optax.readthedocs.io/).\\n\",\n    \"* Set up the model for training.\\n\",\n    \"\\n\",\n    \"<details><summary><b>Performance Note</b></summary>\\n\",\n    \"\\n\",\n    \"Depending on your task, some of the content in this guide may be suboptimal. For example, if you are only going to train a linear classifier on top of a pretrained model, it may be better to just extract the feature embeddings once, which can result in much faster training, and you can use specialized algorithms for linear regression or logistic classification. This guide shows how to do transfer learning with all the model parameters.\\n\",\n    \"\\n\",\n    \"</details><br>\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Setup\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# Note that the Transformers library doesn't use the latest Flax version.\\n\",\n    \"! pip install -q \\\"transformers[flax]\\\"\\n\",\n    \"# Install/upgrade Flax and JAX. For JAX installation with GPU/TPU support,\\n\",\n    \"# visit https://github.com/jax-ml/jax#installation.\\n\",\n    \"! pip install -U -q flax jax jaxlib\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Create a function for model loading\\n\",\n    \"\\n\",\n    \"To load a pre-trained classifier, for convenience first create a function that returns a [Flax `Module`](https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/flax_basics.html#module-basics) and its pretrained variables.\\n\",\n    \"\\n\",\n    \"In the code below, the `load_model` function uses HuggingFace's `FlaxCLIPVisionModel` model from the [Transformers](https://huggingface.co/docs/transformers/index) library and extracts a `FlaxCLIPModule` module.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"%%capture\\n\",\n    \"from IPython.display import clear_output\\n\",\n    \"from transformers import FlaxCLIPModel\\n\",\n    \"\\n\",\n    \"# Note: FlaxCLIPModel is not a Flax Module\\n\",\n    \"def load_model():\\n\",\n    \"  clip = FlaxCLIPModel.from_pretrained('openai/clip-vit-base-patch32')\\n\",\n    \"  clear_output(wait=False) # Clear the loading messages\\n\",\n    \"  module = clip.module # Extract the Flax Module\\n\",\n    \"  variables = {'params': clip.params} # Extract the parameters\\n\",\n    \"  return module, variables\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that `FlaxCLIPVisionModel` itself is not a Flax `Module` which is why we need to do this extra step.\\n\",\n    \"\\n\",\n    \"### Extracting a submodule\\n\",\n    \"\\n\",\n    \"Calling `load_model` from the snippet above returns the `FlaxCLIPModule`, which is composed of `text_model` and `vision_model` submodules.\\n\",\n    \"\\n\",\n    \"An easy way to extract the `vision_model` sub-Module defined inside `.setup()` and its variables is to use [`flax.linen.Module.bind`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.bind) on the `clip` Module immediately followed by [`flax.linen.Module.unbind`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.unbind) on the `vision_model` sub-Module.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import flax.linen as nn\\n\",\n    \"\\n\",\n    \"clip, clip_variables = load_model()\\n\",\n    \"vision_model, vision_model_vars = clip.bind(clip_variables).vision_model.unbind()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Creating a classifier\\n\",\n    \"\\n\",\n    \"To create a classifier define a new Flax [`Module`](https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/flax_basics.html#module-basics) consisting of a `backbone` (the pretrained vision model) and a `head` (the classifier) submodules.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from typing import Callable\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"import jax\\n\",\n    \"\\n\",\n    \"class Classifier(nn.Module):\\n\",\n    \"  num_classes: int\\n\",\n    \"  backbone: nn.Module\\n\",\n    \"  \\n\",\n    \"\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x = self.backbone(x).pooler_output\\n\",\n    \"    x = nn.Dense(\\n\",\n    \"      self.num_classes, name='head', kernel_init=nn.zeros)(x)\\n\",\n    \"    return x\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To construct a classifier `model`, the `vision_model` Module is passed as the `backbone` to `Classifier`. Then the model's `params` can be randomly initialized by passing fake data that is used to infer the parameter shapes.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"num_classes = 3\\n\",\n    \"model = Classifier(num_classes=num_classes, backbone=vision_model)\\n\",\n    \"\\n\",\n    \"x = jnp.empty((1, 224, 224, 3))\\n\",\n    \"variables = model.init(jax.random.key(1), x)\\n\",\n    \"params = variables['params']\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Transfering the parameters\\n\",\n    \"Since `params` are currently random, the pretrained parameters from `vision_model_vars` have to be transfered to the `params` structure at the appropriate location (i.e. the `backbone`):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"params['backbone'] = vision_model_vars['params']\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"**Note:** if the model contains other variable collections such as `batch_stats`, these have to be transfered as well.\\n\",\n    \"\\n\",\n    \"## Optimization\\n\",\n    \"\\n\",\n    \"If you need to to train different parts of the model separately, you have three options:\\n\",\n    \"\\n\",\n    \"1. Use `stop_gradient`.\\n\",\n    \"2. Filter the parameters for `jax.grad`.\\n\",\n    \"3. Use multiple optimizers for different parameters.\\n\",\n    \"\\n\",\n    \"For most situations we recommend using multiple optimizers via [Optax](https://optax.readthedocs.io/)'s [`multi_transform`](https://optax.readthedocs.io/en/latest/api.html#optax.multi_transform) as its both efficient and can be easily extended to implement many fine-tunning strategies. \\n\",\n    \"\\n\",\n    \"### **optax.multi_transform**\\n\",\n    \"\\n\",\n    \"To use `optax.multi_transform` following must be defined:\\n\",\n    \"\\n\",\n    \"1. The parameter partitions.\\n\",\n    \"2. A mapping between partitions and their optimizer.\\n\",\n    \"3. A pytree with the same shape as the parameters but its leaves containing the corresponding partition label.\\n\",\n    \"\\n\",\n    \"To freeze layers with `optax.multi_transform` for the model above, the following setup can be used:\\n\",\n    \"\\n\",\n    \"* Define the `trainable` and `frozen` parameter partitions.\\n\",\n    \"* For the `trainable` parameters select the Adam (`optax.adam`) optimizer.\\n\",\n    \"- For the `frozen` parameters select the `optax.set_to_zero` optimizer. This dummy optimizer zeros-out the gradients so no training is done.\\n\",\n    \"- Map parameters to partitions using [`flax.traverse_util.path_aware_map`](https://flax.readthedocs.io/en/latest/api_reference/flax.traverse_util.html#flax.traverse_util.path_aware_map), mark the leaves from the `backbone` as `frozen`, and the rest as `trainable`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"FrozenDict({\\n\",\n       \"    backbone: {\\n\",\n       \"        embeddings: {\\n\",\n       \"            class_embedding: 'frozen',\\n\",\n       \"            patch_embedding: {\\n\",\n       \"                kernel: 'frozen',\\n\",\n       \"            },\\n\",\n       \"        },\\n\",\n       \"    },\\n\",\n       \"    head: {\\n\",\n       \"        bias: 'trainable',\\n\",\n       \"        kernel: 'trainable',\\n\",\n       \"    },\\n\",\n       \"})\"\n      ]\n     },\n     \"execution_count\": 10,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"from flax import traverse_util\\n\",\n    \"import optax\\n\",\n    \"\\n\",\n    \"partition_optimizers = {'trainable': optax.adam(5e-3), 'frozen': optax.set_to_zero()}\\n\",\n    \"param_partitions = traverse_util.path_aware_map(\\n\",\n    \"  lambda path, v: 'frozen' if 'backbone' in path else 'trainable', params)\\n\",\n    \"tx = optax.multi_transform(partition_optimizers, param_partitions)\\n\",\n    \"\\n\",\n    \"# visualize a subset of the param_partitions structure\\n\",\n    \"flat = list(traverse_util.flatten_dict(param_partitions).items())\\n\",\n    \"traverse_util.unflatten_dict(dict(flat[:2] + flat[-2:]))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To implement [differential learning rates](https://blog.slavv.com/differential-learning-rates-59eff5209a4f), the `optax.set_to_zero` can be replaced with any other optimizer, different optimizers and partitioning schemes can be selected depending on the task. For more information on advanced optimizers, refer to Optax's [Combining Optimizers](https://optax.readthedocs.io/en/latest/api.html#combining-optimizers) documentation.\\n\",\n    \"\\n\",\n    \"## Creating the `TrainState`\\n\",\n    \"\\n\",\n    \"Once the module, params, and optimizer are defined, the `TrainState` can be constructed as usual:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from flax.training.train_state import TrainState\\n\",\n    \"\\n\",\n    \"state = TrainState.create(\\n\",\n    \"  apply_fn=model.apply,\\n\",\n    \"  params=params,\\n\",\n    \"  tx=tx)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"083d8854\",\n   \"metadata\": {},\n   \"source\": [\n    \"Since the optimizer takes care of the freezing or fine-tunning strategy, the `train_step` requires no additional changes, training can proceed normally.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.9.14\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "docs/guides/training_techniques/transfer_learning.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Transfer learning\n\n+++\n\nThis guide demonstrates various parts of the transfer learning workflow with Flax. Depending on the task, a pretrained model can be used just as a feature extractor or it can be fine-tuned as part of a larger model.\n\nThis guide demonstrates how to:\n\n* Load a pretrained model from HuggingFace [Transformers](https://huggingface.co/docs/transformers/index) and extract a specific sub-module from that pretrained model.\n* Create a classifier model.\n* Transfer the pretrained parameters to the new model structure.\n* Create an optimizer for training different parts of the model separately with [Optax](https://optax.readthedocs.io/).\n* Set up the model for training.\n\n<details><summary><b>Performance Note</b></summary>\n\nDepending on your task, some of the content in this guide may be suboptimal. For example, if you are only going to train a linear classifier on top of a pretrained model, it may be better to just extract the feature embeddings once, which can result in much faster training, and you can use specialized algorithms for linear regression or logistic classification. This guide shows how to do transfer learning with all the model parameters.\n\n</details><br>\n\n+++\n\n## Setup\n\n```{code-cell} ipython3\n:tags: [skip-execution]\n\n# Note that the Transformers library doesn't use the latest Flax version.\n! pip install -q \"transformers[flax]\"\n# Install/upgrade Flax and JAX. For JAX installation with GPU/TPU support,\n# visit https://github.com/jax-ml/jax#installation.\n! pip install -U -q flax jax jaxlib\n```\n\n## Create a function for model loading\n\nTo load a pre-trained classifier, for convenience first create a function that returns a [Flax `Module`](https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/flax_basics.html#module-basics) and its pretrained variables.\n\nIn the code below, the `load_model` function uses HuggingFace's `FlaxCLIPVisionModel` model from the [Transformers](https://huggingface.co/docs/transformers/index) library and extracts a `FlaxCLIPModule` module.\n\n```{code-cell} ipython3\n%%capture\nfrom IPython.display import clear_output\nfrom transformers import FlaxCLIPModel\n\n# Note: FlaxCLIPModel is not a Flax Module\ndef load_model():\n  clip = FlaxCLIPModel.from_pretrained('openai/clip-vit-base-patch32')\n  clear_output(wait=False) # Clear the loading messages\n  module = clip.module # Extract the Flax Module\n  variables = {'params': clip.params} # Extract the parameters\n  return module, variables\n```\n\nNote that `FlaxCLIPVisionModel` itself is not a Flax `Module` which is why we need to do this extra step.\n\n### Extracting a submodule\n\nCalling `load_model` from the snippet above returns the `FlaxCLIPModule`, which is composed of `text_model` and `vision_model` submodules.\n\nAn easy way to extract the `vision_model` sub-Module defined inside `.setup()` and its variables is to use [`flax.linen.Module.bind`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.bind) on the `clip` Module immediately followed by [`flax.linen.Module.unbind`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.unbind) on the `vision_model` sub-Module.\n\n```{code-cell} ipython3\nimport flax.linen as nn\n\nclip, clip_variables = load_model()\nvision_model, vision_model_vars = clip.bind(clip_variables).vision_model.unbind()\n```\n\n### Creating a classifier\n\nTo create a classifier define a new Flax [`Module`](https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/flax_basics.html#module-basics) consisting of a `backbone` (the pretrained vision model) and a `head` (the classifier) submodules.\n\n```{code-cell} ipython3\nfrom typing import Callable\nimport jax.numpy as jnp\nimport jax\n\nclass Classifier(nn.Module):\n  num_classes: int\n  backbone: nn.Module\n  \n\n  @nn.compact\n  def __call__(self, x):\n    x = self.backbone(x).pooler_output\n    x = nn.Dense(\n      self.num_classes, name='head', kernel_init=nn.zeros)(x)\n    return x\n```\n\nTo construct a classifier `model`, the `vision_model` Module is passed as the `backbone` to `Classifier`. Then the model's `params` can be randomly initialized by passing fake data that is used to infer the parameter shapes.\n\n```{code-cell} ipython3\nnum_classes = 3\nmodel = Classifier(num_classes=num_classes, backbone=vision_model)\n\nx = jnp.empty((1, 224, 224, 3))\nvariables = model.init(jax.random.key(1), x)\nparams = variables['params']\n```\n\n## Transfering the parameters\nSince `params` are currently random, the pretrained parameters from `vision_model_vars` have to be transfered to the `params` structure at the appropriate location (i.e. the `backbone`):\n\n```{code-cell} ipython3\nparams['backbone'] = vision_model_vars['params']\n```\n\n**Note:** if the model contains other variable collections such as `batch_stats`, these have to be transfered as well.\n\n## Optimization\n\nIf you need to to train different parts of the model separately, you have three options:\n\n1. Use `stop_gradient`.\n2. Filter the parameters for `jax.grad`.\n3. Use multiple optimizers for different parameters.\n\nFor most situations we recommend using multiple optimizers via [Optax](https://optax.readthedocs.io/)'s [`multi_transform`](https://optax.readthedocs.io/en/latest/api.html#optax.multi_transform) as its both efficient and can be easily extended to implement many fine-tunning strategies. \n\n### **optax.multi_transform**\n\nTo use `optax.multi_transform` following must be defined:\n\n1. The parameter partitions.\n2. A mapping between partitions and their optimizer.\n3. A pytree with the same shape as the parameters but its leaves containing the corresponding partition label.\n\nTo freeze layers with `optax.multi_transform` for the model above, the following setup can be used:\n\n* Define the `trainable` and `frozen` parameter partitions.\n* For the `trainable` parameters select the Adam (`optax.adam`) optimizer.\n- For the `frozen` parameters select the `optax.set_to_zero` optimizer. This dummy optimizer zeros-out the gradients so no training is done.\n- Map parameters to partitions using [`flax.traverse_util.path_aware_map`](https://flax.readthedocs.io/en/latest/api_reference/flax.traverse_util.html#flax.traverse_util.path_aware_map), mark the leaves from the `backbone` as `frozen`, and the rest as `trainable`.\n\n```{code-cell} ipython3\nfrom flax import traverse_util\nimport optax\n\npartition_optimizers = {'trainable': optax.adam(5e-3), 'frozen': optax.set_to_zero()}\nparam_partitions = traverse_util.path_aware_map(\n  lambda path, v: 'frozen' if 'backbone' in path else 'trainable', params)\ntx = optax.multi_transform(partition_optimizers, param_partitions)\n\n# visualize a subset of the param_partitions structure\nflat = list(traverse_util.flatten_dict(param_partitions).items())\ntraverse_util.unflatten_dict(dict(flat[:2] + flat[-2:]))\n```\n\nTo implement [differential learning rates](https://blog.slavv.com/differential-learning-rates-59eff5209a4f), the `optax.set_to_zero` can be replaced with any other optimizer, different optimizers and partitioning schemes can be selected depending on the task. For more information on advanced optimizers, refer to Optax's [Combining Optimizers](https://optax.readthedocs.io/en/latest/api.html#combining-optimizers) documentation.\n\n## Creating the `TrainState`\n\nOnce the module, params, and optimizer are defined, the `TrainState` can be constructed as usual:\n\n```{code-cell} ipython3\nfrom flax.training.train_state import TrainState\n\nstate = TrainState.create(\n  apply_fn=model.apply,\n  params=params,\n  tx=tx)\n```\n\nSince the optimizer takes care of the freezing or fine-tunning strategy, the `train_step` requires no additional changes, training can proceed normally.\n"
  },
  {
    "path": "docs/guides/training_techniques/use_checkpointing.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"id\": \"6e9134fa\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Save and load checkpoints\\n\",\n    \"\\n\",\n    \"This guide demonstrates how to save and load Flax checkpoints with [Orbax](https://github.com/google/orbax).\\n\",\n    \"\\n\",\n    \"Orbax provides a variety of features for saving and loading model data, which you will learn about in this doc:\\n\",\n    \"\\n\",\n    \"*  Support for various array types and storage formats\\n\",\n    \"*  Asynchronous saving to reduce training wait time\\n\",\n    \"*  Versioning and automatic bookkeeping of past checkpoints\\n\",\n    \"*  Flexible [`transformations`](https://orbax.readthedocs.io/en/latest/transformations.html) to tweak and load old checkpoints\\n\",\n    \"*  [`jax.sharding`](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html)-based API to save and load in multi-host scenarios\\n\",\n    \"\\n\",\n    \"---\\n\",\n    \"**_Ongoing migration to Orbax:_**\\n\",\n    \"\\n\",\n    \"After July 30 2023, Flax's legacy `flax.training.checkpoints` API will be deprecated in favor of [Orbax](https://github.com/google/orbax).\\n\",\n    \"\\n\",\n    \"*  **If you are a new Flax user**: Use the new `orbax.checkpoint` API, as demonstrated in this guide.\\n\",\n    \"\\n\",\n    \"*  **If you have legacy `flax.training.checkpoints` code in your project**: Consider the following options:\\n\",\n    \"\\n\",\n    \"   * **Migrating your code to Orbax (Recommended)**: Migrate your API calls to `orbax.checkpoint` API by following this [migration guide](https://flax.readthedocs.io/en/latest/guides/converting_and_upgrading/orbax_upgrade_guide.html).\\n\",\n    \"\\n\",\n    \"   * **Automatically use the Orbax backend**: Add `flax.config.update('flax_use_orbax_checkpointing', True)` to your project, which will let your `flax.training.checkpoints` calls automatically use the Orbax backend to save your checkpoints.\\n\",\n    \"\\n\",\n    \"     * **Scheduled flip**: This will become the default mode after **May 2023** (tentative date).\\n\",\n    \"\\n\",\n    \"     * Visit [Orbax-as-backend troubleshooting section](https://flax.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html#orbax-as-backend-troubleshooting) if you meet any issue in the automatic migration.\\n\",\n    \"---\\n\",\n    \"\\n\",\n    \"For backward-compatibility, this guide shows the Orbax-equivalent calls in the Flax legacy `flax.training.checkpoints` API.\\n\",\n    \"\\n\",\n    \"If you need to learn more about `orbax.checkpoint`, refer to the [Orbax docs](https://orbax.readthedocs.io/en/latest/).\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"5a2f6aae\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Setup\\n\",\n    \"\\n\",\n    \"Install/upgrade Flax and [Orbax](https://github.com/google/orbax). For JAX installation with GPU/TPU support, visit [this section on GitHub](https://github.com/jax-ml/jax#installation).\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"id\": \"-icO30rwmKYj\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note: Before running `import jax`, create eight fake devices to mimic a [multi-host environment](https://jax.readthedocs.io/en/latest/jax-101/06-parallelism.html?#aside-hosts-and-devices-in-jax) in this notebook. Note that the order of imports is important here. The `os.environ[\\\"XLA_FLAGS\\\"] = '--xla_force_host_platform_device_count=8'` command works only with the CPU backend, which means it won't work with GPU/TPU acceleration on if you're running this notebook in Google Colab. If you are already running the code on multiple devices (for example, in a 4x2 TPU environment), you can skip running the next cell.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"id\": \"ArKLnsyGRxGv\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import os\\n\",\n    \"os.environ[\\\"XLA_FLAGS\\\"] = '--xla_force_host_platform_device_count=8'\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"id\": \"SJT9DTxTytjn\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:absl:Tensorflow library not found, tensorflow.io.gfile operations will use native shim calls. GCS paths (i.e. 'gs://...') cannot be accessed.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from typing import Optional, Any\\n\",\n    \"import shutil\\n\",\n    \"\\n\",\n    \"import numpy as np\\n\",\n    \"import jax\\n\",\n    \"from jax import random, numpy as jnp\\n\",\n    \"\\n\",\n    \"import flax\\n\",\n    \"from flax import linen as nn\\n\",\n    \"from flax.training import checkpoints, train_state\\n\",\n    \"from flax import struct, serialization\\n\",\n    \"import orbax.checkpoint\\n\",\n    \"\\n\",\n    \"import optax\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"id\": \"afd6db30\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ckpt_dir = '/tmp/flax_ckpt'\\n\",\n    \"\\n\",\n    \"if os.path.exists(ckpt_dir):\\n\",\n    \"    shutil.rmtree(ckpt_dir)  # Remove any existing checkpoints from the last notebook run.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"40d434cd\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Save checkpoints\\n\",\n    \"\\n\",\n    \"In Orbax and Flax, you can save and load any given JAX [pytree](https://jax.readthedocs.io/en/latest/pytrees.html). This includes not only typical Python and NumPy containers, but also customized classes extended from [`flax.struct.dataclass`](https://flax.readthedocs.io/en/latest/api_reference/flax.struct.html#flax.struct.dataclass). That means you can store almost any data generated — not only your model parameters, but any arrays/dictionaries, metadata/configs, and so on.\\n\",\n    \"\\n\",\n    \"First, create a pytree with many data structures and containers, and play with it:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"id\": \"56dec3f6\",\n   \"metadata\": {\n    \"outputId\": \"f1856d96-1961-48ed-bb7c-cb63fbaa7567\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\\n\",\n      \"I0000 00:00:1695322343.254588       1 tfrt_cpu_pjrt_client.cc:349] TfrtCpuClient created.\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'model': TrainState(step=1, apply_fn=<bound method Module.apply of Dense(\\n\",\n       \"     # attributes\\n\",\n       \"     features = 3\\n\",\n       \"     use_bias = True\\n\",\n       \"     dtype = None\\n\",\n       \"     param_dtype = float32\\n\",\n       \"     precision = None\\n\",\n       \"     kernel_init = init\\n\",\n       \"     bias_init = zeros\\n\",\n       \"     dot_general = dot_general\\n\",\n       \"     dot_general_cls = None\\n\",\n       \" )>, params={'bias': Array([-0.001, -0.001, -0.001], dtype=float32), 'kernel': Array([[ 0.26048955, -0.61399287, -0.23458514],\\n\",\n       \"        [ 0.11050402, -0.8765793 ,  0.9800635 ],\\n\",\n       \"        [ 0.36260957,  0.18276349, -0.6856061 ],\\n\",\n       \"        [-0.8519373 , -0.6416717 , -0.4818122 ],\\n\",\n       \"        [-0.6886102 , -0.33987316, -0.05898903]], dtype=float32)}, tx=GradientTransformationExtraArgs(init=<function chain.<locals>.init_fn at 0x13d5d83a0>, update=<function chain.<locals>.update_fn at 0x13d5d8dc0>), opt_state=(EmptyState(), EmptyState())),\\n\",\n       \" 'config': {'dimensions': array([5, 3])},\\n\",\n       \" 'data': [Array([0.59902626, 0.2172144 , 2.4202902 , 0.03266738, 1.2164948 ],      dtype=float32)]}\"\n      ]\n     },\n     \"execution_count\": 4,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# A simple model with one linear layer.\\n\",\n    \"key1, key2 = random.split(random.key(0))\\n\",\n    \"x1 = random.normal(key1, (5,))      # A simple JAX array.\\n\",\n    \"model = nn.Dense(features=3)\\n\",\n    \"variables = model.init(key2, x1)\\n\",\n    \"\\n\",\n    \"# Flax's TrainState is a pytree dataclass and is supported in checkpointing.\\n\",\n    \"# Define your class with `@flax.struct.dataclass` decorator to make it compatible.\\n\",\n    \"tx = optax.sgd(learning_rate=0.001)      # An Optax SGD optimizer.\\n\",\n    \"state = train_state.TrainState.create(\\n\",\n    \"    apply_fn=model.apply,\\n\",\n    \"    params=variables['params'],\\n\",\n    \"    tx=tx)\\n\",\n    \"# Perform a simple gradient update similar to the one during a normal training workflow.\\n\",\n    \"state = state.apply_gradients(grads=jax.tree_util.tree_map(jnp.ones_like, state.params))\\n\",\n    \"\\n\",\n    \"# Some arbitrary nested pytree with a dictionary and a NumPy array.\\n\",\n    \"config = {'dimensions': np.array([5, 3])}\\n\",\n    \"\\n\",\n    \"# Bundle everything together.\\n\",\n    \"ckpt = {'model': state, 'config': config, 'data': [x1]}\\n\",\n    \"ckpt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"8c715b95\",\n   \"metadata\": {},\n   \"source\": [\n    \"### With Orbax\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"6fc59dfa\",\n   \"metadata\": {},\n   \"source\": [\n    \"Save the checkpoint with `orbax.checkpoint.PyTreeCheckpointer`, directly to the `tmp/orbax/single_save` directory.\\n\",\n    \"\\n\",\n    \"Note: An optional `save_args` is provided. This is recommended for performance speedups, as it bundles smaller arrays in your pytree to a single large file instead of multiple smaller files.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"id\": \"61b12da2\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from flax.training import orbax_utils\\n\",\n    \"\\n\",\n    \"orbax_checkpointer = orbax.checkpoint.PyTreeCheckpointer()\\n\",\n    \"save_args = orbax_utils.save_args_from_target(ckpt)\\n\",\n    \"orbax_checkpointer.save('/tmp/flax_ckpt/orbax/single_save', ckpt, save_args=save_args)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"07d4de1a\",\n   \"metadata\": {},\n   \"source\": [\n    \"Next, to use versioning and automatic bookkeeping features, you need to wrap `orbax.checkpoint.CheckpointManager` over `orbax.checkpoint.PyTreeCheckpointer`.\\n\",\n    \"\\n\",\n    \"In addition, provide `orbax.checkpoint.CheckpointManagerOptions` that customizes your needs, such as how often and on what criteria you prefer old checkpoints be deleted. See [documentation](https://orbax.readthedocs.io/en/latest/guides/checkpoint/api_refactor.html) for a full list of options offered.\\n\",\n    \"\\n\",\n    \"`orbax.checkpoint.CheckpointManager` should be placed at the top-level outside your training steps to manage your saves.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"id\": \"d3686ea5\",\n   \"metadata\": {\n    \"outputId\": \"b7132933-566d-440d-c34e-c5468d87cbdc\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"['4', '3']\"\n      ]\n     },\n     \"execution_count\": 6,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"options = orbax.checkpoint.CheckpointManagerOptions(max_to_keep=2, create=True)\\n\",\n    \"checkpoint_manager = orbax.checkpoint.CheckpointManager(\\n\",\n    \"    '/tmp/flax_ckpt/orbax/managed', orbax_checkpointer, options)\\n\",\n    \"\\n\",\n    \"# Inside a training loop\\n\",\n    \"for step in range(5):\\n\",\n    \"    # ... do your training\\n\",\n    \"    checkpoint_manager.save(step, ckpt, save_kwargs={'save_args': save_args})\\n\",\n    \"\\n\",\n    \"os.listdir('/tmp/flax_ckpt/orbax/managed')  # Because max_to_keep=2, only step 3 and 4 are retained\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"8ecbc4cc\",\n   \"metadata\": {},\n   \"source\": [\n    \"### With the legacy API\\n\",\n    \"\\n\",\n    \"And here's how to save with the legacy Flax checkpointing utilities (note that this provides less management features compared with `orbax.checkpoint.CheckpointManagerOptions`):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"id\": \"4cdb35ef\",\n   \"metadata\": {\n    \"outputId\": \"6d849273-15ce-4480-8864-726d1838ac1f\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"'/tmp/flax_ckpt/flax-checkpointing/checkpoint_0'\"\n      ]\n     },\n     \"execution_count\": 7,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# Import Flax Checkpoints.\\n\",\n    \"from flax.training import checkpoints\\n\",\n    \"\\n\",\n    \"checkpoints.save_checkpoint(ckpt_dir='/tmp/flax_ckpt/flax-checkpointing',\\n\",\n    \"                            target=ckpt,\\n\",\n    \"                            step=0,\\n\",\n    \"                            overwrite=True,\\n\",\n    \"                            keep=2)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"6b658bd1\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Restore checkpoints\\n\",\n    \"\\n\",\n    \"### With Orbax\\n\",\n    \"\\n\",\n    \"In Orbax, call `.restore()` for either `orbax.checkpoint.PyTreeCheckpointer` or `orbax.checkpoint.CheckpointManager` to restore your checkpoint in the raw pytree format.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"id\": \"a807a9c1\",\n   \"metadata\": {\n    \"outputId\": \"b4af1ef4-f22f-459b-bdca-2e6bfa16c08b\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'config': {'dimensions': array([5, 3])},\\n\",\n       \" 'data': [array([0.59902626, 0.2172144 , 2.4202902 , 0.03266738, 1.2164948 ],\\n\",\n       \"        dtype=float32)],\\n\",\n       \" 'model': {'opt_state': [None, None],\\n\",\n       \"  'params': {'bias': array([-0.001, -0.001, -0.001], dtype=float32),\\n\",\n       \"   'kernel': array([[ 0.26048955, -0.61399287, -0.23458514],\\n\",\n       \"          [ 0.11050402, -0.8765793 ,  0.9800635 ],\\n\",\n       \"          [ 0.36260957,  0.18276349, -0.6856061 ],\\n\",\n       \"          [-0.8519373 , -0.6416717 , -0.4818122 ],\\n\",\n       \"          [-0.6886102 , -0.33987316, -0.05898903]], dtype=float32)},\\n\",\n       \"  'step': 1}}\"\n      ]\n     },\n     \"execution_count\": 8,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"raw_restored = orbax_checkpointer.restore('/tmp/flax_ckpt/orbax/single_save')\\n\",\n    \"raw_restored\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"8c015a22\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that the `step` number is required for `CheckpointManger`. You can also use `.latest_step()` to find the latest step available.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"id\": \"251d7085\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'config': {'dimensions': array([5, 3])},\\n\",\n       \" 'data': [array([0.59902626, 0.2172144 , 2.4202902 , 0.03266738, 1.2164948 ],\\n\",\n       \"        dtype=float32)],\\n\",\n       \" 'model': {'opt_state': [None, None],\\n\",\n       \"  'params': {'bias': array([-0.001, -0.001, -0.001], dtype=float32),\\n\",\n       \"   'kernel': array([[ 0.26048955, -0.61399287, -0.23458514],\\n\",\n       \"          [ 0.11050402, -0.8765793 ,  0.9800635 ],\\n\",\n       \"          [ 0.36260957,  0.18276349, -0.6856061 ],\\n\",\n       \"          [-0.8519373 , -0.6416717 , -0.4818122 ],\\n\",\n       \"          [-0.6886102 , -0.33987316, -0.05898903]], dtype=float32)},\\n\",\n       \"  'step': 1}}\"\n      ]\n     },\n     \"execution_count\": 9,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"step = checkpoint_manager.latest_step()  # step = 4\\n\",\n    \"checkpoint_manager.restore(step)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"c7fe3bc8\",\n   \"metadata\": {},\n   \"source\": [\n    \"### With the legacy API\\n\",\n    \"\\n\",\n    \"Note that with the migration to Orbax in progress, `flax.training.checkpointing.restore_checkpoint` can automatically identify whether a checkpoint is saved in the legacy Flax format or with an Orbax backend, and restore the pytree correctly. Therefore, adding `flax.config.update('flax_use_orbax_checkpointing', True)` won't hurt your ability to restore old checkpoints.\\n\",\n    \"\\n\",\n    \"Here's how to restore checkpoints using the legacy API:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"id\": \"150b20a0\",\n   \"metadata\": {\n    \"outputId\": \"85ffceca-f38d-46b8-e567-d9d38b7885f9\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'config': {'dimensions': array([5, 3])},\\n\",\n       \" 'data': {'0': array([0.59902626, 0.2172144 , 2.4202902 , 0.03266738, 1.2164948 ],\\n\",\n       \"        dtype=float32)},\\n\",\n       \" 'model': {'opt_state': {'0': None, '1': None},\\n\",\n       \"  'params': {'bias': array([-0.001, -0.001, -0.001], dtype=float32),\\n\",\n       \"   'kernel': array([[ 0.26048955, -0.61399287, -0.23458514],\\n\",\n       \"          [ 0.11050402, -0.8765793 ,  0.9800635 ],\\n\",\n       \"          [ 0.36260957,  0.18276349, -0.6856061 ],\\n\",\n       \"          [-0.8519373 , -0.6416717 , -0.4818122 ],\\n\",\n       \"          [-0.6886102 , -0.33987316, -0.05898903]], dtype=float32)},\\n\",\n       \"  'step': 1}}\"\n      ]\n     },\n     \"execution_count\": 10,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"raw_restored = checkpoints.restore_checkpoint(ckpt_dir='/tmp/flax_ckpt/flax-checkpointing', target=None)\\n\",\n    \"raw_restored\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"987b981f\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Restore with custom dataclasses\\n\",\n    \"\\n\",\n    \"### With Orbax\\n\",\n    \"\\n\",\n    \"*  The pytrees restored in the previous examples are in the form of raw dictionaries. Original pytrees contain custom dataclasses like [`TrainState`](https://flax.readthedocs.io/en/latest/flip/1009-optimizer-api.html?#train-state) and `optax` states.\\n\",\n    \"*  This is because when restoring a pytree, the program does not yet know which structure it once belonged to.\\n\",\n    \"*  To resolve this, you should first provide an example pytree to let Orbax or Flax know exactly which structure to restore to.\\n\",\n    \"\\n\",\n    \"This section demonstrates how to set up any custom Flax dataclass explicitly, and have the same structure as a saved checkpoint.\\n\",\n    \"\\n\",\n    \"Note: Data that was a JAX NumPy array (`jnp.array`) format will be restored as a NumPy array (`numpy.array`). This would not affect your work because JAX will [automatically convert](https://jax.readthedocs.io/en/latest/jax-101/01-jax-basics.html) NumPy arrays to JAX arrays once the computation starts.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"id\": \"58f42513\",\n   \"metadata\": {\n    \"outputId\": \"110c6b6e-fe42-4179-e5d8-6b92d355e11b\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'config': {'dimensions': array([5, 3])},\\n\",\n       \" 'data': [array([0.59902626, 0.2172144 , 2.4202902 , 0.03266738, 1.2164948 ],\\n\",\n       \"        dtype=float32)],\\n\",\n       \" 'model': TrainState(step=1, apply_fn=<bound method Module.apply of Dense(\\n\",\n       \"     # attributes\\n\",\n       \"     features = 3\\n\",\n       \"     use_bias = True\\n\",\n       \"     dtype = None\\n\",\n       \"     param_dtype = float32\\n\",\n       \"     precision = None\\n\",\n       \"     kernel_init = init\\n\",\n       \"     bias_init = zeros\\n\",\n       \"     dot_general = dot_general\\n\",\n       \"     dot_general_cls = None\\n\",\n       \" )>, params={'bias': array([-0.001, -0.001, -0.001], dtype=float32), 'kernel': array([[ 0.26048955, -0.61399287, -0.23458514],\\n\",\n       \"        [ 0.11050402, -0.8765793 ,  0.9800635 ],\\n\",\n       \"        [ 0.36260957,  0.18276349, -0.6856061 ],\\n\",\n       \"        [-0.8519373 , -0.6416717 , -0.4818122 ],\\n\",\n       \"        [-0.6886102 , -0.33987316, -0.05898903]], dtype=float32)}, tx=GradientTransformationExtraArgs(init=<function chain.<locals>.init_fn at 0x13d5d83a0>, update=<function chain.<locals>.update_fn at 0x13d5d8dc0>), opt_state=(EmptyState(), EmptyState()))}\"\n      ]\n     },\n     \"execution_count\": 11,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"empty_state = train_state.TrainState.create(\\n\",\n    \"    apply_fn=model.apply,\\n\",\n    \"    params=jax.tree_util.tree_map(np.zeros_like, variables['params']),  # values of the tree leaf doesn't matter\\n\",\n    \"    tx=tx,\\n\",\n    \")\\n\",\n    \"empty_config = {'dimensions': np.array([0, 0])}\\n\",\n    \"target = {'model': empty_state, 'config': empty_config, 'data': [jnp.zeros_like(x1)]}\\n\",\n    \"state_restored = orbax_checkpointer.restore('/tmp/flax_ckpt/orbax/single_save', item=target)\\n\",\n    \"state_restored\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"f1c18bc6\",\n   \"metadata\": {},\n   \"source\": [\n    \"### With the legacy API\\n\",\n    \"\\n\",\n    \"Alternatively, you can restore from Orbax `CheckpointManager` and from the legacy Flax code as follows:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"id\": \"a61e9a66\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'config': {'dimensions': array([5, 3])},\\n\",\n       \" 'data': [array([0.59902626, 0.2172144 , 2.4202902 , 0.03266738, 1.2164948 ],\\n\",\n       \"        dtype=float32)],\\n\",\n       \" 'model': TrainState(step=1, apply_fn=<bound method Module.apply of Dense(\\n\",\n       \"     # attributes\\n\",\n       \"     features = 3\\n\",\n       \"     use_bias = True\\n\",\n       \"     dtype = None\\n\",\n       \"     param_dtype = float32\\n\",\n       \"     precision = None\\n\",\n       \"     kernel_init = init\\n\",\n       \"     bias_init = zeros\\n\",\n       \"     dot_general = dot_general\\n\",\n       \"     dot_general_cls = None\\n\",\n       \" )>, params={'bias': array([-0.001, -0.001, -0.001], dtype=float32), 'kernel': array([[ 0.26048955, -0.61399287, -0.23458514],\\n\",\n       \"        [ 0.11050402, -0.8765793 ,  0.9800635 ],\\n\",\n       \"        [ 0.36260957,  0.18276349, -0.6856061 ],\\n\",\n       \"        [-0.8519373 , -0.6416717 , -0.4818122 ],\\n\",\n       \"        [-0.6886102 , -0.33987316, -0.05898903]], dtype=float32)}, tx=GradientTransformationExtraArgs(init=<function chain.<locals>.init_fn at 0x13d5d83a0>, update=<function chain.<locals>.update_fn at 0x13d5d8dc0>), opt_state=(EmptyState(), EmptyState()))}\"\n      ]\n     },\n     \"execution_count\": 12,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"checkpoint_manager.restore(4, items=target)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"id\": \"412af50e\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:absl:The transformations API will eventually be replaced by an upgraded design. The current API will not be removed until this point, but it will no longer be actively worked on.\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'model': TrainState(step=1, apply_fn=<bound method Module.apply of Dense(\\n\",\n       \"     # attributes\\n\",\n       \"     features = 3\\n\",\n       \"     use_bias = True\\n\",\n       \"     dtype = None\\n\",\n       \"     param_dtype = float32\\n\",\n       \"     precision = None\\n\",\n       \"     kernel_init = init\\n\",\n       \"     bias_init = zeros\\n\",\n       \"     dot_general = dot_general\\n\",\n       \"     dot_general_cls = None\\n\",\n       \" )>, params={'bias': array([-0.001, -0.001, -0.001], dtype=float32), 'kernel': array([[ 0.26048955, -0.61399287, -0.23458514],\\n\",\n       \"        [ 0.11050402, -0.8765793 ,  0.9800635 ],\\n\",\n       \"        [ 0.36260957,  0.18276349, -0.6856061 ],\\n\",\n       \"        [-0.8519373 , -0.6416717 , -0.4818122 ],\\n\",\n       \"        [-0.6886102 , -0.33987316, -0.05898903]], dtype=float32)}, tx=GradientTransformationExtraArgs(init=<function chain.<locals>.init_fn at 0x13d5d83a0>, update=<function chain.<locals>.update_fn at 0x13d5d8dc0>), opt_state=(EmptyState(), EmptyState())),\\n\",\n       \" 'config': {'dimensions': array([5, 3])},\\n\",\n       \" 'data': [Array([0.59902626, 0.2172144 , 2.4202902 , 0.03266738, 1.2164948 ],      dtype=float32)]}\"\n      ]\n     },\n     \"execution_count\": 13,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"checkpoints.restore_checkpoint(ckpt_dir='/tmp/flax_ckpt/flax-checkpointing', target=target)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"27461ac8\",\n   \"metadata\": {},\n   \"source\": [\n    \"It's often recommended to refactor out the process of initializing a checkpoint's structure (for example, a [`TrainState`](https://flax.readthedocs.io/en/latest/flip/1009-optimizer-api.html?#train-state)), so that saving/loading is easier and less error-prone. This is because functions and complex objects like `apply_fn` and `tx` (optimizer) cannot be serialized into the checkpoint file and must be initialized by code.\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"id\": \"136a300a\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Restore when checkpoint structures differ\\n\",\n    \"\\n\",\n    \"During your development, your checkpoint structure will change when changing the model, adding/removing fields during tweaking, and so on.\\n\",\n    \"\\n\",\n    \"This section explains how to load old data to your new code.\\n\",\n    \"\\n\",\n    \"Below is  a simple example — a `CustomTrainState` extended from `flax.training.train_state.TrainState` that contains an extra field called `batch_stats`. When working on a real-world model, you may need this when applying [batch normalization](https://flax.readthedocs.io/en/latest/guides/training_techniques/batch_norm.html).\\n\",\n    \"\\n\",\n    \"Here, you store the new `CustomTrainState` as step 5, while step 4 contains the old/previous `TrainState`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"id\": \"be65d4af\",\n   \"metadata\": {\n    \"outputId\": \"4fe776f0-65f8-4fc4-d64a-990520b36dce\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"True\"\n      ]\n     },\n     \"execution_count\": 14,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"class CustomTrainState(train_state.TrainState):\\n\",\n    \"    batch_stats: Any = None\\n\",\n    \"\\n\",\n    \"custom_state = CustomTrainState.create(\\n\",\n    \"    apply_fn=state.apply_fn,\\n\",\n    \"    params=state.params,\\n\",\n    \"    tx=state.tx,\\n\",\n    \"    batch_stats=np.arange(10),\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"custom_ckpt = {'model': custom_state, 'config': config, 'data': [x1]}\\n\",\n    \"# Use a custom state to read the old `TrainState` checkpoint.\\n\",\n    \"custom_target = {'model': custom_state, 'config': None, 'data': [jnp.zeros_like(x1)]}\\n\",\n    \"\\n\",\n    \"# Save it in Orbax.\\n\",\n    \"custom_save_args = orbax_utils.save_args_from_target(custom_ckpt)\\n\",\n    \"checkpoint_manager.save(5, custom_ckpt, save_kwargs={'save_args': custom_save_args})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"379c2255\",\n   \"metadata\": {},\n   \"source\": [\n    \"It is recommended to keep your checkpoints up-to-date with your pytree dataclass definitions. However, you might be forced to restore the checkpoints with incompatible reference objects at runtime. When this happens, the checkpoint restoration will try to respect the structure of the reference when given.\\n\",\n    \"\\n\",\n    \"Below are examples of a few common scenarios.\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"id\": \"d5fa9652\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Scenario 1: When a reference object is partial\\n\",\n    \"\\n\",\n    \"If your reference object is a subtree of your checkpoint, the restoration will ignore the additional field(s) and restore a checkpoint with the same structure as the reference.\\n\",\n    \"\\n\",\n    \"Like in the example below, the `batch_stats` field in `CustomTrainState` was ignored, and the checkpoint was restored as a `TrainState`.\\n\",\n    \"\\n\",\n    \"This can also be useful for reading only part of your checkpoint.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"id\": \"68828029\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'config': {'dimensions': array([5, 3])},\\n\",\n       \" 'data': [array([0.59902626, 0.2172144 , 2.4202902 , 0.03266738, 1.2164948 ],\\n\",\n       \"        dtype=float32)],\\n\",\n       \" 'model': TrainState(step=0, apply_fn=<bound method Module.apply of Dense(\\n\",\n       \"     # attributes\\n\",\n       \"     features = 3\\n\",\n       \"     use_bias = True\\n\",\n       \"     dtype = None\\n\",\n       \"     param_dtype = float32\\n\",\n       \"     precision = None\\n\",\n       \"     kernel_init = init\\n\",\n       \"     bias_init = zeros\\n\",\n       \"     dot_general = dot_general\\n\",\n       \"     dot_general_cls = None\\n\",\n       \" )>, params={'bias': array([-0.001, -0.001, -0.001], dtype=float32), 'kernel': array([[ 0.26048955, -0.61399287, -0.23458514],\\n\",\n       \"        [ 0.11050402, -0.8765793 ,  0.9800635 ],\\n\",\n       \"        [ 0.36260957,  0.18276349, -0.6856061 ],\\n\",\n       \"        [-0.8519373 , -0.6416717 , -0.4818122 ],\\n\",\n       \"        [-0.6886102 , -0.33987316, -0.05898903]], dtype=float32)}, tx=GradientTransformationExtraArgs(init=<function chain.<locals>.init_fn at 0x13d5d83a0>, update=<function chain.<locals>.update_fn at 0x13d5d8dc0>), opt_state=(EmptyState(), EmptyState()))}\"\n      ]\n     },\n     \"execution_count\": 15,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"restored = checkpoint_manager.restore(5, items=target)\\n\",\n    \"assert not hasattr(restored, 'batch_stats')\\n\",\n    \"assert type(restored['model']) == train_state.TrainState\\n\",\n    \"restored\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"id\": \"5c6822c6\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Scenario 2: When a checkpoint is partial\\n\",\n    \"\\n\",\n    \"On the other hand, if the reference object contains a value that is not available in the checkpoint, the checkpointing code will by default warn that some data is not compatible.\\n\",\n    \"\\n\",\n    \"To bypass the error, you need to pass an Orbax [`transform`](https://orbax.readthedocs.io/en/latest/guides/checkpoint/transformations.html) that teaches Orbax how to conform this checkpoint into the structure of the `custom_target`.\\n\",\n    \"\\n\",\n    \"In this case, pass a default `{}` that lets Orbax use values in the `custom_target` to fill in the blank. This allows you to restore an old checkpoint into a new data structure, the `CustomTrainState`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"id\": \"a5d14c9f\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:absl:The transformations API will eventually be replaced by an upgraded design. The current API will not be removed until this point, but it will no longer be actively worked on.\\n\"\n     ]\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"KeyError when target state has an unmentioned field: 'batch_stats'\\n\",\n      \"\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'config': None,\\n\",\n       \" 'data': [Array([0.59902626, 0.2172144 , 2.4202902 , 0.03266738, 1.2164948 ],      dtype=float32)],\\n\",\n       \" 'model': CustomTrainState(step=1, apply_fn=<bound method Module.apply of Dense(\\n\",\n       \"     # attributes\\n\",\n       \"     features = 3\\n\",\n       \"     use_bias = True\\n\",\n       \"     dtype = None\\n\",\n       \"     param_dtype = float32\\n\",\n       \"     precision = None\\n\",\n       \"     kernel_init = init\\n\",\n       \"     bias_init = zeros\\n\",\n       \"     dot_general = dot_general\\n\",\n       \"     dot_general_cls = None\\n\",\n       \" )>, params={'bias': Array([-0.001, -0.001, -0.001], dtype=float32), 'kernel': Array([[ 0.26048955, -0.61399287, -0.23458514],\\n\",\n       \"        [ 0.11050402, -0.8765793 ,  0.9800635 ],\\n\",\n       \"        [ 0.36260957,  0.18276349, -0.6856061 ],\\n\",\n       \"        [-0.8519373 , -0.6416717 , -0.4818122 ],\\n\",\n       \"        [-0.6886102 , -0.33987316, -0.05898903]], dtype=float32)}, tx=GradientTransformationExtraArgs(init=<function chain.<locals>.init_fn at 0x13d5d83a0>, update=<function chain.<locals>.update_fn at 0x13d5d8dc0>), opt_state=(EmptyState(), EmptyState()), batch_stats=array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))}\"\n      ]\n     },\n     \"execution_count\": 16,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"try:\\n\",\n    \"    checkpoint_manager.restore(4, items=custom_target)\\n\",\n    \"except KeyError as e:\\n\",\n    \"    print(f'KeyError when target state has an unmentioned field: {e}')\\n\",\n    \"    print('')\\n\",\n    \"\\n\",\n    \"# Step 4 is an original `TrainState`, without the `batch_stats`\\n\",\n    \"custom_restore_args = orbax_utils.restore_args_from_target(custom_target)\\n\",\n    \"restored = checkpoint_manager.restore(4, items=custom_target,\\n\",\n    \"                                      restore_kwargs={'transforms': {}, 'restore_args': custom_restore_args})\\n\",\n    \"assert type(restored['model']) == CustomTrainState\\n\",\n    \"np.testing.assert_equal(restored['model'].batch_stats,\\n\",\n    \"                        custom_target['model'].batch_stats)\\n\",\n    \"restored\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"74a4b0fd\",\n   \"metadata\": {},\n   \"source\": [\n    \"##### With Orbax\\n\",\n    \"\\n\",\n    \"If you have already saved your checkpoints with the Orbax backend, you can use `orbax_transforms` to access this `transforms` argument in the Flax API.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 17,\n   \"id\": \"29fd1e33\",\n   \"metadata\": {\n    \"outputId\": \"cdbb9247-d1eb-4458-aa83-8db0332af7cb\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:absl:The transformations API will eventually be replaced by an upgraded design. The current API will not be removed until this point, but it will no longer be actively worked on.\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'model': CustomTrainState(step=1, apply_fn=<bound method Module.apply of Dense(\\n\",\n       \"     # attributes\\n\",\n       \"     features = 3\\n\",\n       \"     use_bias = True\\n\",\n       \"     dtype = None\\n\",\n       \"     param_dtype = float32\\n\",\n       \"     precision = None\\n\",\n       \"     kernel_init = init\\n\",\n       \"     bias_init = zeros\\n\",\n       \"     dot_general = dot_general\\n\",\n       \"     dot_general_cls = None\\n\",\n       \" )>, params={'bias': Array([-0.001, -0.001, -0.001], dtype=float32), 'kernel': Array([[ 0.26048955, -0.61399287, -0.23458514],\\n\",\n       \"        [ 0.11050402, -0.8765793 ,  0.9800635 ],\\n\",\n       \"        [ 0.36260957,  0.18276349, -0.6856061 ],\\n\",\n       \"        [-0.8519373 , -0.6416717 , -0.4818122 ],\\n\",\n       \"        [-0.6886102 , -0.33987316, -0.05898903]], dtype=float32)}, tx=GradientTransformationExtraArgs(init=<function chain.<locals>.init_fn at 0x13d5d83a0>, update=<function chain.<locals>.update_fn at 0x13d5d8dc0>), opt_state=(EmptyState(), EmptyState()), batch_stats=array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])),\\n\",\n       \" 'config': None,\\n\",\n       \" 'data': [Array([0.59902626, 0.2172144 , 2.4202902 , 0.03266738, 1.2164948 ],      dtype=float32)]}\"\n      ]\n     },\n     \"execution_count\": 17,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# Save in the \\\"Flax-with-Orbax\\\" backend.\\n\",\n    \"flax.config.update('flax_use_orbax_checkpointing', True)\\n\",\n    \"checkpoints.save_checkpoint(ckpt_dir='/tmp/flax_ckpt/flax-checkpointing',\\n\",\n    \"                            target=ckpt,\\n\",\n    \"                            step=4,\\n\",\n    \"                            overwrite=True,\\n\",\n    \"                            keep=2)\\n\",\n    \"\\n\",\n    \"checkpoints.restore_checkpoint('/tmp/flax_ckpt/flax-checkpointing', target=custom_target, step=4,\\n\",\n    \"                               orbax_transforms={})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"830ef07c\",\n   \"metadata\": {},\n   \"source\": [\n    \"##### With the legacy API\\n\",\n    \"\\n\",\n    \"Using the legacy `flax.training.checkpoints` API, similar things are doable too, but they are not as flexible as the [Orbax Transformations](https://orbax.readthedocs.io/en/latest/guides/checkpoint/transformations.html).\\n\",\n    \"\\n\",\n    \"You need to restore the checkpoint to a raw dict with `target=None`, modify the structure accordingly, and then deserialize it back to the original target.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 18,\n   \"id\": \"051e7a16\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'model': CustomTrainState(step=1, apply_fn=<bound method Module.apply of Dense(\\n\",\n       \"     # attributes\\n\",\n       \"     features = 3\\n\",\n       \"     use_bias = True\\n\",\n       \"     dtype = None\\n\",\n       \"     param_dtype = float32\\n\",\n       \"     precision = None\\n\",\n       \"     kernel_init = init\\n\",\n       \"     bias_init = zeros\\n\",\n       \"     dot_general = dot_general\\n\",\n       \"     dot_general_cls = None\\n\",\n       \" )>, params={'bias': array([-0.001, -0.001, -0.001], dtype=float32), 'kernel': array([[ 0.26048955, -0.61399287, -0.23458514],\\n\",\n       \"        [ 0.11050402, -0.8765793 ,  0.9800635 ],\\n\",\n       \"        [ 0.36260957,  0.18276349, -0.6856061 ],\\n\",\n       \"        [-0.8519373 , -0.6416717 , -0.4818122 ],\\n\",\n       \"        [-0.6886102 , -0.33987316, -0.05898903]], dtype=float32)}, tx=GradientTransformationExtraArgs(init=<function chain.<locals>.init_fn at 0x13d5d83a0>, update=<function chain.<locals>.update_fn at 0x13d5d8dc0>), opt_state=(EmptyState(), EmptyState()), batch_stats=array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])),\\n\",\n       \" 'config': {'dimensions': array([5, 3])},\\n\",\n       \" 'data': [array([0.59902626, 0.2172144 , 2.4202902 , 0.03266738, 1.2164948 ],\\n\",\n       \"        dtype=float32)]}\"\n      ]\n     },\n     \"execution_count\": 18,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# Save using the legacy Flax `checkpoints` API.\\n\",\n    \"flax.config.update('flax_use_orbax_checkpointing', False)\\n\",\n    \"checkpoints.save_checkpoint(ckpt_dir='/tmp/flax_ckpt/flax-checkpointing',\\n\",\n    \"                            target=ckpt,\\n\",\n    \"                            step=5,\\n\",\n    \"                            overwrite=True,\\n\",\n    \"                            keep=2)\\n\",\n    \"\\n\",\n    \"# Pass no target to get a raw state dictionary first.\\n\",\n    \"raw_state_dict = checkpoints.restore_checkpoint('/tmp/flax_ckpt/flax-checkpointing', target=None, step=5)\\n\",\n    \"# Add/remove fields as needed.\\n\",\n    \"raw_state_dict['model']['batch_stats'] = np.flip(np.arange(10))\\n\",\n    \"# Restore the classes with correct target now\\n\",\n    \"flax.serialization.from_state_dict(custom_target, raw_state_dict)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a6b39501\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Asynchronized checkpointing\\n\",\n    \"\\n\",\n    \"Checkpointing is I/O heavy, and if you have a large amount of data to save, it may be worthwhile to put it into a background thread, while continuing with your training.\\n\",\n    \"\\n\",\n    \"You can do this by creating an [`orbax.checkpoint.AsyncCheckpointer`](https://github.com/google/orbax/blob/main/checkpoint/orbax/checkpoint/async_checkpointer.py) in place of the `orbax.checkpoint.PyTreeCheckpointer`.\\n\",\n    \"\\n\",\n    \"Note: You should use the same `async_checkpointer` to handle all your async saves across your training steps, so that it can make sure that a previous async save is done before the next one begins. This enables bookkeeping, such as `keep` (the number of checkpoints) and `overwrite` to be consistent across steps.\\n\",\n    \"\\n\",\n    \"Whenever you want to explicitly wait until an async save is done, you can call `async_checkpointer.wait_until_finished()`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 19,\n   \"id\": \"85be68a6\",\n   \"metadata\": {\n    \"outputId\": \"aefce94c-8bae-4355-c142-05f2b61c39e2\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'config': {'dimensions': array([5, 3])},\\n\",\n       \" 'data': [array([0.59902626, 0.2172144 , 2.4202902 , 0.03266738, 1.2164948 ],\\n\",\n       \"        dtype=float32)],\\n\",\n       \" 'model': TrainState(step=1, apply_fn=<bound method Module.apply of Dense(\\n\",\n       \"     # attributes\\n\",\n       \"     features = 3\\n\",\n       \"     use_bias = True\\n\",\n       \"     dtype = None\\n\",\n       \"     param_dtype = float32\\n\",\n       \"     precision = None\\n\",\n       \"     kernel_init = init\\n\",\n       \"     bias_init = zeros\\n\",\n       \"     dot_general = dot_general\\n\",\n       \"     dot_general_cls = None\\n\",\n       \" )>, params={'bias': array([-0.001, -0.001, -0.001], dtype=float32), 'kernel': array([[ 0.26048955, -0.61399287, -0.23458514],\\n\",\n       \"        [ 0.11050402, -0.8765793 ,  0.9800635 ],\\n\",\n       \"        [ 0.36260957,  0.18276349, -0.6856061 ],\\n\",\n       \"        [-0.8519373 , -0.6416717 , -0.4818122 ],\\n\",\n       \"        [-0.6886102 , -0.33987316, -0.05898903]], dtype=float32)}, tx=GradientTransformationExtraArgs(init=<function chain.<locals>.init_fn at 0x13d5d83a0>, update=<function chain.<locals>.update_fn at 0x13d5d8dc0>), opt_state=(EmptyState(), EmptyState()))}\"\n      ]\n     },\n     \"execution_count\": 19,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# `orbax.checkpoint.AsyncCheckpointer` needs some multi-process initialization, because it was\\n\",\n    \"# originally designed for multi-process large model checkpointing.\\n\",\n    \"# For Python notebooks or other single-process settings, just set up with `num_processes=1`.\\n\",\n    \"# Refer to https://jax.readthedocs.io/en/latest/multi_process.html#initializing-the-cluster\\n\",\n    \"# for how to set it up in multi-process scenarios.\\n\",\n    \"jax.distributed.initialize(\\\"localhost:8889\\\", num_processes=1, process_id=0)\\n\",\n    \"\\n\",\n    \"async_checkpointer = orbax.checkpoint.AsyncCheckpointer(\\n\",\n    \"    orbax.checkpoint.PyTreeCheckpointHandler(), timeout_secs=50)\\n\",\n    \"\\n\",\n    \"# Save your job:\\n\",\n    \"async_checkpointer.save('/tmp/flax_ckpt/orbax/single_save_async', ckpt, save_args=save_args)\\n\",\n    \"# ... Continue with your work...\\n\",\n    \"\\n\",\n    \"# ... Until a time when you want to wait until the save completes:\\n\",\n    \"async_checkpointer.wait_until_finished()  # Blocks until the checkpoint saving is completed.\\n\",\n    \"async_checkpointer.restore('/tmp/flax_ckpt/orbax/single_save_async', item=target)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"13e93db6\",\n   \"metadata\": {},\n   \"source\": [\n    \"If you are using Orbax `CheckpointManager`, just pass in the async_checkpointer when initializing it. Then, in practice, call `async_checkpoint_manager.wait_until_finished()` instead.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 20,\n   \"id\": \"af33b138\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"async_checkpoint_manager = orbax.checkpoint.CheckpointManager(\\n\",\n    \"    '/tmp/flax_ckpt/orbax/managed_async', async_checkpointer, options)\\n\",\n    \"async_checkpoint_manager.wait_until_finished()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"bb0e03cd\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Multi-host/multi-process checkpointing\\n\",\n    \"\\n\",\n    \"JAX provides a few ways to scale up your code on multiple hosts at the same time. This usually happens when the number of devices (CPU/GPU/TPU) is so large that different devices are managed by different hosts (CPU). To get started on JAX in multi-process settings, check out [Using JAX in multi-host and multi-process environments](https://jax.readthedocs.io/en/latest/multi_process.html) and the [distributed array guide](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html).\\n\",\n    \"\\n\",\n    \"In the [Single Program Multi Data (SPMD)](https://jax.readthedocs.io/en/latest/glossary.html#term-SPMD) paradigm with JAX `jit`, a large multi-process array can have its data sharded across different devices. (Note that JAX `pjit` and `jit` have been merged into a single unified interface. To learn about compiling and executing JAX functions in multi-host or multi-core environments, refer to [this guide](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html) and the [jax.Array migration guide](https://jax.readthedocs.io/en/latest/jax_array_migration.html).) When a multi-process array is serialized, each host dumps its data shards to a single shared storage, such as a Google Cloud bucket.\\n\",\n    \"\\n\",\n    \"Orbax supports saving and loading pytrees with multi-process arrays in the same fashion as single-process pytrees. However, it's recommended to use the asynchronized [`orbax.AsyncCheckpointer`](https://github.com/google/orbax/blob/main/checkpoint/orbax/checkpoint/async_checkpointer.py) to save large multi-process arrays on another thread, so that you can perform computation alongside the saves. With pure Orbax, saving checkpoints in a multi-process context uses the same API as in a single-process context.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 21,\n   \"id\": \"ubdUvyMrhD-1\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from jax.sharding import PartitionSpec, NamedSharding\\n\",\n    \"\\n\",\n    \"# Create an array sharded across multiple devices.\\n\",\n    \"mesh_shape = (4, 2)\\n\",\n    \"devices = np.asarray(jax.devices()).reshape(*mesh_shape)\\n\",\n    \"mesh = jax.sharding.Mesh(devices, ('x', 'y'))\\n\",\n    \"\\n\",\n    \"mp_array = jax.device_put(np.arange(8 * 2).reshape(8, 2),\\n\",\n    \"                          NamedSharding(mesh, PartitionSpec('x', 'y')))\\n\",\n    \"\\n\",\n    \"# Make it a pytree.\\n\",\n    \"mp_ckpt = {'model': mp_array}\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 22,\n   \"id\": \"a669bc05\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"async_checkpoint_manager.save(0, mp_ckpt)\\n\",\n    \"async_checkpoint_manager.wait_until_finished()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"4deee32e\",\n   \"metadata\": {},\n   \"source\": [\n    \"When restoring a checkpoint with multi-process arrays, you need to specify what `sharding` each array should be restored back to. Otherwise, they will be restored as large `np.array`s on process 0, costing time and memory.\\n\",\n    \"\\n\",\n    \"(In this notebook, since we are on single-process, it will be restored as `np.array` even if we provide shardings.)\\n\",\n    \"\\n\",\n    \"### With Orbax\\n\",\n    \"\\n\",\n    \"Orbax allows you to specify this by passing a pytree of `sharding`s in `restore_args`. If you already have a reference pytree that has all the arrays with the right sharding, you can use `orbax_utils.restore_args_from_target` to transform it into the `restore_args` that Orbax needs.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 23,\n   \"id\": \"b8e7daaa\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'model': Array([[ 0,  1],\\n\",\n       \"        [ 2,  3],\\n\",\n       \"        [ 4,  5],\\n\",\n       \"        [ 6,  7],\\n\",\n       \"        [ 8,  9],\\n\",\n       \"        [10, 11],\\n\",\n       \"        [12, 13],\\n\",\n       \"        [14, 15]], dtype=int32)}\"\n      ]\n     },\n     \"execution_count\": 23,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# The reference doesn't need to be as large as your checkpoint!\\n\",\n    \"# Just make sure it has the `.sharding` you want.\\n\",\n    \"mp_smaller = jax.device_put(np.arange(8).reshape(4, 2),\\n\",\n    \"                            NamedSharding(mesh, PartitionSpec('x', 'y')))\\n\",\n    \"ref_ckpt = {'model': mp_smaller}\\n\",\n    \"\\n\",\n    \"restore_args = orbax_utils.restore_args_from_target(ref_ckpt)\\n\",\n    \"async_checkpoint_manager.restore(\\n\",\n    \"    0, items=ref_ckpt, restore_kwargs={'restore_args': restore_args})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"edc355ce\",\n   \"metadata\": {},\n   \"source\": [\n    \"### With the legacy Flax: use `save_checkpoint_multiprocess`\\n\",\n    \"\\n\",\n    \"In legacy Flax, to save multi-process arrays, use [`flax.training.checkpoints.save_checkpoint_multiprocess()`](https://flax.readthedocs.io/en/latest/api_reference/flax.training.html#flax.training.checkpoints.save_checkpoint_multiprocess) in place of `save_checkpoint()` and with the same arguments.\\n\",\n    \"\\n\",\n    \"If your checkpoint is too large, you can specify `timeout_secs` in the manager and give it more time to finish writing.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 24,\n   \"id\": \"5d10039b\",\n   \"metadata\": {\n    \"outputId\": \"901bb097-0899-479d-b9ae-61dae79e7057\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"'/tmp/flax_ckpt/checkpoint_3'\"\n      ]\n     },\n     \"execution_count\": 24,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"async_checkpointer = orbax.checkpoint.AsyncCheckpointer(orbax.checkpoint.PyTreeCheckpointHandler(), timeout_secs=50)\\n\",\n    \"checkpoints.save_checkpoint_multiprocess(ckpt_dir,\\n\",\n    \"                                         mp_ckpt,\\n\",\n    \"                                         step=3,\\n\",\n    \"                                         overwrite=True,\\n\",\n    \"                                         keep=4,\\n\",\n    \"                                         orbax_checkpointer=async_checkpointer)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 25,\n   \"id\": \"a9f9724c\",\n   \"metadata\": {\n    \"outputId\": \"393c4a0e-8a8c-4ca6-c609-93c8bab38e75\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:absl:The transformations API will eventually be replaced by an upgraded design. The current API will not be removed until this point, but it will no longer be actively worked on.\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'model': Array([[ 0,  1],\\n\",\n       \"        [ 2,  3],\\n\",\n       \"        [ 4,  5],\\n\",\n       \"        [ 6,  7],\\n\",\n       \"        [ 8,  9],\\n\",\n       \"        [10, 11],\\n\",\n       \"        [12, 13],\\n\",\n       \"        [14, 15]], dtype=int32)}\"\n      ]\n     },\n     \"execution_count\": 25,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"mp_restored = checkpoints.restore_checkpoint(ckpt_dir,\\n\",\n    \"                                             target=ref_ckpt,\\n\",\n    \"                                             step=3,\\n\",\n    \"                                             orbax_checkpointer=async_checkpointer)\\n\",\n    \"mp_restored\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"65cfdd59\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Orbax-as-backend troubleshooting\\n\",\n    \"\\n\",\n    \"As an intermediate stage of the migration (to Orbax from the legacy Flax `checkpoints` API), `flax.training.checkpoints` APIs will start to use Orbax as their backend when saving checkpoints starting from May 15, 2023.\\n\",\n    \"\\n\",\n    \"Checkpoints saved with the Orbax backend can be readable by either `flax.training.checkpoints.restore_checkpoint` or `orbax.checkpoint.PyTreeCheckpointer`.\\n\",\n    \"\\n\",\n    \"Code-wise, this is equivalent to setting the config flag [`flax.config.flax_use_orbax_checkpointing`](https://github.com/google/flax/blob/main/flax/configurations.py#L103) default to `True`. You can overwrite this value in your project with `flax.config.update('flax_use_orbax_checkpointing', <BoolValue>)` at any time.\\n\",\n    \"\\n\",\n    \"In general, this automatic migration will not affect most users. However, you may encounter issues if your API usage follows some specific pattern. Check out the sections below for troubleshooting.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"415bceb1\",\n   \"metadata\": {},\n   \"source\": [\n    \"### If your devices hang when writing checkpoints\\n\",\n    \"\\n\",\n    \"If you are running in a multi-host environment (usually anything larger than 8 TPU devices) and your devices hang when writing checkpoints, check if your code is in the following pattern (that is, the `save_checkpoint` only ran on host `0`):\\n\",\n    \"\\n\",\n    \"```\\n\",\n    \"if jax.process_index() == 0:\\n\",\n    \"  flax.training.checkpoints.save_checkpoint(...)\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"Unfortunately this is a legacy pattern that will be deprecated and won't be supported, because in a multi-process environment, the checkpointing code should coordinate among hosts instead of being triggered only on the host `0`. Replacing the code above with the following should resolve the hang issue:\\n\",\n    \"\\n\",\n    \"```\\n\",\n    \"flax.training.checkpoints.save_checkpoint_multiprocess(...)\\n\",\n    \"```\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"70e0ebb3\",\n   \"metadata\": {},\n   \"source\": [\n    \"### If you don't save pytrees\\n\",\n    \"\\n\",\n    \"Orbax uses `orbax.checkpoint.PyTreeCheckpointHandler` to save checkpoints, which means they only save pytrees.\\n\",\n    \"\\n\",\n    \"If you want to save singular arrays or numbers, you have two options:\\n\",\n    \"\\n\",\n    \"1. Use `orbax.ArrayCheckpointHandler` to save them following [this migration section](https://flax.readthedocs.io/en/latest/guides/converting_and_upgrading/orbax_upgrade_guide.html#saving-loading-a-single-jax-or-numpy-array).\\n\",\n    \"\\n\",\n    \"1. Wrap it inside a pytree and save as usual.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"gpuClass\": \"standard\",\n  \"jupytext\": {\n   \"formats\": \"ipynb,md\",\n   \"main_language\": \"python\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.10.11\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs/guides/training_techniques/use_checkpointing.md",
    "content": "---\njupyter:\n  jupytext:\n    formats: ipynb,md\n    main_language: python\n    text_representation:\n      extension: .md\n      format_name: markdown\n      format_version: '1.3'\n      jupytext_version: 1.13.8\n---\n\n# Save and load checkpoints\n\nThis guide demonstrates how to save and load Flax checkpoints with [Orbax](https://github.com/google/orbax).\n\nOrbax provides a variety of features for saving and loading model data, which you will learn about in this doc:\n\n*  Support for various array types and storage formats\n*  Asynchronous saving to reduce training wait time\n*  Versioning and automatic bookkeeping of past checkpoints\n*  Flexible [`transformations`](https://orbax.readthedocs.io/en/latest/transformations.html) to tweak and load old checkpoints\n*  [`jax.sharding`](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html)-based API to save and load in multi-host scenarios\n\n---\n**_Ongoing migration to Orbax:_**\n\nAfter July 30 2023, Flax's legacy `flax.training.checkpoints` API will be deprecated in favor of [Orbax](https://github.com/google/orbax).\n\n*  **If you are a new Flax user**: Use the new `orbax.checkpoint` API, as demonstrated in this guide.\n\n*  **If you have legacy `flax.training.checkpoints` code in your project**: Consider the following options:\n\n   * **Migrating your code to Orbax (Recommended)**: Migrate your API calls to `orbax.checkpoint` API by following this [migration guide](https://flax.readthedocs.io/en/latest/guides/converting_and_upgrading/orbax_upgrade_guide.html).\n\n   * **Automatically use the Orbax backend**: Add `flax.config.update('flax_use_orbax_checkpointing', True)` to your project, which will let your `flax.training.checkpoints` calls automatically use the Orbax backend to save your checkpoints.\n\n     * **Scheduled flip**: This will become the default mode after **May 2023** (tentative date).\n\n     * Visit [Orbax-as-backend troubleshooting section](https://flax.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html#orbax-as-backend-troubleshooting) if you meet any issue in the automatic migration.\n---\n\nFor backward-compatibility, this guide shows the Orbax-equivalent calls in the Flax legacy `flax.training.checkpoints` API.\n\nIf you need to learn more about `orbax.checkpoint`, refer to the [Orbax docs](https://orbax.readthedocs.io/en/latest/).\n\n\n\n## Setup\n\nInstall/upgrade Flax and [Orbax](https://github.com/google/orbax). For JAX installation with GPU/TPU support, visit [this section on GitHub](https://github.com/jax-ml/jax#installation).\n\n\nNote: Before running `import jax`, create eight fake devices to mimic a [multi-host environment](https://jax.readthedocs.io/en/latest/jax-101/06-parallelism.html?#aside-hosts-and-devices-in-jax) in this notebook. Note that the order of imports is important here. The `os.environ[\"XLA_FLAGS\"] = '--xla_force_host_platform_device_count=8'` command works only with the CPU backend, which means it won't work with GPU/TPU acceleration on if you're running this notebook in Google Colab. If you are already running the code on multiple devices (for example, in a 4x2 TPU environment), you can skip running the next cell.\n\n```python\nimport os\nos.environ[\"XLA_FLAGS\"] = '--xla_force_host_platform_device_count=8'\n```\n\n```python\nfrom typing import Optional, Any\nimport shutil\n\nimport numpy as np\nimport jax\nfrom jax import random, numpy as jnp\n\nimport flax\nfrom flax import linen as nn\nfrom flax.training import checkpoints, train_state\nfrom flax import struct, serialization\nimport orbax.checkpoint\n\nimport optax\n```\n\n```python\nckpt_dir = '/tmp/flax_ckpt'\n\nif os.path.exists(ckpt_dir):\n    shutil.rmtree(ckpt_dir)  # Remove any existing checkpoints from the last notebook run.\n```\n\n## Save checkpoints\n\nIn Orbax and Flax, you can save and load any given JAX [pytree](https://jax.readthedocs.io/en/latest/pytrees.html). This includes not only typical Python and NumPy containers, but also customized classes extended from [`flax.struct.dataclass`](https://flax.readthedocs.io/en/latest/api_reference/flax.struct.html#flax.struct.dataclass). That means you can store almost any data generated — not only your model parameters, but any arrays/dictionaries, metadata/configs, and so on.\n\nFirst, create a pytree with many data structures and containers, and play with it:\n\n```python outputId=\"f1856d96-1961-48ed-bb7c-cb63fbaa7567\"\n# A simple model with one linear layer.\nkey1, key2 = random.split(random.key(0))\nx1 = random.normal(key1, (5,))      # A simple JAX array.\nmodel = nn.Dense(features=3)\nvariables = model.init(key2, x1)\n\n# Flax's TrainState is a pytree dataclass and is supported in checkpointing.\n# Define your class with `@flax.struct.dataclass` decorator to make it compatible.\ntx = optax.sgd(learning_rate=0.001)      # An Optax SGD optimizer.\nstate = train_state.TrainState.create(\n    apply_fn=model.apply,\n    params=variables['params'],\n    tx=tx)\n# Perform a simple gradient update similar to the one during a normal training workflow.\nstate = state.apply_gradients(grads=jax.tree_util.tree_map(jnp.ones_like, state.params))\n\n# Some arbitrary nested pytree with a dictionary and a NumPy array.\nconfig = {'dimensions': np.array([5, 3])}\n\n# Bundle everything together.\nckpt = {'model': state, 'config': config, 'data': [x1]}\nckpt\n```\n\n### With Orbax\n\n\nSave the checkpoint with `orbax.checkpoint.PyTreeCheckpointer`, directly to the `tmp/orbax/single_save` directory.\n\nNote: An optional `save_args` is provided. This is recommended for performance speedups, as it bundles smaller arrays in your pytree to a single large file instead of multiple smaller files.\n\n```python\nfrom flax.training import orbax_utils\n\norbax_checkpointer = orbax.checkpoint.PyTreeCheckpointer()\nsave_args = orbax_utils.save_args_from_target(ckpt)\norbax_checkpointer.save('/tmp/flax_ckpt/orbax/single_save', ckpt, save_args=save_args)\n```\n\nNext, to use versioning and automatic bookkeeping features, you need to wrap `orbax.checkpoint.CheckpointManager` over `orbax.checkpoint.PyTreeCheckpointer`.\n\nIn addition, provide `orbax.checkpoint.CheckpointManagerOptions` that customizes your needs, such as how often and on what criteria you prefer old checkpoints be deleted. See [documentation](https://orbax.readthedocs.io/en/latest/guides/checkpoint/api_refactor.html) for a full list of options offered.\n\n`orbax.checkpoint.CheckpointManager` should be placed at the top-level outside your training steps to manage your saves.\n\n```python outputId=\"b7132933-566d-440d-c34e-c5468d87cbdc\"\noptions = orbax.checkpoint.CheckpointManagerOptions(max_to_keep=2, create=True)\ncheckpoint_manager = orbax.checkpoint.CheckpointManager(\n    '/tmp/flax_ckpt/orbax/managed', orbax_checkpointer, options)\n\n# Inside a training loop\nfor step in range(5):\n    # ... do your training\n    checkpoint_manager.save(step, ckpt, save_kwargs={'save_args': save_args})\n\nos.listdir('/tmp/flax_ckpt/orbax/managed')  # Because max_to_keep=2, only step 3 and 4 are retained\n```\n\n### With the legacy API\n\nAnd here's how to save with the legacy Flax checkpointing utilities (note that this provides less management features compared with `orbax.checkpoint.CheckpointManagerOptions`):\n\n```python outputId=\"6d849273-15ce-4480-8864-726d1838ac1f\"\n# Import Flax Checkpoints.\nfrom flax.training import checkpoints\n\ncheckpoints.save_checkpoint(ckpt_dir='/tmp/flax_ckpt/flax-checkpointing',\n                            target=ckpt,\n                            step=0,\n                            overwrite=True,\n                            keep=2)\n```\n\n## Restore checkpoints\n\n### With Orbax\n\nIn Orbax, call `.restore()` for either `orbax.checkpoint.PyTreeCheckpointer` or `orbax.checkpoint.CheckpointManager` to restore your checkpoint in the raw pytree format.\n\n```python outputId=\"b4af1ef4-f22f-459b-bdca-2e6bfa16c08b\"\nraw_restored = orbax_checkpointer.restore('/tmp/flax_ckpt/orbax/single_save')\nraw_restored\n```\n\nNote that the `step` number is required for `CheckpointManger`. You can also use `.latest_step()` to find the latest step available.\n\n```python\nstep = checkpoint_manager.latest_step()  # step = 4\ncheckpoint_manager.restore(step)\n```\n\n### With the legacy API\n\nNote that with the migration to Orbax in progress, `flax.training.checkpointing.restore_checkpoint` can automatically identify whether a checkpoint is saved in the legacy Flax format or with an Orbax backend, and restore the pytree correctly. Therefore, adding `flax.config.update('flax_use_orbax_checkpointing', True)` won't hurt your ability to restore old checkpoints.\n\nHere's how to restore checkpoints using the legacy API:\n\n```python outputId=\"85ffceca-f38d-46b8-e567-d9d38b7885f9\"\nraw_restored = checkpoints.restore_checkpoint(ckpt_dir='/tmp/flax_ckpt/flax-checkpointing', target=None)\nraw_restored\n```\n\n## Restore with custom dataclasses\n\n### With Orbax\n\n*  The pytrees restored in the previous examples are in the form of raw dictionaries. Original pytrees contain custom dataclasses like [`TrainState`](https://flax.readthedocs.io/en/latest/flip/1009-optimizer-api.html?#train-state) and `optax` states.\n*  This is because when restoring a pytree, the program does not yet know which structure it once belonged to.\n*  To resolve this, you should first provide an example pytree to let Orbax or Flax know exactly which structure to restore to.\n\nThis section demonstrates how to set up any custom Flax dataclass explicitly, and have the same structure as a saved checkpoint.\n\nNote: Data that was a JAX NumPy array (`jnp.array`) format will be restored as a NumPy array (`numpy.array`). This would not affect your work because JAX will [automatically convert](https://jax.readthedocs.io/en/latest/jax-101/01-jax-basics.html) NumPy arrays to JAX arrays once the computation starts.\n\n```python outputId=\"110c6b6e-fe42-4179-e5d8-6b92d355e11b\"\nempty_state = train_state.TrainState.create(\n    apply_fn=model.apply,\n    params=jax.tree_util.tree_map(np.zeros_like, variables['params']),  # values of the tree leaf doesn't matter\n    tx=tx,\n)\nempty_config = {'dimensions': np.array([0, 0])}\ntarget = {'model': empty_state, 'config': empty_config, 'data': [jnp.zeros_like(x1)]}\nstate_restored = orbax_checkpointer.restore('/tmp/flax_ckpt/orbax/single_save', item=target)\nstate_restored\n```\n\n### With the legacy API\n\nAlternatively, you can restore from Orbax `CheckpointManager` and from the legacy Flax code as follows:\n\n```python\ncheckpoint_manager.restore(4, items=target)\n```\n\n```python\ncheckpoints.restore_checkpoint(ckpt_dir='/tmp/flax_ckpt/flax-checkpointing', target=target)\n```\n\nIt's often recommended to refactor out the process of initializing a checkpoint's structure (for example, a [`TrainState`](https://flax.readthedocs.io/en/latest/flip/1009-optimizer-api.html?#train-state)), so that saving/loading is easier and less error-prone. This is because functions and complex objects like `apply_fn` and `tx` (optimizer) cannot be serialized into the checkpoint file and must be initialized by code.\n\n\n## Restore when checkpoint structures differ\n\nDuring your development, your checkpoint structure will change when changing the model, adding/removing fields during tweaking, and so on.\n\nThis section explains how to load old data to your new code.\n\nBelow is  a simple example — a `CustomTrainState` extended from `flax.training.train_state.TrainState` that contains an extra field called `batch_stats`. When working on a real-world model, you may need this when applying [batch normalization](https://flax.readthedocs.io/en/latest/guides/training_techniques/batch_norm.html).\n\nHere, you store the new `CustomTrainState` as step 5, while step 4 contains the old/previous `TrainState`.\n\n```python outputId=\"4fe776f0-65f8-4fc4-d64a-990520b36dce\"\nclass CustomTrainState(train_state.TrainState):\n    batch_stats: Any = None\n\ncustom_state = CustomTrainState.create(\n    apply_fn=state.apply_fn,\n    params=state.params,\n    tx=state.tx,\n    batch_stats=np.arange(10),\n)\n\ncustom_ckpt = {'model': custom_state, 'config': config, 'data': [x1]}\n# Use a custom state to read the old `TrainState` checkpoint.\ncustom_target = {'model': custom_state, 'config': None, 'data': [jnp.zeros_like(x1)]}\n\n# Save it in Orbax.\ncustom_save_args = orbax_utils.save_args_from_target(custom_ckpt)\ncheckpoint_manager.save(5, custom_ckpt, save_kwargs={'save_args': custom_save_args})\n```\n\nIt is recommended to keep your checkpoints up-to-date with your pytree dataclass definitions. However, you might be forced to restore the checkpoints with incompatible reference objects at runtime. When this happens, the checkpoint restoration will try to respect the structure of the reference when given.\n\nBelow are examples of a few common scenarios.\n\n\n### Scenario 1: When a reference object is partial\n\nIf your reference object is a subtree of your checkpoint, the restoration will ignore the additional field(s) and restore a checkpoint with the same structure as the reference.\n\nLike in the example below, the `batch_stats` field in `CustomTrainState` was ignored, and the checkpoint was restored as a `TrainState`.\n\nThis can also be useful for reading only part of your checkpoint.\n\n```python\nrestored = checkpoint_manager.restore(5, items=target)\nassert not hasattr(restored, 'batch_stats')\nassert type(restored['model']) == train_state.TrainState\nrestored\n```\n\n### Scenario 2: When a checkpoint is partial\n\nOn the other hand, if the reference object contains a value that is not available in the checkpoint, the checkpointing code will by default warn that some data is not compatible.\n\nTo bypass the error, you need to pass an Orbax [`transform`](https://orbax.readthedocs.io/en/latest/guides/checkpoint/transformations.html) that teaches Orbax how to conform this checkpoint into the structure of the `custom_target`.\n\nIn this case, pass a default `{}` that lets Orbax use values in the `custom_target` to fill in the blank. This allows you to restore an old checkpoint into a new data structure, the `CustomTrainState`.\n\n```python\ntry:\n    checkpoint_manager.restore(4, items=custom_target)\nexcept KeyError as e:\n    print(f'KeyError when target state has an unmentioned field: {e}')\n    print('')\n\n# Step 4 is an original `TrainState`, without the `batch_stats`\ncustom_restore_args = orbax_utils.restore_args_from_target(custom_target)\nrestored = checkpoint_manager.restore(4, items=custom_target,\n                                      restore_kwargs={'transforms': {}, 'restore_args': custom_restore_args})\nassert type(restored['model']) == CustomTrainState\nnp.testing.assert_equal(restored['model'].batch_stats,\n                        custom_target['model'].batch_stats)\nrestored\n```\n\n##### With Orbax\n\nIf you have already saved your checkpoints with the Orbax backend, you can use `orbax_transforms` to access this `transforms` argument in the Flax API.\n\n```python outputId=\"cdbb9247-d1eb-4458-aa83-8db0332af7cb\"\n# Save in the \"Flax-with-Orbax\" backend.\nflax.config.update('flax_use_orbax_checkpointing', True)\ncheckpoints.save_checkpoint(ckpt_dir='/tmp/flax_ckpt/flax-checkpointing',\n                            target=ckpt,\n                            step=4,\n                            overwrite=True,\n                            keep=2)\n\ncheckpoints.restore_checkpoint('/tmp/flax_ckpt/flax-checkpointing', target=custom_target, step=4,\n                               orbax_transforms={})\n```\n\n##### With the legacy API\n\nUsing the legacy `flax.training.checkpoints` API, similar things are doable too, but they are not as flexible as the [Orbax Transformations](https://orbax.readthedocs.io/en/latest/guides/checkpoint/transformations.html).\n\nYou need to restore the checkpoint to a raw dict with `target=None`, modify the structure accordingly, and then deserialize it back to the original target.\n\n```python\n# Save using the legacy Flax `checkpoints` API.\nflax.config.update('flax_use_orbax_checkpointing', False)\ncheckpoints.save_checkpoint(ckpt_dir='/tmp/flax_ckpt/flax-checkpointing',\n                            target=ckpt,\n                            step=5,\n                            overwrite=True,\n                            keep=2)\n\n# Pass no target to get a raw state dictionary first.\nraw_state_dict = checkpoints.restore_checkpoint('/tmp/flax_ckpt/flax-checkpointing', target=None, step=5)\n# Add/remove fields as needed.\nraw_state_dict['model']['batch_stats'] = np.flip(np.arange(10))\n# Restore the classes with correct target now\nflax.serialization.from_state_dict(custom_target, raw_state_dict)\n```\n\n## Asynchronized checkpointing\n\nCheckpointing is I/O heavy, and if you have a large amount of data to save, it may be worthwhile to put it into a background thread, while continuing with your training.\n\nYou can do this by creating an [`orbax.checkpoint.AsyncCheckpointer`](https://github.com/google/orbax/blob/main/checkpoint/orbax/checkpoint/async_checkpointer.py) in place of the `orbax.checkpoint.PyTreeCheckpointer`.\n\nNote: You should use the same `async_checkpointer` to handle all your async saves across your training steps, so that it can make sure that a previous async save is done before the next one begins. This enables bookkeeping, such as `keep` (the number of checkpoints) and `overwrite` to be consistent across steps.\n\nWhenever you want to explicitly wait until an async save is done, you can call `async_checkpointer.wait_until_finished()`.\n\n```python outputId=\"aefce94c-8bae-4355-c142-05f2b61c39e2\"\n# `orbax.checkpoint.AsyncCheckpointer` needs some multi-process initialization, because it was\n# originally designed for multi-process large model checkpointing.\n# For Python notebooks or other single-process settings, just set up with `num_processes=1`.\n# Refer to https://jax.readthedocs.io/en/latest/multi_process.html#initializing-the-cluster\n# for how to set it up in multi-process scenarios.\njax.distributed.initialize(\"localhost:8889\", num_processes=1, process_id=0)\n\nasync_checkpointer = orbax.checkpoint.AsyncCheckpointer(\n    orbax.checkpoint.PyTreeCheckpointHandler(), timeout_secs=50)\n\n# Save your job:\nasync_checkpointer.save('/tmp/flax_ckpt/orbax/single_save_async', ckpt, save_args=save_args)\n# ... Continue with your work...\n\n# ... Until a time when you want to wait until the save completes:\nasync_checkpointer.wait_until_finished()  # Blocks until the checkpoint saving is completed.\nasync_checkpointer.restore('/tmp/flax_ckpt/orbax/single_save_async', item=target)\n```\n\nIf you are using Orbax `CheckpointManager`, just pass in the async_checkpointer when initializing it. Then, in practice, call `async_checkpoint_manager.wait_until_finished()` instead.\n\n```python\nasync_checkpoint_manager = orbax.checkpoint.CheckpointManager(\n    '/tmp/flax_ckpt/orbax/managed_async', async_checkpointer, options)\nasync_checkpoint_manager.wait_until_finished()\n```\n\n## Multi-host/multi-process checkpointing\n\nJAX provides a few ways to scale up your code on multiple hosts at the same time. This usually happens when the number of devices (CPU/GPU/TPU) is so large that different devices are managed by different hosts (CPU). To get started on JAX in multi-process settings, check out [Using JAX in multi-host and multi-process environments](https://jax.readthedocs.io/en/latest/multi_process.html) and the [distributed array guide](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html).\n\nIn the [Single Program Multi Data (SPMD)](https://jax.readthedocs.io/en/latest/glossary.html#term-SPMD) paradigm with JAX `jit`, a large multi-process array can have its data sharded across different devices. (Note that JAX `pjit` and `jit` have been merged into a single unified interface. To learn about compiling and executing JAX functions in multi-host or multi-core environments, refer to [this guide](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html) and the [jax.Array migration guide](https://jax.readthedocs.io/en/latest/jax_array_migration.html).) When a multi-process array is serialized, each host dumps its data shards to a single shared storage, such as a Google Cloud bucket.\n\nOrbax supports saving and loading pytrees with multi-process arrays in the same fashion as single-process pytrees. However, it's recommended to use the asynchronized [`orbax.AsyncCheckpointer`](https://github.com/google/orbax/blob/main/checkpoint/orbax/checkpoint/async_checkpointer.py) to save large multi-process arrays on another thread, so that you can perform computation alongside the saves. With pure Orbax, saving checkpoints in a multi-process context uses the same API as in a single-process context.\n\n```python\nfrom jax.sharding import PartitionSpec, NamedSharding\n\n# Create an array sharded across multiple devices.\nmesh_shape = (4, 2)\ndevices = np.asarray(jax.devices()).reshape(*mesh_shape)\nmesh = jax.sharding.Mesh(devices, ('x', 'y'))\n\nmp_array = jax.device_put(np.arange(8 * 2).reshape(8, 2),\n                          NamedSharding(mesh, PartitionSpec('x', 'y')))\n\n# Make it a pytree.\nmp_ckpt = {'model': mp_array}\n```\n\n```python\nasync_checkpoint_manager.save(0, mp_ckpt)\nasync_checkpoint_manager.wait_until_finished()\n```\n\nWhen restoring a checkpoint with multi-process arrays, you need to specify what `sharding` each array should be restored back to. Otherwise, they will be restored as large `np.array`s on process 0, costing time and memory.\n\n(In this notebook, since we are on single-process, it will be restored as `np.array` even if we provide shardings.)\n\n### With Orbax\n\nOrbax allows you to specify this by passing a pytree of `sharding`s in `restore_args`. If you already have a reference pytree that has all the arrays with the right sharding, you can use `orbax_utils.restore_args_from_target` to transform it into the `restore_args` that Orbax needs.\n\n```python\n# The reference doesn't need to be as large as your checkpoint!\n# Just make sure it has the `.sharding` you want.\nmp_smaller = jax.device_put(np.arange(8).reshape(4, 2),\n                            NamedSharding(mesh, PartitionSpec('x', 'y')))\nref_ckpt = {'model': mp_smaller}\n\nrestore_args = orbax_utils.restore_args_from_target(ref_ckpt)\nasync_checkpoint_manager.restore(\n    0, items=ref_ckpt, restore_kwargs={'restore_args': restore_args})\n```\n\n### With the legacy Flax: use `save_checkpoint_multiprocess`\n\nIn legacy Flax, to save multi-process arrays, use [`flax.training.checkpoints.save_checkpoint_multiprocess()`](https://flax.readthedocs.io/en/latest/api_reference/flax.training.html#flax.training.checkpoints.save_checkpoint_multiprocess) in place of `save_checkpoint()` and with the same arguments.\n\nIf your checkpoint is too large, you can specify `timeout_secs` in the manager and give it more time to finish writing.\n\n```python outputId=\"901bb097-0899-479d-b9ae-61dae79e7057\"\nasync_checkpointer = orbax.checkpoint.AsyncCheckpointer(orbax.checkpoint.PyTreeCheckpointHandler(), timeout_secs=50)\ncheckpoints.save_checkpoint_multiprocess(ckpt_dir,\n                                         mp_ckpt,\n                                         step=3,\n                                         overwrite=True,\n                                         keep=4,\n                                         orbax_checkpointer=async_checkpointer)\n```\n\n```python outputId=\"393c4a0e-8a8c-4ca6-c609-93c8bab38e75\"\nmp_restored = checkpoints.restore_checkpoint(ckpt_dir,\n                                             target=ref_ckpt,\n                                             step=3,\n                                             orbax_checkpointer=async_checkpointer)\nmp_restored\n```\n\n## Orbax-as-backend troubleshooting\n\nAs an intermediate stage of the migration (to Orbax from the legacy Flax `checkpoints` API), `flax.training.checkpoints` APIs will start to use Orbax as their backend when saving checkpoints starting from May 15, 2023.\n\nCheckpoints saved with the Orbax backend can be readable by either `flax.training.checkpoints.restore_checkpoint` or `orbax.checkpoint.PyTreeCheckpointer`.\n\nCode-wise, this is equivalent to setting the config flag [`flax.config.flax_use_orbax_checkpointing`](https://github.com/google/flax/blob/main/flax/configurations.py#L103) default to `True`. You can overwrite this value in your project with `flax.config.update('flax_use_orbax_checkpointing', <BoolValue>)` at any time.\n\nIn general, this automatic migration will not affect most users. However, you may encounter issues if your API usage follows some specific pattern. Check out the sections below for troubleshooting.\n\n\n### If your devices hang when writing checkpoints\n\nIf you are running in a multi-host environment (usually anything larger than 8 TPU devices) and your devices hang when writing checkpoints, check if your code is in the following pattern (that is, the `save_checkpoint` only ran on host `0`):\n\n```\nif jax.process_index() == 0:\n  flax.training.checkpoints.save_checkpoint(...)\n```\n\nUnfortunately this is a legacy pattern that will be deprecated and won't be supported, because in a multi-process environment, the checkpointing code should coordinate among hosts instead of being triggered only on the host `0`. Replacing the code above with the following should resolve the hang issue:\n\n```\nflax.training.checkpoints.save_checkpoint_multiprocess(...)\n```\n\n\n### If you don't save pytrees\n\nOrbax uses `orbax.checkpoint.PyTreeCheckpointHandler` to save checkpoints, which means they only save pytrees.\n\nIf you want to save singular arrays or numbers, you have two options:\n\n1. Use `orbax.ArrayCheckpointHandler` to save them following [this migration section](https://flax.readthedocs.io/en/latest/guides/converting_and_upgrading/orbax_upgrade_guide.html#saving-loading-a-single-jax-or-numpy-array).\n\n1. Wrap it inside a pytree and save as usual.\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. Flax documentation main file, created by\n   sphinx-quickstart on Mon Feb 17 11:41:38 2020.\n   You can adapt this file completely to your liking, but it should at least\n   contain the root `toctree` directive.\n\n******************************\nFlax Linen\n******************************\n\n\n.. div:: sd-text-left sd-font-italic\n\n   Neural networks with JAX\n\n\n----\n\nFlax Linen delivers an **end-to-end and flexible user experience for researchers\nwho use JAX with neural networks**. Flax\nexposes the full power of `JAX <https://jax.readthedocs.io>`__. It is made up of\nloosely coupled libraries, which are showcased with end-to-end integrated\n`guides <https://flax.readthedocs.io/en/latest/guides/index.html>`__\nand `examples <https://flax.readthedocs.io/en/latest/examples.html>`__.\n\nFlax Linen is used by\n`hundreds of projects (and growing) <https://github.com/google/flax/network/dependents?package_id=UGFja2FnZS01MjEyMjA2MA%3D%3D>`__,\nboth in the open source community\n(like `Hugging Face <https://huggingface.co/flax-community>`__)\nand at Google\n(like\n`Gemini <https://deepmind.google/technologies/gemini>`__,\n`Imagen <https://imagen.research.google>`__,\n`Scenic <https://github.com/google-research/scenic/>`__,\nand `Big Vision <https://github.com/google-research/big_vision>`__).\n\n\nFeatures\n^^^^^^^^^\n\n.. grid::\n\n   .. grid-item::\n      :columns: 12 12 12 6\n\n      .. card:: Safety\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-fs-5\n\n         .. div:: sd-font-normal\n\n            Flax is designed for correctness and safety. Thanks to its immutable Modules\n            and Functional API, Flax helps mitigate bugs that arise when handling state\n            in JAX.\n\n   .. grid-item::\n      :columns: 12 12 12 6\n\n      .. card:: Control\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-fs-5\n\n         .. div:: sd-font-normal\n\n            Flax grants more fine-grained control and expressivity than most Neural Network\n            frameworks via its Variable Collections, RNG Collections and Mutability conditions.\n\n   .. grid-item::\n      :columns: 12 12 12 6\n\n      .. card:: Functional API\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-fs-5\n\n         .. div:: sd-font-normal\n\n            Flax's functional API radically redefines what Modules can do via lifted transformations like vmap, scan, etc, while also enabling seamless integration with other JAX libraries like Optax and Chex.\n\n   .. grid-item::\n      :columns: 12 12 12 6\n\n      .. card:: Terse code\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-fs-5\n\n         .. div:: sd-font-normal\n\n            Flax's :meth:`compact <flax.linen.compact>` Modules enables submodules to be defined directly at their callsite, leading to code that is easier to read and avoids repetition.\n\n\n----\n\nInstallation\n^^^^^^^^^^^^\n\n.. code-block:: bash\n\n   pip install flax\n   # or to install the latest version of Flax:\n   pip install --upgrade git+https://github.com/google/flax.git\n\nFlax installs the vanilla CPU version of JAX, if you need a custom version please check out `JAX's installation page <https://github.com/jax-ml/jax#installation>`__.\n\nBasic usage\n^^^^^^^^^^^^\n\n.. testsetup::\n\n   import jax\n   from jax import random\n   import flax.linen as nn\n   import jax.numpy as jnp\n\n.. testcode::\n\n   class MLP(nn.Module):                    # create a Flax Module dataclass\n     out_dims: int\n\n     @nn.compact\n     def __call__(self, x):\n       x = x.reshape((x.shape[0], -1))\n       x = nn.Dense(128)(x)                 # create inline Flax Module submodules\n       x = nn.relu(x)\n       x = nn.Dense(self.out_dims)(x)       # shape inference\n       return x\n\n   model = MLP(out_dims=10)                 # instantiate the MLP model\n\n   x = jnp.empty((4, 28, 28, 1))            # generate random data\n   variables = model.init(random.key(42), x)# initialize the weights\n   y = model.apply(variables, x)            # make forward pass\n\n----\n\nLearn more\n^^^^^^^^^^\n\n.. grid::\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`rocket_launch;2em` Quickstart\n         :class-card: sd-text-black sd-bg-light\n         :link: quick_start.html\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`library_books;2em` Guides\n         :class-card: sd-text-black sd-bg-light\n         :link: guides/index.html\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`science;2em` Examples\n         :class-card: sd-text-black sd-bg-light\n         :link: examples.html\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`import_contacts;2em` Glossary\n         :class-card: sd-text-black sd-bg-light\n         :link: glossary.html\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`settings;2em` Developer notes\n         :class-card: sd-text-black sd-bg-light\n         :link: developer_notes/index.html\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`history_edu;2em` The Flax philosophy\n         :class-card: sd-text-black sd-bg-light\n         :link: https://flax.readthedocs.io/en/latest/philosophy.html\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`menu_book;2em` API reference\n         :class-card: sd-text-black sd-bg-light\n         :link: api_reference/index.html\n\n----\n\nEcosystem\n^^^^^^^^^\n\nNotable examples in Flax include:\n\n\n.. grid::\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: `🤗 Hugging Face <https://huggingface.co/flax-community>`__\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-text-center sd-fs-5\n\n         .. div:: sd-text-center sd-font-italic\n\n            NLP and computer vision models\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: `🥑 DALLE Mini <https://huggingface.co/dalle-mini>`__\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-text-center sd-fs-5\n\n         .. div:: sd-text-center sd-font-italic\n\n            Model for text-to-image generation\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: `PaLM <https://ai.googleblog.com/2022/04/pathways-language-model-palm-scaling-to.html>`__\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-text-center sd-fs-5\n\n         .. div:: sd-text-center sd-font-italic\n\n            540-billion parameter model for text generation\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: `Imagen <https://imagen.research.google>`__\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-text-center sd-fs-5\n\n         .. div:: sd-text-center sd-font-italic\n\n            Text-to-image diffusion models\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: `Scenic <https://github.com/google-research/scenic/>`__\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-text-center sd-fs-5\n\n         .. div:: sd-text-center sd-font-italic\n\n            Libraries for large-scale computer vision\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: `Big Vision <https://github.com/google-research/big_vision>`__\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-text-center sd-fs-5\n\n         .. div:: sd-text-center sd-font-italic\n\n            Large-scale computer vision models\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: `MaxText <https://github.com/google/maxtext>`__\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-text-center sd-fs-5\n\n         .. div:: sd-text-center sd-font-italic\n\n            Open source high performance LLM\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: `T5x <https://github.com/google-research/t5x>`__\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-text-center sd-fs-5\n\n         .. div:: sd-text-center sd-font-italic\n\n            Large language models\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: `Brax <https://github.com/google/brax>`__\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-text-center sd-fs-5\n\n         .. div:: sd-text-center sd-font-italic\n\n            On-device differentiable reinforcement learning environments\n\n\n\n.. role:: bold\n  :class: bold\n\n.. toctree::\n   :hidden:\n   :maxdepth: 2\n\n   Quick start <quick_start>\n   guides/flax_fundamentals/flax_basics\n   guides/index\n   examples/index\n   glossary\n   faq\n   developer_notes/index\n   The Flax philosophy <https://flax.readthedocs.io/en/latest/philosophy.html>\n   How to contribute <https://flax.readthedocs.io/en/latest/contributing.html>\n   api_reference/index\n   Flax NNX <https://flax.readthedocs.io/en/latest/index.html>\n"
  },
  {
    "path": "docs/linen_intro.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google/flax/blob/main/docs/notebooks/linen_intro.ipynb)\\n\",\n    \"[![Open On GitHub](https://img.shields.io/badge/Open-on%20GitHub-blue?logo=GitHub)](https://github.com/google/flax/blob/main/docs/notebooks/linen_intro.ipynb)\\n\",\n    \"\\n\",\n    \"# Preface\\n\",\n    \"\\n\",\n    \"<br>\\n\",\n    \"<div style=\\\"font-variant: small-caps;\\\">CAVEAT PROGRAMMER</div>\\n\",\n    \"\\n\",\n    \"The below is an alpha API preview and things might break.  The surface syntax of the features of the API are not fixed in stone, and we welcome feedback on any points.\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Useful links\\n\",\n    \"\\n\",\n    \"⟶ [Slides](https://docs.google.com/presentation/d/1ngKWUwsSqAwPRvATG8sAxMzu9ujv4N__cKsUofdNno0/edit?usp=sharing) for the core ideas of the new Functional Core and Linen\\n\",\n    \"\\n\",\n    \"⟶ \\\"Design tests\\\" guided our design process. Many are available for [functional core](https://github.com/google/flax/tree/main/examples/core_design_test) and some for the [proposed Module abstraction](https://github.com/google/flax/tree/main/examples/linen_design_test/)\\n\",\n    \"\\n\",\n    \"⟶ Ported examples: [ImageNet](https://github.com/google/flax/tree/main/examples/imagenet) and [WMT](https://github.com/google/flax/tree/main/examples/wmt) (to the proposed Module abstraction). TODO: Port to functional core.\\n\",\n    \"\\n\",\n    \"⟶ Our new [discussion forums](https://github.com/google/flax/discussions/)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Install and Import\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# Install the newest JAXlib version.\\n\",\n    \"!pip install --upgrade -q pip jax jaxlib\\n\",\n    \"# Install Flax at head:\\n\",\n    \"!pip install --upgrade -q git+https://github.com/google/flax.git\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import functools\\n\",\n    \"from typing import Any, Callable, Sequence, Optional\\n\",\n    \"import jax\\n\",\n    \"from jax import lax, random, numpy as jnp\\n\",\n    \"import flax\\n\",\n    \"from flax import linen as nn\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Invoking Modules\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's instantiate a `Dense` layer.\\n\",\n    \" - Modules are actually objects in this API, so we provide _constructor arguments_ when initializing the Module.  In this case, we only have to provide the output `features` dimension.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"model = nn.Dense(features=3)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We need to initialize the Module variables, these include the parameters of the Module as well as any other state variables.\\n\",\n    \"\\n\",\n    \"We call the `init` method on the instantiated Module.  If the Module `__call__` method has args `(self, *args, **kwargs)` then we call `init` with `(rngs, *args, **kwargs)` so in this case, just `(rng, input)`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"outputId\": \"3adfaeaf-977e-4e82-8adf-d254fae6eb91\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"FrozenDict({\\n\",\n       \"    params: {\\n\",\n       \"        kernel: DeviceArray([[ 0.6503669 ,  0.8678979 ,  0.46042678],\\n\",\n       \"                     [ 0.05673932,  0.9909285 , -0.63536596],\\n\",\n       \"                     [ 0.76134115, -0.3250529 , -0.6522163 ],\\n\",\n       \"                     [-0.8243032 ,  0.4150194 ,  0.19405058]], dtype=float32),\\n\",\n       \"        bias: DeviceArray([0., 0., 0.], dtype=float32),\\n\",\n       \"    },\\n\",\n       \"})\"\n      ]\n     },\n     \"execution_count\": 4,\n     \"metadata\": {\n      \"tags\": []\n     },\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# Make RNG Keys and a fake input.\\n\",\n    \"key1, key2 = random.split(random.key(0), 2)\\n\",\n    \"x = random.uniform(key1, (4,4))\\n\",\n    \"\\n\",\n    \"# provide key and fake input to get initialized variables\\n\",\n    \"init_variables = model.init(key2, x)\\n\",\n    \"\\n\",\n    \"init_variables\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We call the `apply` method on the instantiated Module.  If the Module `__call__` method has args `(self, *args, **kwargs)` then we call `apply` with `(variables, *args, rngs=<RNGS>, mutable=<MUTABLEKINDS>, **kwargs)` where\\n\",\n    \" - `<RNGS>` are the optional _call time_ RNGs for things like dropout. For simple Modules this is just a single key, but if your module has multiple __kinds__ of data, it's a dictionary of rng-keys per-kind, e.g. `{'params': key0, 'dropout': key1}` for a Module with dropout layers.\\n\",\n    \" - `<MUTABLEKINDS>` is an optional list of names of __kinds__ that are expected to be mutated during the call. e.g. `['batch_stats']` for a layer updating batchnorm statistics.\\n\",\n    \"\\n\",\n    \"So in this case, just `(variables, input)`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {\n    \"outputId\": \"e8c389a6-29f3-4f93-97ea-703e85a8b811\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"DeviceArray([[ 0.5035518 ,  1.8548559 , -0.4270196 ],\\n\",\n       \"             [ 0.0279097 ,  0.5589246 , -0.43061775],\\n\",\n       \"             [ 0.35471284,  1.5741    , -0.3286552 ],\\n\",\n       \"             [ 0.5264864 ,  1.2928858 ,  0.10089308]], dtype=float32)\"\n      ]\n     },\n     \"execution_count\": 5,\n     \"metadata\": {\n      \"tags\": []\n     },\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"y = model.apply(init_variables, x)\\n\",\n    \"y\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Additional points:\\n\",\n    \" - If you want to `init` or `apply` a Module using a method other than call, you need to provide the `method=` kwarg to `init` and `apply` to use it instead of the default `__call__`, e.g. `method='encode'`, `method='decode'` to apply the encode/decode methods of an autoencoder.\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Defining Basic Modules\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Composing submodules\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We support declaring modules in `setup()` that can still benefit from shape inference by using __Lazy Initialization__ that sets up variables the first time the Module is called.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {\n    \"outputId\": \"1a6c6a17-0b95-42c2-b5bf-b9ad80fd7758\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized parameter shapes:\\n\",\n      \" {'params': {'layers_0': {'bias': (3,), 'kernel': (4, 3)}, 'layers_1': {'bias': (4,), 'kernel': (3, 4)}, 'layers_2': {'bias': (5,), 'kernel': (4, 5)}}}\\n\",\n      \"output:\\n\",\n      \" [[ 4.2292815e-02 -4.3807115e-02  2.9323792e-02  6.5492536e-03\\n\",\n      \"  -1.7147182e-02]\\n\",\n      \" [ 1.2967804e-01 -1.4551792e-01  9.4432175e-02  1.2521386e-02\\n\",\n      \"  -4.5417294e-02]\\n\",\n      \" [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00\\n\",\n      \"   0.0000000e+00]\\n\",\n      \" [ 9.3024090e-04  2.7864411e-05  2.4478839e-04  8.1344356e-04\\n\",\n      \"  -1.0110775e-03]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class ExplicitMLP(nn.Module):\\n\",\n    \"  features: Sequence[int]\\n\",\n    \"\\n\",\n    \"  def setup(self):\\n\",\n    \"    # we automatically know what to do with lists, dicts of submodules\\n\",\n    \"    self.layers = [nn.Dense(feat) for feat in self.features]\\n\",\n    \"    # for single submodules, we would just write:\\n\",\n    \"    # self.layer1 = nn.Dense(feat1)\\n\",\n    \"\\n\",\n    \"  def __call__(self, inputs):\\n\",\n    \"    x = inputs\\n\",\n    \"    for i, lyr in enumerate(self.layers):\\n\",\n    \"      x = lyr(x)\\n\",\n    \"      if i != len(self.layers) - 1:\\n\",\n    \"        x = nn.relu(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"key1, key2 = random.split(random.key(0), 2)\\n\",\n    \"x = random.uniform(key1, (4,4))\\n\",\n    \"\\n\",\n    \"model = ExplicitMLP(features=[3,4,5])\\n\",\n    \"init_variables = model.init(key2, x)\\n\",\n    \"y = model.apply(init_variables, x)\\n\",\n    \"\\n\",\n    \"print('initialized parameter shapes:\\\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(init_variables)))\\n\",\n    \"print('output:\\\\n', y)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here we show the equivalent compact form of the MLP that declares the submodules inline using the `@compact` decorator.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {\n    \"outputId\": \"b3709789-e66e-4e20-f6b2-04022f8a62bb\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized parameter shapes:\\n\",\n      \" {'params': {'layers_0': {'bias': (3,), 'kernel': (4, 3)}, 'layers_1': {'bias': (4,), 'kernel': (3, 4)}, 'layers_2': {'bias': (5,), 'kernel': (4, 5)}}}\\n\",\n      \"output:\\n\",\n      \" [[ 4.2292815e-02 -4.3807115e-02  2.9323792e-02  6.5492536e-03\\n\",\n      \"  -1.7147182e-02]\\n\",\n      \" [ 1.2967804e-01 -1.4551792e-01  9.4432175e-02  1.2521386e-02\\n\",\n      \"  -4.5417294e-02]\\n\",\n      \" [ 0.0000000e+00  0.0000000e+00  0.0000000e+00  0.0000000e+00\\n\",\n      \"   0.0000000e+00]\\n\",\n      \" [ 9.3024090e-04  2.7864411e-05  2.4478839e-04  8.1344356e-04\\n\",\n      \"  -1.0110775e-03]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class SimpleMLP(nn.Module):\\n\",\n    \"  features: Sequence[int]\\n\",\n    \"\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, inputs):\\n\",\n    \"    x = inputs\\n\",\n    \"    for i, feat in enumerate(self.features):\\n\",\n    \"      x = nn.Dense(feat, name=f'layers_{i}')(x)\\n\",\n    \"      if i != len(self.features) - 1:\\n\",\n    \"        x = nn.relu(x)\\n\",\n    \"      # providing a name is optional though!\\n\",\n    \"      # the default autonames would be \\\"Dense_0\\\", \\\"Dense_1\\\", ...\\n\",\n    \"      # x = nn.Dense(feat)(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"key1, key2 = random.split(random.key(0), 2)\\n\",\n    \"x = random.uniform(key1, (4,4))\\n\",\n    \"\\n\",\n    \"model = SimpleMLP(features=[3,4,5])\\n\",\n    \"init_variables = model.init(key2, x)\\n\",\n    \"y = model.apply(init_variables, x)\\n\",\n    \"\\n\",\n    \"print('initialized parameter shapes:\\\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(init_variables)))\\n\",\n    \"print('output:\\\\n', y)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Declaring and using variables\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Flax uses lazy initialization, which allows declared variables to be initialized only at the first site of their use, using whatever shape information is available a the local call site for shape inference.  Once a variable has been initialized, a reference to the data is kept for use in subsequent calls.\\n\",\n    \"\\n\",\n    \"For declaring parameters that aren't mutated inside the model, but rather by gradient descent, we use the syntax:\\n\",\n    \"\\n\",\n    \" `self.param(parameter_name, parameter_init_fn, *init_args, **init_kwargs)`\\n\",\n    \"\\n\",\n    \"with arguments:\\n\",\n    \" - `parameter_name` just the name, a string\\n\",\n    \" - `parameter_init_fn` a function taking an RNG key and a variable number of other arguments, i.e. `fn(rng, *args)`. typically those in `nn.initializers` take an `rng` and a `shape` argument.\\n\",\n    \" - the remaining arguments to feed to the init function when initializing.\\n\",\n    \"\\n\",\n    \"Again, we'll demonstrate declaring things inline as we typically do using the `@compact` decorator.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {\n    \"outputId\": \"bc5cb1f2-c5e9-4159-d131-73247009e32f\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized parameters:\\n\",\n      \" FrozenDict({\\n\",\n      \"    params: {\\n\",\n      \"        kernel: DeviceArray([[ 0.6503669 ,  0.8678979 ,  0.46042678],\\n\",\n      \"                     [ 0.05673932,  0.9909285 , -0.63536596],\\n\",\n      \"                     [ 0.76134115, -0.3250529 , -0.6522163 ],\\n\",\n      \"                     [-0.8243032 ,  0.4150194 ,  0.19405058]], dtype=float32),\\n\",\n      \"        bias: DeviceArray([0., 0., 0.], dtype=float32),\\n\",\n      \"    },\\n\",\n      \"})\\n\",\n      \"output:\\n\",\n      \" [[ 0.5035518   1.8548559  -0.4270196 ]\\n\",\n      \" [ 0.0279097   0.5589246  -0.43061775]\\n\",\n      \" [ 0.35471284  1.5741     -0.3286552 ]\\n\",\n      \" [ 0.5264864   1.2928858   0.10089308]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class SimpleDense(nn.Module):\\n\",\n    \"  features: int\\n\",\n    \"  kernel_init: Callable = nn.initializers.lecun_normal()\\n\",\n    \"  bias_init: Callable = nn.initializers.zeros_init()\\n\",\n    \"\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, inputs):\\n\",\n    \"    kernel = self.param('kernel',\\n\",\n    \"                        self.kernel_init,  # RNG passed implicitly.\\n\",\n    \"                        (inputs.shape[-1], self.features))  # shape info.\\n\",\n    \"    y = lax.dot_general(inputs, kernel,\\n\",\n    \"                        (((inputs.ndim - 1,), (0,)), ((), ())),)\\n\",\n    \"    bias = self.param('bias', self.bias_init, (self.features,))\\n\",\n    \"    y = y + bias\\n\",\n    \"    return y\\n\",\n    \"\\n\",\n    \"key1, key2 = random.split(random.key(0), 2)\\n\",\n    \"x = random.uniform(key1, (4,4))\\n\",\n    \"\\n\",\n    \"model = SimpleDense(features=3)\\n\",\n    \"init_variables = model.init(key2, x)\\n\",\n    \"y = model.apply(init_variables, x)\\n\",\n    \"\\n\",\n    \"print('initialized parameters:\\\\n', init_variables)\\n\",\n    \"print('output:\\\\n', y)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can also declare variables in setup, though in doing so you can't take advantage of shape inference and have to provide explicit shape information at initialization.  The syntax is a little repetitive in this case right now, but we do force agreement of the assigned names.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {\n    \"outputId\": \"1e822bd8-7a08-4e80-e0e6-a86637c46772\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized parameters:\\n\",\n      \" FrozenDict({\\n\",\n      \"    params: {\\n\",\n      \"        kernel: DeviceArray([[ 0.6503669 ,  0.8678979 ,  0.46042678],\\n\",\n      \"                     [ 0.05673932,  0.9909285 , -0.63536596],\\n\",\n      \"                     [ 0.76134115, -0.3250529 , -0.6522163 ],\\n\",\n      \"                     [-0.8243032 ,  0.4150194 ,  0.19405058]], dtype=float32),\\n\",\n      \"        bias: DeviceArray([0., 0., 0.], dtype=float32),\\n\",\n      \"    },\\n\",\n      \"})\\n\",\n      \"output:\\n\",\n      \" [[ 0.5035518   1.8548559  -0.4270196 ]\\n\",\n      \" [ 0.0279097   0.5589246  -0.43061775]\\n\",\n      \" [ 0.35471284  1.5741     -0.3286552 ]\\n\",\n      \" [ 0.5264864   1.2928858   0.10089308]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class ExplicitDense(nn.Module):\\n\",\n    \"  features_in: int  # <-- explicit input shape\\n\",\n    \"  features: int\\n\",\n    \"  kernel_init: Callable = nn.initializers.lecun_normal()\\n\",\n    \"  bias_init: Callable = nn.initializers.zeros_init()\\n\",\n    \"\\n\",\n    \"  def setup(self):\\n\",\n    \"    self.kernel = self.param('kernel',\\n\",\n    \"                             self.kernel_init,\\n\",\n    \"                             (self.features_in, self.features))\\n\",\n    \"    self.bias = self.param('bias', self.bias_init, (self.features,))\\n\",\n    \"\\n\",\n    \"  def __call__(self, inputs):\\n\",\n    \"    y = lax.dot_general(inputs, self.kernel,\\n\",\n    \"                        (((inputs.ndim - 1,), (0,)), ((), ())),)\\n\",\n    \"    y = y + self.bias\\n\",\n    \"    return y\\n\",\n    \"\\n\",\n    \"key1, key2 = random.split(random.key(0), 2)\\n\",\n    \"x = random.uniform(key1, (4,4))\\n\",\n    \"\\n\",\n    \"model = ExplicitDense(features_in=4, features=3)\\n\",\n    \"init_variables = model.init(key2, x)\\n\",\n    \"y = model.apply(init_variables, x)\\n\",\n    \"\\n\",\n    \"print('initialized parameters:\\\\n', init_variables)\\n\",\n    \"print('output:\\\\n', y)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## General Variables\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"For declaring generally mutable _variables_ that may be mutated inside the model we use the call:\\n\",\n    \"\\n\",\n    \" `self.variable(variable_kind, variable_name, variable_init_fn, *init_args, **init_kwargs)`\\n\",\n    \"\\n\",\n    \"with arguments:\\n\",\n    \" - `variable_kind` the \\\"kind\\\" of state this variable is, i.e. the name of the nested-dict collection that this will be stored in inside the top Modules variables.  e.g. `batch_stats` for the moving statistics for a batch norm layer or `cache` for autoregressive cache data.  Note that parameters also have a kind, but they're set to the default `param` kind.\\n\",\n    \" - `variable_name` just the name, a string\\n\",\n    \" - `variable_init_fn` a function taking a variable number of other arguments, i.e. `fn(*args)`. Note that we __don't__ assume the need for an RNG, if you _do_ want an RNG, provide it via a `self.make_rng(variable_kind)` call in the provided arguments.\\n\",\n    \" - the remaining arguments to feed to the init function when initializing.\\n\",\n    \"\\n\",\n    \"⚠️ Unlike parameters, we expect these to be mutated, so `self.variable` returns not a constant, but a _reference_ to the variable.  To __get__ the raw value, you'd write `myvariable.value` and to __set__ it `myvariable.value = new_value`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {\n    \"outputId\": \"2a8f5453-81b1-44dc-a431-d14b372c5710\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized variables:\\n\",\n      \" FrozenDict({\\n\",\n      \"    counter: {\\n\",\n      \"        count: DeviceArray(0, dtype=int32),\\n\",\n      \"    },\\n\",\n      \"})\\n\",\n      \"mutated variables:\\n\",\n      \" FrozenDict({\\n\",\n      \"    counter: {\\n\",\n      \"        count: DeviceArray(1, dtype=int32),\\n\",\n      \"    },\\n\",\n      \"})\\n\",\n      \"output:\\n\",\n      \" 1\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Counter(nn.Module):\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self):\\n\",\n    \"    # easy pattern to detect if we're initializing\\n\",\n    \"    is_initialized = self.has_variable('counter', 'count')\\n\",\n    \"    counter = self.variable('counter', 'count', lambda: jnp.zeros((), jnp.int32))\\n\",\n    \"    if is_initialized:\\n\",\n    \"      counter.value += 1\\n\",\n    \"    return counter.value\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"key1 = random.key(0)\\n\",\n    \"\\n\",\n    \"model = Counter()\\n\",\n    \"init_variables = model.init(key1)\\n\",\n    \"print('initialized variables:\\\\n', init_variables)\\n\",\n    \"\\n\",\n    \"y, mutated_variables = model.apply(init_variables, mutable=['counter'])\\n\",\n    \"\\n\",\n    \"print('mutated variables:\\\\n', mutated_variables)\\n\",\n    \"print('output:\\\\n', y)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Another Mutability and RNGs Example\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's make an artificial, goofy example that mixes differentiable parameters, stochastic layers, and mutable variables:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {\n    \"outputId\": \"8f299a5c-74c8-476c-93fa-e5543901ec45\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"updated variables:\\n\",\n      \" FrozenDict({\\n\",\n      \"    params: {\\n\",\n      \"        Dense_0: {\\n\",\n      \"            kernel: DeviceArray([[ 0.6498898 , -0.5000124 ,  0.78573596],\\n\",\n      \"                         [-0.25609785, -0.7132329 ,  0.2500864 ],\\n\",\n      \"                         [-0.64630085,  0.39321756, -1.0203307 ],\\n\",\n      \"                         [ 0.38721725,  0.86828285,  0.10860055]], dtype=float32),\\n\",\n      \"            bias: DeviceArray([0., 0., 0.], dtype=float32),\\n\",\n      \"        },\\n\",\n      \"        BatchNorm_0: {\\n\",\n      \"            scale: DeviceArray([1., 1., 1.], dtype=float32),\\n\",\n      \"            bias: DeviceArray([0., 0., 0.], dtype=float32),\\n\",\n      \"        },\\n\",\n      \"    },\\n\",\n      \"    batch_stats: {\\n\",\n      \"        BatchNorm_0: {\\n\",\n      \"            mean: DeviceArray([ 0.00059601, -0.00103457,  0.00166948], dtype=float32),\\n\",\n      \"            var: DeviceArray([0.9907686, 0.9923046, 0.992195 ], dtype=float32),\\n\",\n      \"        },\\n\",\n      \"    },\\n\",\n      \"})\\n\",\n      \"initialized variable shapes:\\n\",\n      \" FrozenDict({\\n\",\n      \"    batch_stats: {\\n\",\n      \"        BatchNorm_0: {\\n\",\n      \"            mean: (3,),\\n\",\n      \"            var: (3,),\\n\",\n      \"        },\\n\",\n      \"    },\\n\",\n      \"    params: {\\n\",\n      \"        BatchNorm_0: {\\n\",\n      \"            bias: (3,),\\n\",\n      \"            scale: (3,),\\n\",\n      \"        },\\n\",\n      \"        Dense_0: {\\n\",\n      \"            bias: (3,),\\n\",\n      \"            kernel: (4, 3),\\n\",\n      \"        },\\n\",\n      \"    },\\n\",\n      \"})\\n\",\n      \"output:\\n\",\n      \" [[[-0.21496922  0.21550177 -0.35633382]\\n\",\n      \"  [-0.21496922 -2.0458      1.3015485 ]\\n\",\n      \"  [-0.21496922 -0.925116   -0.35633382]\\n\",\n      \"  [-0.6595459   0.21550177  0.3749205 ]]\\n\",\n      \"\\n\",\n      \" [[-0.21496922  1.642865   -0.35633382]\\n\",\n      \"  [-0.21496922  1.3094063  -0.88034123]\\n\",\n      \"  [ 2.5726683   0.21550177  0.34353197]\\n\",\n      \"  [-0.21496922  0.21550177  1.6778195 ]]\\n\",\n      \"\\n\",\n      \" [[-1.6060593   0.21550177 -1.9460517 ]\\n\",\n      \"  [ 1.4126908  -1.4898677   1.2790381 ]\\n\",\n      \"  [-0.21496922  0.21550177 -0.35633382]\\n\",\n      \"  [-0.21496922  0.21550177 -0.7251308 ]]]\\n\",\n      \"eval output:\\n\",\n      \" [[[ 3.2246590e-01  2.6108384e-02  4.4821960e-01]\\n\",\n      \"  [ 8.5726947e-02 -5.4385906e-01  3.8821870e-01]\\n\",\n      \"  [-2.3933809e-01 -2.7381191e-01 -1.7526165e-01]\\n\",\n      \"  [-6.2515378e-02 -5.2414006e-01  1.7029770e-01]]\\n\",\n      \"\\n\",\n      \" [[ 1.5014435e-01  3.4498507e-01 -1.3554120e-01]\\n\",\n      \"  [-3.6971044e-04  2.6463276e-01 -1.2491019e-01]\\n\",\n      \"  [ 3.8763803e-01  2.9023719e-01  1.6291586e-01]\\n\",\n      \"  [ 4.1320035e-01  4.1468274e-02  4.7670874e-01]]\\n\",\n      \"\\n\",\n      \" [[-1.9433719e-01  5.2831882e-01 -3.7554008e-01]\\n\",\n      \"  [ 2.2608691e-01 -4.0989807e-01  3.8292480e-01]\\n\",\n      \"  [-2.4945706e-01  1.6170470e-01 -2.5247774e-01]\\n\",\n      \"  [-7.2220474e-02  1.2077977e-01 -8.8408351e-02]]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Block(nn.Module):\\n\",\n    \"  features: int\\n\",\n    \"  training: bool\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, inputs):\\n\",\n    \"    x = nn.Dense(self.features)(inputs)\\n\",\n    \"    x = nn.Dropout(rate=0.5)(x, deterministic=not self.training)\\n\",\n    \"    x = nn.BatchNorm(use_running_average=not self.training)(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"key1, key2, key3, key4 = random.split(random.key(0), 4)\\n\",\n    \"x = random.uniform(key1, (3,4,4))\\n\",\n    \"\\n\",\n    \"model = Block(features=3, training=True)\\n\",\n    \"\\n\",\n    \"init_variables = model.init({'params': key2, 'dropout': key3}, x)\\n\",\n    \"_, init_params = flax.core.pop(init_variables, 'params')\\n\",\n    \"\\n\",\n    \"# When calling `apply` with mutable kinds, returns a pair of output,\\n\",\n    \"# mutated_variables.\\n\",\n    \"y, mutated_variables = model.apply(\\n\",\n    \"    init_variables, x, rngs={'dropout': key4}, mutable=['batch_stats'])\\n\",\n    \"\\n\",\n    \"# Now we reassemble the full variables from the updates (in a real training\\n\",\n    \"# loop, with the updated params from an optimizer).\\n\",\n    \"updated_variables = flax.core.freeze(dict(params=init_params,\\n\",\n    \"                                          **mutated_variables))\\n\",\n    \"\\n\",\n    \"print('updated variables:\\\\n', updated_variables)\\n\",\n    \"print('initialized variable shapes:\\\\n',\\n\",\n    \"      jax.tree_util.tree_map(jnp.shape, init_variables))\\n\",\n    \"print('output:\\\\n', y)\\n\",\n    \"\\n\",\n    \"# Let's run these model variables during \\\"evaluation\\\":\\n\",\n    \"eval_model = Block(features=3, training=False)\\n\",\n    \"y = eval_model.apply(updated_variables, x)  # Nothing mutable; single return value.\\n\",\n    \"print('eval output:\\\\n', y)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# JAX transformations inside modules\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## JIT\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"It's not immediately clear what use this has, but you can compile specific submodules if there's a reason to.\\n\",\n    \"\\n\",\n    \"_Known Gotcha_: at the moment, the decorator changes the RNG stream slightly, so comparing jitted an unjitted initializations will look different.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {\n    \"outputId\": \"3f324d0f-259f-40f0-8273-103f7fc281c5\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized parameter shapes:\\n\",\n      \" {'params': {'layers_0': {'bias': (3,), 'kernel': (4, 3)}, 'layers_1': {'bias': (4,), 'kernel': (3, 4)}, 'layers_2': {'bias': (5,), 'kernel': (4, 5)}}}\\n\",\n      \"output:\\n\",\n      \" [[ 0.2524199   0.11621253  0.5246693   0.19144788  0.2096542 ]\\n\",\n      \" [ 0.08557513 -0.04126885  0.2502836   0.03910369  0.16575359]\\n\",\n      \" [ 0.2804383   0.27751124  0.44969672  0.26016283  0.05875347]\\n\",\n      \" [ 0.2440843   0.17069656  0.45499086  0.20377949  0.13428023]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class MLP(nn.Module):\\n\",\n    \"  features: Sequence[int]\\n\",\n    \"\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, inputs):\\n\",\n    \"    x = inputs\\n\",\n    \"    for i, feat in enumerate(self.features):\\n\",\n    \"      # JIT the Module (it's __call__ fn by default.)\\n\",\n    \"      x = nn.jit(nn.Dense)(feat, name=f'layers_{i}')(x)\\n\",\n    \"      if i != len(self.features) - 1:\\n\",\n    \"        x = nn.relu(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"key1, key2 = random.split(random.key(3), 2)\\n\",\n    \"x = random.uniform(key1, (4,4))\\n\",\n    \"\\n\",\n    \"model = MLP(features=[3,4,5])\\n\",\n    \"init_variables = model.init(key2, x)\\n\",\n    \"y = model.apply(init_variables, x)\\n\",\n    \"\\n\",\n    \"print('initialized parameter shapes:\\\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(init_variables)))\\n\",\n    \"print('output:\\\\n', y)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Remat\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"For memory-expensive computations, we can `remat` our method to recompute a Module's output during a backwards pass.\\n\",\n    \"\\n\",\n    \"_Known Gotcha_: at the moment, the decorator changes the RNG stream slightly, so comparing remat'd and undecorated initializations will look different.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {\n    \"outputId\": \"7fe8e13b-7dd6-4e55-ee50-ce334e8ed178\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized parameter shapes:\\n\",\n      \" {'params': {'layers_0': {'bias': (3,), 'kernel': (4, 3)}, 'layers_1': {'bias': (4,), 'kernel': (3, 4)}, 'layers_2': {'bias': (5,), 'kernel': (4, 5)}}}\\n\",\n      \"output:\\n\",\n      \" [[-0.14814317  0.06889858 -0.19695625  0.12019286  0.02068037]\\n\",\n      \" [-0.04439102 -0.06698258 -0.11579747 -0.19906905 -0.04342325]\\n\",\n      \" [-0.08875751 -0.13392815 -0.23153095 -0.39802808 -0.0868225 ]\\n\",\n      \" [-0.01606487 -0.02424064 -0.04190649 -0.07204203 -0.01571464]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class RematMLP(nn.Module):\\n\",\n    \"  features: Sequence[int]\\n\",\n    \"  # For all transforms, we can annotate a method, or wrap an existing\\n\",\n    \"  # Module class. Here we annotate the method.\\n\",\n    \"  @nn.remat\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, inputs):\\n\",\n    \"    x = inputs\\n\",\n    \"    for i, feat in enumerate(self.features):\\n\",\n    \"      x = nn.Dense(feat, name=f'layers_{i}')(x)\\n\",\n    \"      if i != len(self.features) - 1:\\n\",\n    \"        x = nn.relu(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"key1, key2 = random.split(random.key(3), 2)\\n\",\n    \"x = random.uniform(key1, (4,4))\\n\",\n    \"\\n\",\n    \"model = RematMLP(features=[3,4,5])\\n\",\n    \"init_variables = model.init(key2, x)\\n\",\n    \"y = model.apply(init_variables, x)\\n\",\n    \"\\n\",\n    \"print('initialized parameter shapes:\\\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(init_variables)))\\n\",\n    \"print('output:\\\\n', y)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Vmap\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"You can now `vmap` Modules inside.  The transform has a lot of arguments, they have the usual jax vmap args:\\n\",\n    \" - `in_axes` - an integer or `None` for each input argument\\n\",\n    \" - `out_axes` - an integer or `None` for each output argument\\n\",\n    \" - `axis_size` - the axis size if you need to give it explicitly\\n\",\n    \"\\n\",\n    \"In addition, we provide for each __kind__ of variable it's axis rules:\\n\",\n    \"\\n\",\n    \" - `variable_in_axes` - a dict from kinds to a single integer or `None` specifying the input axes to map\\n\",\n    \" - `variable_out_axes` - a dict from kinds to a single integer or `None` specifying the output axes to map\\n\",\n    \" - `split_rngs` - a dict from RNG-kinds to a bool, specifying whether to split the rng along the axis.\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"Below we show an example defining a batched, multiheaded attention module from a single-headed unbatched attention implementation.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {\n    \"outputId\": \"223d880e-c7b2-4210-ebb5-dbfcdd9aed09\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized parameter shapes:\\n\",\n      \" {'params': {'attention': {'key': {'kernel': (2, 64, 32)}, 'out': {'bias': (2, 64), 'kernel': (2, 32, 64)}, 'query': {'kernel': (2, 64, 32)}, 'value': {'kernel': (2, 64, 32)}}}}\\n\",\n      \"output:\\n\",\n      \" (3, 13, 2)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class RawDotProductAttention(nn.Module):\\n\",\n    \"  attn_dropout_rate: float = 0.1\\n\",\n    \"  train: bool = False\\n\",\n    \"\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, query, key, value, bias=None, dtype=jnp.float32):\\n\",\n    \"    assert key.ndim == query.ndim\\n\",\n    \"    assert key.ndim == value.ndim\\n\",\n    \"\\n\",\n    \"    n = query.ndim\\n\",\n    \"    attn_weights = lax.dot_general(\\n\",\n    \"        query, key,\\n\",\n    \"        (((n-1,), (n - 1,)), ((), ())))\\n\",\n    \"    if bias is not None:\\n\",\n    \"      attn_weights += bias\\n\",\n    \"    norm_dims = tuple(range(attn_weights.ndim // 2, attn_weights.ndim))\\n\",\n    \"    attn_weights = jax.nn.softmax(attn_weights, axis=norm_dims)\\n\",\n    \"    attn_weights = nn.Dropout(self.attn_dropout_rate)(attn_weights,\\n\",\n    \"                                                      deterministic=not self.train)\\n\",\n    \"    attn_weights = attn_weights.astype(dtype)\\n\",\n    \"\\n\",\n    \"    contract_dims = (\\n\",\n    \"        tuple(range(n - 1, attn_weights.ndim)),\\n\",\n    \"        tuple(range(0, n  - 1)))\\n\",\n    \"    y = lax.dot_general(\\n\",\n    \"        attn_weights, value,\\n\",\n    \"        (contract_dims, ((), ())))\\n\",\n    \"    return y\\n\",\n    \"\\n\",\n    \"class DotProductAttention(nn.Module):\\n\",\n    \"  qkv_features: Optional[int] = None\\n\",\n    \"  out_features: Optional[int] = None\\n\",\n    \"  train: bool = False\\n\",\n    \"\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, inputs_q, inputs_kv, bias=None, dtype=jnp.float32):\\n\",\n    \"    qkv_features = self.qkv_features or inputs_q.shape[-1]\\n\",\n    \"    out_features = self.out_features or inputs_q.shape[-1]\\n\",\n    \"\\n\",\n    \"    QKVDense = functools.partial(\\n\",\n    \"      nn.Dense, features=qkv_features, use_bias=False, dtype=dtype)\\n\",\n    \"    query = QKVDense(name='query')(inputs_q)\\n\",\n    \"    key = QKVDense(name='key')(inputs_kv)\\n\",\n    \"    value = QKVDense(name='value')(inputs_kv)\\n\",\n    \"\\n\",\n    \"    y = RawDotProductAttention(train=self.train)(\\n\",\n    \"        query, key, value, bias=bias, dtype=dtype)\\n\",\n    \"\\n\",\n    \"    y = nn.Dense(features=out_features, dtype=dtype, name='out')(y)\\n\",\n    \"    return y\\n\",\n    \"\\n\",\n    \"class MultiHeadDotProductAttention(nn.Module):\\n\",\n    \"  qkv_features: Optional[int] = None\\n\",\n    \"  out_features: Optional[int] = None\\n\",\n    \"  batch_axes: Sequence[int] = (0,)\\n\",\n    \"  num_heads: int = 1\\n\",\n    \"  broadcast_dropout: bool = False\\n\",\n    \"  train: bool = False\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, inputs_q, inputs_kv, bias=None, dtype=jnp.float32):\\n\",\n    \"    qkv_features = self.qkv_features or inputs_q.shape[-1]\\n\",\n    \"    out_features = self.out_features or inputs_q.shape[-1]\\n\",\n    \"\\n\",\n    \"    # Make multiheaded attention from single-headed dimension.\\n\",\n    \"    Attn = nn.vmap(DotProductAttention,\\n\",\n    \"                   in_axes=(None, None, None),\\n\",\n    \"                   out_axes=2,\\n\",\n    \"                   axis_size=self.num_heads,\\n\",\n    \"                   variable_axes={'params': 0},\\n\",\n    \"                   split_rngs={'params': True,\\n\",\n    \"                               'dropout': not self.broadcast_dropout})\\n\",\n    \"\\n\",\n    \"    # Vmap across batch dimensions.\\n\",\n    \"    for axis in reversed(sorted(self.batch_axes)):\\n\",\n    \"      Attn = nn.vmap(Attn,\\n\",\n    \"                     in_axes=(axis, axis, axis),\\n\",\n    \"                     out_axes=axis,\\n\",\n    \"                     variable_axes={'params': None},\\n\",\n    \"                     split_rngs={'params': False, 'dropout': False})\\n\",\n    \"\\n\",\n    \"    # Run the vmap'd class on inputs.\\n\",\n    \"    y = Attn(qkv_features=qkv_features // self.num_heads,\\n\",\n    \"             out_features=out_features,\\n\",\n    \"             train=self.train,\\n\",\n    \"             name='attention')(inputs_q, inputs_kv, bias)\\n\",\n    \"\\n\",\n    \"    return y.mean(axis=-2)\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"key1, key2, key3, key4 = random.split(random.key(0), 4)\\n\",\n    \"x = random.uniform(key1, (3, 13, 64))\\n\",\n    \"\\n\",\n    \"model = functools.partial(\\n\",\n    \"  MultiHeadDotProductAttention,\\n\",\n    \"  broadcast_dropout=False,\\n\",\n    \"  num_heads=2,\\n\",\n    \"  batch_axes=(0,))\\n\",\n    \"\\n\",\n    \"init_variables = model(train=False).init({'params': key2}, x, x)\\n\",\n    \"print('initialized parameter shapes:\\\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(init_variables)))\\n\",\n    \"\\n\",\n    \"y = model(train=True).apply(init_variables, x, x, rngs={'dropout': key4})\\n\",\n    \"print('output:\\\\n', y.shape)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Scan\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Scan allows us to apply `lax.scan` to Modules, including their parameters and mutable variables.  To use it we have to specify how we want each \\\"kind\\\" of variable to be transformed.  For scanned variables we specify similar to vmap via in `variable_in_axes`, `variable_out_axes`:\\n\",\n    \" - `nn.broadcast` broadcast the variable kind across the scan steps as a constant\\n\",\n    \" - `<axis:int>` scan along `axis` for e.g. unique parameters at each step\\n\",\n    \"\\n\",\n    \"OR we specify that the variable kind is to be treated like a \\\"carry\\\" by passing to the `variable_carry` argument.\\n\",\n    \"\\n\",\n    \"Further, for `scan`'d variable kinds, we further specify whether or not to split the rng at each step.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"metadata\": {\n    \"outputId\": \"7d9ebed3-64de-4ca8-9dce-4b09ba9e31a1\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized parameter shapes:\\n\",\n      \" {'params': {'lstm_cell': {'hf': {'bias': (2,), 'kernel': (2, 2)}, 'hg': {'bias': (2,), 'kernel': (2, 2)}, 'hi': {'bias': (2,), 'kernel': (2, 2)}, 'ho': {'bias': (2,), 'kernel': (2, 2)}, 'if': {'kernel': (2, 2)}, 'ig': {'kernel': (2, 2)}, 'ii': {'kernel': (2, 2)}, 'io': {'kernel': (2, 2)}}}}\\n\",\n      \"output:\\n\",\n      \" ((DeviceArray([[-0.562219  ,  0.92847174]], dtype=float32), DeviceArray([[-0.31570646,  0.2885693 ]], dtype=float32)), DeviceArray([[[-0.08265854,  0.01302483],\\n\",\n      \"              [-0.10249066,  0.21991298],\\n\",\n      \"              [-0.26609066,  0.22519003],\\n\",\n      \"              [-0.27982554,  0.28393182],\\n\",\n      \"              [-0.31570646,  0.2885693 ]]], dtype=float32))\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class SimpleScan(nn.Module):\\n\",\n    \"  features: int\\n\",\n    \"\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, xs):\\n\",\n    \"    LSTM = nn.scan(nn.LSTMCell,\\n\",\n    \"                   in_axes=1, out_axes=1,\\n\",\n    \"                   variable_broadcast='params',\\n\",\n    \"                   split_rngs={'params': False})\\n\",\n    \"    lstm = LSTM(self.features, name=\\\"lstm_cell\\\")\\n\",\n    \"\\n\",\n    \"    dummy_rng = random.key(0)\\n\",\n    \"    input_shape = xs[:, 0].shape\\n\",\n    \"    init_carry = lstm.initialize_carry(dummy_rng, input_shape)\\n\",\n    \"\\n\",\n    \"    return lstm(init_carry, xs)\\n\",\n    \"\\n\",\n    \"key1, key2 = random.split(random.key(0), 2)\\n\",\n    \"xs = random.uniform(key1, (1, 5, 2))\\n\",\n    \"\\n\",\n    \"model = SimpleScan(2)\\n\",\n    \"init_variables = model.init(key2, xs)\\n\",\n    \"\\n\",\n    \"print('initialized parameter shapes:\\\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(init_variables)))\\n\",\n    \"\\n\",\n    \"y = model.apply(init_variables, xs)\\n\",\n    \"print('output:\\\\n', y)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"name\": \"python\",\n   \"version\": \"3.8.10\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "docs/linen_intro.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google/flax/blob/main/docs/notebooks/linen_intro.ipynb)\n[![Open On GitHub](https://img.shields.io/badge/Open-on%20GitHub-blue?logo=GitHub)](https://github.com/google/flax/blob/main/docs/notebooks/linen_intro.ipynb)\n\n# Preface\n\n<br>\n<div style=\"font-variant: small-caps;\">CAVEAT PROGRAMMER</div>\n\nThe below is an alpha API preview and things might break.  The surface syntax of the features of the API are not fixed in stone, and we welcome feedback on any points.\n\n+++\n\n## Useful links\n\n⟶ [Slides](https://docs.google.com/presentation/d/1ngKWUwsSqAwPRvATG8sAxMzu9ujv4N__cKsUofdNno0/edit?usp=sharing) for the core ideas of the new Functional Core and Linen\n\n⟶ \"Design tests\" guided our design process. Many are available for [functional core](https://github.com/google/flax/tree/main/examples/core_design_test) and some for the [proposed Module abstraction](https://github.com/google/flax/tree/main/examples/linen_design_test/)\n\n⟶ Ported examples: [ImageNet](https://github.com/google/flax/tree/main/examples/imagenet) and [WMT](https://github.com/google/flax/tree/main/examples/wmt) (to the proposed Module abstraction). TODO: Port to functional core.\n\n⟶ Our new [discussion forums](https://github.com/google/flax/discussions/)\n\n+++\n\n# Install and Import\n\n```{code-cell}\n:tags: [skip-execution]\n\n# Install the newest JAXlib version.\n!pip install --upgrade -q pip jax jaxlib\n# Install Flax at head:\n!pip install --upgrade -q git+https://github.com/google/flax.git\n```\n\n```{code-cell}\nimport functools\nfrom typing import Any, Callable, Sequence, Optional\nimport jax\nfrom jax import lax, random, numpy as jnp\nimport flax\nfrom flax import linen as nn\n```\n\n# Invoking Modules\n\n+++\n\nLet's instantiate a `Dense` layer.\n - Modules are actually objects in this API, so we provide _constructor arguments_ when initializing the Module.  In this case, we only have to provide the output `features` dimension.\n\n```{code-cell}\nmodel = nn.Dense(features=3)\n```\n\nWe need to initialize the Module variables, these include the parameters of the Module as well as any other state variables.\n\nWe call the `init` method on the instantiated Module.  If the Module `__call__` method has args `(self, *args, **kwargs)` then we call `init` with `(rngs, *args, **kwargs)` so in this case, just `(rng, input)`:\n\n```{code-cell}\n:outputId: 3adfaeaf-977e-4e82-8adf-d254fae6eb91\n\n# Make RNG Keys and a fake input.\nkey1, key2 = random.split(random.key(0), 2)\nx = random.uniform(key1, (4,4))\n\n# provide key and fake input to get initialized variables\ninit_variables = model.init(key2, x)\n\ninit_variables\n```\n\nWe call the `apply` method on the instantiated Module.  If the Module `__call__` method has args `(self, *args, **kwargs)` then we call `apply` with `(variables, *args, rngs=<RNGS>, mutable=<MUTABLEKINDS>, **kwargs)` where\n - `<RNGS>` are the optional _call time_ RNGs for things like dropout. For simple Modules this is just a single key, but if your module has multiple __kinds__ of data, it's a dictionary of rng-keys per-kind, e.g. `{'params': key0, 'dropout': key1}` for a Module with dropout layers.\n - `<MUTABLEKINDS>` is an optional list of names of __kinds__ that are expected to be mutated during the call. e.g. `['batch_stats']` for a layer updating batchnorm statistics.\n\nSo in this case, just `(variables, input)`:\n\n```{code-cell}\n:outputId: e8c389a6-29f3-4f93-97ea-703e85a8b811\n\ny = model.apply(init_variables, x)\ny\n```\n\nAdditional points:\n - If you want to `init` or `apply` a Module using a method other than call, you need to provide the `method=` kwarg to `init` and `apply` to use it instead of the default `__call__`, e.g. `method='encode'`, `method='decode'` to apply the encode/decode methods of an autoencoder.\n\n+++\n\n# Defining Basic Modules\n\n+++\n\n## Composing submodules\n\n+++\n\nWe support declaring modules in `setup()` that can still benefit from shape inference by using __Lazy Initialization__ that sets up variables the first time the Module is called.\n\n```{code-cell}\n:outputId: 1a6c6a17-0b95-42c2-b5bf-b9ad80fd7758\n:tags: []\n\nclass ExplicitMLP(nn.Module):\n  features: Sequence[int]\n\n  def setup(self):\n    # we automatically know what to do with lists, dicts of submodules\n    self.layers = [nn.Dense(feat) for feat in self.features]\n    # for single submodules, we would just write:\n    # self.layer1 = nn.Dense(feat1)\n\n  def __call__(self, inputs):\n    x = inputs\n    for i, lyr in enumerate(self.layers):\n      x = lyr(x)\n      if i != len(self.layers) - 1:\n        x = nn.relu(x)\n    return x\n\nkey1, key2 = random.split(random.key(0), 2)\nx = random.uniform(key1, (4,4))\n\nmodel = ExplicitMLP(features=[3,4,5])\ninit_variables = model.init(key2, x)\ny = model.apply(init_variables, x)\n\nprint('initialized parameter shapes:\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(init_variables)))\nprint('output:\\n', y)\n```\n\nHere we show the equivalent compact form of the MLP that declares the submodules inline using the `@compact` decorator.\n\n```{code-cell}\n:outputId: b3709789-e66e-4e20-f6b2-04022f8a62bb\n:tags: []\n\nclass SimpleMLP(nn.Module):\n  features: Sequence[int]\n\n  @nn.compact\n  def __call__(self, inputs):\n    x = inputs\n    for i, feat in enumerate(self.features):\n      x = nn.Dense(feat, name=f'layers_{i}')(x)\n      if i != len(self.features) - 1:\n        x = nn.relu(x)\n      # providing a name is optional though!\n      # the default autonames would be \"Dense_0\", \"Dense_1\", ...\n      # x = nn.Dense(feat)(x)\n    return x\n\nkey1, key2 = random.split(random.key(0), 2)\nx = random.uniform(key1, (4,4))\n\nmodel = SimpleMLP(features=[3,4,5])\ninit_variables = model.init(key2, x)\ny = model.apply(init_variables, x)\n\nprint('initialized parameter shapes:\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(init_variables)))\nprint('output:\\n', y)\n```\n\n## Declaring and using variables\n\n+++\n\nFlax uses lazy initialization, which allows declared variables to be initialized only at the first site of their use, using whatever shape information is available a the local call site for shape inference.  Once a variable has been initialized, a reference to the data is kept for use in subsequent calls.\n\nFor declaring parameters that aren't mutated inside the model, but rather by gradient descent, we use the syntax:\n\n `self.param(parameter_name, parameter_init_fn, *init_args, **init_kwargs)`\n\nwith arguments:\n - `parameter_name` just the name, a string\n - `parameter_init_fn` a function taking an RNG key and a variable number of other arguments, i.e. `fn(rng, *args)`. typically those in `nn.initializers` take an `rng` and a `shape` argument.\n - the remaining arguments to feed to the init function when initializing.\n\nAgain, we'll demonstrate declaring things inline as we typically do using the `@compact` decorator.\n\n```{code-cell}\n:outputId: bc5cb1f2-c5e9-4159-d131-73247009e32f\n:tags: []\n\nclass SimpleDense(nn.Module):\n  features: int\n  kernel_init: Callable = nn.initializers.lecun_normal()\n  bias_init: Callable = nn.initializers.zeros_init()\n\n  @nn.compact\n  def __call__(self, inputs):\n    kernel = self.param('kernel',\n                        self.kernel_init,  # RNG passed implicitly.\n                        (inputs.shape[-1], self.features))  # shape info.\n    y = lax.dot_general(inputs, kernel,\n                        (((inputs.ndim - 1,), (0,)), ((), ())),)\n    bias = self.param('bias', self.bias_init, (self.features,))\n    y = y + bias\n    return y\n\nkey1, key2 = random.split(random.key(0), 2)\nx = random.uniform(key1, (4,4))\n\nmodel = SimpleDense(features=3)\ninit_variables = model.init(key2, x)\ny = model.apply(init_variables, x)\n\nprint('initialized parameters:\\n', init_variables)\nprint('output:\\n', y)\n```\n\nWe can also declare variables in setup, though in doing so you can't take advantage of shape inference and have to provide explicit shape information at initialization.  The syntax is a little repetitive in this case right now, but we do force agreement of the assigned names.\n\n```{code-cell}\n:outputId: 1e822bd8-7a08-4e80-e0e6-a86637c46772\n:tags: []\n\nclass ExplicitDense(nn.Module):\n  features_in: int  # <-- explicit input shape\n  features: int\n  kernel_init: Callable = nn.initializers.lecun_normal()\n  bias_init: Callable = nn.initializers.zeros_init()\n\n  def setup(self):\n    self.kernel = self.param('kernel',\n                             self.kernel_init,\n                             (self.features_in, self.features))\n    self.bias = self.param('bias', self.bias_init, (self.features,))\n\n  def __call__(self, inputs):\n    y = lax.dot_general(inputs, self.kernel,\n                        (((inputs.ndim - 1,), (0,)), ((), ())),)\n    y = y + self.bias\n    return y\n\nkey1, key2 = random.split(random.key(0), 2)\nx = random.uniform(key1, (4,4))\n\nmodel = ExplicitDense(features_in=4, features=3)\ninit_variables = model.init(key2, x)\ny = model.apply(init_variables, x)\n\nprint('initialized parameters:\\n', init_variables)\nprint('output:\\n', y)\n```\n\n## General Variables\n\n+++\n\nFor declaring generally mutable _variables_ that may be mutated inside the model we use the call:\n\n `self.variable(variable_kind, variable_name, variable_init_fn, *init_args, **init_kwargs)`\n\nwith arguments:\n - `variable_kind` the \"kind\" of state this variable is, i.e. the name of the nested-dict collection that this will be stored in inside the top Modules variables.  e.g. `batch_stats` for the moving statistics for a batch norm layer or `cache` for autoregressive cache data.  Note that parameters also have a kind, but they're set to the default `param` kind.\n - `variable_name` just the name, a string\n - `variable_init_fn` a function taking a variable number of other arguments, i.e. `fn(*args)`. Note that we __don't__ assume the need for an RNG, if you _do_ want an RNG, provide it via a `self.make_rng(variable_kind)` call in the provided arguments.\n - the remaining arguments to feed to the init function when initializing.\n\n⚠️ Unlike parameters, we expect these to be mutated, so `self.variable` returns not a constant, but a _reference_ to the variable.  To __get__ the raw value, you'd write `myvariable.value` and to __set__ it `myvariable.value = new_value`.\n\n```{code-cell}\n:outputId: 2a8f5453-81b1-44dc-a431-d14b372c5710\n:tags: []\n\nclass Counter(nn.Module):\n  @nn.compact\n  def __call__(self):\n    # easy pattern to detect if we're initializing\n    is_initialized = self.has_variable('counter', 'count')\n    counter = self.variable('counter', 'count', lambda: jnp.zeros((), jnp.int32))\n    if is_initialized:\n      counter.value += 1\n    return counter.value\n\n\nkey1 = random.key(0)\n\nmodel = Counter()\ninit_variables = model.init(key1)\nprint('initialized variables:\\n', init_variables)\n\ny, mutated_variables = model.apply(init_variables, mutable=['counter'])\n\nprint('mutated variables:\\n', mutated_variables)\nprint('output:\\n', y)\n```\n\n## Another Mutability and RNGs Example\n\n+++\n\nLet's make an artificial, goofy example that mixes differentiable parameters, stochastic layers, and mutable variables:\n\n```{code-cell}\n:outputId: 8f299a5c-74c8-476c-93fa-e5543901ec45\n:tags: []\n\nclass Block(nn.Module):\n  features: int\n  training: bool\n  @nn.compact\n  def __call__(self, inputs):\n    x = nn.Dense(self.features)(inputs)\n    x = nn.Dropout(rate=0.5)(x, deterministic=not self.training)\n    x = nn.BatchNorm(use_running_average=not self.training)(x)\n    return x\n\nkey1, key2, key3, key4 = random.split(random.key(0), 4)\nx = random.uniform(key1, (3,4,4))\n\nmodel = Block(features=3, training=True)\n\ninit_variables = model.init({'params': key2, 'dropout': key3}, x)\n_, init_params = flax.core.pop(init_variables, 'params')\n\n# When calling `apply` with mutable kinds, returns a pair of output,\n# mutated_variables.\ny, mutated_variables = model.apply(\n    init_variables, x, rngs={'dropout': key4}, mutable=['batch_stats'])\n\n# Now we reassemble the full variables from the updates (in a real training\n# loop, with the updated params from an optimizer).\nupdated_variables = flax.core.freeze(dict(params=init_params,\n                                          **mutated_variables))\n\nprint('updated variables:\\n', updated_variables)\nprint('initialized variable shapes:\\n',\n      jax.tree_util.tree_map(jnp.shape, init_variables))\nprint('output:\\n', y)\n\n# Let's run these model variables during \"evaluation\":\neval_model = Block(features=3, training=False)\ny = eval_model.apply(updated_variables, x)  # Nothing mutable; single return value.\nprint('eval output:\\n', y)\n```\n\n# JAX transformations inside modules\n\n+++\n\n## JIT\n\n+++\n\nIt's not immediately clear what use this has, but you can compile specific submodules if there's a reason to.\n\n_Known Gotcha_: at the moment, the decorator changes the RNG stream slightly, so comparing jitted an unjitted initializations will look different.\n\n```{code-cell}\n:outputId: 3f324d0f-259f-40f0-8273-103f7fc281c5\n:tags: []\n\nclass MLP(nn.Module):\n  features: Sequence[int]\n\n  @nn.compact\n  def __call__(self, inputs):\n    x = inputs\n    for i, feat in enumerate(self.features):\n      # JIT the Module (it's __call__ fn by default.)\n      x = nn.jit(nn.Dense)(feat, name=f'layers_{i}')(x)\n      if i != len(self.features) - 1:\n        x = nn.relu(x)\n    return x\n\nkey1, key2 = random.split(random.key(3), 2)\nx = random.uniform(key1, (4,4))\n\nmodel = MLP(features=[3,4,5])\ninit_variables = model.init(key2, x)\ny = model.apply(init_variables, x)\n\nprint('initialized parameter shapes:\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(init_variables)))\nprint('output:\\n', y)\n```\n\n## Remat\n\n+++\n\nFor memory-expensive computations, we can `remat` our method to recompute a Module's output during a backwards pass.\n\n_Known Gotcha_: at the moment, the decorator changes the RNG stream slightly, so comparing remat'd and undecorated initializations will look different.\n\n```{code-cell}\n:outputId: 7fe8e13b-7dd6-4e55-ee50-ce334e8ed178\n:tags: []\n\nclass RematMLP(nn.Module):\n  features: Sequence[int]\n  # For all transforms, we can annotate a method, or wrap an existing\n  # Module class. Here we annotate the method.\n  @nn.remat\n  @nn.compact\n  def __call__(self, inputs):\n    x = inputs\n    for i, feat in enumerate(self.features):\n      x = nn.Dense(feat, name=f'layers_{i}')(x)\n      if i != len(self.features) - 1:\n        x = nn.relu(x)\n    return x\n\nkey1, key2 = random.split(random.key(3), 2)\nx = random.uniform(key1, (4,4))\n\nmodel = RematMLP(features=[3,4,5])\ninit_variables = model.init(key2, x)\ny = model.apply(init_variables, x)\n\nprint('initialized parameter shapes:\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(init_variables)))\nprint('output:\\n', y)\n```\n\n## Vmap\n\n+++\n\nYou can now `vmap` Modules inside.  The transform has a lot of arguments, they have the usual jax vmap args:\n - `in_axes` - an integer or `None` for each input argument\n - `out_axes` - an integer or `None` for each output argument\n - `axis_size` - the axis size if you need to give it explicitly\n\nIn addition, we provide for each __kind__ of variable it's axis rules:\n\n - `variable_in_axes` - a dict from kinds to a single integer or `None` specifying the input axes to map\n - `variable_out_axes` - a dict from kinds to a single integer or `None` specifying the output axes to map\n - `split_rngs` - a dict from RNG-kinds to a bool, specifying whether to split the rng along the axis.\n\n\nBelow we show an example defining a batched, multiheaded attention module from a single-headed unbatched attention implementation.\n\n```{code-cell}\n:outputId: 223d880e-c7b2-4210-ebb5-dbfcdd9aed09\n:tags: []\n\nclass RawDotProductAttention(nn.Module):\n  attn_dropout_rate: float = 0.1\n  train: bool = False\n\n  @nn.compact\n  def __call__(self, query, key, value, bias=None, dtype=jnp.float32):\n    assert key.ndim == query.ndim\n    assert key.ndim == value.ndim\n\n    n = query.ndim\n    attn_weights = lax.dot_general(\n        query, key,\n        (((n-1,), (n - 1,)), ((), ())))\n    if bias is not None:\n      attn_weights += bias\n    norm_dims = tuple(range(attn_weights.ndim // 2, attn_weights.ndim))\n    attn_weights = jax.nn.softmax(attn_weights, axis=norm_dims)\n    attn_weights = nn.Dropout(self.attn_dropout_rate)(attn_weights,\n                                                      deterministic=not self.train)\n    attn_weights = attn_weights.astype(dtype)\n\n    contract_dims = (\n        tuple(range(n - 1, attn_weights.ndim)),\n        tuple(range(0, n  - 1)))\n    y = lax.dot_general(\n        attn_weights, value,\n        (contract_dims, ((), ())))\n    return y\n\nclass DotProductAttention(nn.Module):\n  qkv_features: Optional[int] = None\n  out_features: Optional[int] = None\n  train: bool = False\n\n  @nn.compact\n  def __call__(self, inputs_q, inputs_kv, bias=None, dtype=jnp.float32):\n    qkv_features = self.qkv_features or inputs_q.shape[-1]\n    out_features = self.out_features or inputs_q.shape[-1]\n\n    QKVDense = functools.partial(\n      nn.Dense, features=qkv_features, use_bias=False, dtype=dtype)\n    query = QKVDense(name='query')(inputs_q)\n    key = QKVDense(name='key')(inputs_kv)\n    value = QKVDense(name='value')(inputs_kv)\n\n    y = RawDotProductAttention(train=self.train)(\n        query, key, value, bias=bias, dtype=dtype)\n\n    y = nn.Dense(features=out_features, dtype=dtype, name='out')(y)\n    return y\n\nclass MultiHeadDotProductAttention(nn.Module):\n  qkv_features: Optional[int] = None\n  out_features: Optional[int] = None\n  batch_axes: Sequence[int] = (0,)\n  num_heads: int = 1\n  broadcast_dropout: bool = False\n  train: bool = False\n  @nn.compact\n  def __call__(self, inputs_q, inputs_kv, bias=None, dtype=jnp.float32):\n    qkv_features = self.qkv_features or inputs_q.shape[-1]\n    out_features = self.out_features or inputs_q.shape[-1]\n\n    # Make multiheaded attention from single-headed dimension.\n    Attn = nn.vmap(DotProductAttention,\n                   in_axes=(None, None, None),\n                   out_axes=2,\n                   axis_size=self.num_heads,\n                   variable_axes={'params': 0},\n                   split_rngs={'params': True,\n                               'dropout': not self.broadcast_dropout})\n\n    # Vmap across batch dimensions.\n    for axis in reversed(sorted(self.batch_axes)):\n      Attn = nn.vmap(Attn,\n                     in_axes=(axis, axis, axis),\n                     out_axes=axis,\n                     variable_axes={'params': None},\n                     split_rngs={'params': False, 'dropout': False})\n\n    # Run the vmap'd class on inputs.\n    y = Attn(qkv_features=qkv_features // self.num_heads,\n             out_features=out_features,\n             train=self.train,\n             name='attention')(inputs_q, inputs_kv, bias)\n\n    return y.mean(axis=-2)\n\n\nkey1, key2, key3, key4 = random.split(random.key(0), 4)\nx = random.uniform(key1, (3, 13, 64))\n\nmodel = functools.partial(\n  MultiHeadDotProductAttention,\n  broadcast_dropout=False,\n  num_heads=2,\n  batch_axes=(0,))\n\ninit_variables = model(train=False).init({'params': key2}, x, x)\nprint('initialized parameter shapes:\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(init_variables)))\n\ny = model(train=True).apply(init_variables, x, x, rngs={'dropout': key4})\nprint('output:\\n', y.shape)\n```\n\n## Scan\n\n+++\n\nScan allows us to apply `lax.scan` to Modules, including their parameters and mutable variables.  To use it we have to specify how we want each \"kind\" of variable to be transformed.  For scanned variables we specify similar to vmap via in `variable_in_axes`, `variable_out_axes`:\n - `nn.broadcast` broadcast the variable kind across the scan steps as a constant\n - `<axis:int>` scan along `axis` for e.g. unique parameters at each step\n\nOR we specify that the variable kind is to be treated like a \"carry\" by passing to the `variable_carry` argument.\n\nFurther, for `scan`'d variable kinds, we further specify whether or not to split the rng at each step.\n\n```{code-cell}\n:outputId: 7d9ebed3-64de-4ca8-9dce-4b09ba9e31a1\n:tags: []\n\nclass SimpleScan(nn.Module):\n  features: int\n\n  @nn.compact\n  def __call__(self, xs):\n    LSTM = nn.scan(nn.LSTMCell,\n                   in_axes=1, out_axes=1,\n                   variable_broadcast='params',\n                   split_rngs={'params': False})\n    lstm = LSTM(self.features, name=\"lstm_cell\")\n\n    dummy_rng = random.key(0)\n    input_shape = xs[:, 0].shape\n    init_carry = lstm.initialize_carry(dummy_rng, input_shape)\n\n    return lstm(init_carry, xs)\n\nkey1, key2 = random.split(random.key(0), 2)\nxs = random.uniform(key1, (1, 5, 2))\n\nmodel = SimpleScan(2)\ninit_variables = model.init(key2, xs)\n\nprint('initialized parameter shapes:\\n', jax.tree_util.tree_map(jnp.shape, flax.core.unfreeze(init_variables)))\n\ny = model.apply(init_variables, xs)\nprint('output:\\n', y)\n```\n"
  },
  {
    "path": "docs/quick_start.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"6eea21b3\",\n   \"metadata\": {},\n   \"source\": [\n    \"[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google/flax/blob/main/docs/quick_start.ipynb)\\n\",\n    \"[![Open On GitHub](https://img.shields.io/badge/Open-on%20GitHub-blue?logo=GitHub)](https://github.com/google/flax/blob/main/docs/quick_start.ipynb)\\n\",\n    \"\\n\",\n    \"# Quick start\\n\",\n    \"\\n\",\n    \"Welcome to Flax!\\n\",\n    \"\\n\",\n    \"Flax is an open source Python neural network library built on top of [JAX](https://github.com/jax-ml/jax). This tutorial demonstrates how to construct a simple convolutional neural\\n\",\n    \"network (CNN) using the [Flax](https://flax.readthedocs.io) Linen API and train\\n\",\n    \"the network for image classification on the MNIST dataset.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"nwJWKIhdwxDo\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 1. Install Flax\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"bb81587e\",\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"!pip install -q flax>=0.7.5\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"b529fbef\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 2. Loading data\\n\",\n    \"\\n\",\n    \"Flax can use any\\n\",\n    \"data-loading pipeline and this example demonstrates how to utilize TFDS. Define a function that loads and prepares the MNIST dataset and converts the\\n\",\n    \"samples to floating-point numbers.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 48,\n   \"id\": \"bRlrHqZVXZvk\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import tensorflow_datasets as tfds  # TFDS for MNIST\\n\",\n    \"import tensorflow as tf             # TensorFlow operations\\n\",\n    \"\\n\",\n    \"def get_datasets(num_epochs, batch_size):\\n\",\n    \"  \\\"\\\"\\\"Load MNIST train and test datasets into memory.\\\"\\\"\\\"\\n\",\n    \"  train_ds = tfds.load('mnist', split='train')\\n\",\n    \"  test_ds = tfds.load('mnist', split='test')\\n\",\n    \"\\n\",\n    \"  train_ds = train_ds.map(lambda sample: {'image': tf.cast(sample['image'],\\n\",\n    \"                                                           tf.float32) / 255.,\\n\",\n    \"                                          'label': sample['label']}) # normalize train set\\n\",\n    \"  test_ds = test_ds.map(lambda sample: {'image': tf.cast(sample['image'],\\n\",\n    \"                                                         tf.float32) / 255.,\\n\",\n    \"                                        'label': sample['label']}) # normalize test set\\n\",\n    \"\\n\",\n    \"  train_ds = train_ds.repeat(num_epochs).shuffle(1024) # create shuffled dataset by allocating a buffer size of 1024 to randomly draw elements from\\n\",\n    \"  train_ds = train_ds.batch(batch_size, drop_remainder=True).prefetch(1) # group into batches of batch_size and skip incomplete batch, prefetch the next sample to improve latency\\n\",\n    \"  test_ds = test_ds.shuffle(1024) # create shuffled dataset by allocating a buffer size of 1024 to randomly draw elements from\\n\",\n    \"  test_ds = test_ds.batch(batch_size, drop_remainder=True).prefetch(1) # group into batches of batch_size and skip incomplete batch, prefetch the next sample to improve latency\\n\",\n    \"\\n\",\n    \"  return train_ds, test_ds\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"7057395a\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 3. Define network\\n\",\n    \"\\n\",\n    \"Create a convolutional neural network with the Linen API by subclassing\\n\",\n    \"[Flax Module](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html).\\n\",\n    \"Because the architecture in this example is relatively simple—you're just\\n\",\n    \"stacking layers—you can define the inlined submodules directly within the\\n\",\n    \"`__call__` method and wrap it with the\\n\",\n    \"[`@compact`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/decorators.html#flax.linen.compact)\\n\",\n    \"decorator. To learn more about the Flax Linen `@compact` decorator, refer to the [`setup` vs `compact`](https://flax.readthedocs.io/en/latest/guides/setup_or_nncompact.html) guide.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 49,\n   \"id\": \"cbc079cd\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from flax import linen as nn  # Linen API\\n\",\n    \"\\n\",\n    \"class CNN(nn.Module):\\n\",\n    \"  \\\"\\\"\\\"A simple CNN model.\\\"\\\"\\\"\\n\",\n    \"\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x = nn.Conv(features=32, kernel_size=(3, 3))(x)\\n\",\n    \"    x = nn.relu(x)\\n\",\n    \"    x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\\n\",\n    \"    x = nn.Conv(features=64, kernel_size=(3, 3))(x)\\n\",\n    \"    x = nn.relu(x)\\n\",\n    \"    x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\\n\",\n    \"    x = x.reshape((x.shape[0], -1))  # flatten\\n\",\n    \"    x = nn.Dense(features=256)(x)\\n\",\n    \"    x = nn.relu(x)\\n\",\n    \"    x = nn.Dense(features=10)(x)\\n\",\n    \"    return x\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"hy7iRu7_zlx-\",\n   \"metadata\": {},\n   \"source\": [\n    \"### View model layers\\n\",\n    \"\\n\",\n    \"Create an instance of the Flax Module and use the [`Module.tabulate`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.tabulate) method to visualize a table of the model layers by passing an RNG key and template image input.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 50,\n   \"id\": \"lDHfog81zLQa\",\n   \"metadata\": {\n    \"outputId\": \"2c580f41-bf5d-40ec-f1cf-ab7f319a84da\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\n\",\n      \"\\u001b[3m                                  CNN Summary                                   \\u001b[0m\\n\",\n      \"┏━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━┓\\n\",\n      \"┃\\u001b[1m \\u001b[0m\\u001b[1mpath   \\u001b[0m\\u001b[1m \\u001b[0m┃\\u001b[1m \\u001b[0m\\u001b[1mmodule\\u001b[0m\\u001b[1m \\u001b[0m┃\\u001b[1m \\u001b[0m\\u001b[1minputs    \\u001b[0m\\u001b[1m \\u001b[0m┃\\u001b[1m \\u001b[0m\\u001b[1moutputs  \\u001b[0m\\u001b[1m \\u001b[0m┃\\u001b[1m \\u001b[0m\\u001b[1mflops  \\u001b[0m\\u001b[1m \\u001b[0m┃\\u001b[1m \\u001b[0m\\u001b[1mvjp_flops\\u001b[0m\\u001b[1m \\u001b[0m┃\\u001b[1m \\u001b[0m\\u001b[1mparams    \\u001b[0m\\u001b[1m \\u001b[0m┃\\n\",\n      \"┡━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━┩\\n\",\n      \"│         │ CNN    │ \\u001b[2mfloat32\\u001b[0m[1… │ \\u001b[2mfloat32\\u001b[0m[… │ 8708106 │ 26957556  │            │\\n\",\n      \"├─────────┼────────┼────────────┼───────────┼─────────┼───────────┼────────────┤\\n\",\n      \"│ Conv_0  │ Conv   │ \\u001b[2mfloat32\\u001b[0m[1… │ \\u001b[2mfloat32\\u001b[0m[… │ 455424  │ 1341472   │ bias:      │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[2mfloat32\\u001b[0m[3… │\\n\",\n      \"│         │        │            │           │         │           │ kernel:    │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[2mfloat32\\u001b[0m[3… │\\n\",\n      \"│         │        │            │           │         │           │            │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[1m320 \\u001b[0m\\u001b[1;2m(1.3 \\u001b[0m  │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[1;2mKB)\\u001b[0m        │\\n\",\n      \"├─────────┼────────┼────────────┼───────────┼─────────┼───────────┼────────────┤\\n\",\n      \"│ Conv_1  │ Conv   │ \\u001b[2mfloat32\\u001b[0m[1… │ \\u001b[2mfloat32\\u001b[0m[… │ 6566144 │ 19704320  │ bias:      │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[2mfloat32\\u001b[0m[6… │\\n\",\n      \"│         │        │            │           │         │           │ kernel:    │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[2mfloat32\\u001b[0m[3… │\\n\",\n      \"│         │        │            │           │         │           │            │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[1m18,496 \\u001b[0m    │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[1;2m(74.0 KB)\\u001b[0m  │\\n\",\n      \"├─────────┼────────┼────────────┼───────────┼─────────┼───────────┼────────────┤\\n\",\n      \"│ Dense_0 │ Dense  │ \\u001b[2mfloat32\\u001b[0m[1… │ \\u001b[2mfloat32\\u001b[0m[… │ 1605888 │ 5620224   │ bias:      │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[2mfloat32\\u001b[0m[2… │\\n\",\n      \"│         │        │            │           │         │           │ kernel:    │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[2mfloat32\\u001b[0m[3… │\\n\",\n      \"│         │        │            │           │         │           │            │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[1m803,072 \\u001b[0m   │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[1;2m(3.2 MB)\\u001b[0m   │\\n\",\n      \"├─────────┼────────┼────────────┼───────────┼─────────┼───────────┼────────────┤\\n\",\n      \"│ Dense_1 │ Dense  │ \\u001b[2mfloat32\\u001b[0m[1… │ \\u001b[2mfloat32\\u001b[0m[… │ 5130    │ 17940     │ bias:      │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[2mfloat32\\u001b[0m[1… │\\n\",\n      \"│         │        │            │           │         │           │ kernel:    │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[2mfloat32\\u001b[0m[2… │\\n\",\n      \"│         │        │            │           │         │           │            │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[1m2,570 \\u001b[0m     │\\n\",\n      \"│         │        │            │           │         │           │ \\u001b[1;2m(10.3 KB)\\u001b[0m  │\\n\",\n      \"├─────────┼────────┼────────────┼───────────┼─────────┼───────────┼────────────┤\\n\",\n      \"│\\u001b[1m \\u001b[0m\\u001b[1m       \\u001b[0m\\u001b[1m \\u001b[0m│\\u001b[1m \\u001b[0m\\u001b[1m      \\u001b[0m\\u001b[1m \\u001b[0m│\\u001b[1m \\u001b[0m\\u001b[1m          \\u001b[0m\\u001b[1m \\u001b[0m│\\u001b[1m \\u001b[0m\\u001b[1m         \\u001b[0m\\u001b[1m \\u001b[0m│\\u001b[1m \\u001b[0m\\u001b[1m       \\u001b[0m\\u001b[1m \\u001b[0m│\\u001b[1m \\u001b[0m\\u001b[1m    Total\\u001b[0m\\u001b[1m \\u001b[0m│\\u001b[1m \\u001b[0m\\u001b[1m824,458   \\u001b[0m\\u001b[1m \\u001b[0m│\\n\",\n      \"│\\u001b[1m         \\u001b[0m│\\u001b[1m        \\u001b[0m│\\u001b[1m            \\u001b[0m│\\u001b[1m           \\u001b[0m│\\u001b[1m         \\u001b[0m│\\u001b[1m           \\u001b[0m│\\u001b[1m \\u001b[0m\\u001b[1;2m(3.3 MB)\\u001b[0m\\u001b[1m  \\u001b[0m\\u001b[1m \\u001b[0m│\\n\",\n      \"└─────────┴────────┴────────────┴───────────┴─────────┴───────────┴────────────┘\\n\",\n      \"\\u001b[1m                                                                                \\u001b[0m\\n\",\n      \"\\u001b[1m                       Total Parameters: 824,458 \\u001b[0m\\u001b[1;2m(3.3 MB)\\u001b[0m\\u001b[1m                       \\u001b[0m\\n\",\n      \"\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"import jax\\n\",\n    \"import jax.numpy as jnp  # JAX NumPy\\n\",\n    \"\\n\",\n    \"cnn = CNN()\\n\",\n    \"print(cnn.tabulate(jax.random.key(0), jnp.ones((1, 28, 28, 1)),\\n\",\n    \"                   compute_flops=True, compute_vjp_flops=True))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"4b5ac16e\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 4. Create a `TrainState`\\n\",\n    \"\\n\",\n    \"A common pattern in Flax is to create a single dataclass that represents the\\n\",\n    \"entire training state, including step number, parameters, and optimizer state.\\n\",\n    \"\\n\",\n    \"Because this is such a common pattern, Flax provides the class\\n\",\n    \"[`flax.training.train_state.TrainState`](https://flax.readthedocs.io/en/latest/flax.training.html#train-state)\\n\",\n    \"that serves most basic usecases.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"qXr7JDpIxGNZ\",\n   \"metadata\": {\n    \"outputId\": \"1249b7fb-6787-41eb-b34c-61d736300844\"\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"!pip install -q clu\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 52,\n   \"id\": \"CJDaJNijyOji\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from clu import metrics\\n\",\n    \"from flax.training import train_state  # Useful dataclass to keep train state\\n\",\n    \"from flax import struct                # Flax dataclasses\\n\",\n    \"import optax                           # Common loss functions and optimizers\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"8b86b5f1\",\n   \"metadata\": {},\n   \"source\": [\n    \"We will be using the `clu` library for computing metrics. For more information on `clu`, refer to the [repo](https://github.com/google/CommonLoopUtils) and [notebook](https://colab.research.google.com/github/google/CommonLoopUtils/blob/master/clu_synopsis.ipynb#scrollTo=ueom-uBWLbeQ).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 53,\n   \"id\": \"7W0qf7FC9uG5\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"@struct.dataclass\\n\",\n    \"class Metrics(metrics.Collection):\\n\",\n    \"  accuracy: metrics.Accuracy\\n\",\n    \"  loss: metrics.Average.from_output('loss')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"f3ce5e4c\",\n   \"metadata\": {},\n   \"source\": [\n    \"You can then subclass `train_state.TrainState` so that it also contains metrics. This has the advantage that we only need\\n\",\n    \"to pass around a single argument to functions like `train_step()` (see below) to calculate the loss, update the parameters and compute the metrics all at once.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 54,\n   \"id\": \"e0102447\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class TrainState(train_state.TrainState):\\n\",\n    \"  metrics: Metrics\\n\",\n    \"\\n\",\n    \"def create_train_state(module, rng, learning_rate, momentum):\\n\",\n    \"  \\\"\\\"\\\"Creates an initial `TrainState`.\\\"\\\"\\\"\\n\",\n    \"  params = module.init(rng, jnp.ones([1, 28, 28, 1]))['params'] # initialize parameters by passing a template image\\n\",\n    \"  tx = optax.sgd(learning_rate, momentum)\\n\",\n    \"  return TrainState.create(\\n\",\n    \"      apply_fn=module.apply, params=params, tx=tx,\\n\",\n    \"      metrics=Metrics.empty())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a15de484\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 5. Training step\\n\",\n    \"\\n\",\n    \"A function that:\\n\",\n    \"\\n\",\n    \"- Evaluates the neural network given the parameters and a batch of input images\\n\",\n    \"  with [`TrainState.apply_fn`](https://flax.readthedocs.io/en/latest/api_reference/flax.training.html#flax.training.train_state.TrainState) (which contains the [`Module.apply`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.apply)\\n\",\n    \"  method (forward pass)).\\n\",\n    \"- Computes the cross entropy loss, using the predefined [`optax.softmax_cross_entropy_with_integer_labels()`](https://optax.readthedocs.io/en/latest/api.html#optax.softmax_cross_entropy_with_integer_labels). Note that this function expects integer labels, so there is no need to convert labels to onehot encoding.\\n\",\n    \"- Evaluates the gradient of the loss function using\\n\",\n    \"  [`jax.grad`](https://jax.readthedocs.io/en/latest/jax.html#jax.grad).\\n\",\n    \"- Applies a\\n\",\n    \"  [pytree](https://jax.readthedocs.io/en/latest/pytrees.html#pytrees-and-jax-functions)\\n\",\n    \"  of gradients to the optimizer to update the model's parameters.\\n\",\n    \"\\n\",\n    \"Use JAX's [@jit](https://jax.readthedocs.io/en/latest/jax.html#jax.jit)\\n\",\n    \"decorator to trace the entire `train_step` function and just-in-time compile\\n\",\n    \"it with [XLA](https://www.tensorflow.org/xla) into fused device operations\\n\",\n    \"that run faster and more efficiently on hardware accelerators.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 55,\n   \"id\": \"9b0af486\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"@jax.jit\\n\",\n    \"def train_step(state, batch):\\n\",\n    \"  \\\"\\\"\\\"Train for a single step.\\\"\\\"\\\"\\n\",\n    \"  def loss_fn(params):\\n\",\n    \"    logits = state.apply_fn({'params': params}, batch['image'])\\n\",\n    \"    loss = optax.softmax_cross_entropy_with_integer_labels(\\n\",\n    \"        logits=logits, labels=batch['label']).mean()\\n\",\n    \"    return loss\\n\",\n    \"  grad_fn = jax.grad(loss_fn)\\n\",\n    \"  grads = grad_fn(state.params)\\n\",\n    \"  state = state.apply_gradients(grads=grads)\\n\",\n    \"  return state\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"0ff5145f\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 6. Metric computation\\n\",\n    \"\\n\",\n    \"Create a separate function for loss and accuracy metrics. Loss is calculated using the `optax.softmax_cross_entropy_with_integer_labels` function, while accuracy is calculated using `clu.metrics`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 56,\n   \"id\": \"961bf70b\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"@jax.jit\\n\",\n    \"def compute_metrics(*, state, batch):\\n\",\n    \"  logits = state.apply_fn({'params': state.params}, batch['image'])\\n\",\n    \"  loss = optax.softmax_cross_entropy_with_integer_labels(\\n\",\n    \"        logits=logits, labels=batch['label']).mean()\\n\",\n    \"  metric_updates = state.metrics.single_from_model_output(\\n\",\n    \"    logits=logits, labels=batch['label'], loss=loss)\\n\",\n    \"  metrics = state.metrics.merge(metric_updates)\\n\",\n    \"  state = state.replace(metrics=metrics)\\n\",\n    \"  return state\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"497241c3\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 7. Download data\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 57,\n   \"id\": \"bff5393e\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"num_epochs = 10\\n\",\n    \"batch_size = 32\\n\",\n    \"\\n\",\n    \"train_ds, test_ds = get_datasets(num_epochs, batch_size)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"id\": \"809ae1a0\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 8. Seed randomness\\n\",\n    \"\\n\",\n    \"- Set the TF random seed to ensure dataset shuffling (with `tf.data.Dataset.shuffle`) is reproducible.\\n\",\n    \"- Get one\\n\",\n    \"  [PRNGKey](https://jax.readthedocs.io/en/latest/_autosummary/jax.random.PRNGKey.html#jax.random.PRNGKey)\\n\",\n    \"  and use it for parameter initialization. (Learn\\n\",\n    \"  more about\\n\",\n    \"  [JAX PRNG design](https://jax.readthedocs.io/en/latest/jax-101/05-random-numbers.html)\\n\",\n    \"  and [PRNG chains](https://flax.readthedocs.io/en/latest/philosophy.html#how-are-parameters-represented-and-how-do-we-handle-general-differentiable-algorithms-that-update-stateful-variables).)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 58,\n   \"id\": \"xC4MFyBsfT-U\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"tf.random.set_seed(0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 59,\n   \"id\": \"e4f6f4d3\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"init_rng = jax.random.key(0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"80fbb60b\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 9. Initialize the `TrainState`\\n\",\n    \"\\n\",\n    \"Remember that the function `create_train_state` initializes the model parameters, optimizer and metrics\\n\",\n    \"and puts them into the training state dataclass that is returned.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 60,\n   \"id\": \"445fcab0\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"learning_rate = 0.01\\n\",\n    \"momentum = 0.9\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 61,\n   \"id\": \"5221eafd\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"state = create_train_state(cnn, init_rng, learning_rate, momentum)\\n\",\n    \"del init_rng  # Must not be used anymore.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"b1c00230\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 10. Train and evaluate\\n\",\n    \"\\n\",\n    \"Create a \\\"shuffled\\\" dataset by:\\n\",\n    \"- Repeating the dataset equal to the number of training epochs\\n\",\n    \"- Allocating a buffer of size 1024 (containing the first 1024 samples in the dataset) of which to randomly sample batches from\\n\",\n    \"  - Everytime a sample is randomly drawn from the buffer, the next sample in the dataset is loaded into the buffer\\n\",\n    \"\\n\",\n    \"Define a training loop that:\\n\",\n    \"- Randomly samples batches from the dataset.\\n\",\n    \"- Runs an optimization step for each training batch.\\n\",\n    \"- Computes the mean training metrics across each batch in an epoch.\\n\",\n    \"- Computes the metrics for the test set using the updated parameters.\\n\",\n    \"- Records the train and test metrics for visualization.\\n\",\n    \"\\n\",\n    \"Once the training and testing is done after 10 epochs, the output should show that your model was able to achieve approximately 99% accuracy.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 62,\n   \"id\": \"74295360\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# since train_ds is replicated num_epochs times in get_datasets(), we divide by num_epochs\\n\",\n    \"num_steps_per_epoch = train_ds.cardinality().numpy() // num_epochs\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 63,\n   \"id\": \"cRtnMZuQFlKl\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"metrics_history = {'train_loss': [],\\n\",\n    \"                   'train_accuracy': [],\\n\",\n    \"                   'test_loss': [],\\n\",\n    \"                   'test_accuracy': []}\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 64,\n   \"id\": \"2c40ce90\",\n   \"metadata\": {\n    \"outputId\": \"258a2c76-2c8f-4a9e-d48b-dde57c342a87\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"train epoch: 1, loss: 0.20290373265743256, accuracy: 93.87000274658203\\n\",\n      \"test epoch: 1, loss: 0.07591685652732849, accuracy: 97.60617065429688\\n\",\n      \"train epoch: 2, loss: 0.05760224163532257, accuracy: 98.28500366210938\\n\",\n      \"test epoch: 2, loss: 0.050395529717206955, accuracy: 98.3974380493164\\n\",\n      \"train epoch: 3, loss: 0.03897436335682869, accuracy: 98.83000183105469\\n\",\n      \"test epoch: 3, loss: 0.04574578255414963, accuracy: 98.54767608642578\\n\",\n      \"train epoch: 4, loss: 0.028721099719405174, accuracy: 99.15166473388672\\n\",\n      \"test epoch: 4, loss: 0.035722777247428894, accuracy: 98.91827392578125\\n\",\n      \"train epoch: 5, loss: 0.021948494017124176, accuracy: 99.37999725341797\\n\",\n      \"test epoch: 5, loss: 0.035723842680454254, accuracy: 98.87820434570312\\n\",\n      \"train epoch: 6, loss: 0.01705147698521614, accuracy: 99.54833221435547\\n\",\n      \"test epoch: 6, loss: 0.03456473350524902, accuracy: 98.96835327148438\\n\",\n      \"train epoch: 7, loss: 0.014007646590471268, accuracy: 99.6116714477539\\n\",\n      \"test epoch: 7, loss: 0.04089202359318733, accuracy: 98.7880630493164\\n\",\n      \"train epoch: 8, loss: 0.011265480890870094, accuracy: 99.73333740234375\\n\",\n      \"test epoch: 8, loss: 0.03337760642170906, accuracy: 98.93830108642578\\n\",\n      \"train epoch: 9, loss: 0.00918484665453434, accuracy: 99.78334045410156\\n\",\n      \"test epoch: 9, loss: 0.034478139132261276, accuracy: 98.96835327148438\\n\",\n      \"train epoch: 10, loss: 0.007260234095156193, accuracy: 99.84166717529297\\n\",\n      \"test epoch: 10, loss: 0.032822880893945694, accuracy: 99.07852172851562\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"for step,batch in enumerate(train_ds.as_numpy_iterator()):\\n\",\n    \"\\n\",\n    \"  # Run optimization steps over training batches and compute batch metrics\\n\",\n    \"  state = train_step(state, batch) # get updated train state (which contains the updated parameters)\\n\",\n    \"  state = compute_metrics(state=state, batch=batch) # aggregate batch metrics\\n\",\n    \"\\n\",\n    \"  if (step+1) % num_steps_per_epoch == 0: # one training epoch has passed\\n\",\n    \"    for metric,value in state.metrics.compute().items(): # compute metrics\\n\",\n    \"      metrics_history[f'train_{metric}'].append(value) # record metrics\\n\",\n    \"    state = state.replace(metrics=state.metrics.empty()) # reset train_metrics for next training epoch\\n\",\n    \"\\n\",\n    \"    # Compute metrics on the test set after each training epoch\\n\",\n    \"    test_state = state\\n\",\n    \"    for test_batch in test_ds.as_numpy_iterator():\\n\",\n    \"      test_state = compute_metrics(state=test_state, batch=test_batch)\\n\",\n    \"\\n\",\n    \"    for metric,value in test_state.metrics.compute().items():\\n\",\n    \"      metrics_history[f'test_{metric}'].append(value)\\n\",\n    \"\\n\",\n    \"    print(f\\\"train epoch: {(step+1) // num_steps_per_epoch}, \\\"\\n\",\n    \"          f\\\"loss: {metrics_history['train_loss'][-1]}, \\\"\\n\",\n    \"          f\\\"accuracy: {metrics_history['train_accuracy'][-1] * 100}\\\")\\n\",\n    \"    print(f\\\"test epoch: {(step+1) // num_steps_per_epoch}, \\\"\\n\",\n    \"          f\\\"loss: {metrics_history['test_loss'][-1]}, \\\"\\n\",\n    \"          f\\\"accuracy: {metrics_history['test_accuracy'][-1] * 100}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"gfsecJzvzgCT\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 11. Visualize metrics\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 65,\n   \"id\": \"Zs5atiqIG9Kz\",\n   \"metadata\": {\n    \"outputId\": \"431a2fcd-44fa-4202-f55a-906555f060ac\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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j7L+MM65Rc652c652RkZh4+Irc/owDLE/z+ISA9RuBMROUb60tx9Q/g9XAxc64+aeTJQ7pwrAZYDk8xsvJlFAVf4+x6TIfz+Dkj69xKR7lKzTBERkR5mZn8APgqkm1kR8G0gEsA59yCwBDgX2AzUADf425rM7FZgKRAOPOqcW9vnL0BERAYk1dyJiAxAZWVl/PrXv+7yceeeey5lZWVdPu7666/nmWee6fJxQ5Vz7krnXKZzLtI5l+Wce8Q596Af7PBHyfy8c+4459ws59yKgGOXOOcm+9t+ELpX0X19/TkVERnqhlS4W1tczlPvHHFCdxGRAeNIX5qbm5uPetySJUtISUnppVKJHGqwfk47K7+IDG1NzS0cqG7gw33VrC4q5/VNe1myuoSn393Bpt2VvfrcQTXLNLNzgPvwmog87Jy7p932zwBf8RergP92zhUc7VgzSwX+CIwDtgOXO+cOdPP1HNVL63Zz37JNLMgbTUK0WqSKyMB11113sWXLFvLy8oiMjCQhIYHMzEzy8/NZt24dF110EYWFhdTV1XH77bezcOFCAMaNG8eKFSuoqqpi/vz5nH766bz55puMHj2av//978TGxnb63MuWLePOO++kqamJk046iQceeIDo6GjuuusuFi9eTEREBGeddRb33nsvf/7zn7n77rsJDw8nOTmZ1157rbffGulH+vpz+tBDD7Fo0SIaGhqYOHEiTz75JHFxcezevZtbbrmFrVu3AvDAAw9w6qmn8sQTT3DvvfdiZuTk5PDkk09y/fXXc/7553PppZcCkJCQQFVVFa+++ip33313UOX/5z//yde+9jWam5tJT0/npZdeYsqUKbz55ptkZGTQ0tLC5MmTefvtt0lPT++DfwkR6YrmFkdlXSMVtU1U1DVSUdvo3zdR3va4kYq6pkO2ta6vbjjyD0DfXTCDSSMSe63snSYcMwsH7gc+iTeK13IzW+ycWxew2zbgTOfcATObDywC5nZy7F3AMufcPWZ2l7/8FXpRbnYKzsHqonJOOS6tN59KRIaQu/+xlnXFFT16zumjkvj2BTOOuP2ee+5hzZo15Ofn8+qrr3LeeeexZs2atiHUH330UVJTU6mtreWkk07iU5/6FGlph/7d27RpE3/4wx946KGHuPzyy/nLX/7C1VdffdRy1dXVcf3117Ns2TImT57MtddeywMPPMC1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6qmnAvCjH/2IJ598krCwMObPn8/NN9/MZZddxnvvvQfApk2buOKKK1i5cmUfvOuDV21DMx/srjwsyJXXHqyNyxoWy7TMJM6dOZKpmV6QG5Map1o4GTKGbrjLTgGgoLBM4U5EuueFu2DX6p4958hZMP+eI27u7S/NgX7605/y6KOPAnDTTTdxxx13dHjuT3/609x1110sXryYiIgIzjrrLO69994ee0ukG0LwGQVv4u41a9aQn5/Piy++yDPPPMO7776Lc44LL7yQ1157jdLSUkaNGsXzzz8PQHl5OcnJyfz0pz/lX//611Frj3/wgx+QmppKc3Mz8+bNY9WqVUydOpVPf/rT/PGPf+Skk06ioqKC2NhYFi1axLZt23j//feJiIhg//79nb7ElStX8vrrrxMbG0tNTQ0vvfQSMTExbNq0iSuvvJIVK1bwwgsv8Le//Y133nmHuLg49u/fT2pqKsnJyeTn55OXl8djjz3G9ddf36W3dyhzzrGzrJYNJQFBblcF2/dWt9XGxUV5tXHn5WQybWQiU/3auKQY1cbJ0DZkw924tDiSYiIoKCrjijljQl0cEZEu6e0vza1WrlzJY489xjvvvINzjrlz53LmmWeydevWw869f/9+nn32WTZs2ICZUVZW1ptvgQwwL774Ii+++CLHH388AFVVVWzatImPfOQj3HnnnXzlK1/h/PPP5yMf+UjQ5/zTn/7EokWLaGpqoqSkhHXr1mFmZGZmctJJJwGQlOT9gPvyyy9zyy23tDWvTE1N7fT8F154IbGxsQA0NjZy6623kp+fT3h4OB988EHbeW+44Ya2mvDW895000089thj/PSnP+WPf/wj7777btCvayipaWhi465KNuyqZEPJwSAXOGXAmNQ4po5M5IKcUUzLTGTqSK82Lky1cSKHGbLhzszIzU6hoLA81EURkYGuk9qL3tYbX5pbvf7661x88cXEx8cDcMkll/Cf//yHc84557BzNzU1ERMTw0033cR5553H+eef36OvU7ohxJ9R8GpjvvrVr/Jf//Vfh21buXIlS5Ys4atf/SpnnXUW3/rWtzo937Zt27j33ntZvnw5w4YN4/rrr6eurg7nXIcjFR5pfUREBC0tLQDU1dUdsq31cw/ws5/9jBEjRlBQUEBLSwsxMTFHPe+nPvUp7r77bj7+8Y9z4oknkpaW1ulrGuz2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\",\n      \"text/plain\": [\n       \"<Figure size 1500x500 with 2 Axes>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<Figure size 600x400 with 0 Axes>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"import matplotlib.pyplot as plt  # Visualization\\n\",\n    \"\\n\",\n    \"# Plot loss and accuracy in subplots\\n\",\n    \"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\\n\",\n    \"ax1.set_title('Loss')\\n\",\n    \"ax2.set_title('Accuracy')\\n\",\n    \"for dataset in ('train','test'):\\n\",\n    \"  ax1.plot(metrics_history[f'{dataset}_loss'], label=f'{dataset}_loss')\\n\",\n    \"  ax2.plot(metrics_history[f'{dataset}_accuracy'], label=f'{dataset}_accuracy')\\n\",\n    \"ax1.legend()\\n\",\n    \"ax2.legend()\\n\",\n    \"plt.show()\\n\",\n    \"plt.clf()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"qQbKS0tV3sZ1\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 12. Perform inference on test set\\n\",\n    \"\\n\",\n    \"Define a jitted inference function `pred_step`. Use the learned parameters to do model inference on the test set and visualize the images and their corresponding predicted labels.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 66,\n   \"id\": \"DFwxgBQf44ks\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"@jax.jit\\n\",\n    \"def pred_step(state, batch):\\n\",\n    \"  logits = state.apply_fn({'params': state.params}, test_batch['image'])\\n\",\n    \"  return logits.argmax(axis=1)\\n\",\n    \"\\n\",\n    \"test_batch = test_ds.as_numpy_iterator().next()\\n\",\n    \"pred = pred_step(state, test_batch)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 67,\n   \"id\": \"5d5nF3u44JFI\",\n   \"metadata\": {\n    \"outputId\": \"1db5a01c-9d70-4f7d-8c0d-0a3ad8252d3e\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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AADgHa97UqtVq2bysGHDTHb7v4444oikjxmrH+STTz4xOScnx2S3pzXW/sqXL2/yAw88EHX/J510UuSgowhL/1i6uT9ndy3J4447zuS5c+eafOWVV0bs012XN+zC2D/m9lLdcMMNUbdfvny5ye56grH06NHD5EMPPdTk6tWrJ7S/grh977fccovJ06dPT/oYyQjLOcX9N//ll1+afPjhh5vsrnvqrqtdqpRdVvy8884z2V3X210X1ZWJntRY+3v//fdN7tChQ1LHd4XxnOJasGCBye5rhfszf+KJJ0zu1q2bye45w/X222+bPHbs2Iht3HW93fdHuGstu2NcuHChySeccELUMaUbPakAAAAIFYpUAAAAeIciFQAAAN7xuic1lkqVKpncq1cvk90e1WuuucbkqlWrRuwz1WuUpXp/bl9ULGHpH0s3d03c888/3+T9+/eb7PY2umv2FkVFoX/soYceMrlfv34mly5dOp2HL5C7frO7FqU75kcffTTq87MtrOeUmTNnmnzaaaeZ7K5Hu2fPHpPd9TGTPZdv3rw54r7HH3/cZLd30P2c92+//dZkd21Yd/vZs2eb/NZbb5m8YsWKKCNOXFE4p7h96BMnTnSPb3Ki82LWrFkmd+3a1eRdu3bF3EeDBg1MHjdunMktWrQwedKkSSa7awRnGj2pAAAACBWKVAAAAHiHIhUAAADeCXVPaqIOPvhgk9u1axexzSmnnGKy+xnvbu9H/fr1ox4z2X4Vt7fkqquuSuj5Ye0fS5a7nuybb75psrsG76hRo0zu379/OobltaLQP+Zy130cM2aMyXXr1jU50Z5vt49x2rRpEdu4/dDuWsphE9ZzStu2bU2eOnWqye57HAoYj8mxzuVuT6vb/9mnT5+I5/z8889R9xk2ReGc4q63+9xzz5ns9qy68+K7774zefjw4SaPHz/e5F9++aVQ44zGrWOWLFmS8mMkg55UAAAAhApFKgAAALxDkQoAAADvFKue1FRw+1rd7Orbt29C+1+3bp3JDz/8sMm7d+9OaH9h7R9LlNsr7H7+ubs+5kcffWTy2WefbXJR6w2LR1HoH0uU27tcs2bNhJ7vrju5c+fOpMfku6JyTjnjjDNMvuKKK0w+77zz3PGY7K41+dprr5m8bNkykxcvXlyYYYZacTynIHH0pAIAACBUKFIBAADgHYpUAAAAeIee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AHhHgyD4/QdVf//BNGjTpo3Js2bNMrl06dImq6rJ0b4XEZENGzaYvH///ohtatWqFXWf3bt3N/mNN96IesxsC4JAY2+VvEzPlTBasWKFyQ888IDJTz31VCaHEyETc6WozZNy5cqZ3Lp164ht3PPa4YcfbvJZZ51l8tKlS012+xLvvvtuk7dt2xbXWFOFc0qul156yeQLLrgg6X3Gek37/vvvTR41apTJI0eOTHoMqcQ5JTvatWtn8tixY01u0KCByW5PqvtadfTRR6ducAWINk+4kgoAAADvUKQCAADAOxSpAAAA8I5XPamu+++/3+SBAwea7K5R+sEHH5js9pcecsghJrv9PSIiW7ZsMfmLL74w+YknnjD5hx9+iNiHT+gfy3XppZeavHbtWpPfeeedlB/TXZvu008/NfmFF14wuU+fPikfQyLoH4vkrn15xx13mNy1a1eT3XNOOsycOdPk888/3+Tt27en9fjF5ZxSvnx5k59//nmTzzjjDJMrVKiQ9DETfZ+F+76K9957z+SLLrrI5J9++qnwgysEzinpcfDBB5vcuHFjkydMmGByzZo1Td65c6fJ7uuh+3rpvnalGj2pAAAACBWKVAAAAHiHIhUAAADeKZXtAUQzefJkk92eVPdzi88991yTy5QpEzXv3r074ph79+5NeJzwT8+ePU121yA99dRT0z6GE044wWS336ygnmhkVokS9u/0k08+2WR3HeTq1atH3V9B5xT389hXrlyZyBAj1t90e50rVqxocrp7UouLKlWqmOyukZ2sTZs2Rdznzh93/rmvcX/4wx9M7tixo8m9e/c22bd1VFE4bdu2NfnJJ580uWrVqlGf/91335ns9i5//vnnSYwutbiSCgAAAO9QpAIAAMA7FKkAAADwjtc9qccee2xSz9+zZ0/UjKLDXaPQ/XzzHTt2mJyTk5PuIUmPHj1MXr9+vclunywyz12L+ZZbbkno+dOmTTP5yiuvjNgm2bUpBw0aZLLbqz9//nyTO3ToYPKqVauSOn5xdeaZZ0Z9/OOPPzb5scceM7l58+YmL1682GR37ohErsv7zTffmHz99deb/O2335p8xBFHmOyu5froo4+azHswwsntRS5dunRCz3fXVW3Tpo3J9KQCAAAAUVCkAgAAwDsUqQAAAPCO1z2py5cvN9ldZ9JVt25dk+vXr2+yu25l2bJlI/YxY8YMk7/44ouY40T23XjjjSY3adLEZHcduTVr1qR8DC1btjT5lFNOMdld13f16tUpHwOsQw45xORRo0aZfN5550V9/tatW03+y1/+YvInn3xisvtZ6oXhrnF43XXXmdy+ffuoz3/mmWdMzsSawEVRo0aNoj7urnPsfl66m+Ph9qDG8vLLL5t82223JXxM+O2uu+6KuG/IkCFJ7fO+++4z2e2n9glXUgEAAOAdilQAAAB4hyIVAAAA3vG6J/Wss84yOQgCk9015WbPnm1yw4YNEz6mu77mww8/bLK7RqG79t2BAwcSPiaS5/YebtmyxeR///vfaR9DuXLlTC5Vyut/XsVCr169TO7Zs2fU7X/88UeTTzrpJJPdz7xOhWbNmpnsfr56rB5U9/Pe//Wvf6ViWIjB/fddsWJFk921meNRooS9buT2xd5zzz0mx+qt//nnn03et29fwmNCZvXt29fkgvpPY9UZc+fONdmtW3zuQXVxJRUAAADeoUgFAACAdyhSAQAA4B2vm+Y6deoU9fEGDRqY7Pasrlu3zuRZs2aZvGTJkoh9uusg3n777VGzuy7diBEjfn/ASJkyZcqY3LVrV5Nfeuklk7/66qu0j+m0006L+rjbw+b20br9kEjeBRdcEPVxdx3UHj16mJyOHtRLLrnE5AEDBph87LHHJrS//v37m/zGG28Ualyw3NcL91zvzpVu3bqZ3KdPH5PdPsGCXt/ctb7vvffeuMb6m19//dVkd21xt29+586dCe0fqVetWjWTY/XNx8OtjebNm5f0PrOFK6kAAADwDkUqAAAAvEORCgAAAO943ZNar169hLZ3+3cefPBBk7dt2xZzH+5ne3fs2NHkiRMnmux+Bq67buo777wT85hIXNOmTU2uX7++yW6vYTqULl3aZHdNTddRRx1l8owZM0w+/vjjUzOwYqx8+fImV6pUKer2OTk5Jn/00UcJHa958+Ymu73RIpG9i+7al+48imXTpk0mP/PMMwk9H/H573//a7L7HofatWub7Pacv/jiiya7a5q6/acikT2k7vssYpkyZYrJF198cULPR/q5Pelnn322yaecckrC+3Rf7wYOHGjywoULE96nL7iSCgAAAO9QpAIAAMA7FKkAAADwjtc9qdOnTzfZ7eW4/PLLTXb7RQuzBtzevXtNnjlzpsnnn3++ya+99prJzz33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\",\n      \"text/plain\": [\n       \"<Figure size 1200x1200 with 25 Axes>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"fig, axs = plt.subplots(5, 5, figsize=(12, 12))\\n\",\n    \"for i, ax in enumerate(axs.flatten()):\\n\",\n    \"    ax.imshow(test_batch['image'][i, ..., 0], cmap='gray')\\n\",\n    \"    ax.set_title(f\\\"label={pred[i]}\\\")\\n\",\n    \"    ax.axis('off')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"edb528b6\",\n   \"metadata\": {},\n   \"source\": [\n    \"Congratulations! You made it to the end of the annotated MNIST example. You can revisit\\n\",\n    \"the same example, but structured differently as a couple of Python modules, test\\n\",\n    \"modules, config files, another Colab, and documentation in Flax's Git repo:\\n\",\n    \"\\n\",\n    \"[https://github.com/google/flax/tree/main/examples/mnist](https://github.com/google/flax/tree/main/examples/mnist)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\",\n   \"main_language\": \"python\"\n  },\n  \"language_info\": {\n   \"name\": \"python\",\n   \"version\": \"3.9.6\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs/quick_start.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  main_language: python\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google/flax/blob/main/docs/quick_start.ipynb)\n[![Open On GitHub](https://img.shields.io/badge/Open-on%20GitHub-blue?logo=GitHub)](https://github.com/google/flax/blob/main/docs/quick_start.ipynb)\n\n# Quick start\n\nWelcome to Flax!\n\nFlax is an open source Python neural network library built on top of [JAX](https://github.com/jax-ml/jax). This tutorial demonstrates how to construct a simple convolutional neural\nnetwork (CNN) using the [Flax](https://flax.readthedocs.io) Linen API and train\nthe network for image classification on the MNIST dataset.\n\n+++\n\n## 1. Install Flax\n\n```{code-cell}\n:tags: [skip-execution]\n\n!pip install -q flax>=0.7.5\n```\n\n## 2. Loading data\n\nFlax can use any\ndata-loading pipeline and this example demonstrates how to utilize TFDS. Define a function that loads and prepares the MNIST dataset and converts the\nsamples to floating-point numbers.\n\n```{code-cell}\nimport tensorflow_datasets as tfds  # TFDS for MNIST\nimport tensorflow as tf             # TensorFlow operations\n\ndef get_datasets(num_epochs, batch_size):\n  \"\"\"Load MNIST train and test datasets into memory.\"\"\"\n  train_ds = tfds.load('mnist', split='train')\n  test_ds = tfds.load('mnist', split='test')\n\n  train_ds = train_ds.map(lambda sample: {'image': tf.cast(sample['image'],\n                                                           tf.float32) / 255.,\n                                          'label': sample['label']}) # normalize train set\n  test_ds = test_ds.map(lambda sample: {'image': tf.cast(sample['image'],\n                                                         tf.float32) / 255.,\n                                        'label': sample['label']}) # normalize test set\n\n  train_ds = train_ds.repeat(num_epochs).shuffle(1024) # create shuffled dataset by allocating a buffer size of 1024 to randomly draw elements from\n  train_ds = train_ds.batch(batch_size, drop_remainder=True).prefetch(1) # group into batches of batch_size and skip incomplete batch, prefetch the next sample to improve latency\n  test_ds = test_ds.shuffle(1024) # create shuffled dataset by allocating a buffer size of 1024 to randomly draw elements from\n  test_ds = test_ds.batch(batch_size, drop_remainder=True).prefetch(1) # group into batches of batch_size and skip incomplete batch, prefetch the next sample to improve latency\n\n  return train_ds, test_ds\n```\n\n## 3. Define network\n\nCreate a convolutional neural network with the Linen API by subclassing\n[Flax Module](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html).\nBecause the architecture in this example is relatively simple—you're just\nstacking layers—you can define the inlined submodules directly within the\n`__call__` method and wrap it with the\n[`@compact`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/decorators.html#flax.linen.compact)\ndecorator. To learn more about the Flax Linen `@compact` decorator, refer to the [`setup` vs `compact`](https://flax.readthedocs.io/en/latest/guides/setup_or_nncompact.html) guide.\n\n```{code-cell}\nfrom flax import linen as nn  # Linen API\n\nclass CNN(nn.Module):\n  \"\"\"A simple CNN model.\"\"\"\n\n  @nn.compact\n  def __call__(self, x):\n    x = nn.Conv(features=32, kernel_size=(3, 3))(x)\n    x = nn.relu(x)\n    x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n    x = nn.Conv(features=64, kernel_size=(3, 3))(x)\n    x = nn.relu(x)\n    x = nn.avg_pool(x, window_shape=(2, 2), strides=(2, 2))\n    x = x.reshape((x.shape[0], -1))  # flatten\n    x = nn.Dense(features=256)(x)\n    x = nn.relu(x)\n    x = nn.Dense(features=10)(x)\n    return x\n```\n\n### View model layers\n\nCreate an instance of the Flax Module and use the [`Module.tabulate`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.tabulate) method to visualize a table of the model layers by passing an RNG key and template image input.\n\n```{code-cell}\n:outputId: 2c580f41-bf5d-40ec-f1cf-ab7f319a84da\n\nimport jax\nimport jax.numpy as jnp  # JAX NumPy\n\ncnn = CNN()\nprint(cnn.tabulate(jax.random.key(0), jnp.ones((1, 28, 28, 1)),\n                   compute_flops=True, compute_vjp_flops=True))\n```\n\n## 4. Create a `TrainState`\n\nA common pattern in Flax is to create a single dataclass that represents the\nentire training state, including step number, parameters, and optimizer state.\n\nBecause this is such a common pattern, Flax provides the class\n[`flax.training.train_state.TrainState`](https://flax.readthedocs.io/en/latest/flax.training.html#train-state)\nthat serves most basic usecases.\n\n```{code-cell}\n:outputId: 1249b7fb-6787-41eb-b34c-61d736300844\n\n!pip install -q clu\n```\n\n```{code-cell}\nfrom clu import metrics\nfrom flax.training import train_state  # Useful dataclass to keep train state\nfrom flax import struct                # Flax dataclasses\nimport optax                           # Common loss functions and optimizers\n```\n\nWe will be using the `clu` library for computing metrics. For more information on `clu`, refer to the [repo](https://github.com/google/CommonLoopUtils) and [notebook](https://colab.research.google.com/github/google/CommonLoopUtils/blob/master/clu_synopsis.ipynb#scrollTo=ueom-uBWLbeQ).\n\n```{code-cell}\n@struct.dataclass\nclass Metrics(metrics.Collection):\n  accuracy: metrics.Accuracy\n  loss: metrics.Average.from_output('loss')\n```\n\nYou can then subclass `train_state.TrainState` so that it also contains metrics. This has the advantage that we only need\nto pass around a single argument to functions like `train_step()` (see below) to calculate the loss, update the parameters and compute the metrics all at once.\n\n```{code-cell}\nclass TrainState(train_state.TrainState):\n  metrics: Metrics\n\ndef create_train_state(module, rng, learning_rate, momentum):\n  \"\"\"Creates an initial `TrainState`.\"\"\"\n  params = module.init(rng, jnp.ones([1, 28, 28, 1]))['params'] # initialize parameters by passing a template image\n  tx = optax.sgd(learning_rate, momentum)\n  return TrainState.create(\n      apply_fn=module.apply, params=params, tx=tx,\n      metrics=Metrics.empty())\n```\n\n## 5. Training step\n\nA function that:\n\n- Evaluates the neural network given the parameters and a batch of input images\n  with [`TrainState.apply_fn`](https://flax.readthedocs.io/en/latest/api_reference/flax.training.html#flax.training.train_state.TrainState) (which contains the [`Module.apply`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module.apply)\n  method (forward pass)).\n- Computes the cross entropy loss, using the predefined [`optax.softmax_cross_entropy_with_integer_labels()`](https://optax.readthedocs.io/en/latest/api.html#optax.softmax_cross_entropy_with_integer_labels). Note that this function expects integer labels, so there is no need to convert labels to onehot encoding.\n- Evaluates the gradient of the loss function using\n  [`jax.grad`](https://jax.readthedocs.io/en/latest/jax.html#jax.grad).\n- Applies a\n  [pytree](https://jax.readthedocs.io/en/latest/pytrees.html#pytrees-and-jax-functions)\n  of gradients to the optimizer to update the model's parameters.\n\nUse JAX's [@jit](https://jax.readthedocs.io/en/latest/jax.html#jax.jit)\ndecorator to trace the entire `train_step` function and just-in-time compile\nit with [XLA](https://www.tensorflow.org/xla) into fused device operations\nthat run faster and more efficiently on hardware accelerators.\n\n```{code-cell}\n@jax.jit\ndef train_step(state, batch):\n  \"\"\"Train for a single step.\"\"\"\n  def loss_fn(params):\n    logits = state.apply_fn({'params': params}, batch['image'])\n    loss = optax.softmax_cross_entropy_with_integer_labels(\n        logits=logits, labels=batch['label']).mean()\n    return loss\n  grad_fn = jax.grad(loss_fn)\n  grads = grad_fn(state.params)\n  state = state.apply_gradients(grads=grads)\n  return state\n```\n\n## 6. Metric computation\n\nCreate a separate function for loss and accuracy metrics. Loss is calculated using the `optax.softmax_cross_entropy_with_integer_labels` function, while accuracy is calculated using `clu.metrics`.\n\n```{code-cell}\n@jax.jit\ndef compute_metrics(*, state, batch):\n  logits = state.apply_fn({'params': state.params}, batch['image'])\n  loss = optax.softmax_cross_entropy_with_integer_labels(\n        logits=logits, labels=batch['label']).mean()\n  metric_updates = state.metrics.single_from_model_output(\n    logits=logits, labels=batch['label'], loss=loss)\n  metrics = state.metrics.merge(metric_updates)\n  state = state.replace(metrics=metrics)\n  return state\n```\n\n## 7. Download data\n\n```{code-cell}\nnum_epochs = 10\nbatch_size = 32\n\ntrain_ds, test_ds = get_datasets(num_epochs, batch_size)\n```\n\n## 8. Seed randomness\n\n- Set the TF random seed to ensure dataset shuffling (with `tf.data.Dataset.shuffle`) is reproducible.\n- Get one\n  [PRNGKey](https://jax.readthedocs.io/en/latest/_autosummary/jax.random.PRNGKey.html#jax.random.PRNGKey)\n  and use it for parameter initialization. (Learn\n  more about\n  [JAX PRNG design](https://jax.readthedocs.io/en/latest/jax-101/05-random-numbers.html)\n  and [PRNG chains](https://flax.readthedocs.io/en/latest/philosophy.html#how-are-parameters-represented-and-how-do-we-handle-general-differentiable-algorithms-that-update-stateful-variables).)\n\n```{code-cell}\ntf.random.set_seed(0)\n```\n\n```{code-cell}\ninit_rng = jax.random.key(0)\n```\n\n## 9. Initialize the `TrainState`\n\nRemember that the function `create_train_state` initializes the model parameters, optimizer and metrics\nand puts them into the training state dataclass that is returned.\n\n```{code-cell}\nlearning_rate = 0.01\nmomentum = 0.9\n```\n\n```{code-cell}\nstate = create_train_state(cnn, init_rng, learning_rate, momentum)\ndel init_rng  # Must not be used anymore.\n```\n\n## 10. Train and evaluate\n\nCreate a \"shuffled\" dataset by:\n- Repeating the dataset equal to the number of training epochs\n- Allocating a buffer of size 1024 (containing the first 1024 samples in the dataset) of which to randomly sample batches from\n  - Everytime a sample is randomly drawn from the buffer, the next sample in the dataset is loaded into the buffer\n\nDefine a training loop that:\n- Randomly samples batches from the dataset.\n- Runs an optimization step for each training batch.\n- Computes the mean training metrics across each batch in an epoch.\n- Computes the metrics for the test set using the updated parameters.\n- Records the train and test metrics for visualization.\n\nOnce the training and testing is done after 10 epochs, the output should show that your model was able to achieve approximately 99% accuracy.\n\n```{code-cell}\n# since train_ds is replicated num_epochs times in get_datasets(), we divide by num_epochs\nnum_steps_per_epoch = train_ds.cardinality().numpy() // num_epochs\n```\n\n```{code-cell}\nmetrics_history = {'train_loss': [],\n                   'train_accuracy': [],\n                   'test_loss': [],\n                   'test_accuracy': []}\n```\n\n```{code-cell}\n:outputId: 258a2c76-2c8f-4a9e-d48b-dde57c342a87\n\nfor step,batch in enumerate(train_ds.as_numpy_iterator()):\n\n  # Run optimization steps over training batches and compute batch metrics\n  state = train_step(state, batch) # get updated train state (which contains the updated parameters)\n  state = compute_metrics(state=state, batch=batch) # aggregate batch metrics\n\n  if (step+1) % num_steps_per_epoch == 0: # one training epoch has passed\n    for metric,value in state.metrics.compute().items(): # compute metrics\n      metrics_history[f'train_{metric}'].append(value) # record metrics\n    state = state.replace(metrics=state.metrics.empty()) # reset train_metrics for next training epoch\n\n    # Compute metrics on the test set after each training epoch\n    test_state = state\n    for test_batch in test_ds.as_numpy_iterator():\n      test_state = compute_metrics(state=test_state, batch=test_batch)\n\n    for metric,value in test_state.metrics.compute().items():\n      metrics_history[f'test_{metric}'].append(value)\n\n    print(f\"train epoch: {(step+1) // num_steps_per_epoch}, \"\n          f\"loss: {metrics_history['train_loss'][-1]}, \"\n          f\"accuracy: {metrics_history['train_accuracy'][-1] * 100}\")\n    print(f\"test epoch: {(step+1) // num_steps_per_epoch}, \"\n          f\"loss: {metrics_history['test_loss'][-1]}, \"\n          f\"accuracy: {metrics_history['test_accuracy'][-1] * 100}\")\n```\n\n## 11. Visualize metrics\n\n```{code-cell}\n:outputId: 431a2fcd-44fa-4202-f55a-906555f060ac\n\nimport matplotlib.pyplot as plt  # Visualization\n\n# Plot loss and accuracy in subplots\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\nax1.set_title('Loss')\nax2.set_title('Accuracy')\nfor dataset in ('train','test'):\n  ax1.plot(metrics_history[f'{dataset}_loss'], label=f'{dataset}_loss')\n  ax2.plot(metrics_history[f'{dataset}_accuracy'], label=f'{dataset}_accuracy')\nax1.legend()\nax2.legend()\nplt.show()\nplt.clf()\n```\n\n## 12. Perform inference on test set\n\nDefine a jitted inference function `pred_step`. Use the learned parameters to do model inference on the test set and visualize the images and their corresponding predicted labels.\n\n```{code-cell}\n@jax.jit\ndef pred_step(state, batch):\n  logits = state.apply_fn({'params': state.params}, test_batch['image'])\n  return logits.argmax(axis=1)\n\ntest_batch = test_ds.as_numpy_iterator().next()\npred = pred_step(state, test_batch)\n```\n\n```{code-cell}\n:outputId: 1db5a01c-9d70-4f7d-8c0d-0a3ad8252d3e\n\nfig, axs = plt.subplots(5, 5, figsize=(12, 12))\nfor i, ax in enumerate(axs.flatten()):\n    ax.imshow(test_batch['image'][i, ..., 0], cmap='gray')\n    ax.set_title(f\"label={pred[i]}\")\n    ax.axis('off')\n```\n\nCongratulations! You made it to the end of the annotated MNIST example. You can revisit\nthe same example, but structured differently as a couple of Python modules, test\nmodules, config files, another Colab, and documentation in Flax's Git repo:\n\n[https://github.com/google/flax/tree/main/examples/mnist](https://github.com/google/flax/tree/main/examples/mnist)\n"
  },
  {
    "path": "docs/robots.txt",
    "content": "User-agent: *\n\nDisallow: /api_reference/flax.linen/_autosummary/ # for SEO, since Google still indexes this deprecated link\n\nSitemap: https://flax.readthedocs.io/sitemap.xml\n"
  },
  {
    "path": "docs_nnx/.gitignore",
    "content": "_formatted_howtos\n"
  },
  {
    "path": "docs_nnx/.readthedocs.yaml",
    "content": "# .readthedocs.yml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.12\"\n  jobs:\n    pre_build:\n      - pip install \".[all, testing, docs]\"\n      - pip install -U --pre jax jaxlib -i https://us-python.pkg.dev/ml-oss-artifacts-published/jax/simple/\n\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n  configuration: docs_nnx/conf.py\n\n# Optionally build your docs in additional formats such as PDF and ePub\nformats:\n  - htmlzip\n  - epub\n  # - pdf\n"
  },
  {
    "path": "docs_nnx/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the environment for the first two.\nSPHINXOPTS    ?=\nSPHINXBUILD   ?= sphinx-build\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "docs_nnx/README.md",
    "content": "# Where to find the docs\n\nThe FLAX documentation can be found here:\nhttps://flax.readthedocs.io/en/latest/\n\n# How to build the docs\n\n1. Clone the `flax` repository with `git clone https://github.com/google/flax.git`.\n1. In the main `flax` folder, install the required dependencies using `uv pip install -e .[docs]`.\n1. [Optional] If you need to make any local changes to the docs, create and switch to a branch. Make your changes to the docs in that branch.\n1. To build the docs, in the `flax/docs_nnx` folder run the make script: `make html`. Alternatively, install [`entr`](https://github.com/eradman/entr/), which helps run arbitrary commands when files change. Then run `find ../ ! -regex '.*/[\\.|\\_].*' | entr -s 'make html'`.\n1. If the build is successful, you should get the `The HTML pages are in _build/html.` message. You can preview the docs in `flax/docs/_build/html`.\n\n# How to run embedded code tests\n\nWe use `doctest` blocks for embedded code in documents, that are also\ntested. Learn more at https://www.sphinx-doc.org/en/master/usage/extensions/doctest.html\n\nTo run tests locally, run `make doctest`\n\n# How to write code documentation\n\nOur documentation is written in reStructuredText for Sphinx. It is a\nmeta-language that is compiled into online documentation. For more details,\ncheck out\n[Sphinx's documentation](https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html).\nAs a result, our docstrings adhere to a specific syntax that has to be kept in\nmind. Below we provide some guidelines.\n\nTo learn how to contribute to Jupyter Notebooks or other formats in Flax docs,\nrefer to the dedicated\n[Contributing](https://flax.readthedocs.io/en/latest/contributing.html) page.\n\n## How much information to put in a docstring\n\nDocstring should be informative. We prefer to err on the side of too much\ndocumentation than too little. For instance, providing a one-line explanation\nto a new `Module` which implements new functionality is not sufficient.\n\nFurthermore, we highly encourage adding examples to your docstrings, so users\ncan directly see how code can be used.\n\n## How to write inline tested code\n\nWe use [doctest](https://docs.python.org/3/library/doctest.html) syntax for\nwriting examples in documentation. These examples are ran as tests as part of\nour CI process. In order to write `doctest` code in your documentation, please\nuse the following notation:\n\n```bash\n# Example code::\n#\n#   def sum(a, b):\n#     return a + b\n#\n#   sum(0, 1)\n```\n\nThe `Example code` string at the beginning can be replaced by anything as long\nas there are two semicolons and a newline following it, and the code is\nindented.\n\n## How to use \"code font\"\n\nWhen writing code font in a docstring, please use double backticks. Example:\n\n```bash\n# This returns a ``str`` object.\n```\n\nNote that argument names and objects like True, None or any strings should\nusually be put in `code`.\n\n## How to create cross-references/links\n\nIt is possible to create cross-references to other classes, functions, and\nmethods. In the following, `obj_typ` is either `class`, `func`, or `meth`.\n\n```bash\n# First method:\n# <obj_type>:`path_to_obj`\n\n# Second method:\n# :<obj_type>:`description <path_to_obj>`\n```\n\nYou can use the second method if the `path_to_obj` is very long. Some examples:\n\n```bash\n# Create: a reference to class flax.linen.Module.\n# :class:`flax.linen.Module`\n\n# Create a reference to local function my_func.\n# :func:`my_func`\n\n# Create a reference \"Module.apply()\" to method flax.linen.Module.apply.\n# :meth:`Module.apply() <flax.linen.Module.apply>`  #\n```\n\nTo creata a hyperlink, use the following syntax:\n```bash\n# Note the double underscore at the end:\n# `Link to Google <http://www.google.com>`__\n```\n\n### How to specify arguments for classes and methods\n\n*  Class attributes should be specified using the `Attributes:` tag.\n*  Method argument should be specified using the `Args:` tags.\n*  All attributes and arguments should have types.\n\nHere is an example from our library:\n\n```python\nclass DenseGeneral(Module):\n  \"\"\"A linear transformation with flexible axes.\n    Attributes:\n      features: int or tuple with number of output features.\n      axis: int or tuple with axes to apply the transformation on. For instance,\n        (-2, -1) will apply the transformation to the last two axes.\n      batch_dims: tuple with batch axes.\n      use_bias: whether to add a bias to the output (default: True).\n      dtype: the dtype of the computation (default: float32).\n      kernel_init: initializer function for the weight matrix.\n      bias_init: initializer function for the bias.\n      precision: numerical precision of the computation see `jax.lax.Precision`\n        for details.\n  \"\"\"\n  features: Union[int, Iterable[int]]\n  axis: Union[int, Iterable[int]] = -1\n  batch_dims: Iterable[int] = ()\n  use_bias: bool = True\n  dtype: Dtype = jnp.float32\n  kernel_init: Callable[[PRNGKey, Shape, Dtype], Array] = default_kernel_init\n  bias_init: Callable[[PRNGKey, Shape, Dtype], Array] = zeros\n  precision: Any = None\n\n  @compact\n  def __call__(self, inputs: Array) -> Array:\n    \"\"\"Applies a linear transformation to the inputs along multiple dimensions.\n    Args:\n      inputs: The nd-array to be transformed.\n    Returns:\n      The transformed input.\n    \"\"\"\n    ...\n```"
  },
  {
    "path": "docs_nnx/_ext/codediff.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Sphinx directive for creating code diff tables.\n\nUse directive as follows:\n\n.. codediff::\n  :title: <LEFT_CODE_BLOCK_TITLE>, <RIGHT_CODE_BLOCK_TITLE>\n\n  <CODE_BLOCK_LEFT>\n  ---\n  <CODE_BLOCK_RIGHT>\n\nIn order to highlight a line of code, append \"#!\" to it.\n\"\"\"\n\n\nimport sphinx\nfrom docutils import nodes\nfrom docutils.parsers.rst import directives\nfrom docutils.statemachine import ViewList\nfrom sphinx.util.docutils import SphinxDirective\n\nMISSING = object()\n\n\nclass CodeDiffParser:\n  def parse(\n    self,\n    lines: list[str],\n    title: str,\n    groups: list[str] | None = None,\n    skip_test: str | None = None,\n    code_sep: str = '---',\n    sync: object = MISSING,\n  ):\n    \"\"\"Parse the code diff block and format it so that it\n    renders in different tabs and is tested by doctest.\n\n    For example:\n\n      .. testcode:: tab0, tab2, tab3\n\n        <CODE_BLOCK_A>\n\n      .. codediff::\n        :title: Tab 0, Tab 1, Tab 2, Tab 3\n        :groups: tab0, tab1, tab2, tab3\n        :skip_test: tab1, tab3\n\n        <CODE_BLOCK_B0>\n\n        ---\n\n        <CODE_BLOCK_B1>\n\n        ---\n\n        <CODE_BLOCK_B2>\n\n        ---\n\n        <CODE_BLOCK_B3>\n\n    For group tab0: <CODE_BLOCK_A> and <CODE_BLOCK_B0> are executed.\n    For group tab1: Nothing is executed.\n    For group tab2: <CODE_BLOCK_A> and <CODE_BLOCK_B2> are executed.\n    For group tab3: <CODE_BLOCK_A> is executed.\n\n    Arguments:\n      lines: a string list, where each element is a single string code line\n      title: a single string that contains the titles of each tab (they should\n        be separated by commas)\n      groups: a single string that contains the group of each tab (they should\n        be separated by commas). Code snippets that are part of the same group\n        will be executed together. If groups=None, then the group names will\n        default to the tab title names.\n      skip_test: a single string denoting which group(s) to skip testing (they\n        should be separated by commas). This is useful for legacy code snippets\n        that no longer run correctly anymore. If skip_test=None, then no tests\n        are skipped.\n      code_sep: the separator character(s) used to denote a separate code block\n        for a new tab. The default code separator is '---'.\n      sync: an option for Sphinx directives, that will sync all tabs together.\n        This means that if the user clicks to switch to another tab, all tabs\n        will switch to the new tab.\n    \"\"\"\n    titles = [t.strip() for t in title.split(',')]\n    num_tabs = len(titles)\n\n    sync = sync is not MISSING\n    # skip legacy code snippets in upgrade guides\n    if skip_test is not None:\n      skip_tests = {index.strip() for index in skip_test.split(',')}\n    else:\n      skip_tests = set()\n\n    code_blocks = '\\n'.join(lines)\n    if code_blocks.count(code_sep) != num_tabs - 1:\n      raise ValueError(\n        f'Expected {num_tabs-1} code separator(s) for {num_tabs} tab(s), but got {code_blocks.count(code_sep)} code separator(s) instead.'\n      )\n    code_blocks = [\n      code_block.split('\\n')\n      for code_block in code_blocks.split(code_sep + '\\n')\n    ]  # list[code_tab_list1[string_line1, ...], ...]\n\n    # by default, put each code snippet in a different group denoted by an index number, to be executed separately\n    if groups is not None:\n      groups = [group_name.strip() for group_name in groups.split(',')]\n    else:\n      groups = titles\n    if len(groups) != num_tabs:\n      raise ValueError(\n        f'Expected {num_tabs} group assignment(s) for {num_tabs} tab(s), but got {len(groups)} group assignment(s) instead.'\n      )\n\n    tabs = []\n    test_codes = []\n    for i, code_block in enumerate(code_blocks):\n      if groups[i] not in skip_tests:\n        test_codes.append((code_block, groups[i]))\n      tabs.append((titles[i], self._code_block(code_block)))\n    output = self._tabs(*tabs, sync=sync)\n\n    return output, test_codes\n\n  def _code_block(self, lines):\n    \"\"\"Creates a codeblock.\"\"\"\n    # Remove right trailing whitespace so we can detect the comments.\n    lines = [x.rstrip() for x in lines]\n    highlight = lambda x: x.endswith('#!')\n    code = map(lambda x: x[:-2].rstrip() if highlight(x) else x, lines)\n    highlights = [i + 1 for i in range(len(lines)) if highlight(lines[i])]\n    highlights = ','.join(str(i) for i in highlights)\n\n    directive = ['.. code-block:: python']\n    if highlights:\n      directive += [f'  :emphasize-lines: {highlights}']\n\n    # Indent code and add empty line so the code is picked up by the directive.\n    return directive + [''] + list(map(lambda x: '  ' + x, code))\n\n  def _tabs(self, *contents: tuple[str, list[str]], sync):\n    output = ['.. tab-set::'] + ['  ']\n\n    for title, content in contents:\n      output += [f'  .. tab-item:: {title}']\n\n      if sync:\n        key = title.strip()\n        output += [f'    :sync: {key}']\n\n      output += ['    ']\n      output += ['    ' + line for line in content]\n\n    return output\n\n\nclass CodeDiffDirective(SphinxDirective):\n  has_content = True\n  option_spec = {\n    'title': directives.unchanged,\n    'groups': directives.unchanged,\n    'skip_test': directives.unchanged,\n    'code_sep': directives.unchanged,\n    'sync': directives.flag,\n  }\n\n  def run(self):\n    table_code, test_codes = CodeDiffParser().parse(\n      list(self.content), **self.options\n    )\n\n    # Create a test node as a comment node so it won't show up in the docs.\n    # We add attribute \"testnodetype\" so it is be picked up by the doctest\n    # builder. This functionality is not officially documented but can be found\n    # in the source code:\n    # https://github.com/sphinx-doc/sphinx/blob/master/sphinx/ext/doctest.py\n    # (search for 'testnodetype').\n    test_nodes = []\n    for test_code, group in test_codes:\n      test_node = nodes.comment(\n        '\\n'.join(test_code),\n        '\\n'.join(test_code),\n        testnodetype='testcode',\n        groups=[group],\n      )\n      self.set_source_info(test_node)\n      test_node['options'] = {}\n      test_node['language'] = 'python3'\n      test_nodes.append(test_node)\n\n    # The table node is the side-by-side diff view that will be shown on RTD.\n    table_node = nodes.paragraph()\n    self.content = ViewList(table_code, self.content.parent)\n    self.state.nested_parse(self.content, self.content_offset, table_node)\n\n    return [table_node] + test_nodes\n\n\ndef setup(app):\n  app.add_directive('codediff', CodeDiffDirective)\n\n  return {\n    'version': sphinx.__display_version__,\n    'parallel_read_safe': True,\n    'parallel_write_safe': True,\n  }\n"
  },
  {
    "path": "docs_nnx/_ext/codediff_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for codediff Sphinx extension.\"\"\"\n\nfrom absl.testing import parameterized\nfrom codediff import CodeDiffParser\n\n\nclass CodeDiffTest(parameterized.TestCase):\n  def test_parse(self):\n    input_text = r\"\"\"@jax.jit #!\ndef get_initial_params(key):   #!\n  init_val = jnp.ones((1, 28, 28, 1), jnp.float32)\n  initial_params = CNN().init(key, init_val)['params']\n  extra_line\n  return initial_params\n---\n@jax.pmap #!\ndef get_initial_params(key):\n  init_val = jnp.ones((1, 28, 28, 1), jnp.float32)\n  initial_params = CNN().init(key, init_val)['params']\n  return initial_params\"\"\"\n\n    expected_table = \"\"\".. tab-set::\\n  \\n  .. tab-item:: Single device\\n    \\n    .. code-block:: python\\n      :emphasize-lines: 1,2\\n    \\n      @jax.jit\\n      def get_initial_params(key):\\n        init_val = jnp.ones((1, 28, 28, 1), jnp.float32)\\n        initial_params = CNN().init(key, init_val)['params']\\n        extra_line\\n        return initial_params\\n      \\n  .. tab-item:: Ensembling on multiple devices\\n    \\n    .. code-block:: python\\n      :emphasize-lines: 1\\n    \\n      @jax.pmap\\n      def get_initial_params(key):\\n        init_val = jnp.ones((1, 28, 28, 1), jnp.float32)\\n        initial_params = CNN().init(key, init_val)['params']\\n        return initial_params\"\"\"\n\n    expected_testcodes = [\n      r\"\"\"@jax.jit #!\ndef get_initial_params(key):   #!\n  init_val = jnp.ones((1, 28, 28, 1), jnp.float32)\n  initial_params = CNN().init(key, init_val)['params']\n  extra_line\n  return initial_params\n\"\"\",\n      r\"\"\"@jax.pmap #!\ndef get_initial_params(key):\n  init_val = jnp.ones((1, 28, 28, 1), jnp.float32)\n  initial_params = CNN().init(key, init_val)['params']\n  return initial_params\"\"\",\n    ]\n\n    title_left = 'Single device'\n    title_right = 'Ensembling on multiple devices'\n\n    actual_table, actual_testcodes = CodeDiffParser().parse(\n      lines=input_text.split('\\n'),\n      title=f'{title_left}, {title_right}',\n    )\n\n    actual_table = '\\n'.join(actual_table)\n    actual_testcodes = ['\\n'.join(testcode) for testcode, _ in actual_testcodes]\n\n    self.assertEqual(expected_table, actual_table)\n    self.assertEqual(expected_testcodes[0], actual_testcodes[0])\n    self.assertEqual(expected_testcodes[1], actual_testcodes[1])\n\n  @parameterized.parameters(\n    {\n      'input_text': r\"\"\"x = 1\n  ---\n  x = 2\n\"\"\",\n      'title': 'Tab 0, Tab1, Tab2',\n      'groups': None,\n      'error_msg': 'Expected 2 code separator\\\\(s\\\\) for 3 tab\\\\(s\\\\), but got 1 code separator\\\\(s\\\\) instead.',\n    },\n    {\n      'input_text': r\"\"\"x = 1\n  ---\n  x = 2\n  ---\n  x = 3\n  ---\n  x = 4\n\"\"\",\n      'title': 'Tab 0, Tab1, Tab2',\n      'groups': None,\n      'error_msg': 'Expected 2 code separator\\\\(s\\\\) for 3 tab\\\\(s\\\\), but got 3 code separator\\\\(s\\\\) instead.',\n    },\n    {\n      'input_text': r\"\"\"x = 1\n  ---\n  x = 2\n  ---\n  x = 3\n\"\"\",\n      'title': 'Tab 0, Tab1, Tab2',\n      'groups': 'tab0, tab2',\n      'error_msg': 'Expected 3 group assignment\\\\(s\\\\) for 3 tab\\\\(s\\\\), but got 2 group assignment\\\\(s\\\\) instead.',\n    },\n    {\n      'input_text': r\"\"\"x = 1\n  ---\n  x = 2\n  ---\n  x = 3\n\"\"\",\n      'title': 'Tab 0, Tab1, Tab2',\n      'groups': 'tab0, tab1, tab2, tab3',\n      'error_msg': 'Expected 3 group assignment\\\\(s\\\\) for 3 tab\\\\(s\\\\), but got 4 group assignment\\\\(s\\\\) instead.',\n    },\n  )\n  def test_parse_errors(self, input_text, title, groups, error_msg):\n    with self.assertRaisesRegex(ValueError, error_msg):\n      _, _ = CodeDiffParser().parse(\n        lines=input_text.split('\\n'),\n        title=title,\n        groups=groups,\n      )\n"
  },
  {
    "path": "docs_nnx/_ext/flax_module.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Sphinx directive for visualizing Flax modules.\n\nUse directive as follows:\n\n.. flax_module::\n  :module: flax.linen\n  :class: Dense\n\"\"\"\n\nimport importlib\n\nimport sphinx\nimport sphinx.ext.autosummary.generate as ag\nfrom docutils import nodes\nfrom docutils.parsers.rst import directives\nfrom docutils.statemachine import ViewList\nfrom sphinx.util.docutils import SphinxDirective\n\nfrom docs.conf_sphinx_patch import generate_autosummary_content\n\n\ndef render_module(modname: str, qualname: str, app):\n  parent = importlib.import_module(modname)\n  obj = getattr(parent, qualname)\n  template = ag.AutosummaryRenderer(app)\n  template_name = 'flax_module'\n  imported_members = False\n  recursive = False\n  context = {}\n  return generate_autosummary_content(\n    qualname,\n    obj,\n    parent,\n    template,\n    template_name,\n    imported_members,\n    app,\n    recursive,\n    context,\n    modname,\n    qualname,\n  )\n\n\nclass FlaxModuleDirective(SphinxDirective):\n  has_content = True\n  option_spec = {\n    'module': directives.unchanged,\n    'class': directives.unchanged,\n  }\n\n  def run(self):\n    module_template = render_module(\n      self.options['module'], self.options['class'], self.env.app\n    )\n    module_template = module_template.splitlines()\n\n    # Create a container for the rendered nodes\n    container_node = nodes.container()\n    self.content = ViewList(module_template, self.content.parent)\n    self.state.nested_parse(self.content, self.content_offset, container_node)\n\n    return [container_node]\n\n\ndef setup(app):\n  app.add_directive('flax_module', FlaxModuleDirective)\n\n  return {\n    'version': sphinx.__display_version__,\n    'parallel_read_safe': True,\n    'parallel_write_safe': True,\n  }\n"
  },
  {
    "path": "docs_nnx/_static/css/flax_theme.css",
    "content": "@import url(\"theme.css\");\n\n.wy-nav-content {\n  max-width: 1290px;\n}\n\n.rst-content table.docutils {\n  width: 100%;\n}\n\n.rst-content table.docutils td {\n  vertical-align: top;\n  padding: 0;\n}\n\n.rst-content table.docutils td p {\n  padding: 8px;\n}\n\n.rst-content div[class^=highlight] {\n  border: 0;\n  margin: 0;\n}\n"
  },
  {
    "path": "docs_nnx/_templates/autosummary/flax_module.rst",
    "content": "{{ fullname | escape | underline }}\n\n.. currentmodule:: {{ module }}\n\n.. autoclass:: {{ objname }}\n   :exclude-members:\n\n   .. automethod:: __call__\n\n   {% block methods %}\n\n   {% for item in methods %}\n   {%- if item not in inherited_members and item not in annotations and not item in ['__init__', 'setup'] %}\n   .. automethod:: {{ item }}\n   {%- endif %}\n   {%- endfor %}\n\n   {% if methods %}\n   .. rubric:: Methods\n\n   .. autosummary::\n\n   {% for item in methods %}\n   {%- if item not in inherited_members and item not in annotations and not item in ['__init__', 'setup'] %}\n       ~{{ name }}.{{ item }}\n   {%- endif %}\n   {%- endfor %}\n   {% endif %}\n   {% endblock %}"
  },
  {
    "path": "docs_nnx/api_reference/flax.config.rst",
    "content": "\nflax.config package\n====================\n\n.. automodule:: flax.configurations\n    :members:\n    :undoc-members:\n    :exclude-members: FlagHolder, bool_flag, static_bool_env\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.core.frozen_dict.rst",
    "content": "\nflax.core.frozen_dict package\n=============================\n\n.. currentmodule:: flax.core.frozen_dict\n\n.. autoclass:: FrozenDict\n  :members: pretty_repr, copy, pop, unfreeze, tree_flatten\n\n.. autofunction:: freeze\n\n.. autofunction:: unfreeze\n\n.. autofunction:: copy\n\n.. autofunction:: pop\n\n.. autofunction:: pretty_repr\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/bridge.rst",
    "content": "bridge\n------------------------\n\n.. automodule:: flax.nnx.bridge\n.. currentmodule:: flax.nnx.bridge\n\n.. flax_module::\n  :module: flax.nnx.bridge\n  :class: ToNNX\n\n.. flax_module::\n  :module: flax.nnx.bridge\n  :class: ToLinen\n\n.. autofunction:: to_linen\n\n.. flax_module::\n  :module: flax.nnx.bridge\n  :class: NNXMeta\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/filterlib.rst",
    "content": "filterlib\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n\n.. autofunction:: flax.nnx.filterlib.to_predicate\n.. autoclass:: WithTag\n.. autoclass:: PathContains\n.. autoclass:: OfType\n.. autoclass:: Any\n.. autoclass:: All\n.. autoclass:: Not\n.. autoclass:: Everything\n.. autoclass:: Nothing"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/graph.rst",
    "content": "graph\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n\n.. autofunction:: split\n.. autofunction:: merge\n.. autofunction:: update\n.. autofunction:: pop\n.. autofunction:: state\n.. autofunction:: variables\n.. autofunction:: graph\n.. autofunction:: graphdef\n.. autofunction:: iter_graph\n.. autofunction:: recursive_map\n.. autofunction:: clone\n.. autofunction:: call\n.. autofunction:: set_metadata\n.. autofunction:: cached_partial\n\n.. autoclass:: GraphDef\n  :members:\n\n.. autoclass:: UpdateContext\n  :members:\n\n.. autofunction:: update_context\n.. autofunction:: current_update_context\n\n.. autofunction:: find_duplicates\n.. autofunction:: pure\n.. autofunction:: flatten\n.. autofunction:: unflatten\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/helpers.rst",
    "content": "helpers\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n\n.. autoclass:: Sequential\n   :members:\n.. autoclass:: List\n   :members:\n.. autoclass:: Dict\n   :members:\n.. autoclass:: TrainState\n   :members:"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/index.rst",
    "content": "flax.nnx\n------------------------\n\nExperimental API. See the `NNX page <https://flax.readthedocs.io/en/latest/nnx/index.html>`__ for more details.\n\n.. toctree::\n  :maxdepth: 3\n\n  graph\n  object\n  module\n  nn/index\n  rnglib\n  spmd\n  state\n  training/index\n  transforms\n  variables\n  helpers\n  visualization\n  filterlib\n  bridge\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/module.rst",
    "content": "module\n------------------------\n\n.. automodule:: flax.nnx\n    :members: iter_children, iter_modules\n.. currentmodule:: flax.nnx\n.. autoclass:: Module\n   :members:\n.. autofunction:: view\n.. autofunction:: view_info\n.. autofunction:: with_attributes\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/nn/activations.rst",
    "content": "Activation functions\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n.. autofunction:: celu\n.. autofunction:: elu\n.. autofunction:: gelu\n.. autofunction:: glu\n.. autofunction:: hard_sigmoid\n.. autofunction:: hard_silu\n.. autofunction:: hard_swish\n.. autofunction:: hard_tanh\n.. autofunction:: leaky_relu\n.. autofunction:: log_sigmoid\n.. autofunction:: log_softmax\n.. autofunction:: logsumexp\n.. autofunction:: one_hot\n.. autofunction:: relu\n.. autofunction:: relu6 as relu6,\n.. autofunction:: selu\n.. autofunction:: sigmoid\n.. autofunction:: identity\n.. autofunction:: silu\n.. autofunction:: soft_sign\n.. autofunction:: softmax\n.. autofunction:: softplus\n.. autofunction:: standardize\n.. autofunction:: swish\n.. autofunction:: tanh"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/nn/attention.rst",
    "content": "Attention\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n.. flax_module::\n  :module: flax.nnx\n  :class: MultiHeadAttention\n\n.. autofunction:: combine_masks\n.. autofunction:: dot_product_attention\n.. autofunction:: make_attention_mask\n.. autofunction:: make_causal_mask"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/nn/dtypes.rst",
    "content": "Dtypes\n------------------------\n\n.. automodule:: flax.nnx.nn.dtypes\n.. currentmodule:: flax.nnx.nn.dtypes\n\n.. autofunction:: canonicalize_dtype\n.. autofunction:: promote_dtype"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/nn/index.rst",
    "content": "nn\n----------------------------\n\nNeural network layers and activation functions used in NNX :class:`Module`'s.\nSee the `NNX page <https://flax.readthedocs.io/en/latest/nnx/index.html>`__ for more details.\n\n.. toctree::\n  :maxdepth: 3\n\n  activations\n  attention\n  dtypes\n  initializers\n  linear\n  lora\n  normalization\n  recurrent\n  stochastic\n\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/nn/initializers.rst",
    "content": "Initializers\n------------------------\n\n.. automodule:: flax.nnx.initializers\n.. currentmodule:: flax.nnx.initializers\n\n.. autofunction:: constant\n.. autofunction:: delta_orthogonal\n.. autofunction:: glorot_normal\n.. autofunction:: glorot_uniform\n.. autofunction:: he_normal\n.. autofunction:: he_uniform\n.. autofunction:: kaiming_normal\n.. autofunction:: kaiming_uniform\n.. autofunction:: lecun_normal\n.. autofunction:: lecun_uniform\n.. autofunction:: normal\n.. autofunction:: truncated_normal\n.. autofunction:: ones\n.. autofunction:: ones_init\n.. autofunction:: orthogonal\n.. autofunction:: uniform\n.. autofunction:: variance_scaling\n.. autofunction:: xavier_normal\n.. autofunction:: xavier_uniform\n.. autofunction:: zeros\n.. autofunction:: zeros_init\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/nn/linear.rst",
    "content": "Linear\n------------------------\n\nNNX linear layer classes.\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n.. flax_module::\n  :module: flax.nnx\n  :class: Conv\n\n.. flax_module::\n  :module: flax.nnx\n  :class: ConvTranspose\n\n.. flax_module::\n  :module: flax.nnx\n  :class: Embed\n\n.. flax_module::\n  :module: flax.nnx\n  :class: Linear\n\n.. flax_module::\n  :module: flax.nnx\n  :class: LinearGeneral\n\n.. flax_module::\n  :module: flax.nnx\n  :class: Einsum"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/nn/lora.rst",
    "content": "LoRA\n------------------------\n\nNNX LoRA classes.\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n.. flax_module::\n  :module: flax.nnx\n  :class: LoRA\n\n.. flax_module::\n  :module: flax.nnx\n  :class: LoRALinear\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/nn/normalization.rst",
    "content": "Normalization\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n.. flax_module::\n  :module: flax.nnx\n  :class: BatchNorm\n\n.. flax_module::\n  :module: flax.nnx\n  :class: LayerNorm\n\n.. flax_module::\n  :module: flax.nnx\n  :class: RMSNorm\n\n.. flax_module::\n  :module: flax.nnx\n  :class: GroupNorm\n\n.. flax_module::\n  :module: flax.nnx\n  :class: InstanceNorm\n\n.. flax_module::\n  :module: flax.nnx\n  :class: SpectralNorm\n\n.. flax_module::\n  :module: flax.nnx\n  :class: WeightNorm\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/nn/recurrent.rst",
    "content": "Recurrent\n------------------------\n\n.. automodule:: flax.nnx.nn.recurrent\n.. currentmodule:: flax.nnx.nn.recurrent\n\n.. flax_module::\n  :module: flax.nnx.nn.recurrent\n  :class: LSTMCell\n\n.. flax_module::\n  :module: flax.nnx.nn.recurrent\n  :class: OptimizedLSTMCell\n\n.. flax_module::\n  :module: flax.nnx.nn.recurrent\n  :class: SimpleCell\n\n.. flax_module::\n  :module: flax.nnx.nn.recurrent\n  :class: GRUCell\n\n.. flax_module::\n  :module: flax.nnx.nn.recurrent\n  :class: RNN\n\n.. flax_module::\n  :module: flax.nnx.nn.recurrent\n  :class: Bidirectional\n\n\n.. autofunction:: flip_sequences"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/nn/stochastic.rst",
    "content": "Stochastic\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n.. autoclass:: Dropout\n   :members:"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/object.rst",
    "content": "object\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n.. autoclass:: Pytree\n   :members:\n.. autoclass:: Object\n   :members:\n.. autofunction:: data\n.. autodata:: Data\n   :annotation:\n.. autofunction:: static\n.. autodata:: Static\n   :annotation:\n.. autofunction:: is_data\n.. autofunction:: register_data_type\n.. autofunction:: check_pytree"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/rnglib.rst",
    "content": "rnglib\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n.. autoclass:: Rngs\n   :members: __init__\n.. autoclass:: RngStream\n   :members:\n.. autofunction:: split_rngs\n.. autofunction:: fork_rngs\n.. autofunction:: reseed\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/spmd.rst",
    "content": "spmd\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n.. autofunction:: get_partition_spec\n.. autofunction:: get_named_sharding\n.. autofunction:: with_partitioning\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/state.rst",
    "content": "state\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n\n.. autoclass:: State\n  :members:\n\n.. autoclass:: FlatState\n  :members:\n\n.. autofunction:: filter_state\n.. autofunction:: from_flat_state\n.. autofunction:: map_state\n.. autofunction:: merge_state\n.. autofunction:: replace_by_pure_dict\n.. autofunction:: restore_int_paths\n.. autofunction:: to_flat_state\n.. autofunction:: to_pure_dict\n.. autofunction:: split_state\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/summary.rst",
    "content": "summary\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n.. autofunction:: tabulate"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/training/index.rst",
    "content": "training\n----------------------------\n\nExperimental API. See the `NNX page <https://flax.readthedocs.io/en/latest/nnx/index.html>`__ for more details.\n\n.. toctree::\n  :maxdepth: 3\n\n  metrics\n  optimizer\n\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/training/metrics.rst",
    "content": "Metrics\n------------------------\n\n.. automodule:: flax.nnx.metrics\n.. currentmodule:: flax.nnx.metrics\n\n\n.. autoclass:: Metric\n   :members: __init__, reset, update, compute\n\n.. autoclass:: Average\n   :members: __init__, reset, update, compute\n\n.. autoclass:: Accuracy\n   :members: update\n\n.. autoclass:: Welford\n   :members: __init__, reset, update, compute\n\n.. autoclass:: MultiMetric\n   :members: __init__, reset, update, compute\n\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/training/optimizer.rst",
    "content": "Optimizer\n------------------------\n\n.. automodule:: flax.nnx.optimizer\n.. currentmodule:: flax.nnx.optimizer\n\n.. autoclass:: Optimizer\n   :members: __init__, update\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/transforms.rst",
    "content": "transforms\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n.. autofunction:: grad\n.. autofunction:: jit\n.. autofunction:: shard_map\n.. autofunction:: remat\n.. autofunction:: scan\n.. autoclass:: Carry\n.. autofunction:: value_and_grad\n.. autofunction:: vmap\n.. autofunction:: eval_shape\n.. autofunction:: custom_vjp\n.. autofunction:: vjp\n.. autofunction:: jvp\n.. autofunction:: cond\n.. autofunction:: switch\n.. autofunction:: while_loop\n.. autofunction:: fori_loop\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/variables.rst",
    "content": "variables\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n.. autoclass:: BatchStat\n   :members:\n.. autoclass:: Cache\n   :members:\n.. autoclass:: Intermediate\n   :members:\n.. autoclass:: Param\n   :members:\n.. autoclass:: Variable\n   :members:\n.. autoclass:: VariableMetadata\n   :members:\n\n.. autofunction:: with_metadata\n\n.. autofunction:: variable_name_from_type\n.. autofunction:: variable_type_from_name\n.. autofunction:: register_variable_name\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.nnx/visualization.rst",
    "content": "visualization\n------------------------\n\n.. automodule:: flax.nnx\n.. currentmodule:: flax.nnx\n\n.. autofunction:: display"
  },
  {
    "path": "docs_nnx/api_reference/flax.struct.rst",
    "content": "\nflax.struct package\n=====================\n\n.. currentmodule:: flax.struct\n\n.. automodule:: flax.struct\n\n\n.. autofunction:: dataclass\n\n\n.. autoclass:: PyTreeNode"
  },
  {
    "path": "docs_nnx/api_reference/flax.training.rst",
    "content": "\nflax.training package\n=====================\n\nTrain state\n------------------------\n\n.. currentmodule:: flax.training.train_state\n\n.. autoclass:: TrainState\n    :members: apply_gradients, create\n\n"
  },
  {
    "path": "docs_nnx/api_reference/flax.traverse_util.rst",
    "content": "flax.traverse_util package\n============================\n\n.. currentmodule:: flax.traverse_util\n\n.. automodule:: flax.traverse_util\n\nDict utils\n------------\n\n.. autofunction:: flatten_dict\n\n.. autofunction:: unflatten_dict\n\n.. autofunction:: path_aware_map\n"
  },
  {
    "path": "docs_nnx/api_reference/index.rst",
    "content": "API Reference\n=============\n\n.. toctree::\n   :maxdepth: 4\n\n   flax.nnx/index\n   flax.core.frozen_dict\n   flax.struct\n   flax.training"
  },
  {
    "path": "docs_nnx/conf.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Configuration file for the Sphinx documentation builder.\"\"\"\n\n\n# This file only contains a selection of the most common options. For a full\n# list see the documentation:\n# https://www.sphinx-doc.org/en/master/usage/configuration.html\n\n# -- Path setup --------------------------------------------------------------\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#\n# import os\n# import sys\n# sys.path.insert(0, os.path.abspath('.'))\n\nimport os\nimport sys\nimport doctest\n\nsys.path.insert(0, os.path.abspath('..'))\n# Include local extension.\nsys.path.append(os.path.abspath('./_ext'))\n# Set environment variable to indicate that we are building the docs.\nos.environ['FLAX_DOC_BUILD'] = 'true'\n\n# patch sphinx\n# -- Project information -----------------------------------------------------\n\nproject = 'Flax'\ncopyright = '2023, The Flax authors'  # pylint: disable=redefined-builtin\nauthor = 'The Flax authors'\n\n\n# -- General configuration ---------------------------------------------------\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n  'sphinx.ext.autodoc',\n  'sphinx.ext.autosummary',\n  'sphinx.ext.autosectionlabel',\n  'sphinx.ext.doctest',\n  'sphinx.ext.intersphinx',\n  'sphinx.ext.mathjax',\n  'sphinx.ext.napoleon',\n  'sphinx.ext.viewcode',\n  'myst_nb',\n  'codediff',\n  'flax_module',\n  'sphinx_design',\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path.\nexclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']\n\n# The suffix(es) of source filenames.\n# Note: important to list ipynb before md here: we have both md and ipynb\n# copies of each notebook, and myst will choose which to convert based on\n# the order in the source_suffix list. Notebooks which are not executed have\n# outputs stored in ipynb but not in md, so we must convert the ipynb.\nsource_suffix = ['.rst', '.ipynb', '.md']\n\nautosummary_generate = True\n\nmaster_doc = 'index'\n\nautodoc_typehints = 'none'\n\n\n# -- Options for HTML output -------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\n# html_theme = 'pydata_sphinx_theme'\nhtml_theme = 'sphinx_book_theme'\nhtml_css_files = ['css/flax_theme.css']\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\nhtml_logo = './flax.png'\nhtml_favicon = './flax.png'\n\n# title of the website\nhtml_title = ''\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named 'default.css' will overwrite the builtin 'default.css'.\nhtml_static_path = ['_static']\n\nhtml_extra_path = ['robots.txt']\n\n# href with no underline and white bold text color\nannouncement = \"\"\"\n<a\n  href=\"https://flax-linen.readthedocs.io/en/latest\"\n  style=\"text-decoration: none; color: white;\"\n>\n  This site covers the new Flax NNX API. <span style=\"color: lightgray;\">[Click here for the old <b>Flax Linen</b> API]</span>\n</a>\n\"\"\"\n\nhtml_theme_options = {\n  'repository_url': 'https://github.com/google/flax',\n  'use_repository_button': True,  # add a 'link to repository' button\n  'use_issues_button': False,  # add an 'Open an Issue' button\n  'path_to_docs': (\n    'docs_nnx'\n  ),  # used to compute the path to launch notebooks in colab\n  'launch_buttons': {\n    'colab_url': 'https://colab.research.google.com/',\n  },\n  'prev_next_buttons_location': None,\n  'show_navbar_depth': 1,\n  'announcement': announcement,\n}\n\n# -- Options for myst ----------------------------------------------\n# uncomment line below to avoid running notebooks during development\nnb_execution_mode = os.environ.get(\"NB_EXECUTION_MODE\", 'off')\n# Notebook cell execution timeout; defaults to 30.\nnb_execution_timeout = 100\n# List of patterns, relative to source directory, that match notebook\n# files that will not be executed.\nmyst_enable_extensions = ['dollarmath']\nnb_execution_excludepatterns = [\n  'mnist_tutorial.ipynb',  # <-- times out\n  'transfer_learning.ipynb',  # <-- transformers requires flax<=0.7.0\n  'flax/nnx',  # exclude nnx\n  'guides/demo.ipynb',  # TODO(cgarciae): broken, remove or update\n  'examples/gemma.ipynb',\n  'guides/bridge_guide.ipynb',  # TODO(cgarciae): broken, bridge doesn't support Linen sow yet\n]\n# raise exceptions on execution so CI can catch errors\nnb_execution_allow_errors = False\nnb_execution_raise_on_error = True\n\n# -- Extension configuration -------------------------------------------------\n\n# Tell sphinx-autodoc-typehints to generate stub parameter annotations including\n# types, even if the parameters aren't explicitly documented.\nalways_document_param_types = True\n\n# -- doctest configuration -------------------------------------------------\n\ndoctest_default_flags = doctest.NORMALIZE_WHITESPACE\ndoctest_global_setup = \"\"\"\nimport jax\nimport jax.numpy as jnp\nfrom flax import nnx\n\nimport logging as slog\nfrom absl import logging as alog\n\n# Avoid certain absl logging messages to break doctest\nfiltered_message = [\n  'SaveArgs.aggregate is deprecated',\n  '',\n]\n\nclass _CustomLogFilter(slog.Formatter):\n  def format(self, record):\n    message = super(_CustomLogFilter, self).format(record)\n    for m in filtered_message:\n      if m in message:\n        return ''\n    return message\n\nalog.use_absl_handler()\nalog.get_absl_handler().setFormatter(_CustomLogFilter())\n\"\"\"\n"
  },
  {
    "path": "docs_nnx/conf_sphinx_patch.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Patch Sphinx to improve documentation aesthetics.\"\"\"\n\n# TODO(cgarciae): Send a PR to sphinx to upstream this fix. Issue: https://github.com/google/flax/issues/2196\n# This patch is needed to make autosummary provide the \"annotations\"\n# variable so we can exclude function attributes from the methods list\n# in flax_module.rst. The patch as such only adds this single line:\n#\n#     ns['annotations'] = list(getattr(obj, '__annotations__', {}).keys())'\n#\n# We should consider sending a PR to sphinx so we can get rid of this.\n# Original source: https://github.com/sphinx-doc/sphinx/blob/0aedcc9a916daa92d477226da67d33ce1831822e/sphinx/ext/autosummary/generate.py#L211-L351\nfrom typing import Any\n\nimport sphinx.ext.autodoc\nimport sphinx.ext.autosummary.generate as ag\n\n\ndef generate_autosummary_content(\n  name: str,\n  obj: Any,\n  parent: Any,\n  template: ag.AutosummaryRenderer,\n  template_name: str,\n  imported_members: bool,\n  app: Any,\n  recursive: bool,\n  context: dict,\n  modname: str = None,\n  qualname: str = None,\n) -> str:\n  doc = ag.get_documenter(app, obj, parent)\n\n  def skip_member(obj: Any, name: str, objtype: str) -> bool:\n    try:\n      return app.emit_firstresult(\n        'autodoc-skip-member', objtype, name, obj, False, {}\n      )\n    except Exception as exc:\n      ag.logger.warning(\n        __(\n          'autosummary: failed to determine %r to be documented, '\n          'the following exception was raised:\\n%s'\n        ),\n        name,\n        exc,\n        type='autosummary',\n      )\n      return False\n\n  def get_class_members(obj: Any) -> dict[str, Any]:\n    members = sphinx.ext.autodoc.get_class_members(\n      obj, [qualname], ag.safe_getattr\n    )\n    return {name: member.object for name, member in members.items()}\n\n  def get_module_members(obj: Any) -> dict[str, Any]:\n    members = {}\n    for name in ag.members_of(obj, app.config):\n      try:\n        members[name] = ag.safe_getattr(obj, name)\n      except AttributeError:\n        continue\n    return members\n\n  def get_all_members(obj: Any) -> dict[str, Any]:\n    if doc.objtype == 'module':\n      return get_module_members(obj)\n    elif doc.objtype == 'class':\n      return get_class_members(obj)\n    return {}\n\n  def get_members(\n    obj: Any,\n    types: set[str],\n    include_public: list[str] = [],\n    imported: bool = True,\n  ) -> tuple[list[str], list[str]]:\n    items: list[str] = []\n    public: list[str] = []\n\n    all_members = get_all_members(obj)\n    for name, value in all_members.items():\n      documenter = ag.get_documenter(app, value, obj)\n      if documenter.objtype in types:\n        # skip imported members if expected\n        if imported or getattr(value, '__module__', None) == obj.__name__:\n          skipped = skip_member(value, name, documenter.objtype)\n          if skipped is True:\n            pass\n          elif skipped is False:\n            # show the member forcedly\n            items.append(name)\n            public.append(name)\n          else:\n            items.append(name)\n            if name in include_public or not name.startswith('_'):\n              # considers member as public\n              public.append(name)\n    return public, items\n\n  def get_module_attrs(members: Any) -> tuple[list[str], list[str]]:\n    \"\"\"Find module attributes with docstrings.\"\"\"\n    attrs, public = [], []\n    try:\n      analyzer = ag.ModuleAnalyzer.for_module(name)\n      attr_docs = analyzer.find_attr_docs()\n      for namespace, attr_name in attr_docs:\n        if namespace == '' and attr_name in members:\n          attrs.append(attr_name)\n          if not attr_name.startswith('_'):\n            public.append(attr_name)\n    except ag.PycodeError:\n      pass  # give up if ModuleAnalyzer fails to parse code\n    return public, attrs\n\n  def get_modules(obj: Any) -> tuple[list[str], list[str]]:\n    items: list[str] = []\n    for _, modname, _ispkg in ag.pkgutil.iter_modules(obj.__path__):\n      fullname = name + '.' + modname\n      try:\n        module = ag.import_module(fullname)\n        if module and hasattr(module, '__sphinx_mock__'):\n          continue\n      except ImportError:\n        pass\n\n      items.append(fullname)\n    public = [x for x in items if not x.split('.')[-1].startswith('_')]\n    return public, items\n\n  ns: dict[str, Any] = {}\n  ns.update(context)\n\n  if doc.objtype == 'module':\n    scanner = ag.ModuleScanner(app, obj)\n    ns['members'] = scanner.scan(imported_members)\n    ns['functions'], ns['all_functions'] = get_members(\n      obj, {'function'}, imported=imported_members\n    )\n    ns['classes'], ns['all_classes'] = get_members(\n      obj, {'class'}, imported=imported_members\n    )\n    ns['exceptions'], ns['all_exceptions'] = get_members(\n      obj, {'exception'}, imported=imported_members\n    )\n    ns['attributes'], ns['all_attributes'] = get_module_attrs(ns['members'])\n    ispackage = hasattr(obj, '__path__')\n    if ispackage and recursive:\n      ns['modules'], ns['all_modules'] = get_modules(obj)\n  elif doc.objtype == 'class':\n    ns['members'] = dir(obj)\n    ns['inherited_members'] = set(dir(obj)) - set(obj.__dict__.keys())\n    ns['methods'], ns['all_methods'] = get_members(\n      obj, {'method'}, ['__init__']\n    )\n    ns['attributes'], ns['all_attributes'] = get_members(\n      obj, {'attribute', 'property'}\n    )\n    ns['annotations'] = list(getattr(obj, '__annotations__', {}).keys())\n\n  if modname is None or qualname is None:\n    modname, qualname = ag.split_full_qualified_name(name)\n\n  if doc.objtype in ('method', 'attribute', 'property'):\n    ns['class'] = qualname.rsplit('.', 1)[0]\n\n  if doc.objtype in ('class',):\n    shortname = qualname\n  else:\n    shortname = qualname.rsplit('.', 1)[-1]\n\n  ns['fullname'] = name\n  ns['module'] = modname\n  ns['objname'] = qualname\n  ns['name'] = shortname\n\n  ns['objtype'] = doc.objtype\n  ns['underline'] = len(name) * '='\n\n  if template_name:\n    return template.render(template_name, ns)\n  else:\n    return template.render(doc.objtype, ns)\n\n\nag.generate_autosummary_content = generate_autosummary_content\n"
  },
  {
    "path": "docs_nnx/contributing.md",
    "content": "# How to contribute\n\nEveryone can contribute to Flax, and the Flax development team values everyone's contributions!\nYou can contribute in many more ways than just writing code. Answering questions\non the [Flax GitHub Discussions page](https://github.com/google/flax/discussions), helping\neach other, and improving Flax documentation are extremely valuable to the Flax\necosystem.\n\nWe also appreciate if you spread the word, for instance by starring the [Flax GitHub repository](https://github.com/google/flax),\nor referencing Flax in blog posts of projects that used it.\n\nThis project follows\n[Google's Open Source Community Guidelines](https://opensource.google/conduct/).\n\n## Ways to contribute\n\nWe welcome pull requests (PRs), in particular for those issues\n[marked as PR-ready](https://github.com/google/flax/issues?q=is%3Aopen+is%3Aissue+label%3A%22Status%3A+pull+requests+welcome%22).\nFor other proposals, you should first open a GitHub Issue or a GitHub Discussion to\nstart a conversation about your planned contribution.\n\n## Contributing code using pull requests\n\nThe Flax development team performs all development using [Git](https://git-scm.com/). To contribute,\nyou should have basic knowledge of [Git](https://git-scm.com/) and [GitHub](https://docs.github.com).\n(You can learn how to set up Git by following Git's official\n[Getting Started - First-Time Git Setup](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup)\nand GitHub's [Set Up Git](https://docs.github.com/en/get-started/quickstart/set-up-git) guides.)\n\nTo contribute code to Flax on GitHub, follow these steps:\n\n### To create a pull request from a fork\n\n1. Using GitHub's web UI, fork the Flax repository by clicking the 'Fork' button on the\n   [`github.com/google/flax` repository page](http://www.github.com/google/flax). This creates a\n   fork (a copy) of the Flax repository in your own GitHub.\n\n   Reference: [Creating a pull request from a fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork).\n\n2. Install [Python >=3.7](https://www.python.org/downloads/).\n\n3. (Optional) Create a virtual environment or a Docker container. See\n   [`dev/README.md`](https://github.com/google/flax/blob/main/dev/README.md)\n   for details on how to set up a Docker Container. To set up a virtual environment,\n   run the following:\n\n   ```bash\n   python3 -m virtualenv env\n   . env/bin/activate\n   ```\n\n   This ensures all your dependencies are installed in this environment.\n\n4. Clone your local forked Flax repo with `git clone`. Then, install the required packages\n   with [PyPi](https://pip.pypa.io/en/stable/cli/pip_install/). This enables you to immediately\n   test the code after modifying it:\n\n   ```bash\n   git clone https://github.com/YOUR_USERNAME/flax\n   cd flax\n   pip install -e \".[all,testing,docs]\"\n   ```\n\n   You can also use [uv](https://docs.astral.sh/uv/) to setup\n   the development environment:\n\n   ```bash\n   uv sync --all-extras\n   ```\n\n5. Set up pre-commit hooks, this will run some automated checks during each `git` commit and\n   possibly update some files that require changes.\n\n   ```bash\n   pip install pre-commit\n   pre-commit install\n   ```\n\n6. Add the Google Flax repo (not your fork) as an upstream remote, so you can use it to sync your\n   changes.\n\n   ```bash\n   git remote add upstream http://www.github.com/google/flax\n   ```\n\n\n7. Create a branch, such as `my_development_branch`, you will develop from:\n\n   ```bash\n   git checkout -b my_development_branch\n   ```\n\n8. Implement your changes using your favorite editor (we recommend\n   [Visual Studio Code](https://code.visualstudio.com/)).\n\n   Make sure the tests pass by running the following command from the top of\n   the repository:\n\n   ```bash\n   ./tests/run_all_tests.sh\n   ```\n\n9. Once you finish making changes, don't forget to create commits\n   ([learn how to write a commit message](https://chris.beams.io/posts/git-commit/)):\n\n   ```bash\n   git add file1.py file2.py ...\n   # or use `git add .` to add all changed files\n   git commit -m \"Your commit message\"\n   ```\n\n   Then sync your code with the main repository:\n\n   ```bash\n   git fetch upstream\n   git rebase upstream/main\n   ```\n\n10. Finally, push your commit on your `my_development_branch`, and create a remote\n   branch in your fork that you can use to create a pull request from:\n\n   ```bash\n   git push --set-upstream origin my_development_branch\n   ```\n\n   After running the command, you should get a GitHub link in your (VS Code) terminal output for creating a pull request.\n   If you don't receive a link after `git push`, use the [GitHub web UI](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request?tool=webui) to create a pull request.\n\n11. Make sure your pull request passes the\n   [Flax PR checklist](https://github.com/google/flax/blob/main/.github/pull_request_template.md#checklist).\n   If so, create a pull request from the Flax repository and send it for review.\n   Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/)\n   for more information on using pull requests.\n\nYou can learn more in GitHub's [Creating a pull request from a fork\n](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). documentation.\n\n### Adding or updating dependencies\n\nTo add or update dependencies, you must use `uv` after\nupdating the `pyproject.toml` file to ensure that the `uv.lock` file is up-to-date.\n\n```bash\nuv sync --all-extras\n```\nAlternatively use can use `uv add` to add or update the dependencies automatically, for example:\n\n```bash\nuv add 'some-package>=1.2.3'\n```\n\n### Updating Jupyter Notebooks\n\nWe use [jupytext](https://jupytext.readthedocs.io/) to maintain two synced copies of docs\nin `docs/notebooks`: one in the Jupyter Notebook (`.ipynb`) format, and one in Markdown (`.md`).\n\nThe former can be opened and executed directly in [Google Colab](https://colab.research.google.com/).\nMarkdown makes it easier to track changes/diffs within version control and, for example, GitHub\nweb UI, since `.ipynb` files are based on JSON.\n\n#### Editing Jupyter Notebooks (`.ipynb`)\n\nFor making large changes that substantially modify code and outputs, it's recommended to edit\nthe notebooks in [Jupyter](https://jupyter.org/install) or in [Colab](https://colab.research.google.com/).\n\nIf you choose to work in Colab, go to **File** and click **Upload notebook**, then pick your file.\nAfter loading it into Colab and editing it, make sure you run the cells, and that there aren't any errors.\nClick on **Runtime**, then select **Run all**. After you finish, click **File** > **Download** > **Download ipynb**.\nYou may also want to test that the file executes properly by using `sphinx-build`, as explained above.\n\nAfter you make changes in your Jupyter Notebook, follow the steps _Syncing notebooks_ below.\n\n#### Editing Markdown files (`.md`)\n\nFor making smaller changes to the text content of the notebooks, it is easiest to edit the\n`.md` versions using a text editor.\n\nAfter you make changes in your Markdown file, follow the steps _Syncing notebooks_ below.\n\n#### Syncing notebooks\n\nAfter editing either the `.ipynb` or `.md` versions of the docs, sync the two versions\nusing [jupytext](https://jupytext.readthedocs.io/) by running `jupytext --sync` on the updated\nnotebooks.\n\nFirst, make sure you have jupytext installed. The jupytext version should match\nthe one specified in [.pre-commit-config.yaml](https://github.com/google/flax/blob/main/.pre-commit-config.yaml)\n(currently, it is v1.13.8).\n\n```bash\npip install jupytext==1.13.8\n```\n\nThen, after you have made your changes in the Jupyter Notebook, sync the contents with its Markdown-equivalent\nfile by running the following command:\n\n```bash\njupytext --sync path/to/the/file.ipynb\n```\n\nSimilarly, to sync your Markdown file with its Jupyter Notebook version, run:\n\n```bash\njupytext --sync path/to/the/file.md\n```\n\nNote that if you receive an error, and it is the first time you worked in a Jupyter Notebook, you may need\nto (re)create a synced copy of the document (which is explained in detail in _Creating new notebooks_ section below):\n\n```bash\njupytext --set-formats ipynb,md:myst path/to/the/notebook.ipynb\n```\n\nOnce you're finished with syncing the `.md` and `.ipynb` files, you can check that they are properly synced using the\n[pre-commit](https://pre-commit.com/) framework to perform the same checks used\nin the Flax GitHub CI:\n\n```bash\ngit add docs -u  # pre-commit runs on files in git staging.\npre-commit run jupytext\n```\n\n#### Creating new notebooks\n\nIf you are adding a new Jupyter Notebook to the documentation, you can use `jupytext --set-formats`.\nIt can set up both the Jupyter Notebook (`.ipynb`) and Markdown (`.md`) versions of the file:\n\n```bash\njupytext --set-formats ipynb,md:myst path/to/the/notebook.ipynb\n```\n\nThis works by adding a `\"jupytext\"` metadata field to the notebook file which specifies the\ndesired formats. The `jupytext --sync` command can then recognize them when invoked.\n\nAfter you make changes in your file(s), follow the steps from the _Syncing notebooks_\nsection above to keep the contents of both Markdown and Jupyter Notebook files in sync.\n\n#### Notebooks within the Sphinx build\n\nSome of the notebooks are built automatically as part of the pre-submit checks and\nas part of the [Read the Docs](https://flax.readthedocs.io/en/latest) build.\nThe build will fail if cells raise errors. If the errors are intentional, you can either catch them,\nor tag the cell with `raises-exceptions` metadata ([example PR](https://github.com/jax-ml/jax/pull/2402/files)).\nYou have to add this metadata by hand in the `.ipynb` file. It will be preserved when somebody else\nre-saves the notebook.\n\nWe exclude some notebooks from the build because, for example, they contain long computations.\nSee `exclude_patterns` in [`conf.py`](https://github.com/google/flax/blob/main/docs/conf.py).\n\n### Updating the pull request contents\n\nEvery pull request should ideally be limited to just one commit, so if you have multiple commits please squash them.\n\nAssuming you now have only one commit in your pull request, and want to add changes requested during review:\n\n1. Make the changes locally in your editor.\n2. Run `git commit -a --amend`. This updates the commit contents and allows you to edit the commit message.\n3. At this point, `git push` alone will result in an error. Instead, use `git push --force`.\n4. Check that it's done: The changes to your commit should be immediately reflected in the Github web UI.\n\n## Troubleshooting\n\n### Too many commits in a pull request\n\nIf your PR has too many commits associated with it (for example, more than five),\nyou need to squash them. Otherwise, the Flax docs build process may fail with an\nerror message. This is because of the following reasons:\n\n* There are more than five commits in your pull request; and\n* The Flax source sync process fails when the commit tree is too large.\n\nTo squash your commits, you can rebase your branch to `main` and create a new\ncommit containing all your changes, run the following command:\n\n```bash\ngit rebase main && git reset --soft main && git commit\n```\n\nThis will apply all your changes to the main branch. Note that if you had to\nresolve any conflicts while working on your change (for instance, you did a\n`pull upstream main` which led to conflict), then you will have to resolve these\nconflicts again.\n\nAfter you have successfully rebased your branch, you should push your changes.\nAnd because you changed the commit history, you may have to use `git push --force`.\n\n## Contributor License Agreement\n\nContributions to this project must be accompanied by a Contributor License\nAgreement. You (or your employer) retain the copyright to your contribution;\nthis simply gives us permission to use and redistribute your contributions as\npart of the project. Head over to <https://cla.developers.google.com/> to see\nyour current agreements on file or to sign a new one.\n\nYou generally only need to submit a CLA once, so if you've already submitted one\n(even if it was for a different project), you probably don't need to do it\nagain.\n\nAll submissions to Google Open Source projects need to follow Google’s Contributor License Agreement (CLA), in which contributors agree that their contribution is an original work of authorship. This doesn’t prohibit the use of coding assistance tools, but what’s submitted does need to be a contributor’s original creation.\n"
  },
  {
    "path": "docs_nnx/examples/core_examples.rst",
    "content": "Core examples\n=============\n\nCore examples are hosted on the GitHub Flax repository in the `examples <https://github.com/google/flax/tree/main/examples>`__\ndirectory.\n\nEach example is designed to be **self-contained and easily forkable**, while\nreproducing relevant results in different areas of machine learning.\n\nSome of the examples below have a link \"Interactive🕹\" that lets you run them\ndirectly in Colab.\n\nTransformers\n********************\n\n- :octicon:`mark-github;0.9em` `Gemma <https://github.com/google/flax/tree/main/examples/gemma/>`__ :\n  A family of open-weights Large Language Model (LLM) by Google DeepMind, based on Gemini research and technology.\n\n-  :octicon:`mark-github;0.9em` `LM1B <https://github.com/google/flax/tree/main/examples/lm1b/>`__ :\n   Transformer encoder trained on the One Billion Word Benchmark.\n\nToy examples\n********************\n\n`NNX toy examples <https://github.com/google/flax/tree/main/examples/nnx_toy_examples/>`__\ndirectory contains a few smaller, standalone toy examples for simple training scenarios.\n"
  },
  {
    "path": "docs_nnx/examples/gemma.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Example: Using pretrained Gemma for inference with Flax NNX\\n\",\n    \"\\n\",\n    \"This example shows how to use Flax NNX to load the [Gemma](https://ai.google.dev/gemma) open model files and use them to perform sampling/inference for generating text. You will use [Flax NNX `gemma` modules](https://github.com/google/flax/tree/main/examples/gemma) written with Flax and JAX for model parameter configuration and inference.\\n\",\n    \"\\n\",\n    \"> Gemma is a family of lightweight, state-of-the-art open models based on Google DeepMind’s [Gemini](https://deepmind.google/technologies/gemini/#introduction). Read more about [Gemma](https://blog.google/technology/developers/gemma-open-models/) and [Gemma 2](https://blog.google/technology/developers/google-gemma-2/).\\n\",\n    \"\\n\",\n    \"You are recommended to use [Google Colab](https://colab.research.google.com/) with access to A100 GPU acceleration to run the code.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Installation\\n\",\n    \"\\n\",\n    \"Install the necessary dependencies, including `kagglehub`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"! pip install --no-deps -U flax\\n\",\n    \"! pip install jaxtyping kagglehub treescope\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Download the model\\n\",\n    \"\\n\",\n    \"To use Gemma model, you'll need a [Kaggle](https://www.kaggle.com/models/google/gemma/) account and API key:\\n\",\n    \"\\n\",\n    \"1. To create an account, visit [Kaggle](https://www.kaggle.com/) and click on 'Register'.\\n\",\n    \"2. If/once you have an account, you need to sign in, go to your ['Settings'](https://www.kaggle.com/settings), and under 'API' click on 'Create New Token' to generate and download your Kaggle API key.\\n\",\n    \"3. In [Google Colab](https://colab.research.google.com/), under 'Secrets' add your Kaggle username and API key, storing the username as `KAGGLE_USERNAME` and the key as `KAGGLE_KEY`. If you are using a [Kaggle Notebook](https://www.kaggle.com/code) for free TPU or other hardware acceleration, it has a key storage feature under 'Add-ons' > 'Secrets', along with instructions for accessing stored keys.\\n\",\n    \"\\n\",\n    \"Then run the cell below.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"2e7cf9f0345845f1a3edc72fa4411eb4\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"VBox(children=(HTML(value='<center> <img\\\\nsrc=https://www.kaggle.com/static/images/site-logo.png\\\\nalt=\\\\'Kaggle…\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"import kagglehub\\n\",\n    \"kagglehub.login()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If everything went well, it should say `Kaggle credentials set. Kaggle credentials successfully validated.`.\\n\",\n    \"\\n\",\n    \"**Note:** In Google Colab, you can instead authenticate into Kaggle using the code below after following the optional step 3 from above.\\n\",\n    \"\\n\",\n    \"```\\n\",\n    \"import os\\n\",\n    \"from google.colab import userdata # `userdata` is a Colab API.\\n\",\n    \"\\n\",\n    \"os.environ[\\\"KAGGLE_USERNAME\\\"] = userdata.get('KAGGLE_USERNAME')\\n\",\n    \"os.environ[\\\"KAGGLE_KEY\\\"] = userdata.get('KAGGLE_KEY')\\n\",\n    \"``` \\n\",\n    \"\\n\",\n    \"Now, load the Gemma model you want to try. The code in the next cell utilizes [`kagglehub.model_download`](https://github.com/Kaggle/kagglehub/blob/8efe3e99477aa4f41885840de6903e61a49df4aa/src/kagglehub/models.py#L16) to download model files.\\n\",\n    \"\\n\",\n    \"**Note:** For larger models, such as `gemma 7b` and `gemma 7b-it` (instruct), you may require a hardware accelerator with plenty of memory, such as the NVIDIA A100.\\n\",\n    \"\\n\",\n    \"**Note:** To avoid 403 error when downloading the model, you need to consent to the license for Gemma models on Kaggle. To do that, open https://www.kaggle.com/models/google/gemma/flax/ in the browser and click on \\\"Download\\\" button choosing any version of Gemma model. In the next window you will be proposed to agree with Gemma models usage license. Once, this step is done, you will be able to download the model using the code below.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from IPython.display import clear_output\\n\",\n    \"\\n\",\n    \"VARIANT = '2b-it' # @param ['2b', '2b-it', '7b', '7b-it'] {type:\\\"string\\\"}\\n\",\n    \"weights_dir = kagglehub.model_download(f'google/gemma/Flax/{VARIANT}')\\n\",\n    \"ckpt_path = f'{weights_dir}/{VARIANT}'\\n\",\n    \"vocab_path = f'{weights_dir}/tokenizer.model'\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Python imports\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from flax import nnx\\n\",\n    \"import sentencepiece as spm\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To interact with the Gemma model, you will use the Flax NNX `gemma` code from [`google/flax` examples on GitHub](https://github.com/google/flax/tree/main/examples/gemma). Since it is not exposed as a package, you need to use the following workaround to import from the Flax NNX `examples/gemma` on GitHub.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Cloning into '/tmp/tmp_68d13pv/flax'...\\n\",\n      \"remote: Enumerating objects: 31912, done.\\u001b[K\\n\",\n      \"remote: Counting objects: 100% (605/605), done.\\u001b[K\\n\",\n      \"remote: Compressing objects: 100% (250/250), done.\\u001b[K\\n\",\n      \"remote: Total 31912 (delta 406), reused 503 (delta 352), pack-reused 31307 (from 1)\\u001b[K\\n\",\n      \"Receiving objects: 100% (31912/31912), 23.92 MiB | 18.17 MiB/s, done.\\n\",\n      \"Resolving deltas: 100% (23869/23869), done.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"import sys\\n\",\n    \"import tempfile\\n\",\n    \"with tempfile.TemporaryDirectory() as tmp:\\n\",\n    \"  # Create a temporary directory and clone the `flax` repo.\\n\",\n    \"  # Then, append the `examples/gemma` folder to the path for loading the `gemma` modules.\\n\",\n    \"  ! git clone https://github.com/google/flax.git {tmp}/flax\\n\",\n    \"  sys.path.append(f\\\"{tmp}/flax/examples/gemma\\\")\\n\",\n    \"  import params as params_lib\\n\",\n    \"  import sampler as sampler_lib\\n\",\n    \"  import transformer as transformer_lib\\n\",\n    \"  sys.path.pop();\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Load and prepare the Gemma model\\n\",\n    \"\\n\",\n    \"First, load the Gemma model parameters for use with Flax.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {\n    \"cellView\": \"form\"\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"params = params_lib.load_and_format_params(ckpt_path)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Next, load the tokenizer file constructed using the [SentencePiece](https://github.com/google/sentencepiece) library.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {\n    \"cellView\": \"form\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"True\"\n      ]\n     },\n     \"execution_count\": 7,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"vocab = spm.SentencePieceProcessor()\\n\",\n    \"vocab.Load(vocab_path)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Then, use the Flax NNX [`gemma.transformer.TransformerConfig.from_params`](https://github.com/google/flax/blob/3f3c03b23d4fd3d85d1c5d4d97381a8a2c48b475/examples/gemma/transformer.py#L193) function to automatically load the correct configuration from a checkpoint.\\n\",\n    \"\\n\",\n    \"**Note:** The vocabulary size is smaller than the number of input embeddings due to unused tokens in this release.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_df9b9695e3b04d9fad2d17b732ae6a1b\\\" ></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_df9b9695e3b04d9fad2d17b732ae6a1b\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_df9b9695e3b04d9fad2d17b732ae6a1b\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_df9b9695e3b04d9fad2d17b732ae6a1b\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"transformer = transformer_lib.Transformer.from_params(params)\\n\",\n    \"nnx.display(transformer)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Perform sampling/inference\\n\",\n    \"\\n\",\n    \"Build a Flax NNX [`gemma.Sampler`](https://github.com/google/flax/blob/main/examples/gemma/sampler.py) on top of your model and tokenizer with the right parameter shapes.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {\n    \"cellView\": \"form\"\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sampler = sampler_lib.Sampler(\\n\",\n    \"    transformer=transformer,\\n\",\n    \"    vocab=vocab,\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"You're ready to start sampling!\\n\",\n    \"\\n\",\n    \"**Note:** This Flax NNX [`gemma.Sampler`](https://github.com/google/flax/blob/main/examples/gemma/sampler.py) uses JAX’s [just-in-time (JIT) compilation](https://jax.readthedocs.io/en/latest/jit-compilation.html), so changing the input shape triggers recompilation, which can slow things down. For the fastest and most efficient results, keep your batch size consistent.\\n\",\n    \"\\n\",\n    \"Write a prompt in `input_batch` and perform inference. Feel free to tweak `total_generation_steps` (the number of steps performed when generating a response).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {\n    \"cellView\": \"form\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Prompt:\\n\",\n      \"\\n\",\n      \"# Python program for implementation of Bubble Sort\\n\",\n      \"\\n\",\n      \"def bubbleSort(arr):\\n\",\n      \"Output:\\n\",\n      \"\\n\",\n      \"    for i in range(len(arr)):\\n\",\n      \"        for j in range(len(arr) - i - 1):\\n\",\n      \"            if arr[j] > arr[j + 1]:\\n\",\n      \"                swap(arr, j, j + 1)\\n\",\n      \"\\n\",\n      \"\\n\",\n      \"def swap(arr, i, j):\\n\",\n      \"    temp = arr[i]\\n\",\n      \"    arr[i] = arr[j]\\n\",\n      \"    arr[j] = temp\\n\",\n      \"\\n\",\n      \"\\n\",\n      \"# Driver code\\n\",\n      \"arr = [5, 2, 8, 3, 1, 9]\\n\",\n      \"print(\\\"Unsorted array:\\\")\\n\",\n      \"print(arr)\\n\",\n      \"bubbleSort(arr)\\n\",\n      \"print(\\\"Sorted array:\\\")\\n\",\n      \"print(arr)\\n\",\n      \"\\n\",\n      \"\\n\",\n      \"# Time complexity of Bubble sort O(n^2)\\n\",\n      \"# where n is the length of the array\\n\",\n      \"\\n\",\n      \"\\n\",\n      \"# Space complexity of Bubble sort O(1)\\n\",\n      \"# as it only requires constant extra space for the swap operation\\n\",\n      \"\\n\",\n      \"\\n\",\n      \"# This program uses the bubble sort algorithm to sort the given array in ascending order.\\n\",\n      \"\\n\",\n      \"```python\\n\",\n      \"# This program uses the bubble sort algorithm to sort the given array in ascending order.\\n\",\n      \"\\n\",\n      \"def bubbleSort(arr):\\n\",\n      \"    for i in range(len(arr)):\\n\",\n      \"        for j in range(len(arr) - i - 1):\\n\",\n      \"            if arr[j] > arr[j + 1]:\\n\",\n      \"                swap(arr, j, j + 1)\\n\",\n      \"\\n\",\n      \"\\n\",\n      \"def swap(\\n\",\n      \"\\n\",\n      \"##########\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"input_batch = [\\n\",\n    \"    \\\"\\\\n# Python program for implementation of Bubble Sort\\\\n\\\\ndef bubbleSort(arr):\\\",\\n\",\n    \"  ]\\n\",\n    \"\\n\",\n    \"out_data = sampler(\\n\",\n    \"    input_strings=input_batch,\\n\",\n    \"    total_generation_steps=300,  # The number of steps performed when generating a response.\\n\",\n    \"  )\\n\",\n    \"\\n\",\n    \"for input_string, out_string in zip(input_batch, out_data.text):\\n\",\n    \"  print(f\\\"Prompt:\\\\n{input_string}\\\\nOutput:\\\\n{out_string}\\\")\\n\",\n    \"  print()\\n\",\n    \"  print(10*'#')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"You should get a Python implementation of the bubble sort algorithm.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.9\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "docs_nnx/examples/gemma.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Example: Using pretrained Gemma for inference with Flax NNX\n\nThis example shows how to use Flax NNX to load the [Gemma](https://ai.google.dev/gemma) open model files and use them to perform sampling/inference for generating text. You will use [Flax NNX `gemma` modules](https://github.com/google/flax/tree/main/examples/gemma) written with Flax and JAX for model parameter configuration and inference.\n\n> Gemma is a family of lightweight, state-of-the-art open models based on Google DeepMind’s [Gemini](https://deepmind.google/technologies/gemini/#introduction). Read more about [Gemma](https://blog.google/technology/developers/gemma-open-models/) and [Gemma 2](https://blog.google/technology/developers/google-gemma-2/).\n\nYou are recommended to use [Google Colab](https://colab.research.google.com/) with access to A100 GPU acceleration to run the code.\n\n+++\n\n## Installation\n\nInstall the necessary dependencies, including `kagglehub`.\n\n```{code-cell} ipython3\n! pip install --no-deps -U flax\n! pip install jaxtyping kagglehub treescope\n```\n\n## Download the model\n\nTo use Gemma model, you'll need a [Kaggle](https://www.kaggle.com/models/google/gemma/) account and API key:\n\n1. To create an account, visit [Kaggle](https://www.kaggle.com/) and click on 'Register'.\n2. If/once you have an account, you need to sign in, go to your ['Settings'](https://www.kaggle.com/settings), and under 'API' click on 'Create New Token' to generate and download your Kaggle API key.\n3. In [Google Colab](https://colab.research.google.com/), under 'Secrets' add your Kaggle username and API key, storing the username as `KAGGLE_USERNAME` and the key as `KAGGLE_KEY`. If you are using a [Kaggle Notebook](https://www.kaggle.com/code) for free TPU or other hardware acceleration, it has a key storage feature under 'Add-ons' > 'Secrets', along with instructions for accessing stored keys.\n\nThen run the cell below.\n\n```{code-cell} ipython3\nimport kagglehub\nkagglehub.login()\n```\n\nIf everything went well, it should say `Kaggle credentials set. Kaggle credentials successfully validated.`.\n\n**Note:** In Google Colab, you can instead authenticate into Kaggle using the code below after following the optional step 3 from above.\n\n```\nimport os\nfrom google.colab import userdata # `userdata` is a Colab API.\n\nos.environ[\"KAGGLE_USERNAME\"] = userdata.get('KAGGLE_USERNAME')\nos.environ[\"KAGGLE_KEY\"] = userdata.get('KAGGLE_KEY')\n``` \n\nNow, load the Gemma model you want to try. The code in the next cell utilizes [`kagglehub.model_download`](https://github.com/Kaggle/kagglehub/blob/8efe3e99477aa4f41885840de6903e61a49df4aa/src/kagglehub/models.py#L16) to download model files.\n\n**Note:** For larger models, such as `gemma 7b` and `gemma 7b-it` (instruct), you may require a hardware accelerator with plenty of memory, such as the NVIDIA A100.\n\n**Note:** To avoid 403 error when downloading the model, you need to consent to the license for Gemma models on Kaggle. To do that, open https://www.kaggle.com/models/google/gemma/flax/ in the browser and click on \"Download\" button choosing any version of Gemma model. In the next window you will be proposed to agree with Gemma models usage license. Once, this step is done, you will be able to download the model using the code below.\n\n```{code-cell} ipython3\nfrom IPython.display import clear_output\n\nVARIANT = '2b-it' # @param ['2b', '2b-it', '7b', '7b-it'] {type:\"string\"}\nweights_dir = kagglehub.model_download(f'google/gemma/Flax/{VARIANT}')\nckpt_path = f'{weights_dir}/{VARIANT}'\nvocab_path = f'{weights_dir}/tokenizer.model'\n```\n\n## Python imports\n\n```{code-cell} ipython3\nfrom flax import nnx\nimport sentencepiece as spm\n```\n\nTo interact with the Gemma model, you will use the Flax NNX `gemma` code from [`google/flax` examples on GitHub](https://github.com/google/flax/tree/main/examples/gemma). Since it is not exposed as a package, you need to use the following workaround to import from the Flax NNX `examples/gemma` on GitHub.\n\n```{code-cell} ipython3\nimport sys\nimport tempfile\nwith tempfile.TemporaryDirectory() as tmp:\n  # Create a temporary directory and clone the `flax` repo.\n  # Then, append the `examples/gemma` folder to the path for loading the `gemma` modules.\n  ! git clone https://github.com/google/flax.git {tmp}/flax\n  sys.path.append(f\"{tmp}/flax/examples/gemma\")\n  import params as params_lib\n  import sampler as sampler_lib\n  import transformer as transformer_lib\n  sys.path.pop();\n```\n\n## Load and prepare the Gemma model\n\nFirst, load the Gemma model parameters for use with Flax.\n\n```{code-cell} ipython3\n:cellView: form\n\nparams = params_lib.load_and_format_params(ckpt_path)\n```\n\nNext, load the tokenizer file constructed using the [SentencePiece](https://github.com/google/sentencepiece) library.\n\n```{code-cell} ipython3\n:cellView: form\n\nvocab = spm.SentencePieceProcessor()\nvocab.Load(vocab_path)\n```\n\nThen, use the Flax NNX [`gemma.transformer.TransformerConfig.from_params`](https://github.com/google/flax/blob/3f3c03b23d4fd3d85d1c5d4d97381a8a2c48b475/examples/gemma/transformer.py#L193) function to automatically load the correct configuration from a checkpoint.\n\n**Note:** The vocabulary size is smaller than the number of input embeddings due to unused tokens in this release.\n\n```{code-cell} ipython3\ntransformer = transformer_lib.Transformer.from_params(params)\nnnx.display(transformer)\n```\n\n## Perform sampling/inference\n\nBuild a Flax NNX [`gemma.Sampler`](https://github.com/google/flax/blob/main/examples/gemma/sampler.py) on top of your model and tokenizer with the right parameter shapes.\n\n```{code-cell} ipython3\n:cellView: form\n\nsampler = sampler_lib.Sampler(\n    transformer=transformer,\n    vocab=vocab,\n)\n```\n\nYou're ready to start sampling!\n\n**Note:** This Flax NNX [`gemma.Sampler`](https://github.com/google/flax/blob/main/examples/gemma/sampler.py) uses JAX’s [just-in-time (JIT) compilation](https://jax.readthedocs.io/en/latest/jit-compilation.html), so changing the input shape triggers recompilation, which can slow things down. For the fastest and most efficient results, keep your batch size consistent.\n\nWrite a prompt in `input_batch` and perform inference. Feel free to tweak `total_generation_steps` (the number of steps performed when generating a response).\n\n```{code-cell} ipython3\n:cellView: form\n\ninput_batch = [\n    \"\\n# Python program for implementation of Bubble Sort\\n\\ndef bubbleSort(arr):\",\n  ]\n\nout_data = sampler(\n    input_strings=input_batch,\n    total_generation_steps=300,  # The number of steps performed when generating a response.\n  )\n\nfor input_string, out_string in zip(input_batch, out_data.text):\n  print(f\"Prompt:\\n{input_string}\\nOutput:\\n{out_string}\")\n  print()\n  print(10*'#')\n```\n\nYou should get a Python implementation of the bubble sort algorithm.\n"
  },
  {
    "path": "docs_nnx/examples/index.rst",
    "content": "Examples\n========\n\n.. toctree::\n   :maxdepth: 2\n\n   gemma\n   core_examples\n\n\n"
  },
  {
    "path": "docs_nnx/faq.rst",
    "content": "Frequently Asked Questions (FAQ)\n================================\n\nThis is a collection of answers to frequently asked questions (FAQ). You can contribute to the Flax FAQ by starting a new topic in `GitHub Discussions <https://github.com/google/flax/discussions>`__.\n\nWhere to search for an answer to a Flax-related question?\n*********************************************************\n\nThere are a number of official Flax resources to search for information:\n\n- `Flax Documentation on ReadTheDocs <https://flax.readthedocs.io/en/latest/>`__ (this site): Use the `search bar <https://flax.readthedocs.io/en/search.html>`__ or the table of contents on the left-hand side.\n- `google/flax GitHub Discussions <https://github.com/google/flax/discussions>`__: Search for an existing topic or start a new one. If you can't find what you're looking for, feel free to ask the Flax team or community a question.\n- `google/flax GitHub Issues <https://github.com/google/flax/issues>`__: Use the search bar to look for an existing issue or a feature request, or start a new one.\n\nHow to take the derivative with respect to an intermediate value (using :code:`Module.perturb`)?\n************************************************************************************************\n\nTo take the derivative(s) or gradient(s) of the output with respect to a hidden/intermediate activation inside a model layer, you can use :meth:`flax.linen.Module.perturb`. You define a zero-value :class:`flax.linen.Module` \"perturbation\" parameter – :code:`perturb(...)` – in the forward pass with the same shape as the intermediate activation, define the loss function with :code:`'perturbations'` as an added standalone argument, perform a JAX derivative operation with :code:`jax.grad` on the perturbation argument.\n\nFor full examples and detailed documentation, go to:\n\n- The :meth:`flax.linen.Module.perturb` API docs\n- The `Extracting gradients of intermediate values <https://flax.readthedocs.io/en/latest/guides/model_inspection/extracting_intermediates.html#extracting-gradients-of-intermediate-values>`_ guide\n- `Flax GitHub Discussions #1152 <https://github.com/google/flax/discussions/1152>`__\n\nIs Flax Linen :code:`remat_scan()` the same as :code:`scan(remat(...))`?\n************************************************************************\n\nFlax :code:`remat_scan()` (:meth:`flax.linen.remat_scan()`) and :code:`scan(remat(...))` (:meth:`flax.linen.scan` over :meth:`flax.linen.remat`) are not the same, and :code:`remat_scan()` is limited in cases it supports. Namely, :code:`remat_scan()` treats the inputs and outputs as carries (hidden states that are carried through the training loop). You are recommended to use :code:`scan(remat(...))`, as typically you would need the extra parameters, such as ``in_axes`` (for input array axes) or ``out_axes`` (output array axes), which :meth:`flax.linen.remat_scan` does not expose.\n\nWhat are the recommended training loop libraries?\n*************************************************\n\nConsider using CLU (Common Loop Utils) `google/CommonLoopUtils <https://github.com/google/CommonLoopUtils>`__. To get started, go to this `CLU Synopsis Colab <https://colab.research.google.com/github/google/CommonLoopUtils/blob/main/clu_synopsis.ipynb>`__. You can find answers to common questions about CLU with Flax on `google/flax GitHub Discussions <https://github.com/google/flax/discussions?discussions_q=clu>`__.\n\nCheck out the official `google/flax Examples <https://github.com/google/flax/tree/main/examples>`__ for examples of using the training loop with  (CLU) metrics. For example, this is `Flax ImageNet's train.py <https://github.com/google/flax/blob/main/examples/imagenet/train.py>`__.\n\nFor computer vision research, consider `google-research/scenic <https://github.com/google-research/scenic>`__. Scenic is a set of shared light-weight libraries solving commonly encountered tasks when training large-scale vision models (with examples of several projects). Scenic is developed in JAX with Flax. To get started, go to the `README page on GitHub <https://github.com/google-research/scenic#getting-started>`__."
  },
  {
    "path": "docs_nnx/flip/0000-template.md",
    "content": "- Start Date: (fill me in with today's date, YYYY-MM-DD)\n- FLIP PR: [#0000](https://github.com/google/flax/pull/0000)\n- FLIP Issue: [#0000](https://github.com/google/flax/issues/0000)\n\n(Below sections are just a possible structure - please adapt to your FLIP.)\n\n# Summary\n[summary]: #summary\n\nOne paragraph explanation of the FLIP.\n\n# Motivation\n[motivation]: #motivation\n\nWhy are we doing this? What use cases does it support? What is the expected outcome?\n\n# Implementation\n[implementation]: #implementation\n\nThe technical part.\n\n# Discussion\n[discussion]: #discussion\n\nSummarize the discussion from the original issue and from the pull request.\n"
  },
  {
    "path": "docs_nnx/flip/1009-optimizer-api.md",
    "content": "- Start Date: 2021-02-08\n- FLIP PR: [#1011](https://github.com/google/flax/pull/1011)\n- FLIP Issue: [#1009](https://github.com/google/flax/issues/1009)\n\nTable of contents:\n\n- [Summary]\n- [Motivation]\n- [Using Optax]\n  - [Gradient Transformations]\n  - [Optax Training Step]\n  - [Multi Optimizer]\n  - [Train State]\n- [Previous API]\n  - [Optimizer and OptimizerDef]\n  - [Previous Training Step]\n- [Update Plan]\n- [Appendix]\n  - [Setup Code]\n\n# Summary\n[Summary]: #summary\n\nThis FLIP proposes to replace our current `flax.optim` API (referred to as\n[previous API] in this document) with [Optax], DeepMind's optimizer library.\n\n# Motivation\n[motivation]: #motivation\n\nOur current API (referred to as [previous API] in this document) uses a pattern\nwhere an `Optimizer` dataclass is created from a pytree of `target` variables\nand from an `OptimizerDef` that defines how to update optimizer state,\nhyperparameters, and target variables. This pattern is relatively complex for\nimplementing a simple optimizer, while being quite verbose in the typical Linen\ntrain step (especially when using mutable state collections).\n\nThis package `flax.optim` contains some optimizers, but that list is far from\nexhaustive and ideally we would instead use JAX optimizers from a dedicated PyPi\npackage.\n\nDeepMind already has a dedicated library — [Optax] — that implements a wide\nrange of interesting optimizers and provides a framework to compose new\noptimizers from reusable gradient transformations.\n\n[Optax]: https://github.com/deepmind/optax\n\n# Using Optax\n[Using Optax]: #using-optax\n\n## Gradient Transformations\n[Gradient Transformations]: #gradient-transformations\n\nWhile [Optax] does provide predefined optimizers (like `optax.adam`, or\n`optax.sgd` with momentum), it is really a library of *gradient transformations*\nand the idiomatic way of instantiating an optimizer is by providing a\ncombination of these gradient transformations. To emulate the momentum\noptimizer from the example when using the [previous API] we would write:\n\n```python\nimport optax\n\ntx = optax.chain(\n    optax.trace(decay=0.9, nesterov=False),\n    optax.scale_by_schedule(lambda step: -get_learning_rate(step)),\n)\n```\n\nRemarks:\n\n- Above gradient transformation would be equivalent with the example under\n  [Optimizer and OptimizerDef] where we define a Momentum optimizer without\n  Nesterov momentum (note that the `beta` parameter corresponds to the `decay`\n  parameter of the `optax.trace()` transformation, and the learning rate is\n  applied in a second chained transformation).\n- Note that hyper parameters like `decay` or `nesterov` only exist in the inner\n  scope of the higher order functions returning the `GradientTransformation`.\n  Such a gradient transformation is currently defined as a `NamedTuple` of the\n  `init()` and the `update()` function. In principle this pattern could be\n  extended to also store hyperparameters, maybe a point to discuss on the\n  [Optax] repo.\n- We can use a `get_learning_rate()` that returns the learning rate depending on\n  the step number when defining the Optax gradient update transformation. Above\n  code illustrates how this can be a drop-in replacement for a function we also\n  use in our [previous training step], where this update function already exists\n  (notice how we need to invert the sign because we add the gradient update to\n  the parameters). In addition, you can use\n  [`inject_hyperparams()`](https://github.com/deepmind/optax/pull/48) to\n  schedule arbitrary hyper parameters with Optax.\n\n## Optax Training Step\n[Optax Training Step]: #optax-training-step\n\n```python\n@functools.partial(jax.jit, static_argnums=(4, 5))\ndef train_step(opt_state, variables, inputs, labels, apply_fn, tx_update_fn):\n\n  def loss_fn(params):\n    logits, new_model_state = apply_fn(\n        {**variables, 'params': params}, inputs, mutable=['batch_stats'])\n    loss = xent_loss(logits, labels)\n    return loss, new_model_state\n\n  variables, params = variables.pop('params')\n  (loss, new_model_state), grads = jax.value_and_grad(loss_fn, has_aux=True)(\n      params)\n  updates, new_opt_state = tx_update_fn(grads, opt_state, params)\n  new_params = optax.apply_updates(params, updates)\n  new_variables = {**variables, **new_model_state, 'params': new_params}\n  return new_opt_state, new_variables, loss\n\n\nopt_state = tx.init(variables['params'])\nfor batch in ds.as_numpy_iterator():\n  opt_state, variables, loss = train_step(\n      opt_state, variables, batch['image'], batch['label'], model.apply,\n      tx.update)\n  print(loss)\n```\n\nRemarks:\n\n- Since `tx.update()` only transforms the gradient, we still need to call\n  `optax.apply_updates()` to apply these transformed gradients to the\n  parameters.\n- Compared with the [previous API], we can now keep the entire `variables`\n  including the `params` as an input and output to the `train_step()`.\n- Splitting `params` from `variables` is still necessary inside the train step\n  because we only want to compute gradients with respect to `params` and not the\n  entire `variables`.\n- We can still log internal optimizer state, such as the learning rate, as long\n  as Optax transformations expose that information in their respective state.\n  For example, `optax.scale_by_schedule()` currently only exposes\n  `opt_state.count` but could easily be extend to also expose the `step_size`.\n  The same is true for internal optimizer states that change over time.\n\n## Multi Optimizer\n[Multi Optimizer]: #multi-optimizer\n\nThe [previous API] defined `flax.optim.MultiOptimizer` for processing different\nparts of the parameter tree with different optimizers:\n\n```python\nbiases_traversal = flax.optim.ModelParamTraversal(\n    lambda path, _: path.endswith('/bias'))\nnot_biases_traversal = flax.optim.ModelParamTraversal(\n    lambda path, _: not path.endswith('/bias'))\n\noptimizer_def = flax.optim.MultiOptimizer(\n    (biases_traversal, flax.optim.GradientDescent(learning_rate=0.1)),\n    (not_biases_traversal, flax.optim.GradientDescent(learning_rate=0.05)),\n)\n```\n\nNote how we first define a traversal that selects parameters based on their\npath (which is the concatenation of module scopes and variable name), and then\ncreate a `MultiOptimizer` that binds a different optimizer for each of these\nseparate traversals.\n\nOptax has recently implemented `optax.masked()` that can be used for specifying\ngradient transformations that only applied to a subset of the gradients:\n\n```python\ndef flattened_traversal(fn):\n  def mask(data):\n    flat = traverse_util.flatten_dict(data)\n    return traverse_util.unflatten_dict({k: fn(k, v) for k, v in flat.items()})\n  return mask\n\ntx = optax.chain(\n    optax.masked(optax.sgd(learning_rate=0.1),\n                 mask=flattened_traversal(lambda path, _: path[-1] == 'bias')),\n    optax.masked(optax.sgd(learning_rate=0.05),\n                 mask=flattened_traversal(lambda path, _: path[-1] != 'bias')),\n)\n```\n\n## Train State\n[Train State]: #train-state\n\nIn Flax it is common to hand around a `TrainState` object that can then be\nused for checkpointing. This simplifies the above [Optax training step] a bit by\nreducing the number of arguments and getting rid of the `static_argnums`.\n\nWe can define a `TrainState` dataclass that wraps the common pattern of updating\nthe optimizer state and parameters by applying the gradients.\n\n```python\n# Small helper class in flax.training\nclass TrainState(flax.struct.PyTreeNode):\n  step: int\n  apply_fn: Callable = flax.struct.field(pytree_node=False)\n  params: flax.core.FrozenDict[str, Any]\n  tx: optax.GradientTransformation = flax.struct.field(pytree_node=False)\n  opt_state: optax.OptState\n\n  def apply_gradients(self, *, grads, **kwargs):\n    updates, new_opt_state = self.tx.update(\n        grads, self.opt_state, self.params)\n    new_params = optax.apply_updates(self.params, updates)\n    return self.replace(\n        step=self.step + 1,\n        params=new_params,\n        opt_state=new_opt_state,\n        **kwargs,\n    )\n\n  @classmethod\n  def create(cls, *, apply_fn, params, tx, **kwargs):\n    opt_state = tx.init(params)\n    return cls(\n        step=0,\n        apply_fn=apply_fn,\n        params=params,\n        tx=tx,\n        opt_state=opt_state,\n        **kwargs,\n    )\n```\n\nUsers can then derive from this dataclass and add more fields, for example\nmutable model state:\n\n```python\nfrom flax.training import train_state\n\nclass TrainState(train_state.TrainState):\n  batch_stats: flax.core.FrozenDict[str, Any]\n```\n\nWith this the [Optax Training Step] becomes:\n\n```python\n@jax.jit\ndef train_step(state, inputs, labels):\n\n  def loss_fn(params):\n    outputs, new_model_state = state.apply_fn(\n        {'params': params, 'batch_stats': state.batch_stats},\n        inputs,\n        mutable=['batch_stats'])\n    loss = xent_loss(outputs, labels)\n    return loss, new_model_state\n\n  (loss, new_model_state), grads = jax.value_and_grad(\n      loss_fn, has_aux=True)(state.params)\n  new_state = state.apply_gradients(\n      grads=grads,\n      batch_stats=new_model_state['batch_stats'],\n  )\n\n  return new_state, loss\n\n\nstate = TrainState.create(\n    apply_fn=model.apply,\n    params=variables['params'],\n    tx=tx,\n    batch_stats=variables['batch_stats'],\n)\nfor batch in ds.as_numpy_iterator():\n  state, loss = train_step(state, batch['image'], batch['label'])\n```\n\nThe train step without mutable state reduces to:\n\n```python\n@jax.jit\ndef train_step(state, inputs, labels):\n\n  def loss_fn(params):\n    outputs = state.apply_fn({'params': params}, inputs)\n    loss = xent_loss(outputs, labels)\n    return loss\n\n  loss, grads = jax.value_and_grad(loss_fn)(state.params)\n  new_state = state.update(grads=grads)\n\n  return new_state, loss\n\n\nstate = flax.training.TrainState.create(\n    apply_fn=model.apply,\n    params=variables['params'],\n    tx=tx,\n)\nfor batch in ds.as_numpy_iterator():\n  state, loss = train_step(state, batch['image'], batch['label'])\n```\n\nRemarks:\n\n- It is a common pattern in Flax training loops to have a `TrainState` dataclass\n  that is updated with new state after every step.\n- The simple solution proposed in `flax.training.train_state` an be extended\n  with additional data, but advanced usecases (e.g. multiple different models\n  and/or optimizers) are not supported. Users should instead fork the dataclass\n  and re-implement it to their needs.\n- As opposed to the `Optimizer` abstraction in the [previous API], the\n  `TrainState` now directly contains the `.params`, without having to to through\n  `.optimizer`\n\n# Previous API\n[previous API]: #previous-api\n\n## Optimizer and OptimizerDef\n[Optimizer and OptimizerDef]: #optimizer-and-optimizerdef\n\nThe optimizer itself would be implemented by creating a new class derived\nfrom `OpimizerDef`:\n\n```python\n# flax/optim/momentum.py\n\n@flax.struct.dataclass\nclass _MomentumHyperParams:\n  learning_rate: jnp.ndarray\n  beta: jnp.ndarray\n\n\n@flax.struct.dataclass\nclass _MomentumParamState:\n  momentum: np.ndarray\n\n\nclass Momentum(flax.optim.OptimizerDef):\n\n  def __init__(self, learning_rate=None, beta=0.9):\n    super().__init__(\n      _MomentumHyperParams(learning_rate, beta)\n    )\n\n  def init_param_state(self, param):\n    return _MomentumParamState(jnp.zeros_like(param))\n\n  def apply_param_gradient(self, step, hyper_params, param, state, grad):\n    del step\n    assert hyper_params.learning_rate is not None\n    new_momentum = state.momentum * hyper_params.beta + grad\n    new_params = param - hyper_params.learning_rate * new_momentum\n    return new_params, _MomentumParamState(new_momentum)\n```\n\nRemarks:\n\n- Note the relationship between `OptimizerDef` and `Optimizer` : When the\n  function `Optimizer.apply_gradient()` is called from the user code, it calls\n  into `OptimizerDef.apply_gradient()` (among other things) which in turn will\n  call `OptimizerDef.apply_param_gradient()` (implemented by subclasses of\n  `OptimizerDef`).\n- The functions `init_param_state()` and `apply_param_gradient()` are called\n  for every leaf in the params/grads pytree. This makes it possible to write the\n  calculations directly without `jax.tree_util.tree_map()`.\n- The interface was defined in pre-Linen without the distinction of `params` vs.\n  other collections in `variables` in mind. The original API was elegant because\n  one only needed to pass around the optimizer, which included the parameters,\n  optimizer state, optimizer hyperparameters, and a reference to the\n  `OptimizerDef` to perform the param/state update.\n\n## Previous Training Step\n[Previous Training Step]: #previous-training-step\n\nAn optimizer would first be constructed from its definition and the pytree of\ntarget params:\n\n```python\noptimizer_def = flax.optim.Momentum(learning_rate=0.1, beta=0.9)\noptimizer = optimizer_def.create(variables['params'])\n```\n\nThen, the target variables would optimized in the train step (assuming a single\nnon-params collection \"batch_stats\"):\n\n```python\ndef make_train_step(apply_fn):\n  @jax.jit\n  def train_step(optimizer, batch_stats, inputs, labels):\n\n    def loss_fn(params):\n      variables = {'params': params, 'batch_stats': batch_stats}\n      logits, new_model_state = apply_fn(\n          variables, inputs, mutable=['batch_stats'])\n      loss = xent_loss(logits, labels)\n      return loss, new_model_state['batch_stats']\n\n    (loss, new_batch_stats), grad = jax.value_and_grad(loss_fn, has_aux=True)(\n        optimizer.target)\n    lr = get_learning_rate(step)\n    new_optimizer = optimizer.apply_gradient(grad, learning_rate=lr)\n    return new_optimizer, new_batch_stats, loss\n\n  return train_step\n\n\nbatch_stats = variables['batch_stats']\ntrain_step = make_train_step(model.apply)\nfor step, batch in enumerate(ds)\n  optimizer, batch_stats, loss = train_step(\n      optimizer, batch_stats, batch['image'], batch['label'])\n```\n\nRemarks:\n\n- Notice how `optimizer.apply_gradient()` can take additional arguments to\n  update hyperparameters, such as learning rate from an independent function\n  `get_learning_rate()` in this case.\n\n\n# Update Plan\n[Update Plan]: #update-plan\n\n1. Finalize discussions on this FLIP\n2. Add [equivalence tests] to Optax that guarantee that existing `flax.optim`\n   optimizers return identical values with corresponding `optax` optimizers.\n3. Update examples to use Optax and verify that they reach the same final\n   performance with the same computational cost.\n4. Port missing optimizers to Optax (e.g. Adafactor) - and verify above points.\n5. Update all documentation (including README, Flax guided tour, HOWTOs, ...) to\n   talk exclusively about Optax optimizers.\n6. Create a transition guide for updating users from `flax.optim` to using\n   Optax. This transition guide should also point to Optax's [equivalence tests]\n   and the pull requests updating the examples.\n7. Mark optimizers in `flax.optim` as deprecated.\n\n[equivalence tests]: https://github.com/deepmind/optax/blob/master/optax/_src/equivalence_test.py\n\nNote that all current Flax examples use an optimizer that is already available\nin Optax:\n\n| Example  |      Flax      |    Optax    |              Comments               |\n| -------- | -------------- | ----------- | ----------------------------------- |\n| imagenet | optim.Momentum | optax.sgd   | DynamicScale can be used unchanged. |\n| mnist    | optim.Momentum | optax.sgd   |                                     |\n| nlp_seq  | optim.Adam     | optax.adamw |                                     |\n| pixelcnn | optim.Adam     | optax.adam  |                                     |\n| ppo      | optim.Adam     | optax.adam  |                                     |\n| seq2seq  | optim.Adam     | optax.adam  |                                     |\n| vae      | optim.Adam     | optax.adam  |                                     |\n| wmt      | optim.Adam     | optax.adamw |                                     |\n\n(Flax's Adam implementation has an optional parameter for weight decay, but in\nOptax Adam with and without weight decay are two different aliases.)\n\n# Appendix\n[Appendix]: #appendix\n\n## Setup Code\n[Setup Code]: #setup-code\n\nThe following setup code can be used for running the code snippets in this\nFLIP:\n\n```python\nimport functools\nfrom typing import Callable, Sequence\n\nimport jax\nimport jax.numpy as jnp\nimport flax\nimport flax.linen as nn\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\n\ndef pp(features):\n  return {\n      'image': tf.cast(features['image'], tf.float32) / 255 - 0.5,\n      'label': features['label'],\n  }\n\n\nclass Model(nn.Module):\n\n  @nn.compact\n  def __call__(self, inputs):\n    x = inputs.reshape([inputs.shape[0], -1])\n    x = nn.normalization.BatchNorm(True)(x)\n    x = nn.Dense(10)(x)\n    x = nn.log_softmax(x)\n    return x\n\n\ndef onehot(labels, num_classes, on_value=1.0, off_value=0.0):\n  x = (labels[..., None] == jnp.arange(num_classes)[None])\n  x = jax.lax.select(\n      x, jnp.full(x.shape, on_value), jnp.full(x.shape, off_value))\n  return x.astype(jnp.float32)\n\n\ndef xent_loss(logits, labels):\n  return -jnp.sum(\n      onehot(labels, num_classes=10) * logits) / labels.size\n\n\ndef get_learning_rate(step):\n  return 0.1\n\n\nmodel = Model()\nrng = jax.random.key(0)\nds = tfds.load('mnist')['train'].take(160).map(pp).batch(16)\nbatch = next(iter(ds))\nvariables = model.init(rng, jnp.array(batch['image'][:1]))\njax.tree_util.tree_map(jnp.shape, variables)\n```\n"
  },
  {
    "path": "docs_nnx/flip/1777-default-dtype.md",
    "content": "# FLIP: Default dtypes\n\n\n- Start Date: 2022-01-11\n- FLIP PR: [#1776](https://github.com/google/flax/pull/1776)\n- FLIP Issue: [#1777](https://github.com/google/flax/issues/1777)\n- Status: Implemented\n\n\n## Summary\n\nThis FLIP proposes to replace the default dtype which is currently fixed to float32, and instead use the JAX type promotion results to derive a default dtype from the input and parameters of a layer.\n\n\n## Motivation\n\nCurrently, Linen Modules always produce `module.dtype` (defaults to float32) outputs regardless of input and parameter dtypes. Half-precision types like float16 and bfloat16 are supported by explicitly passing the half-precision type to each Module. The way this is currently implemented is that each Module has a dtype argument with float32 as the default value. The layer guarantees that this dtype will be the return type of the result returned by `__call__`.\n\nThe current behavior is problematic and results in silent bugs, especially for dtypes that do not fit inside float32 (complex, float64). Also, the Linen dtype behavior is significantly different from how NumPy and by extension JAX handle dtypes.\n\n\n### Dtypes in JAX\n\nJAX uses a NumPy-inspired [dtype promotion](https://github.com/jax-ml/jax/blob/main/jax/_src/dtypes.py) mechanism as explained [here](https://jax.readthedocs.io/en/latest/type_promotion.html?highlight=lattice#type-promotion-semantics). The type promotion rules are summarized by the following type lattice:\n\n![JAX type promotion lattice](https://jax.readthedocs.io/en/latest/_images/type_lattice.svg)\n\n\n## Dtypes in Linen\n\nBesides input arguments, state and in particular parameters could affect dtype promotion. For example: we might feed a float64 input to a Dense layer with float32 parameters. Currently, the result would be truncated to float32. If the input is a complex number the result is even worse because the imaginary part will be silently dropped when casting to float32.\n\nBy using the dtype promotion rules already available in JAX we can avoid this issue. A public API is available called `jax.numpy.result_dtype(*args)`, which returns the dtype that JAX would promote the given arguments to, in accordance with the type promotion lattice. For Linen layers the arguments would be the layer inputs together with the parameters. For example, for a linear layer this would be inputs, kernel, and bias.\n\nNote that there is also a `param_dtype` attribute in standard Linen Modules that also defaults to flaot32. This behavior is left untouched and encodes the common case of having float32 parameters.\nThere are a few reasons why float32 is almost always the correct dtype for parameters:\n1. Storing weights in half-precision often leads to underflow during optimization.\n2. Double precision is rarely used because it severely slows down modern accelerators (GPU, TPU). Therefore, such a cost should be explicitly opted-in for.\n3. Complex Modules are relatively uncommon. Even within complex networks, the complex inputs can be projected with a real matrix.\n\n\n# Implementation\n\nA simplified example implementation:\n\n\n```python\ndef promote_arrays(*xs, dtype):\n if dtype is None:\n   dtype = jnp.result_type(*jax.tree_util.tree_leaves(xs))\n return jax.tree_util.tree_map(lambda x: jnp.asarray(x, dtype), xs)\n\nDtype = Any\nclass Dense(nn.Module):\n features: int\n kernel_init: Callable\n bias_init: Callable\n dtype: Optional[Dtype] = None\n param_dtype: Dtype = jnp.float32\n\n @nn.compact\n def __call__(self, x):\n   kernel = self.param(\"kernel\",\n                       self.kernel_init,\n                       (x.shape[-1], self.features), self.param_dtype)\n   bias = self.param(\"bias\", self.bias_init, (self.features,), self.param_dtype)\n   x, kernel, bias = promote_arrays(x, kernel, bias, dtype=self.dtype)\n   return x @ kernel + bias\n```\n\n\n## Half-precision dtypes\n\nSome layers don’t work with half-precision dtypes internally. For example: The normalization layers currently compute mean and variance in float32 even when a half-precision dtype is specified to avoid numerical issues. We can replicate this behavior by calling result_dtype with a dummy argument that has the minimum precision for the sub computation to work correctly.\n\n\n## Backward compatibility\n\nThis proposal causes some layers to behave differently in cases where the dtype is not specified to a Linen Module. By default, parameters are in float32. Therefore, passing in half or float32 precision inputs will cause a float32 dtype and no functional differences with current behavior.\n\nWhen passing complex or float64 precision, the result will no longer truncate the imaginary component or the precision. The silent truncation is problematic and has caused [user complaints](https://github.com/google/flax/issues/805#issuecomment-981468837). Therefore, this change can be considered a bugfix.\n\nThus, although this proposal strictly speaking changes behavior it is unlikely to cause problems for users. There are 2 exceptions to this which should be rare and easy to fix:\n1. A user relies on the enforced float32 to downcast a double precision value.\n2. A user relies on the float32 to explicitly upcast a half precision value even though the weights are in half precision.\n\n\n## Corner cases\n\nIn this section we describe corner cases where the implementation of the proposal is not obvious. The two main concerns are how complex numbers are handled in existing layers and how to determine the dtype of state variables.\n\n**Autoregressive decoding cache**\n\nCurrently, only attention implements autoregressive caching and the stored key and value mirror the dtype of the key and value passed to the layer. Forcing the cache dtype to be the same as the output dtype could result in reduced precision during cached decoding vs uncached. This seems undesirable. Decision: keep the current behavior.\n\n**Batch statistics**\n\nBatchNorm layers are often used with a half precision output dtype. However, calculating statistics is by default always done in float32 to avoid numerical precision issues and over/underflow for float16. With float64 this would actually cause a downcast so we should now use `np.promote_types(float32, dtype)` such that the precision is at least float32. The running batch statistics will be stored with the same dtype for consistency.\n\n**Complex number support**\n\nCurrently, our complex number support is brittle because the default behavior is to truncate the output to the real part. This issue will be fixed by the automatic type promotion proposed in this FLIP. However, some layers require some additional thought to extend to complex numbers correctly:\n\n1. Normalization layers use the complex conjugate to calculate norms instead of normal squaring.\n2. Attention: It’s not exactly clear how the dot product and softmax are defined in this case. Raise an error on complex inputs.\n3. Recurrent layers: might require special gating / activation functions to function correctly, but these can be specified by the user.\n\n\n# Discussion\n\nSummarizing the main points from the discussion:\n\n\n## Consider implicit complex truncation an error\n\nQ:\nI'm wondering if we should always raise an error if one of the xs tree leaves is complex but dtype is not. Users should maybe remove imaginary part by themselves if that's really what they want to do.\n(Maybe it's a contrived example, but I can imagine cases where layers have their dtype set by parent modules based on assumptions without complex numbers in mind)\n\nA:\nThis is worth considering in a follow-up CL but this might as well be solved in JAX directly where the safeguard would apply more generally. In NumPy this was also considered but abandoned because it is not backwards compatible.\n\n\n## Dtype attribute names\n\nQ:\nAre the dtype and param_dtype arguments confusing? In particular, should dtype perhaps be called output_dtype to make the difference between the two dtypes more explicit?\n\nA:\nThis would be a large and orthogonal change wrt to this proposal so leaving it out for now.\nAlso, this breaks with the standard dtype argument in NumPY/JAX.\nAlthough dtype indeed constrains the output dtype it is also a hint for the dtype we would like the computation to happen in.\n\n"
  },
  {
    "path": "docs_nnx/flip/2396-rnn.md",
    "content": "# RNN Flip\n\n- Start Date: 2022-08-18\n- FLIP PR: [#2604](https://github.com/google/flax/pull/2604)\n- FLIP Issue: [#2396](https://github.com/google/flax/issues/2396)\n- Authors: Jasmijn Bastings (@bastings) and Cristian Garcia (@cgarciae)\n\n## Summary\nThis FLIP adds support for higher-level recurrent layers (RNN, GRU, LSTM) that can help users process input sequences using the recurrent cells already available in Flax.\n\n## Motivation\nImplementing well known recurrent architectures is tricky and prone to user errors, even a simple LSTM layers involves the manual creation and handling of the carry/memory and correctly setting up `nn.scan`:\n\n```python\n@nn.compact\ndef __call__(self, x):\n  LSTM = nn.scan(\n    nn.LSTMCell, variable_broadcast=\"params\", split_rngs={\"params\": False}\n  )\n  carry = LSTM.initialize_carry(\n    jax.random.key(0), batch_dims=x.shape[:1], size=self.hidden_size\n  )\n  carry, x = LSTM()(carry, x)\n  return x\n```\nSlightly more complicated cases involving padding like in the [seq2seq](https://github.com/google/flax/blob/main/examples/seq2seq/models.py) example require even more work but couple potentially be simplified to a couple of lines with the right abstractions. We propose providing users with clean, correct, and efficient abstractions to use recurrent cells.\n\n## Requirements\n\n* **Masking**: We need to support a batch of sequences that contain padding at the end of each sequence.\n   * We do not intend to support non-contiguous padding, i.e. padding that is not at the end of a sequence, for performance reasons, except in the case of packing (see below).\n* **Bidirectionality**: The ability to process a sequence in both the forward and reverse directions, respecting padding (i.e., the reverse direction should start with the actual inputs, not with padding values).\n* **Performance**: The proposed classes should be benchmarked to provide the best performance in terms of step time and/or memory use.\n* **Recurrent Dropout**: Support for recurrent dropout in cells (e.g. dropout on the state of the cell).\n\n## Implementation\n### High-level structure\n\nWe propose to have these 3 levels of abstraction:\n\n* **Cells (unchanged)**: all RNNCellBase subclasses such as LSTMCell and GRUCell, these implement the stepwise logic. These already exist in Flax today.\n* **Layers (new)**: a class (RNN) that takes a cell and scans over a sequence respecting possible padding values and optionally also allows packed sequences.\n* **Bidirectional (new)**: a single class that takes a forward and a backward RNN instance and correctly processes the input sequence in both directions and merges the results.\n\n### Example of proposed API\nWe start with a code example of what you could do with the proposed API, and then we discuss the API in detail below.\n\n```python\ncell = nn.LSTMCell()\n# Encodes a batch of input sequences.\ncarry, outputs = nn.RNN(cell, cell_size)(inputs, seq_lengths)\n```\n\nA Bidirectional layer with a LSTM RNNs for the forward and backward directions respectively would look like this:\n\n```python\nforward_rnn = nn.RNN(nn.LSTMCell(), cell_size=32)\nbackward_rnn = nn.RNN(nn.LSTMCell(), cell_size=32)\n# Bidirectional combinator.\nbi_rnn = nn.Bidirectional(forward_rnn, backward_rnn)\n# Encodes a batch of input sequences in both directions.\ncarry, outputs = bi_rnn(inputs, seq_lengths)\n```\n\nNext we will discuss `RNN`, `Bidirectional`, and proposed changes to `RNNCellBase`.\n\n### RNNBase\nThe `RNNBase` class serves as a base class for the `RNN` class, it specifies\nthe API that all RNN layers should implement to be compatible with the `Bidirectional`.\n`RNNBase` contains the `__call__` and `flip_sequences` methods:\n\n```python\nclass RNNBase(Protocol):\n  def __call__(\n      self,\n      inputs: jax.Array,\n      *,\n      initial_carry: Optional[Carry] = None,\n      init_key: Optional[random.KeyArray] = None,\n      seq_lengths: Optional[Array] = None,\n      return_carry: Optional[bool] = None,\n      time_major: Optional[bool] = None,\n      reverse: Optional[bool] = None,\n      keep_order: Optional[bool] = None,\n  ) -> Union[Output, Tuple[Carry, Output]]:\n    ...\n```\nWhere:\n\n* `inputs`: the input sequence.\n* `initial_carry`: the initial carry, if not provided it will be initialized\n  using the cell's :meth:`RNNCellBase.initialize_carry` method.\n* `init_key`: a PRNG key used to initialize the carry, if not provided\n  ``jax.random.key(0)`` will be used. Most cells will ignore this\n  argument.\n* `seq_lengths`: an optional integer array of shape ``(*batch)`` indicating\n  the length of each sequence, elements whose index in the time dimension\n  is greater than the corresponding length will be considered padding and\n  will be ignored.\n* `return_carry`: if ``return_carry=False`` (default) only the output sequence is returned,\n  else it will return a tuple of the final carry and the output sequence.\n* `time_major`: if ``time_major=False`` (default) it will expect inputs with shape\n  ``(*batch, time, *features)``, else it will expect inputs with shape ``(time, *batch, *features)``.\n* `reverse`: if ``reverse=False`` (default) the sequence is\n  processed from left to right and returned in the original order, else it will be processed\n  from right to left, and returned in reverse order. If ``seq_lengths`` is passed,\n  padding will always remain at the end of the sequence.\n* `keep_order`: if ``keep_order=True``, when ``reverse=True``\n  the output will be reversed back to the original order after processing, this is\n  useful to align sequences in bidirectional RNNs. If ``keep_order=False`` (default),\n  the output will remain in the order specified by ``reverse``.\n* `Returns`: if ``return_carry=False`` (default) only the output sequence is returned,\nelse it will return a tuple of the final carry and the output sequence.\n\n### RNN\nThe `RNN` module inherits from `RNNBase`, it main function is to apply an `RNNCellBase` instance over a batch of input sequences, it can be used with any type of cell (e.g., `GRUCell`, `LSTMCell`, etc). It accepts the following parameters:\n\n```python\nclass RNN(RNNBase):\n  cell: RNNCellBase,\n  cell_size: int | Tuple[int, ...]\n  time_axis: int = -2,\n  variable_axes = FrozenDict(),\n  variable_broadcast: CollectionFilter = 'params'\n  variable_carry: CollectionFilter = False\n  split_rngs = FrozenDict({'params': False})\n  # implement RNNBase\n  ...\n```\n\nAttributes like `variable_axes`, `variable_broadcast`, `variable_carry`, and `split_rngs` are directly passed to `nn.scan`, their default values are set such that common cells like `LSTMCell` and `GRUCell` work out of the box.\n\n### Masking\n`seq_lengths` is defined as an integer array of shape `(*batch,)` indicating the length of each sequence.\n\n<details><summary>Discussion</summary>\n\nThere are various masking formats found in other frameworks, here are some of the most popular ones:\n\n* **Binary masking**: specifies per-sample and timestep whether that data point should be included or not in the computation, it can be non-contigous (e.g., [1, 1, 0, 1]). This is used by Keras.\n* **Sequence length masking**: specifies per-sample the number of non-padding examples contained in the sequence, any padding contained in the sequence should be stacked at the end. This is used by FlaxFormer.\n* **Segmentation Mask**: specifies row and timestep to which sample the data point belongs to, this format allows more than one sample per row which potentially reduces the total amount of padding needed (e.g. [1, 1, 1, 2, 2, 0, 0]). Pytorch uses this representation (see [pack_padded_sequence](https://pytorch.org/docs/stable/generated/torch.nn.utils.rnn.pack_padded_sequence.html)).\n\nWhile Sequence packing (see [LM1B example](https://github.com/google/flax/blob/main/examples/lm1b/input_pipeline.py#L90-L92)) is is more powerful, its implementation is more complex and it is not clear whether it is worth the effort. The simplest format is sequence length masking, which is the one we propose to use.\n\n</details>\n\n### Bidirectional\nBidirectional processing can be achieved via a Module that accepts a `forward_rnn` Module and a `backward_rnn` Module, both of which should be `RNN` instances, in order to process the input sequence in both directions. Here we present some pseudo code of the implementation:\n\n```python\ndef __call__(self, inputs, seq_lengths):\n  # Encode in the forward direction.\n  carry_forward, outputs_forward = self.forward_rnn(\n    inputs, seq_lengths=seq_lengths,\n    return_carry=True, reverse=False,\n  )\n  # Encode in the reverse order.\n  carry_backward, outputs_backward = self.backward_rnn(\n    inputs, seq_lengths=seq_lengths,\n    return_carry=True, reverse=True, # process in reverse order\n    keep_order=True, # but return the sequence in the original order\n  )\n  # Merge both sequences.\n  outputs = jax.tree.map(self.merge_fn, outputs_forward, outputs_backward)\n\n  return (carry_forward, carry_backward), outputs\n```\n\nHere `merge_fn` a function that takes both outputs and fuses them (`concat` by default). As showcased in the beginning of this document, usage would look like this:\n\n```python\nforward_rnn = nn.RNN(nn.LSTMCell(), cell_size=32)\nbackward_rnn = nn.RNN(nn.GRUCell(), cell_size=32)\n# Bidirectional combinator.\nbi_rnn = nn.Bidirectional(forward_rnn, backward_rnn)\n# Encodes a batch of input sequences in both directions.\ncarry, outputs = bi_rnn(inputs, seq_lengths)\n```\n\n### Recurrent Dropout\nThere are two main uses of dropout in RNNs:\n1. Input dropout: regular dropout applied to the inputs, different for every step.\n4. Recurrent dropout: applies dropout to a recurrent input/output, same for every step.\n\nFlax's `nn.scan` can easily express both types of dropout via `split_rns`, input dropout would split rngs while recurrent dropout would not. [#2540](https://github.com/google/flax/pull/2540) was introduces such that the `rng_name` in `nn.Dropout` can now be defined by the user, this way Cells could define both types of dropout e.g:\n\n```python\nself.dropout = nn.Dropout(...) # input dropout\nself.recurrent_dropout = nn.Dropout(..., rng_collection='recurrent_dropout')\n```\nBased on this, `nn.scan` / `nn.RNN` can now specify `split_rngs` accordingly e.g:\n```\nnn.scan(scan_fn, ..., split_rngs={'dropout': True, 'recurrent_dropout': False})\n```\n\n# Future ideas\n\n<details><summary>show</summary>\n\n### Sequence Packing\nAllow packing multiple sequences to make efficient use of space/memory. This might result in a trade-off where step time is higher (because at each step we need to check whether we are starting a new sequence and reset the carry/initial state), but where less padding is used increasing efficiency overall.\n\n### RNNCell redesign\n\n#### Make initialize_state an instance method\nFirst altenative is to make `initialize_carry` a instance method. With this change hyperparameters can be passed directly to the cell, it signature would look like this:\n\n```python\ndef initialize_carry(self, sample_input) -> Carry:\n  ...\n```\n\nUsage would look like this:\n\n```python\nLSTM = nn.scan(\n  nn.LSTMCell, variable_broadcast='params',\n  split_rngs={'dropout': True})\nlstm = LSTM(features=32)\ncarry = lstm.initialize_carry(x[:, 0])\ncarry, y = lstm(carry, x)\n```\n\n#### Remove initialize_carry\n\nAn alternative is to remove `initialize_carry` entirely and have the carry state be handled as a carry collection. This would simplify usage quite a bit:\n\n```python\nLSTM = nn.scan(\n  nn.LSTMCell, variable_broadcast='params',\n  split_rngs={'dropout': True})\ny = LSTM(features=32)(carry, x)\n```\n\nHowever, this would require `nn.scan` to support initialization of carry collections which is currently not possible. Also, users would have to specify that a collection is mutable e.g. `mutable=['carry']`, even if they are not interested in the output carry state.\n\n</details>\n"
  },
  {
    "path": "docs_nnx/flip/2434-general-metadata.md",
    "content": "# FLIP: Axis Metadata\n\n\n- Start Date: 2022-08-08\n- FLIP Issue: [#2434](https://github.com/google/flax/issues/2434)\n- FLIP PR: [#2435](https://github.com/google/flax/pull/2435)\n- Status: Proposal\n\n\n## Summary\n\nThis FLIP proposes to extend Flax's variable collections with a generic axis metadata API.\nThe core of the API is an abstract base class that is recognized by lifting transformations that can add an axis (vmap, scan).\nUsers can extend the base class to keep track of per-axis metadata in a way that works with lifted transformations.\n\n\n## Motivation\n\nGenerally, there is no way in Flax to track metadata for variables across lifted transformations.\nAxis metadata is used to keep track of semantic information about axes into other (Flax independent) APIs.\nFor example, optimizers like AdaFactor can be configured on a per-axis level and partitioning APIs\nin JAX like xmap or pjit require per variable annotations to map effectiently to parallel hardware.\n\nCurrently, there is an experimental [API](https://github.com/google/flax/blob/main/flax/linen/partitioning.py)\nsupporting partitioning annotations with wrappers around lifted transforms that change axes (``nn.scan_with_axes``, ``nn.vmap_with_axes``)\nand a special APIs to create variables (``param_with_axes`` and ``variable_with_axes``).\nThe experimental partitioning API stores the metadata in a separate collection named \"[collection]_axes\".\n\n\nThe experimental API has a number of shortcomings that we like to solve:\n1. The current API works for tracking PartitionSpecs but not for other types of metadata like optimizer annotations.\n2. The implementation using an \"xxx_axes\" collection requires error-prone and non-composable string manipulation.\n3. Special, partioning-aware variable creators and lifted transforms are required\n4. The partioning API is hard to use with pre-existing Modules that aren't partioning aware.\n\n\n## Proposal\n\nTo generalize metadata tracking and keep the specific metadata out of core Flax we propose the following abstract base class:\n\n```python\nTAxisMetadata = TypeVar(\"TAxisMetadata\", bound=\"AxisMetadata\")\n\nclass AxisMetadata(metaclass=abc.ABCMeta):\n  \"\"\"Abstract base class for boxed Metadata.\n\n  ``AxisMetadata`` enables arbitrary, per axis metadata for variables.\n  By using ``unbox`` the metadata is stripped away to obtain the original\n  variables. By using unboxing, most code handling variables does not need\n  to handle ``AxisMetadata`` specifically, but can directly operate on the JAX\n  arrays that they wrap.\n\n  Additionally, ``AxisMetadata`` supports updating metadata whenever an axis\n  is added or removed by a functional transformation\n  (e.g.: ``nn.scan`` or ``nn.vmap``) using the ``add_axis`` and ``remove_axis``\n  methods.\n\n  By extending ``AxisMetadata``, custom metadata can be stored. See\n  ``Partitioned`` for a specific implementation.\n  \"\"\"\n\n  @abc.abstractmethod\n  def unbox(self) -> Any:\n    \"\"\"Returns the content of the AxisMetadata box.\n\n    Note that unlike ``meta.unbox`` the unbox call should recursively unbox\n    metadata. It should simply return value that it wraps directly even\n    if that value itself is an instance of AxisMetadata.\n\n    In practise, AxisMetadata subclasses should be registred as PyTree nodes to\n    support passing instances to JAX and Flax APIs. The leaves returned for this\n    note should correspond to the value returned by unbox.\n\n    Returns:\n      The unboxed value.\n    \"\"\"\n    pass\n\n  @abc.abstractmethod\n  def add_axis(self: TAxisMetadata, index: int,\n               params: Dict[Any, Any]) -> TAxisMetadata:\n    \"\"\"Adds a new axis to the axis metadata.\n\n    Note that add_axis and remove_axis should act as each other's inverse\n    (meaning: ``x.add_axis(i, p).remove_axis(i, p) == x``)\n\n    Args:\n      index: The position at which the new axis will be inserted\n      params: An arbitrary dictionary of parameters passed by the transformation\n        that introduces the new axis (e.g.: ``nn.scan`` or ``nn.vmap``). The\n        user passes this dictionary as the `metadata_param` argument to the\n        transformation.\n    Returns:\n      A new instance of the same type as self and with the same ``unbox``\n      content with updated axis metadata.\n    \"\"\"\n    pass\n\n  @abc.abstractmethod\n  def remove_axis(self: TAxisMetadata, index: int,\n                  params: Dict[Any, Any]) -> TAxisMetadata:\n    \"\"\"Removes an axis from the axis metadata.\n\n    Note that add_axis and remove_axis should act as each other's inverse\n    (meaning: ``x.remove_axis(i, p).add_axis(i, p) == x``)\n\n    Args:\n      index: The position of the axis that is to be removed\n      params: An arbitrary dictionary of parameters passed by the transformation\n        that introduced the axis (e.g.: ``nn.scan`` or ``nn.vmap``). The\n        user passes this dictionary as the `metadata_param` argument to the\n        transformation.\n    Returns:\n      A new instance of the same type as self and with the same ``unbox``\n      content with updated axis metadata.\n    \"\"\"\n    pass\n```\n\nWe call this type of class wrapping a value and keeping track of some additional data a **box**.\nBy defining an abstract base class for this box, the API does not need to be aware of the specifics of the metadata that is tracked.\nThis should make the API future proof and modular.\n\nThe ``add_axis`` and ``remove_axis`` method return an instance of their own type instead of mutating in-place.\nTypically, an implementation would be a ``flax.struct.PyTreeNode`` because the box should still be a valid JAX value and must therefore be handled by the PyTree API.\nCalling ``jax.tree.map`` on a boxed value will simply map over the value in the box.\nThe lifted transforms that need to handle metadata will call ``jax.tree.map(..., is_leaf=lambda x: isinstance(x, AxisMetadata))`` to find the AxisMetadata instances within a PyTree.\n\nAdvantages of the boxing approach:\n1. Boxing can be used outside of Flax and metadata is automatically \"inherited\". For example, the optimizer state will\n   have the same partitioning spec as the parameters, because the state is initialized using a ``jax.tree.map`` over the boxed parameters.\n2. Boxes are composable.\n3. Boxing avoids string manipulation and generally avoids having to handle additional auxiliary collections like \"param_axes\" in the current\n   partitioning API.\n4. No need to lift metadata collections separately.\n\n\nDisadvantages:\n1. Adding the boxes changes the PyTree hierarchy and introduces dataclasses within the otherwise plain, nested dict of variables.\n3. Custom Pytree nodes have a small runtime overhead. It's hard to observe this in practise because JAX calls are async.\n\n\n### Init syntax\n\n\nBoxes can be created directly by the init function of a variable. Therefore, we propose to create metadata using higher-order initializers.\nThe main advantage of this is that we can decouple metadata handling completely from the Module definition. Also, most Modules already overwrite\nattributes to override the default initialzers so users can add metadata to existing Modules without requiring any code changes.\n\nTo illustrate this, let's consider a metadata class that keeps track of PartitionSpecs used by ``pjit``:\n\n```python\nclass Partitioned(flax.struct.PyTreeNode, AxisMetadata):\n  value: Any\n  names: Tuple[Optional[str], ...] = flax.struct.field(pytree_node=False)\n\n  def add_axis(self, index: int, params: Dict[Any, Any]) -> TAxisMetadata:\n    axis_name = self._get_partition_name(params)\n    names = list(self.names)\n    names.insert(index, axis_name)\n    return self.replace(names=tuple(names))\n\n  def remove_axis(self, index: int, params: Dict[Any, Any]) -> TAxisMetadata:\n    axis_name = self._get_partition_name(params)\n    names = list(self.names)\n    assert names.pop(index) == axis_name\n    return self.replace(names=tuple(names))\n\ndef with_partitioning(init_fn, names):\n  def wrapper(*args, **kwargs):\n    return Partitioned(init_fn(*args, **kwargs), names)\n  return wrapper\n```\n\nHere we also defined a small utility called ``with_partitioning`` that we can use to wrap existing initialzers to add metadata:\n\n\n```python\n# init kernel with lecun normal and split the output features over the data axis\npartitioned_dense = nn.Dense(features, kernel_init=with_partitioning(nn.initializers.lecun_normal, (None, \"data\")))\n```\n\nInitializing a model that creates partitioned weights would result in the following variable structure:\n\n```python\nvariables = partitioned_dense.init(rng, jnp.ones((4,)))\njax.tree.map(np.shape, variables)  # => {\"params\": {\"kernel\": Partitioned(value=(4, 8), names=(None, \"data\")), bias: (8,)}}\n```\n\nThe variable tree with metadata can be used to integrate with other libraries and APIs.\nFor example, we can turn the ``Partitioned`` metadata into ``jax.pjit`` sharding annotations:\n\n```python\ndef to_sharding_spec(x):\n  if isinstance(x, Partitioned):\n    return PartitionSpec(*x.names)\n  else:\n    # fully replicated\n    return PartitionSpec()\n\n# Result: {\"params\": {\"kernel\": PartitionSpec(None, \"data\"), bias: PartitionSpec()}}\nvariables_pspec = jax.tree.map(to_sharding_spec, variables, is_leaf=lambda x: isinstance(x, Partitioned))\n```\n\n### Unbox syntax\n\n\nMetadata typically doesn't need to be handled by Modules directly. Therefore, we propose to make Modules agnostic to Metadata boxes by default.\nThe ``unbox`` method can be used to unpack a variable such that only the original JAX arrays remain. Users can manually call unbox but to make\nsure Module classes don't have to call it everywhere we add an unbox keyword arg to variable returning APIs (e.g.: ``.param``, ``.variable``, ``.get_variable``).\nThe keyword arg ``unbox`` will default to ``True`` such that a Modules are metadata agnostic by default. This also means existing Modules will be backward compatible\nwith the new API.\n\n```python\nkernel = self.param(\"kernel\", self.kernel_init, shape)  # No AxisMetadata instances\nkernel_box = self.get_variable(\"param\", \"kernel\", unbox=False)  # AxisMetadata boxes are preserved\n```\n\n\n### Lift syntax\n\nWhen calling a lifted transformation that adds an axis you will now be able to pass a dictionary with arguments.\nThese params will be passed to ``AxisMetadata`` add_axis/remove_axis callbacks:\n\n```python\nnn.scan(..., variable_axes={\"params\": 0}, metadata_params={nn.Partitioned.AXIS_NAME: \"layers\"})\n```\n\nA dict is used such that users can add their own arguments to custom AxisMetadata classes.\n\n"
  },
  {
    "path": "docs_nnx/flip/2974-kw-only-dataclasses.md",
    "content": "# FLIP: kw_only dataclasses\nAuthors: Brennan Saeta, Ivy Zheng\n\n - Start Date: Mar 23, 2023\n - FLIP Issue: [TBD]\n - FLIP PR: #2974\n - Status: Implementing\n\n\n## Summary\n\nPython 3.10 adds support for `kw_only` dataclasses. Subclasses of `flax.linen.Module` are automatically converted to `dataclasses` on users' behalf, but today, Flax doesn't allow setting the `kw_only` parameter to this dataclass transform, even if users are running Python 3.10. This proposal allows users to use this new feature with `nn.Module`'s.\n\n\n## Motivation\n\nIn larger Flax-based codebases (e.g. [`PaxML`](https://github.com/google/paxml) / [`Praxis`](https://github.com/google/praxis)), it’s not uncommon to define an (abstract) subclass of nn.Module that contains shared functionality that is itself further subclassed for specific implementations (e.g. [`BaseLayer`](https://github.com/google/praxis/blob/main/praxis/base_layer.py), or [`StackedTransformerRepeat`](https://github.com/google/praxis/blob/81479b260fcc13de8549cdbfb0fdf5c3f188ac90/praxis/layers/transformers.py#L1836) which is further subclassed by [`PipelineCompatibleStackedTransformerRepeat`](https://github.com/google/praxis/blob/81479b260fcc13de8549cdbfb0fdf5c3f188ac90/praxis/layers/transformers.py#L2198)).\n\nOften, these parent types define hyperparameters (constructor arguments), often with default values. Without `kw_only` on the `dataclass` transform, default values must be specified for all child layers hyperparameters. This is suboptimal, because users could forget to set them when instantiating the modules. For example, `Child` must set a default value for `num_heads` (because a non-defaulted argument can’t come after a defaulted argument if they are positional), but no reasonable default is available:\n\n```python\nclass BaseLayer(nn.Module):\n  mesh: Optional[jax.experimental.mesh.Mesh] = None\n\n  def with_sharding(self, some_variable, some_sharding):\n    if self.mesh:\n      # Do something useful here.\n\nclass Child(BaseLayer):\n  num_heads: int  # Don't want to have to set a default argument!\n\n  def __call__(self, x):\n    ...\n```\n\nNote: Flax already has this problem, which is why `nn.Module` has its own fancy `kw_only_dataclasses.dataclass` transform: it moves the `name` and `parent` dataclass fields to the end, so they can have defaults.\n\n\n## Implementation\n\nTo allow modules to optionally opt into this `kw_only` dataclass behavior, we leverage arguments to `__init_subclass__`. This would look as follows:\n\n```python\nclass BaseLayer(nn.Module, kw_only=True):\n  ...\n\nclass Child(BaseLayer):\n  ...\n```\n\nThe implementation of `nn.Module`’s `__init_subclass__` will be tweaked as follows:\n\n```python\nclass Module(ModuleBase):\n  def __init_subclass__(self, kw_only: Optional[bool] = None):\n    # ...\n    if kw_only:\n     if is_python_310_or_above():\n       dataclass_transform_args = {'kw_only': True}\n     else:\n       raise TypeError(\"Can't use `kw_only` before Py3.10.\")\n    else:\n       dataclass_transform_args = {}\n\n    kw_only_dataclasses.dataclass(\n      cls, unsafe_hash='__hash__' not in cls.__dict__,\n      repr=False,\n      **dataclass_transform_args)\n```\n\n### Forward compatibility\n\nFor future simplification, if `kw_only` is requested and the Python version is 3.10 or above, bypass the `kw_only_dataclasses` implementation and just use the regular `dataclasses` transform.\n\nThat means we may one day remove `flax/linen/kw_only_dataclasses.py` when Flax rolls over 3.10.\n\n\n## Discussion\n\n### Aligned with Python `dataclass`\n\nWe prefer to keep the behavior of `nn.Module`’s `kw_only` aligned with the Python dataclasses. Note that this means `kw_only` will not be inheritable, and this could happen:\n\n```python\nclass BaseLayer(nn.Module, kw_only=True):\n  base_muliplier: Optional[int] = -1\n\nclass ChildLayer(BaseLayer):\n  child_multiplier: int\n\nBaseLayer(2)   # This will throw error\nChildLayer(2)  # But this will not\n```\n\n### `flax.struct.dataclass`\n\nThere’s a potentially related feature to allow `kw_only` to be specified for `flax.struct.dataclass`. This should be considered an orthogonal decision.\n\n\n"
  },
  {
    "path": "docs_nnx/flip/3099-rnnbase-refactor.md",
    "content": "# Refactor RNNCellBase in FLIP\n\nAuthors: Cristian Garcia, Marcus Chiam, Jasmijn Bastings\n\n - Start Date: May 1, 2023\n - FLIP Issue: [TBD]\n - FLIP PR: #3053\n - Status: Implemented\n\n## Summary\nThis proposal aims to improve the usability of the `RNNCellBase` class by refactoring the `initialize_carry` method and other relevant components.\n\n## Motivation\n\nCurrently, `initialize_carry` is used to both initialize the carry and pass crucial metadata like the number of features. The API can be unintuitive as it requires users to manually calculate things that could typically be inferred by the modules themselves, such as the shape of batch dimensions and the shape of feature dimensions.\n\n### Example: ConvLSTM\nThe current API can be unintuitive in cases like `ConvLSTM` where a the `size` parameter contains both the input image shape and output feature dimensions:\n\n```python\nx = jnp.ones((2, 4, 4, 3)) # (batch, *image_shape, channels)\n\n#                                        image shape: vvvvvvv\ncarry = nn.ConvLSTMCell.initialize_carry(key1, (16,), (64, 64, 16))\n#                                   batch size: ^^             ^^ :output features\n\nlstm = nn.ConvLSTMCell(features=6, kernel_size=(3, 3))\n(carry, y), initial_params = lstm.init_with_output(key2, carry, x)\n```\n\nThis FLIP will propose some changes to `initialize_carry` such that the previous example can be simplified to:\n\n```python\nx = jnp.ones((2, 4, 4, 3)) # (batch, *image_shape, channels)\n\nlstm = nn.ConvLSTMCell(features=6, kernel_size=(3, 3))\ncarry = lstm.initialize_carry(key1, input_shape=x.shape)\n\n(carry, y), initial_params = lstm.init_with_output(key2, carry, x)\n```\n\n## Implementation\nThe proposal suggests the following changes:\n\n### initialize_carry\n`initialize_carry` should be refactored as an instance method with the following signature:\n\n```python\ndef initialize_carry(self, key, sample_input):\n```\n\n`sample_input` should be an array of the same shape that will be processed by the cell, excluding the time axis.\n\n### Refactor RNNCellBase subclasses\n`RNNCellBase` should be refactored to include the metadata required to initialize the cell and execute its forward pass. For `LSTMCell` and `GRUCell`, this means adding a `features` attribute that should be provided by the user upon construction. This change aligns with the structure of most other `Module`s, making them more familiar to users.\n\n```python\nx = jnp.ones((2, 100, 10)) # (batch, time, features)\n\ncell = nn.LSTMCell(features=32)\ncarry = cell.initialize_carry(PRNGKey(0), x[:, 0]) # sample input\n\n(carry, y), variables = cell.init_with_output(PRNGKey(1), carry, x)\n```\n\n### num_feature_dims\nTo simplify the handling of `RNNCellBase` instances in abstractions like `RNN`, each cell should implement the `num_feature_dims` property. For most cells, such as `LSTMCell` and `GRUCell`, this is always 1. For cells like `ConvLSTM`, this depends on their `kernel_size`.\n\n## Discussion\n### Alternative Approaches\n* To eliminate the need for `num_feature_dims`, `RNN` could support only a single batch dimension, i.e., inputs of the form `(batch, time, *features)`. Currently, it supports both multiple batch dimensions and multiple feature dimensions.\n* Another approach could be a complete redesign of how Flax deals with recurrent states. For example, a `memory` collection could be handled as part of the variables. However, this introduces challenges such as handling stateless cells during training, passing state from one layer to another, and performing initialization inside `scan`.\n\n### Refactor Cost\nInitial TGP results showed 761 broken and 110 failed tests. However, after fixing one test, TGP results in 231 broken and 13 failed tests so there seems to be a lot\nof overlap between the broken tests.\n\nTo minimize refactor costs, the current implementation will be kept for Google internal users under a deprecated name. This will allow users to migrate to the new API at their own pace. For Open Source users we should bump Flax version to\n`0.7.0` so existing users can continue to depend on `0.6.x` versions.\n"
  },
  {
    "path": "docs_nnx/flip/4105-jax-style-nnx-transforms.md",
    "content": "# JAX-style NNX Transforms\n\n- Authors: Cristian Garcia, Anselm Levskaya\n- Date: Jun/2024\n- FLIP PR: #4107\n- Status: Implementing\n\n## Motivation\n\nNNX allows users to utilize Modules at the top level due to their eager initialization and self-contained state. This naturally leads users to want to use them with transforms and soon start playing with NNX transforms. Since NNX Modules resemble PyTrees in that they contain Arrays, new users often attempt to apply JAX conventions, for example:\n\n```py\n@nnx.vmap(in_axes=(1, 0))\ndef f(m1: Module, m2: Module):\n  ...\n```\n\nHowever, this can be misleading. Currently, NNX transforms follow Linen's convention of treating input Modules as a single unit (all Modules are split together to preserve shared references) and provide APIs for transforming that State separately. The previous example effectively translates to:\n\n```py\n# this is what is really happening\n@nnx.vmap(in_axes=(IGNORE, IGNORE), state_axes={BatchStat: None, ...: 0})\ndef f(m1: Module, m2: Module):\n  ...\n```\n\nNote that `IGNORE` is not a real symbol, but represents the fact that any value placed here won't affect the outcome, as Modules are replaced by empty PyTree placeholders (similar to `None`). The `state_axes` parameter controls how the State is vectorized through a mapping of high-level `Filter`s to their desired axes. In this example, `...` (ellipsis) is a filter that accepts everything, so by default all States are vectorized on the 0th axis.\n\nTo express their original intention, users must resort to more complex custom filters that guess the index of each Module in the monolith. While this is straightforward in simple cases, users generally need to calculate the index (Modules appear in the order specified by `jax.tree.leaves` over the `args`):\n\n```py\nselect_m1 = lambda path, value: path[0] == 0\nselect_m2 = lambda path, value: path[0] == 1\n\n# To select modules individually, you must create a filter (which can be tricky)\n@nnx.vmap(state_axes={select_m1: 1, select_m2: 0})\ndef f(m1: Module, m2: Module):\n  ...\n```\n\n## What if JAX conventions Just Worked™?\n\nThis proposal aims to align NNX transforms with user's expectations based on their JAX experience, making the syntax work as intuitively as possible. The original example would function **as if** `m1` and `m2` were PyTrees vectorized in axes `1` and `0` respectively:\n\n```py\n@nnx.vmap(in_axes=(1, 0))\ndef f(m1: Module, m2: Module):\n  ...\n```\n\nThe primary advantage of this approach is that for `vmap` and `scan`, we could eliminate the `state_axes` and `split_rngs` arguments, relying solely on the `in_axes` API. This syntax alone would likely suffice for 80-90% of use cases, as users tend to manage state in predictable ways.\n\n### The Lift symbols\n\nTo enable more fine-grained state control within each Module, we introduce the `Lift` API. By using special types containing State Filters in place of a tree prefix, state lifting can now be done **structurally**. This allows different Filters to be applied to different Modules in the arguments without the need for complex path-based filters. Ideally, each transform would support its own Lift type, adding the desired behavior through existing JAX APIs.\n\nFor example, in `vmap`, we could allow `StateAxes` instances (vmap's Lift type) to be accepted by `in/out_axes` to control how substates are handled by mapping state `Filter`s to an axis specifier:\n\n```py\nstate_axes = StateAxes({Param: 1, BatchStat: None})\n\n@nnx.vmap(in_axes=(state_axes, 0))\ndef f(m1: Module, m2: Module):\n  ...\n```\n\nIn this case, `m1`'s `Param`s are vectorized in axis `1` while its `BatchStat`s are broadcasted, and `m2`'s entire state is vectorized in axis `0`.\n\nFor `nnx.grad`, we could allow `DiffState` to be used in the `argnums` parameter to specify both the position of the argument to be differentiated and a Filter specifying the differentiable State of the Module:\n\n```py\ngrads = nnx.grad(loss_fn, argnums=(DiffState(0, LoRAParam),))(model, x, y)\n```\n\n## Rng Handling\n\nTo simplify RNG state handling, we propose removing the separate `split_rngs` parameter in `vmap` and `scan`. Instead, we suggest introducing a new `nnx.split_rngs` API that would manage RNG handling before and after the transformation. This approach provides more explicit control to the user and aligns better with JAX transform behavior.\n\n## Consistent Aliasing\n\nTo ensure the correctness of transformations with objects that obey reference semantics, we must enforce consistent lifting/lowering specifications for all aliases of a reference. Transforms must adhere to two rules:\n\n1. All aliases of a reference must receive the **exact same** lifting/lowering specification.\n2. Captured references are not allowed on the output of transformed functions.\n\nFor example:\n\n```py\n@nnx.vmap(in_axes=(m1_axes, m2_axes, m1_axes), out_axes=m2_axes)\ndef f(m1, m2, m1_alias):\n  return m2\n\nm2 = f(m1, m2, m1)\n```\n\nHere, `m1` has two input aliases as it is passed as the first and third input to `f`, but this is acceptable because `m1_axes` is assigned to both in `in_axes`. `m2` is passed as the second input and has an output alias, which is also acceptable because `m2_axes` is assigned in both `in_axes` and `out_axes`.\n\nLet's examine some examples of programs that should be **rejected** based on these criteria:\n\n### Inconsistent input aliases\n\nConsider a function with two arguments `m1` and `m2` being vectorized in axis `0` and `1` respectively. Passing the same Module as both arguments would be inconsistent:\n\n```py\n@nnx.vmap(in_axes=(0, 1))\ndef f(m1: Module, m2: Module):\n  ...\n\nf(m, m)  # This should be rejected\n```\n\n### Inconsistent input / output aliases\n\nNow consider an identity function `g` under `vmap` with `in_axes=0` and `out_axes=1`. In JAX, this would result in transposing the arrays in the inputs:\n\n```py\n@nnx.vmap(in_axes=0, out_axes=1)\ndef g(m: Module):\n  return m\n```\n\nWhile this appears correct, in NNX this behavior is not well-defined because shared mutable references behave as auxiliary outputs. Under the hood, `g` is converted into a function that has the inputs as an extra first output, and `out_axes` is set to the same values as `in_axes` for that output:\n\n```py\n@nnx.vmap(in_axes=0, out_axes=(0, 1))\ndef g_real(m: Module):\n  return m, m\n```\n\nThis return structure reveals an inconsistency: we're attempting to lower `m` with both `out_axes=0` and `out_axes=1`.\n\n### Inconsistent aliases in nested structures\n\nSimilar issues can arise in less obvious cases, such as when `m` is contained within another structure:\n\n```py\n@nnx.vmap(in_axes=0, out_axes=1)\ndef f(m: Module):\n  return SomeModule(m)\n```\n\nThis means we must traverse the entire graph of both inputs and outputs to check for consistent assignments. The same problem occurs when passing shared reference inputs/outputs with different specifications:\n\n```py\nshared = Shared()\nm1, m2 = Foo(shared), Foo(shared)\n\n@nnx.vmap(in_axes=(0, 1))\ndef f(m1, m2):  # shared is passed through both\n  ...\n```\n\n### Captured Modules cannot be outputs\n\nFinally, let's consider the second consistent aliasing rule, which states that captured Modules cannot be outputs. The main issue here is that NNX needs to split all input references together to track changes, but captured Modules bypass this process. Treating them as new references would result in **implicit cloning**:\n\n```py\nm = SomeModule()\n\n@nnx.vmap(out_axes=0, axis_size=5)\ndef f():\n  return m\n\nassert m is not f()  # implicit cloning\n```\n\nTo preserve reference identity, we must disallow captured Modules as outputs. In practice, we can detect captured Modules using the trace level context machinery used to restrict stateful updates on Modules from a different level.\n\n## Recap\n\nIn this document, we have:\n\n* Discussed issues with the current implementation that make it unintuitive for JAX users.\n* Proposed refactoring NNX transforms to allow users to use regular JAX semantics when interacting with objects, removing extra arguments introduced by NNX transforms.\n* Introduced the use of Lift types in JAX APIs to compensate for the lack of a \"prefix\" notion in NNX objects, enabling independent lifting of Module substates.\n* Proposed a new `nnx.split_rngs` API to replace the `split_rngs` arguments in `vmap` and `scan`, making RNG handling an explicit operation and giving users more control.\n* Analyzed edge cases resulting from aliasing shared mutable references and proposed enforcing **consistent aliasing** on all transforms with semantics over the inputs."
  },
  {
    "path": "docs_nnx/flip/4844-var-eager-sharding.md",
    "content": "- Start Date: 2025-09-12\n- FLIP PR: [#4844](https://github.com/google/flax/pull/4844)\n\n# FLIP 4844: Variable eager sharding\n\n## Summary\n[summary]: #summary\n\nSimplify the creation of sharded NNX models. When a sharding annotation is provided, all `nnx.Variable` creation will **require a mesh context** and automatically be sharded as annotated.\n\nSee [GSPMD Guide](https://flax.readthedocs.io/en/latest/guides/flax_gspmd.html) for a comprehensive guide on how to make sharded NNX models.\n\n# Motivation\n\nTo create a sharded model, user should only need to do this:\n\n```python\nmesh = jax.make_mesh(((2, 4)), (\"data\", \"model\"))\nwith jax.set_mesh(mesh):\n  model = YourModelWithShardingAnnotations()\n```\n\nInstead of the current boilerplate combo of `nnx.jit`, `nnx.get_partition_spec`, `with_sharding_constraint` and `nnx.update`:\n\n```python\n@nnx.jit\ndef create_sharded_model():\n  model = YourModelWithShardingAnnotations() # Unsharded at this moment.\n  state = nnx.state(model)                   # The model's state, a pure pytree.\n  pspecs = nnx.get_partition_spec(state)     # Strip out the annotations from state.\n  sharded_state = jax.lax.with_sharding_constraint(state, pspecs)\n  nnx.update(model, sharded_state)           # The model is sharded now!\n  return model\n\nmesh = jax.make_mesh(((2, 4)), (\"data\", \"model\"))\nwith jax.set_mesh(mesh):\n  sharded_model = create_sharded_model()\n```\n\n# Backward compatibility\n\nUser can turn off this feature in two ways:\n\n* **Global config flag**: Run `flax.config.update('flax_always_shard_variable', False)` before running any NNX model initialization.\n\n* **Variable-specific flag**: Create a specific variable with metadata `eager_sharding=False`, such as: `nnx.Param(..., eager_sharding=False)`.\n\n\n# Flexibility options\n\nFor debugging in a CPU environment, make a dummy mesh to run the model:\n\n```python\nmesh = jax.make_mesh(((1, 1, 1)), ('your', 'axes', 'names'))\nwith jax.set_mesh(mesh):\n  ...\n```\n\nFor JAX explicit mode, remove the `out_sharding=` annotation on the `nnx.Variable`.\n\n\n# Implementation\n[implementation]: #implementation\n\nWhen an `nnx.Variable` is created, check for the metadata `out_sharding`, and if present, check if under a valid global mesh context of was supplied with a valid mesh. If no, throw error; if yes, call `jax.lax.with_sharding_constraint` to apply sharding constraint on the value.\n\nNote that this only works in auto sharding mode. User should use JAX-level APIs to annotate shardings for explicit mode."
  },
  {
    "path": "docs_nnx/flip/5310-tree-mode-nnx.md",
    "content": "# Tree Mode NNX\n\nMar 4, 2026\nCristian Garcia, Samuel Anklesaria, Flax Team\n\n## Motivation\n\nCurrent NNX APIs allow general graph structures and graph transformations, this includes:\n\n1. Tracking Variable state updates  \n2. Handling shared references (graphs)  \n3. Supporting prefix filters (StateAxes, DiffState, StateSharding)  \n4. Propagating graph updates (static state and structure changes)\n\nWhile powerful, some of these capabilities (**3** and **4**) are beyond what JAX transform APIs offer and supporting them results in both internal complexity, harder to reason about code, and a larger set of APIs a user must learn.  We wish to tackle all these issues by simplifying NNX.\n\n## Proposal\n\nTo do this we propose two things. First, the introduction of **Tree Mode NNX**: a reimplementation of the NNX APIs that only handles trees, assumes referential transparency, and has a more limited support for state updates. Concretely, this means:\n\n* Automatic state updates only for Variables in NNX transforms.  \n* Tree structure assumed and enforced on all APIs (no sharing)  \n* Modules treated as stateless pytrees (no graph updates).  \n* Full JAX transform compatibility (remove [prefix filters](#prefix-filters): StateAxes, DiffState, StateSharding).\n\nSecond, simplifying graph support. Graphs stand out as an important feature for some NNX users. However, we will be limiting support to **1** and **2**, meaning that prefix filters and graph updates will be dropped. This will make it such that tree and graph transforms can share the same underlying implementation and semantics while still allowing for a great deal of expressivity.\n\n## Implementation\n\nTree mode will be implemented on top of the current APIs by introducing a `graph` argument, when `True` graph support is enabled, when `False` only trees are supported and internals rely on `jax.tree.*` APIs. Additionally, a `graph_updates` argument will be added to NNX transforms, when `False` transforms will no longer propagate graph structure update (**4**) or support prefix filters (**3**).\n\n```py\ndef split(..., graph: bool | None = None)\n...\ndef jit(..., graph: bool | None = None, graph_updates: bool | None = None)\n...\n```\n\nIf `graph` or `graph_updates` are not provided, their default values will be taken from the `nnx_graph_mode` and `nnx_graph_updates` config flags respectively. These can be easily fetched and updated via `set_graph_mode` and `set_graph_updates`.\n\n```py\n# status\nprint(nnx.set_graph_mode.current_value())\nprint(nnx.set_graph_updates.current_value())\n\n# set value\nnnx.set_graph_mode(True/False)\nnnx.set_graph_updates(True/False)\n\n# via env vars \n# NNX_GRAPH_MODE=true/false\n# NNX_GRAPH_UPDATES=true/false\n\n# context managers\nwith nnx.set_graph_mode(True/False):\n  ...\nwith nnx.set_graph_updates(True/False):\n  ...\n```\n\nThe goal will be to have the default value for `nnx_graph_mode` and `nnx_graph_updates` to be set to `False`, thus enabling tree mode for new projects. Users that don’t want to migrate can use these flags to make sure their code continues to work with current features.\n\n### Simple transforms\n\nThese new transforms are highly simplified compared to current transforms, they are easier to implement and optimize, while supporting both trees and graphs. Given a user function f, most simplified transforms follow this pattern:\n\n```py\ndef transform_wrapper(*args):\n  if graph: args = to_tree(args)\n  check_no_aliases(args=args)\n  \n  @jax_transform\n  def transformed_f(*args):\n    updates, snapshot = updates_and_snapshot(args)\n    if graph: args = from_tree(args)\n    out = f(*args)\n    if graph: out = to_tree(out)\n    check_no_aliases(args=updates, out=out)\n    updates = mask_variable_updates(updates, snapshot)\n    return out, updates\n  \n  out, updates = transformed_f(*args)\n  apply_variable_updates(args, updates)\n  if graph: out = from_tree(out)\n  return out\n```\n\nThe transformed function tracks input Variable `updates`, applies  f, and masks Variable updates (no updates for Variables that didn’t change). It also checks that there are no Variable aliases between the inputs and outputs (no shared references), and returns the user output plus Variable updates. The wrapper function calls the transformed function, applies the Variable updates to the input Variables, and returns the user output. To support graphs, we simply convert objects to a tree representation before passing them to jax, and back to graphs before passing them to the user code.\n\n## Backward Compatibility\n\nWhen tree mode is on by default, code that relies on graphs, graph updates, and prefix filters will stop working. There are two ways to port existing code, the first is reverting the defaults config via `set_graph_mode` and `set_graph_updates` somewhere in the after the imports:\n\n```py\nfrom flax import nnx\n...\nnnx.set_graph_mode(True)\nnnx.set_graph_updates(True)\n```\n\nThe previous implementation of the transform APIs will also be accessible via the `nnx.compat` module. They are implemented as partials that set `graph=True` and `graph_updates=True`:\n\n```py\nnnx.compat.split = partial(nnx.split, graph=True)\n...\nnnx.compat.jit = partial(nnx.jit, graph=True, graph_updates=True)\n...\n```\n\n   \nThe above shortcuts will make it such that porting existing code (if needed) is as simple as performing some rewrites:\n\n`nnx.split` → `nnx.compat.split`  \n`nnx.jit` → `nnx.compat.jit`  \n…\n\n## Breaking changes\n\n### Prefix filters {#prefix-filters}\n\nCode that relies on prefix filters such as StateAxes, StateSharding, and DiffState will require some restructuring as JAX has no equivalent mechanisms (these were added to make Linen migration easier). The solution is to use `split` and `merge` to create state groups, and pass each group through their corresponding tree prefix on the jax transform. For example:\n\n```py\n# previous code\nstate_axes = nnx.StateAxes({some_filter: 0, ...: None})\n\n@nnx.vmap(in_axis=state_axes, graph=True, graph_updates=True)\ndef f(model):\n  ...\n```\n\nThis can be rewritten to `split` the model into two state groups using the previous filter, passing the groups as separate arguments, one vectorized and the other broadcasted, and using `merge` to reconstruct the model inside the transform.\n\n```py\n# new code\ngraphdef, vectorized, broadcasted = nnx.split(model, some_filter, ...)\n\n@nnx.vmap(in_axis=(0, None))\ndef f(vectorized, broadcasted):\n  model = nnx.merge(graphdef, vectorized, broadcasted)\n  ...\n```\n\nThis is roughly how prefix filters were implemented under the hood.\n\n### nnx.grad\n\nCode that uses `nnx.grad` will change in two ways:\n\n1. The first argument will no longer be differentiated w.r.t. to `Param`s only, this is because `grad` used this prefix filter by default: `DiffState(0, Param)`.  \n2. The gradients of NNX Pytree/Module types will no longer be `State` types. Now they just follow JAX and return the same input type.\n\nConcretely it means that code like this:\n\n```py\n# previous code\ndef loss_fn(model: Foo):\n  ...\n\n# uses argnums=nnx.DiffState(0, nnx.Param)\ngrads = nnx.grad(loss_fn)(model)\n```\n\nNow has to explicitly use `split` and `merge` if to avoid calculating gradients for the non-differentiable state:\n\n```py\n# new code\ndef graphdef, params, nondiff = nnx.split(model, nnx.Param, ...)\n\ndef loss_fn(params, nondiff):\n  model = nnx.merge(graphdef, params, nondiff)\n  ...\n\n# uses argnums=0\ngrads = nnx.grad(loss_fn)(params, nondiff)\n```\n\nIf there is no non-differentiable the `model` can be passed in directly but the gradients will now be of the same type:\n\n```py\n# new code\ndef loss_fn(model: Foo):\n  ...\n\n# uses argnums=0\ngrads: Foo = nnx.grad(loss_fn)(model)\n```\n\n### nnx.custom_vjp\n\nPreviously `nnx.custom_vjp` did two particular things:\n\n1. The backward function returned the gradients of the Variable updates (`m_updates_g`) along with the output gradient.  \n2. The tangent for nnx.Pytree/Module objects were of type `nnx.State`.\n\nFor a `Foo` Module with `x: Param` and `y: Param` attributes, a simple example could look like this:\n\n```py\n# previous code\n@nnx.custom_vjp\ndef f(m: Foo):\n  return jnp.sin(m.x) * m.y\n\ndef f_fwd(m: Foo):\n  return f(m), (jnp.cos(m.x), jnp.sin(m.x), m)\n\ndef f_bwd(res, g):\n  (m_updates_g,), out_g = g\n  cos_x, sin_x, m = res\n  m_g: nnx.State = nnx.clone(m_updates_g) # create copy\n  m_g['x'][...] = cos_x * out_g * m.y\n  m_g['y'][...] = sin_x * out_g\n  return (m_g,)  # State gradient\n```\n\nIn the new implementation gradients for Variable updates are not returned, and the tangent type is the same as the input type (`Foo`), this matches the behavior of `jax.custom_vjp`: \n\n```py\n# new code\n@nnx.custom_vjp\ndef f(m: Foo):\n  return jnp.sin(m.x) * m.y\n\ndef f_fwd(m: Foo):\n  return f(m), (jnp.cos(m.x), jnp.sin(m.x), m)\n\ndef f_bwd(res, g): # no gradients for updates\n  cos_x, sin_x, m = res\n  m_g: Foo = nnx.clone(m) # create copy\n  m_g.x[...] = cos_x * g * m.y\n  m_g.y[...] = sin_x * g\n  return (m_g,) # Foo gradient\n```\n\nNote that to avoid losing information, now differentiable Variables are not allowed to be updated inside `nnx.custom_vjp`.\n\n### transform\\_metadata\n\nPreviously NNX transforms like `vmap` and `scan` had a `transform_metadata` metadata argument that allowed them to update the sharding metadata.\n\n```py\n# old code\n@nnx.split_rngs(8)\n@nnx.vmap(in_axes=0, out_axes=0, transform_metadata={nnx.PARTITION_NAME: 'din'})\nclass create_stack(rngs):  # 'din' added to out_sharding metadata\n  return nnx.Variable(rngs.uniform((16,)), out_sharding=('dout',))\n\nv_stack = create_stack(nnx.Rngs(0))\nassert v_stack.shape == (8, 16)\nassert v_stack.out_shardings == ('din', 'dout')\n```\n\nThe new simplified NNX transform implementations don’t support this argument. However, to keep supporting the behavior, a new `nnx.transform_metadata` transform is introduced that can be inserted to get back the same results. TODO: mention it works on `jax.vmap`.\n\n```py\n# new code\n@nnx.split_rngs(8)\n@nnx.vmap(in_axes=0, out_axes=0)\n@nnx.transform_metadata(in_axes=0, out_axes=0, partition='din')\nclass create_stack(rngs):  # 'din' added to out_sharding metadata\n  return nnx.Variable(rngs.uniform((16,)), out_sharding=('dout',))\n\nv_stack = create_stack(nnx.Rngs(0))\nassert v_stack.shape == (8, 16)\nassert v_stack.out_shardings == ('din', 'dout')\n```\n\n`transform_metada` accepts `in_axes` and `out_axes`, these should match the values passed to the corresponding transform.\n\n### Module.sow\n\nPreviously, `Module.sow` used graph updates to capture intermediate values during computations and propagate them outside, it was used in conjunction with `nnx.pop` to log and extract intermediates:\n\n```py\n# old code\nclass Foo(nnx.Module):\n  def __call__(self, x):\n    self.sow(nnx.Intermediate, \"y_mean\", jnp.mean(x))\n    return x\n\nmodel = Foo()\nresult = model(x)\nintermediates = nnx.pop(model, nnx.Intermediate) # extract intermediate values\n```\n\nTo achieve the same without graph updates we’ve added a new `nnx.capture` API which allows for a similar workflow.\n\n```py\n# New Code\nclass Foo(nnx.Module):\n  def __call__(self, x):\n    self.sow(nnx.Intermediate, \"y_mean\", jnp.mean(x))\n    return x\n\nmodel = Foo()\nresult, intermediates = nnx.capture(model, nnx.Intermediate)(x)\n```\n\nIn general, `nnx.capture` takes a function or Module to be transformed, a `nnx.Variable` subclass to collect, and an optional `init` argument to initialize the collected state, which will be stored within `nnx.Variable` objects. `nnx.capture` creates a `__captures__: tuple[Variable, ...]` attribute on each `Module` instance, each Variable in `__captures__` contains a dictionary which `sow` and `perturb` populate.\n\n### Module.perturb\n\nSimilarly, `Module.perturb` was previously used to extract the gradients of intermediate values. This was done in two steps: initializing a perturbation state by running a module once, and then passing the perturbation state as a differentiable target to `grad`.\n\n```py\nclass Model(nnx.Module):\n  def __call__(self, x):\n    x = self.perturb('grad_of_x', x)\n    ...\n    return y\n\n# old code\n@nnx.jit\ndef train_step(model, optimizer, x, y):\n  model(x) # Initialize perturbation state\n  def loss_fn(model):\n    y_pred = model(x)\n    return jnp.mean((y_pred - y) ** 2)\n  diff_state = nnx.DiffState(0, (nnx.Param, nnx.Perturbation))\n  grads = nnx.grad(loss_fn, argnums=diff_state)(model)\n  grads, interm_grads = nnx.state(grads, nnx.Param, nnx.Perturbation)\n  optimizer.update(model, grads)\n  nnx.pop(model, nnx.Perturbation) # clean up perturbations\n  return interm_grads\n```\n\nSimilar pattern can be used with  `nnx.capture` during both perturbation initialization and when running the forward pass to insert the differentiable perturbations state. In this version explicitly pass the `perturbs` state as a separate argument and use `argnums` to specify that both arguments are differentiable:\n\n```py\n# new code\n@nnx.jit\ndef train_step(model, optimizer, x, y):\n  _, perturbs = nnx.capture(model, nnx.Perturbation)(x) # init perturbations\n  def loss_fn(model, perturbs):\n    y_pred = nnx.capture(model, init=perturbs)(x)\n    return jnp.mean((y_pred - y) ** 2)\n  grads, interm_grads = nnx.grad(loss_fn, argnums=(0, 1))(model, perturbs)\n  optimizer.update(model, grads)\n  return interm_grads\n```\n"
  },
  {
    "path": "docs_nnx/flip/README.md",
    "content": "# FLIP: Flax Improvement Process\n\nMost changes can be discussed with simple issues/discussions and pull requests.\n\nSome changes though are a bit larger in scope or require more discussion, and\nthese should be implemented as FLIPs. This allows for writing longer documents\nthat can be discussed in a pull request themselves.\n\nThe structure of FLIPs is kept as lightweight as possible to start and might\nbe extended later on.\n\n## When you should use a FLIP\n\n- When your change requires a design doc. We prefer collecting the designs as\n  FLIPs for better discoverability and further reference.\n\n- When your change requires extensive discussion. It's fine to have relatively\n  short discussions on issues or pull requests, but when the discussion gets\n  longer this becomes unpractical for later digestion. FLIPs allow to update the\n  main document with a summary of the discussion and these updates can be\n  discussed themselves in the pull request adding the FLIP.\n\n## How to start a FLIP\n\nFirst, create an issue with the [FLIP label]. All pull requests that relate to\nthe FLIP (i.e. adding the FLIP itself as well as any implementing pull requests)\nshould be linked to this issue.\n\nThen create a pull request that consists of a copy of the `0000-template.md`\nrenamed to `%04d-{short-title}.md` - with the number being the issue number.\n\n[FLIP label]: https://github.com/google/flax/issues?q=label%3AFLIP\n"
  },
  {
    "path": "docs_nnx/guides/blog.md",
    "content": "### Do we need another JAX NN library?\n\nHello, today I want to talk to you about a new JAX library that I have been working on, but before I do that, I wanted to discuss the topic: Do we need another JAX NN library?\n\n### JAX Libraries\n\nJAX NN libraries come in a wide variety ranging from functional like Flax and Haiku, to Pytree-based like Equinox."
  },
  {
    "path": "docs_nnx/guides/bridge_guide.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Use Flax NNX and Linen together\\n\",\n    \"\\n\",\n    \"This guide is for existing Flax users who want to make their codebase a mixture of Flax Linen and Flax NNX `Module`s, which is made possible thanks to the `flax.nnx.bridge` API.\\n\",\n    \"\\n\",\n    \"This will be helpful if you:\\n\",\n    \"\\n\",\n    \"* Want to migrate your codebase to NNX gradually, one module at a time;\\n\",\n    \"* Have external dependency that already moved to NNX but you haven't, or is still in Linen while you've moved to NNX.\\n\",\n    \"\\n\",\n    \"We hope this allows you to move and try out NNX at your own pace, and leverage the best of both worlds. We will also talk about how to resolve the caveats of interoperating the two APIs, on a few aspects that they are fundamentally different.\\n\",\n    \"\\n\",\n    \"**Note**:\\n\",\n    \"\\n\",\n    \"This guide is about glueing Linen and NNX modules. To migrate an existing Linen module to NNX, check out the [Migrate from Flax Linen to Flax NNX](https://flax.readthedocs.io/en/latest/guides/linen_to_nnx.html) guide.\\n\",\n    \"\\n\",\n    \"And all built-in Linen layers should have equivalent NNX versions! Check out the list of [Built-in NNX layers](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/index.html).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import os\\n\",\n    \"os.environ[\\\"XLA_FLAGS\\\"] = '--xla_force_host_platform_device_count=8'\\n\",\n    \"\\n\",\n    \"from flax import nnx\\n\",\n    \"from flax import linen as nn\\n\",\n    \"from flax.nnx import bridge\\n\",\n    \"import jax\\n\",\n    \"from jax import numpy as jnp\\n\",\n    \"from jax.experimental import mesh_utils\\n\",\n    \"from typing import *\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Submodule is all you need\\n\",\n    \"\\n\",\n    \"A Flax model is always a tree of modules - either old Linen modules (`flax.linen.Module`, usually written as `nn.Module`) or NNX modules (`nnx.Module`).\\n\",\n    \"\\n\",\n    \"An `nnx.bridge` wrapper glues the two types together, in both ways:\\n\",\n    \"\\n\",\n    \"* `nnx.bridge.ToNNX`: Convert a Linen module to NNX, so that it can be a submodule of another NNX module, or stand alone to be trained in NNX-style training loops.\\n\",\n    \"* `nnx.bridge.ToLinen`: Vice versa, convert a NNX module to Linen.\\n\",\n    \"\\n\",\n    \"This means you can move in either top-down or bottom-up behavior: convert the whole Linen module to NNX, then gradually move down, or convert all the lower level modules to NNX then move up.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## The Basics\\n\",\n    \"\\n\",\n    \"There are two fundamental difference between Linen and NNX modules:\\n\",\n    \"\\n\",\n    \"* **Stateless vs. stateful**: Linen module instances are stateless: variables are returned from a purely functional `.init()` call and managed separately. NNX modules, however, owns its variables as instance attributes.\\n\",\n    \"\\n\",\n    \"* **Lazy vs. eager**: Linen modules only allocate space to create variables when they actually see their input. Whereas NNX module instances create their variables the moment they are instantiated, without seeing a sample input.\\n\",\n    \"\\n\",\n    \"With that in mind, let's look at how the `nnx.bridge` wrappers tackle the differences.\\n\",\n    \"\\n\",\n    \"### Linen -> NNX\\n\",\n    \"\\n\",\n    \"Since Linen modules may require an input to create variables, we semi-formally supported lazy initialization in the NNX modules converted from Linen. The Linen variables are created when you give it a sample input.\\n\",\n    \"\\n\",\n    \"For you, it's calling `nnx.bridge.lazy_init()` where you call `module.init()` in Linen code.\\n\",\n    \"\\n\",\n    \"(Note: you can call `nnx.display` upon any NNX module to inspect all its variables and state.)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_7a625519c112482ea3aa899dacf66732\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_7a625519c112482ea3aa899dacf66732\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_7a625519c112482ea3aa899dacf66732\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_7a625519c112482ea3aa899dacf66732\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"class LinenDot(nn.Module):\\n\",\n    \"  out_dim: int\\n\",\n    \"  w_init: Callable[..., Any] = nn.initializers.lecun_normal()\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    # Linen might need the input shape to create the weight!\\n\",\n    \"    w = self.param('w', self.w_init, (x.shape[-1], self.out_dim))\\n\",\n    \"    return x @ w\\n\",\n    \"\\n\",\n    \"x = jax.random.normal(jax.random.key(42), (4, 32))\\n\",\n    \"model = bridge.ToNNX(LinenDot(64),\\n\",\n    \"                     rngs=nnx.Rngs(0))  # => `model = LinenDot(64)` in Linen\\n\",\n    \"bridge.lazy_init(model, x)              # => `var = model.init(key, x)` in Linen\\n\",\n    \"y = model(x)                            # => `y = model.apply(var, x)` in Linen\\n\",\n    \"\\n\",\n    \"nnx.display(model)\\n\",\n    \"\\n\",\n    \"# In-place swap your weight array and the model still works!\\n\",\n    \"model.w.value = jax.random.normal(jax.random.key(1), (32, 64))\\n\",\n    \"assert not jnp.allclose(y, model(x))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"`nnx.bridge.lazy_init` also works even if the top-level module is a pure-NNX one, so you can do sub-moduling as you wish:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_51238e6b592f4415a7fc6fc089ddda0e\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_51238e6b592f4415a7fc6fc089ddda0e\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_51238e6b592f4415a7fc6fc089ddda0e\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNrsvWma20iSKPg/T8FWV1VElEtB7IuUqf64byDpJMFVT58KIEAQIAiQWJxLtv7Pu8fMAeYKc5R3knGAG7hFhFRZr2e+zsgMBQk3Nzc3N7fNF/yqmSjlBxtb/+2dZvoLW9l8TDmuo79Lmdpv7yau903TJ7rn6do3huF4imO1Ca9MGJYTBfw/oRK8QhCTiUoJ7z7/6i8UB/8b4fv8PHZtXH3szue6E3xbTXXnm77GAJqufXTc4PF54tqaotr6N8fV9I9TxX/8bCuqbn8+L/kWuIZh63Gdj+OpPp7p2tNT6u9Pqd9TcRsfU/+uxD+fUt9/Te9aj0lJjW3F96NuJNBhMnet/Go6izBIBZsF7nyMV3XX727W2ZOAC3fNf/41HVc+b2bXYUVVPR29u1V0kxcXkHEZBreVhR8V/s0OPuFO7Rh7CXfC8e+pG0APmGUxPQ+fLWX9nPE8ZXMTLkZ2BDyAzPRN1PrE2/zNCD49Pt1B/QrCu8QnO2mcOnnxZz9WL3T9rMzEz5wAS+t4atqapzsvlr/7HPPk8fHpfUqLBeG8z08pF+kenhSmY3z85QuRIr7eIfP8D55Vn3/9galFaAKeRDrL0xTHkCoh0ASrMZoyVmlBYUj9NLXuiltE9XGQH6lDf0LTCWjq6RaDf5RITtAFjaMETRc4RlcEladpkdMpgaZVfiLql/NfiShB5hYPtBMopqN7eMKupmagf8BwYz1qyJsrNp61qVvAz6YzcXGVCX7yYaLMTRvTNncdN6796WruzxXPMJ0PqhsE7vxjinimWH3+6bzFhae/3Fw8rb/sdIKnOIb+7ismAQtBYI4V+4Nim4aDqTA1zcaYJqYd6JgGA2Pzcbn+SGKRwU2ZweaReGaffrixj9NI4qJeX6H+MXxOOFd1DyOM1ebEHYd+pDBV19N074OnaGbof0zRi/U/h3L3OSY6qY/5+OfTvrmPKXKxTvmubWqnohdaffYxpO75l/Ly0ujFJATmAtc5E+RPqYXrm4Hp4mFTVExDGOBnqjKeGZ4bOtqHPclxQ7cIVm0Mi7EomhZpgViuxtMILZ5amJU6wgrFPzS2MrVg+hGPXvAhIg4XfYp1yMR2Vx9TyPRNNRKc625tP0SqaY1bJgji5V5iM/XGXrrrD/5U0aKmifi/qFtxh97vH1D4wb7rtzt0pEt8gayxbY5nmhIoPzJitqtEHP02131fMfQXrXng6bo/dhf6By90Pkx1L7LwY89c7O33g7JYYBqUiANpdxzowQcf11HmD59/iX5ws36QOlCR+i2FVX7qt8+p339JpfD/k9AZR1VTmu7rnoln+VbvYm4Ij9GkwQCplKcHoeek4qexjn2eeO78UQlcFQO9Tz3OY4Rz7PZoOoxYmQkeiaenT7j291/uN1PEbMAa+tTQjlR1E+g+pvOn2jsgmUS4IyyOvkrtG9rZhxj9sxpOsG7fV9l3cFfnNaorzv8WmuNmfojiHcm2HqRykTDNlUW7lM1jyfx02RtDD3JYGE0ndEM/Bn5Eih3q73diiGtG1Q49jDCqiq9/i2fD+5Q7mfh6sKPDnKR2VVO//pYiDjVSCXjcHeLT/umu5unJ95Ru+3oCyeffUuQdJEnKnm3dMYJp6kOKukJNPp8jPyDbsXgc+EeMuyb/nnq8jZp8+nSLjroSTJ8x3zHPjsierqg4tfPXFLmnJzHS3kWHvpya+PqF+BoRRWISduieUmCPPnWvUgqkyH3F5OjsGjNeaoz82cbI242pLzVG/Wxj1GVje/n/4r1PGe9T6tfbk3bjYNdpnPHGvulM2zrG/rhvD/u6sc7v7cXeNhd1Bcu2p6zqprP7G33foygpi4NYHrH7QRQjdSL/RNs38YhrBEp4kuBIsv/N9Iumgw3DY1z0n/+5EyFsqh7XT6l0VCH1a4rUPzCnescOrg+SdSHNR4AYl489swjZ32Nkfz/CRD8xgO0aj9etgn3tpRfgQdl/W7irx/UO4H2Keno6yvb3hBQf5/4ZH1O/nemAqHzHz4uCC/ZHErPj/93OXsLv0N6i7BLy2OuzgoMWjUf5ADVX1o+Hcd8T9PTpTk9/PUIcqfwv+rObdni0ojmT+u2Xi5HHseLjUQTOeYCn4Um8sewcwQ5dOw79sQ3MqkNL6d1EPmdL+oJxO+OBGYt1La57a8ocp140Oz4lROdQ60J0jsjwjCGJcxE4qtdoqi/0SMtG0o7t7kuTNb3HecC1n1kHJDetyT89WiehP3Dy9PWWqF4iiviKw3l3bjrYx/COMmw6jwkRuNXtC9W3Z0FMQkLbvX8Fy2GiRNXPhu2MqIuxOyf45gDumf+mYUugO5nYKysQuJ3Ike4EHva4d7b+3Hc7GpGD6TozMf/wDPXxL79731N/+d2I/lG/P/3jprmJ/HtP8bFbZfxciwmIKBxxcFSzwRDPFElxeH56WEU/8yRL4c9G9JngqeizelJSp2qfUyQlnHi/78xDHAA93BTpA0gcxDycMfSiozg+t6ES4IDJwT4QHg/8/+Y9DhPjR0dDiQf5MRJTM3b48J9fDyB7Jws/A+DpwkPz3BWG3wN+Mb8eBOSIztqhszA6DHtEZSVR7Y2Pu/pifU0+xY0E6+eI/rY+Dh4j78LCtOM/5vsU+T7h850k8vuVaO0I1UzDDGLnGXrmXPGiofoSwz78+yT+eXiPP5ITnleZ+ONkwk8IPf5IjRWCGscfNY7iKSH+KDIcr2rxR2HMcoz68H6PUKd5fkzFJepY1ajdR5JX9fHkAcPEbLqkq6PjJ9o5Zfwk+i+urehjXhf2lKkqv6dB0CaCsn8qCiIXfxyzKqGxu4+MOBaZI2UTXuW0HTmaqqnCjnxR1xSdPVL2y5G6sW7bHRxFYZL4T7uCi6AFRyYT07iKWTSscZqOnsP1Dxoulj08yHHY8j61D2FMH2s2UzvFMjuE748K/SAQe+cshk5I4V5A4imMqXxwZ/Z4+siyf42SBk8Pn365IUi4KTwR+Zia3Yfo/6dPL+PkiQucVxNrjzdygo+4d1++HOX0C/E+dfr/6/uzAjJ+Sl4X/CE1vj5dxXHnTH+OMhTYDOAuj48B58PllB/v3f4z7+UK1QHqzNqYfkNp7ILHp+REv+J4UvH96PDdQBcZBYplY37gv08JzD81iJjFZILF5Nek/b07LMeBIa+G8uVa99r6+nThgp9CczyGFTwOOJDZnDF6N4LRUGG+3Egs4Hj8fepq7K5ZestM/1HD9aJl/udHbsdi8seH5+eKbrb18sh9+KmhI/5bDN2Jo7cG4d70eXk+vjxVyVcm3eUQ7Q2XFgW+exu2V3mfzv2dA9znKAt24ff87Dz90eH+0QH/6SH/6UF/cYK9Vkgm5h/5QzXvF35Nep2n2Xsczl9TxA8PJ/HfbziJl/hO/PRw/jDaG8OZHLqT23IY4KfzsT3G+D/ue9xs8ArFWVn0cy4CN6TpQGlCpM6G7vtPy8tFlPWaI5l6WCg4WAj22eGHq/TbP+VRns+qQ/GXWNd+TbqdexBMT4jJwKZV1/70Pv947/PSEN7Jm9zi0TFt8z7O27xPJG5+fAjeLJxx7K26d+UyCmKh4gV+dpOPQI+BecyXZMAV8Y//ev0EDxv1PkXfLsGMZV+F4PYQTPSXxgL0KiS7h4xrMD9Tg47+sv9cTfyXi+TlFH0mc8ymg6I4HjN0ouBRSk7WnRf81xSZ+reLfOTJezrWDrxQf0UKHd1QAhPpxyXEX08rnAeYuWJgdzvUzpYgLn22RL4mWvU91nmO9HGsip+e/YVtBo8PDxeu3q7SYbHy1yvB2pfcchoi0G+LCBa3eVnvyxnir0kdH3F54cU7JL55+kJXAv+bO4lWq0PbPrPjNxJ/Z2g/pQAwL23efob7Aabmfco3NX1Pw57KHcmJhOAVDzFgI96ms+cOhn06Bz5yLlbd1525pOlardxIru1YdkFW6m2sumOvX2p3n2z8kVZj0Itmb/oHsS688g5OFmQ/OCcrcjVayc93PYQLZ+gwn55e9lqureiVq3TP+CX0+peL7n257fudgrybBTccwq93untjgJP9v6l4km5RMtm1A/7T0/gjPY2be0XezIa3s/fOUkrkRuyW4jKOVnE0c6z7j5eZbHP3PPrgYzmJq15sQDoo9C97lRAr4cNaEq6UwprgVu2DZOGSLw+RH/PwNfZksNXRPcV+SApb3MbzIvSnhwoxoQ83c0/XKC899gPpx00z+25+2VdV1qb/8PXrueE7AP+W2kP5M3PxLdZDDxdLPQly//GX32+Af/94/lh3NPzwH7ej8X3Dv/5ou7t6t7C+Vif14VZb0c6kaL3qZbrfMh67wbtcNLtaELkmL674Az16eJwr/kzXUm4YPD38FJnfbNedhYsrag/rN6m//S31b/u6puG4XhQgxtryhdG5T9d1d3ai6oeqH2AfLZ67RxHc0fYtXqt++HoRLh4oPa96L3K8ojB0Zo67cs7Iu+M1JOolG7tnl97C+2gK3mJ95N3dnrbPUZXbc/bz61PniPNnZsC1XP3QsO2avCD9raP26pi9PEPujNf3e4FIsrlf/+Pzw0VQ4dr6s+55rvf40N3RktT9D3s7cnNn134XwK4ByzWdQ+xxtsE0gwe5s9DHV6u037Dzp2y6TmDavd2G70dNj3J/8fbk9yklBjvwLmG/4u3hJrY6m6bq6x6KN+/s98Hqnq/H9Q5Fj4/Yx/VM3T9uZD6M1/75F+Lrs5mo2I6axwJIXFuf/VbsuuLNom33v6US9D4vQ93bdLA/Ow5cL2Pbjw+XW7eTrN917jG5LHEIhXQ7mjAXjZ2LEAZ59vS5i/THp1uifM2hZ830cSecyPe4HMz3qd+/H/cK4274QcbBgUNEYNFT5npiE/gd5O7uQ3L8Dq7M7Z3damjaWma/z7xoGqF3MfjjOFty6PVronJO4Le3Yj8nMSmaP0jfwXmauLskdpToib4ld7vGu+qziq9zzAko8fAKNr9LFZ2Bxs+SkLHdqmPTdIn5oiBZJzqBECUNtHgb+h4+8TAJuz5M3wTo6VkScnMDcnMT0rexDdBugF8UJOucZ9FOVcYXO0ESXq65xpoQ6l60DeRU4ezxBSdDvRj72NH6QyXhAJ9x9R7Qp19OeiqaxvF4nbZbJYcUW8vJ7phBwlLGADudcnnoISEl17soLhDHB9leRrs7lXAX6aFmMI1ik0ivFnb2IXT8cLFwvQC7QfGxuYen6+3qsdx9i5yl80Z350QupPIpybQj51w7CnL3W+GjB+PQ87C2Pn/o64s4iCGSUcxFKum4V/ckss+H5MPm8tHTxR6zqIGd3dy3f8r9H+mJSAWH78ed/TH9Zw+/X/bzvMeBGyh2zrX9i367dj86KBX3k/x6Kth1B3P1Koy7YMBVv2/uo/MxAK5zAk6kzvZ5zGhHLi55xt4T9qnij7GLdQA7UbRj2ck1Sfbt9PnvGCWIWYzbinb6O2fHKI4932E71jsTuCPn2u7qgnNYcsu6aUyDK9Zt3sq6zY+wbvNPsG7zMuv2nTt9foV1p64neBdVfLojinuTM47yap1IAzdj7zZq9Pfv9/lzoazfwKSLGglOXTf+JeaTg72Pr4kzQL9cUj1WcOTmJ5fuHjV3HEbHx5/Hnq4EesHWo2+PDzvQh+Mxqvjrc3wQMdr7fRJNkKKi4xGH3Ydn4NOYs0f4eDxuwyeYmovrRuuk+jo4o3WPdb+KiksfHygtJvGGktjvuI7PptT2G7PP8sSnnD12PyPzFg3huWuUWAU5WM72aQN8MuX80tLVzf15N6vsaT73Xvf7/HGDV/DzxHGAxOb+q93h+939lzvDr9aXd3ti4hMgGdW/1eKx8HxjwrGisn6hYlx4RfDtgfptx9/rtYyrYdhvaI+3HEcMibbcR5u3L5lze/1+70DGG+DrB5QvHG86/dyi+33U6vsTC9+fmPI+RTyT7NOnt3bnjKboIYgO8KVP5/JeTYbv18uiQ0I3RMd0bgzhj8jZ7vDRPHlQ57VOEc/sG0bkhRH+EHUnHuGI0t23V5Iwv9w43YGd98rlCcpEWjdWwDimvNDAZ+ctjijAb8kIIamI//6yov50SeE+hDjZk4Q7EkngTjcRF3sqNif4zcvwezV2Mv2p6Fi1rWPt7EWHln5PrJ/svYId7PHpXfCzj4kAAdePHecvR359vRa6Q6LxtzNveK8AUv8Rr9+kPqb+7d9OxXfw3djJnswgXNiWH9jh/svVut2ZjJ7J4O2PO2dKQ4oz1nNu6ARJ2ftZp2rvER1kC/s3IDG4Rwcnenryc2Kwg0N0DntXqs8ctnPxPWmEJB2ff0s4atFy8dU67MXXc95cd+O8mxe8O6f9w6u046l5Rt0lrohnGMvjHaY9vVz/zsLzxVcV+1uz+0vH3y/U7YXs/HZkx839CBetXbWVtBupX653bZzpgfV9sf2ZICqOf14Q2UhFgd9OMc1deb0rreuXpTVi5/pcVtcvyerZl/Urcrp+SUrvyuj6poyu78tYxKRIQm9z6emlyjfF883icua+ri+Fcv2SUP5yt4V7xvr8zw0V/mybjt7fByXkpxcA/cBzZ/qdxfx7mHPKIgL2l6Hi6a9CV93Y1XqYRwu8D/9SE/vLi4Zt39lbe052C+nHo2IfIpfs/Q3t9xrMsfTvSRkkv6b+4z8iNzXam/BCjYRevVflnkm9ZUfJu3aU/NOO/mlHX7Cjn/84O/rL24wnecd4kn8az//WxvPzH2A843+TybDTjSp6cJaneHT01eHz+X6lREFk0G8lOZ72S9kHEu5l2k6YfjBLFm05eT0zlszcXS3SXmYEj5zYbU2A0bJWdN/B5t4xhzhOTtxIFCcf4gTMwZBFt3yQn26csE/W2typdc6QaJ9zdH1NVH//MXr6OTmRTzn+0z1KXzHkL+fq9vOZiUpktxO1btyGk8xlvuyFHQ7O+aEdJHLef1hm5bA4vENyyv/jCfqmdEpqT9t5SjzG9+kH0zdnlb6fZ1EiaY/K9/dNXd2lYJ7nb07cj/T8512w9OHDdcdfWlw6wsTEpl5X8/uknquFduifw8e3Px3rxN+S9S77d/r61wO+T1fHXV1Pu7jGK1kxvaf7apv+6R6yCAG4seZzmChR+X5+7Cp9Pi0WnavLG2Kd1L//ehHBMy/JQvz1pohsfl5ENm8SkVf91UsZSVZ4UUiue/hzQnJW8b+PkBzuWbvMj75P3Uxzvt+Tc0pafn1xgfJ0JWaUz0/cE3TaghWlLh0tF11ZfHcZUDPRw9P5XYqmE2NNrMtdrqjsGv5x/DHms2q7Fb/DAt8JdXyB53E18iFxMerDp5ugx5XIN8BGVwMX45uB42j+cDfwHejAUxw/2mze9ExjlwAI3AXWAZN7+LHhgp670L1g8/hgzhVD/+DpkfSbjhFd8RLvucFM0h6e3oDgw4fDBaQftq47jxCQb6wY3Tb2Ib4I2ceeSVyTWawfDuzeDcc5q/8xVuzxI1K8x4t2I6/5L78nl4m/L9aHU4FJTMeReBuqHfgdXIfraeODBBHPsHvwcBCWXf03D1IS/IxP8T3JEXNukfpwvfh8yfT9Hb8SbuvY67/8fqX4v8cXOD6z+jzSs7i/hw7fwSe7iwS69ZvQXe1isG0pugk9ubljv6oUP4cXRx/2tiVaf5/jeRHDxCm1eOk8vlT9bP08fnKQoOjyq040kSLuLxL5tCTU7qbkbHzbdgRHPJO4B4k7k2/WimR4d/VRQhU9xKyJrm0j42XZR+KZi5Tr9SBG9/7vC86nxNPhOMr3f/2eobg/F6eqX9aatziRnBGHK5tvssxOiuP9SbiPWhLz73K89iO1Q0QSxF+xvP3ldzOSv1j8btfb65NEb1+j5NJ1PWUobxAXZUFzOyV/CCLiglukRLCZ6Ab2iGcJjZC6Keh7KT9GtFcm67z8OMN2u4uShd//5Xup/jeL1JnW9NwAo4/U5geR0HTj4SbuG3p5L1FepPZvNuOd2Y83S9wtSf2Q+nnJ/2EJvnasXxThG8J5zpM3SOcv5x40FlXpp0XiVP3V2ZUAfYMIXUGrJ+1/H+ggBS/BYN1/WBt6iMb7PuSZ6B6F8oMbS2UkxGdSeSHKydl/wvyzKuIGhoQx3sH8HgN9TPTjfXTSKnoU4/7+dG8HYJx4ybpr3b/vwf9TMcKpgef4jSKS6QfP2GPBnq4zcSNW7l/DcPScfiQ9tAfb3XL44jbRfRS021Xc2RN1IfgXKwkJ0t/S7ei1JA+JW5wv2trjePzH/3D+8vtpjnz/8o+LDTzRCzCuSLvXaPzmjMSs3FXeZ1FTD7uXaTxclB624lxzaQ+w2yN2v1hZn7ObvADwA32RXNu4zYodO3dVXuPaw9fUwwWfdnLzk3zaVT7yKX4vysNF4X027QHuselQfJdNe4BLNh0fX0dYkb/KEONpdLuKvzcQJwvxEn93SK95N44Sp7gkh/VN9KKS+HUVOgrOHPYrXmCAZ9xVQw92j06pjwvZug/4y/lK9f1ty/srJi4xnU3vRF7oTv7/csix7ilEFzdEikjH6vIgG+9vMeVyXv1M5WulG2VxbIzhD1S5e4wn/XN48hxvpHs4+h/z3YG4h6uz69FV31jVRgf5o0ttntn4TD0ZXUKTXCE470FzsTfjZ/Qf234L6W6MIqk4z5EfJep4b3jyvEzkTl0rgjeqgVPlm4ogUbyb6Q/kjZJ4kj9QN4r20/tBcTbXhYde3ThQlUShB5kg8EwVG/PHh3iE3yeH9iIRN3GjN239cQm+PcablvsC5OWg/gB10OYjN3p51odLJPviEwNulz+AOJ6PTyZP3KfrPMbxNU13J9i1E/YmhhwQ7zjSwIoq6ufh6cOnSxouDPkfR0OkjK5piO7DugD6sXTddfhxgejN2bprTHsP7PgSpz8wD33Aec/FPAKcp+cS16yv6/F73bDhuNB0UQIo2jgV5/qj8cJ2KBu90st0jJxtYmLaZ4eCE0s+Dg6L2gd2/eX3A6Z9qPLhiDrOvWBG/ePmitAhB5Dw+e/sBDm6uIc0XaLO+RLPHuQ5jhEuAqgj1Tfgk7nW2yFSxO3kQe2oF7Gfu+fhBapXmLlbwjnWP0jf5xPrdk8uLt96jdwLP/+c+l2i4jFOUjyl4qIo+f74gXhmI/+LfKb0+cUlEf9MD1PncrLP314Jy4kJe2mJDk6Np8l79m7sRflp1MR54uRyV8mZnE9C+9D5A+6XJPkU374mx3ukxwrRdp5X+Zmg5vzU1CT4eHpzxwEq7vL7Q2Pxt7MXcgTu4lYt/PhUCX85qxPz9ePp9OyxVlxwqhd/Pat5eKvkddVdyanu7nui8vcb72d5adkhIQdx6vdD6owlSXX00oLDVbpr90LMOAN3bCBaWkngx1/P8lt38CdU5zkHk6rT268GvYbsuIKQxLZP4iTQ7Z6c8H2/OJUeGSaMLXJf4i1D/mlfy05qwwU2M3pUXvTceWfvmd4+Rni2OyC67USD6931Gfv18Pjp48p0NHf1rOkIRxhxqzHQ2eX8d2Cit12dbe65bIZMpZNNRV9fa+78/T+LI0BGs0I/mO9ygBcN3cV6/407Y9+HazkO++KXlHj+7jz/46UPfcaHZK2Lvp8jJC+7E9U+q/73i048pf6aeKXGb1cXnPxzS7q3r8D8IZxKGLgPd4YpXrOM5DDZw+QmtL/fHMl7k+qVBWc8x+IGExdXJT2ssyW0HX0rJRhPE3J7OWP2m1J0zTy/aHgvVvOoej0qffzHo6fHCeX4LaV/+f2O4H3XFpEKukiL2Puw/qr91K7t/a0xFymA8TQOGt8fqyes0S19kLRWFx1P5C0OH3YNXyce7rX6/SqIvJ+0OO/kC8rteIGcre3WmRPod7c54ccH0Jf7/MvZyPljz7Xt7JlP9ntsdG+1EL3OMKbgfUrVpwoyoxe5PkRXLClO8PD9so1kIB23U3ECt2fqq8ffb1THOG13PMNPHF3BQnRC+P380Ps1P+du6MeSEfH0Mod2uPwo2meKOZfccBqltnadG7xPnb4Mz3TaoeaNE9DHcHQ3Lfcv640Tv66jJ26QvIji7gHeuwz6eBryG7q4JvG4+ejYs7c0GjP6Pnm7tegzL/2VcDPeNBsfiL6zhnbVRnDhvbzeRHzg4/UmouE4XxC7ffPkWdv3GfzClTlPn94gCJesTiQnsGuEIbjLsshj0rXDQtkR8040d4/BEcHtyuVDluCi9v75rerxlWKn6YBnepKOzyki9be/nbEsCQwugHehV4Li8wDxglv7rQ8PZ/fdXsDso+OLyPFtS8fn3Tp0/UJ2bgRvN8m8ScGbmgb3mr7X2TOOfL8zUsPjSJUPwfkLQzU8DtUROjlW5RvR/AVt8bx9eawO68X/5GAN/5nBulYvPzBWwx8Yq9Pi+MPdIyBvMl/uziO4tF5vNDFvMjCvEhLn6V6zoe3DUYOftKSH+tfm9PpUTATw88bx28kq7hr9dHmj6jd0fQPsy1TcvtnjmAA9tzwPuahA1z5GyfILaXzlPuTLzlxdd3Lr523GKnX+xqzbR4rOT1S9xpM7B7Guql2+F+D2DSFvktXVVNftW7J60I+KHeB2rxNbmm4HyjAKMXZ1n553T05URbX318vn9YmCBecyJXp6F/WV0/4UvXvhVL7DffHemxc9/SRPrgGjfe0X5O5DA2w1IjgJ022/MWQ/3k+XrHiG6NPlQehrij6nmNd69+G3FHMpf2et/nqH3Og2m6vrbM7JPfsKzk8w3jrqd7cfv6Y+vNoR8FpHPt/riOn8UEc+vN6RG4uZSRRvj4GvP/zc0st1UPn7ZSY3npjjuO4wmfeLUrip9IUM//1C5z1ecOgM+iyPu0s2JxobXC73/JGt3YplT+N147raheL7JtI/7l7g8v1sTezWLoqXBvBmAuPqwtpdoqrp6Pn9a3v+uOtqz28XSl40emPz6y55sQc6PLkBd0wXJ0F3Dz+dX890fYXe2y/Ru7pG7/wSvOtr867KdyQEl7vF7l+Md30vUvxCid17G/bGP7prAktJtCXiY+JFS1hy9lb34mTs8WDRT+wivXuEhxwri4d7UKfTOy+CzeOM427MH4hnntXnd2ETHqvpRLd3fDiPnK/WFW4fNbkNHDnHEzs+DPyAdiJ9F/bl00Y3OHb3HMUlZNSrY2T+QD2zD2/K776Wvr7X5zCIGowHabG+OLLxy0tnw46HvM4mwYvykQS5JxxJmDsbkH+5WHSWI7NwdY15VFKwr2LxN0r8vV4f0Cado/2jy75Tyb3LV2A3D7+koqR8NAVwqHlcziTjfV7YEh06e7hp4Ls+/8dd/C9u3r6CPiRXPrAv0XyKYknyJbiz7e9j3UlerHPd9Jm8k7cAz8OlS4Pw/VIedhbgv49I7Pr7XyYV+1zPf7lIHAz/uVTc27J0HMvrO/z3Z3x/378s4MIn+nj9aOfH3bhO/+OthxH090+/fH+KvbBgasYuQNt1g4ar6Y9Pz1PXD3C0OXH850MC6nAjI/746dc09pzNRfD513Tg6bo/xibggxc6H6a6p3/+Ndrgnop3WP32buLaWvQ2j28Oxvzu868xnz7/Gi8spSLP4bd346k+nuE+vLtZ51vgGoYdVU3Hlc7Rx7d9fFNUFUfB7z7/zQ4+JYsfHDeICx8+W8r6OWZCChOPIc7QRDG6cwI8gOwvj3+kqfcpjnm6gzn1v/7n/0E8EwTJp/6f/5t4JoXUl//1P/+vD8Qz8x4X/Z/479eU4zpb3XM/Ut8IRniFgEPx4c+eY8lex9D6Gj/RdO3deVl0ZhwLpfZtHAkZFpQXy3FhtBN0X3YY4G9H0byofPGmjXef2wdrvxOO5+fnA903BSOWmv2w46lgm+N4WqTdcaAHH3xcR5m/+3x8fdZuasXyufsWieenY3pkEr8y4EXx/bQDe8Y0dFzXObxiY48iWOwTEJHmPXuxyONDoM8XkesS4dE9DzMMO4nR4ZOTO7t7Q0K102w8x8H0Y4TweX9G/BLfru8PT2d6A2OLqhzfMZI6n3PPt96PsSP39HKNSG+8MiEPfTkM41mX3r0wLpbvYvn5/V20P/Ldx9S7/C4Fd7xkB0cQUeIQx8wpJd4M8PScKkdObHr3PNrWtXszQpx0fPc+9S7xPoQIY48JAtQlm55EBE1ZEEWhSyHLm7TU1XLZWQG5XgVI3SotlalPOgZgRqCNpkK353EbJ98Bir/V0SAtbzxq4hczglQtiNAut808WUmPBGDYHESy0NHyvGcvl0K4scdwPmDrKt8Y0kuwsTYGGtHZQGLnusSIfVYYwrnP2hZDyHUgFvvCANppuWL52qC8BI3FiEW9UVjM86CrEdwAiVsoz8lCnl3LzBBU+qM6tHmx4bHT/NQAk5w9gHqfzFjcMMtwIt3QaSiXDSRTRXVUEv15Ooe0ZrO1ZZp9JQTlguaiabYdqkzetwUg5VwLhhlT49k1xSxBT+7OYZhtud7KaPUV4FcnRTgRWcPiKp3QFCWzgaDM92WZqTVKM1ZdSxrqTlredtMd5QuAsyUZyeNOQ2a0kaGL5X5nA+dcxvWI1qSeE6XSdoTahdlIJXi4dkW1Yiho7tueRZd4uyCWN1ofups05v/CKFOA6vhThKR1I8+wUq0AlgYqQ30Liyrbq/WWIEuW0ihrhFWJmzEmA2rWBMFMq0yp21HANcXpWmmgzpip87S1KXSFuV3vopnmhzLV63TGoLLoysi3ZnOeUlbDDhDSQgUuLWYt8cPpvA+USk9ELTFby9Om0moKJkW4aMEWbYtNMx4jtsjcAlbEzFrmuMqkILpaJwuXI2Sp1HLQNFhlQfagsmIaWH7KygwUrGEfhowKvS0J+L6Ym3kIju1KKNEMU5mBnCvKyCbXXZkfLmZDsKbzfdSQFwuVl+vLmgg4aoksmzM9Wl7l68DuCQO0KMy6KqcUDR/Y1W0RKUZ/syW7DkcAlS1zSB8rhEW7xW4OZGedNjIWk+aWETIMB0rNFY3c3GAg89UF7u8sm8vD7tgrqQzLdDtiY9wYQgTankWS9bwOdJPiYQcFfZVQyyMCCCW3iixRHUrbZbuhiNAqlWBX9UyVmsntljhZDCswXNF4Higtzgc1cgCQRi9W0qbVXc9Arz0rwM6kWZMpJ10YAtCozqBRKvTUrZRr58BA32ahuqhvLDLd73FCb7XsIb8TZD2+vhhsQNNYzhEK0vQ2LPcKobBaCUM8f2VuS4sjuyaSrcwEzruq5PEgs+lytixySN1UOyrn93stsU6CAlpaguHxQ2nAgS1cqlBCnGRRWF1RYlmDDmzTOVPmbbrSAf64tUaznCZ4RD0IV0IvX2sh1B2b6lpfDzH/1U0ZeYtqsGUabt4ElWWzjUaLwUJl6+1SHcgFxoC2w1gqn1WLBgj7SwB7RcuQmKkxWAlSutKGXrdu52m+xHIgCIcVpJf8cZ71pQwHxI5Ugy7XLFhsFjA1oWSYBaTpHYPn+4XJUiyq1RD51ZXO8z2zXhGtWtVCfS+fwfPRzXdFbpyvoUJZIPJ0MFE7wmI070N9kNc8zt6UumI7V20gg22UPGqjqIDnkJ2FIbIHHk3KtY1INnsEnMzInsV64bIuLjW7CQPOmEtkezge8yQhjODcC0Z5roupBtOuP4CBpmZlBmSyLljVwi3K1p3Jlp815RzQvVwbdiedhUov+vWV2OGqGeRTQiFPF5DZEWuhWURVM+R4yiQtV3BCxUILvmZ7FJvjTIB42Uf6JlxsSWJTwfOZDNtwtp0oWxYVsILdTKoKlLdZiqc8iamIrVW2hvolqcez44XSEanScIs8VpNlemqBmpDfuAzsSYuCxEltsgLkmSlheVqbEj/WUV8s1wdlzH/GVTeyMeKEtdTHujaQLIl0R2NDMEaWjbRszlI3K4XIAeQsZTjWXcVi53ypBSaOl0VKDXbyzBhbKZCbSW2oe6tsnjHXzY3QUfoFaCvi0mPHAZ6PNcszmttQmqm8S2Q7gOb8BfJbTl7lVrruiuEy2CKj2ljLxGyN5yNKi3M412bhljT6w5IoS90JmiqDubcpLnM6YErWArYmyihPd8z8ChRL+hxWe2NM/walO2JWazDQ5dWpygKnOwY9rt5ASPWxvguAT3Gl1SAPfVpUJHIoj2qgVqkiNK8E4y25Uas+KIyYDrSaelqmCdWZAYZaTaCL2KzEGRmTEAuDZhe25yHcbuZEZSyi8YpBbkXB5cCduGJGLYWwy/GKR5dLFVOQ+i6DglxN3vKLcm8M+mJjA1v5AfBIpyUMBWZrkXAybc9kpjQozsHGBjRqTdpLlSosB3WOHoFNuJ73O3h+r9iWAJ16BxbKrTrPb2WvJBpOqQet5ViRmJBx5yIMQhtq1KAg8e5itBRtpROgESgvPFIxWQM0TWGGRtPCQqJ6IpkBfVcbo1nWBx7lNeguEMqTKSpXWxmZzc0rGyAgToOzQinwtlbIlcS2WDNhW5vP8oztLptCr7wwYKlNjCyaLbdXohXMWDhbz4gthdJODdRZEqHJYpD1uLlNCrgzswkM/a1lMf6SmYGVEFQhqpTG3ooF6b64lBYCRNWKJJFlstblF0xGQ+M+5eVJbp0hRNpcILhY9Of57XQ7bYHQbNbhUva7FjNuoQ5PdccjaJYsLG81DmSESasIkQO7FZXju10O5LLDCWotFz2VUkqNjgA1S4WTklOTWGCOBVHlwxlSAbR4ouNaXbApU3k0zDeRuuZWbQ6ASq0Bew6gLEpsMAWgNCoIon7dl7fV+iYDiiHbgPmgFMpsJ+OHQqDSLOqrs7ZKGXN2BjKLZhG1uGlXZtzqtCuOBtMKdOdON8+s9CoQzAbMogBtfYmsOf0VqHbdEvQcGslEKT8MxeJ63kFhV+9JdHVo4R4SWD/nWyLnrVpFihB5jtfgmF5oKm/kR2OhRGUGaFi2DJ4BpSAjtH2ljIrOYiERc0muiN58EsIslLE/lmkuAGC3qwx0WLEm++VWqyZqS6MIi/3W1lt33bIhUl0Gm6sJbUvkKpjlwDS9VlF/oRRxf3IVPP41egELVU6XeSK9bgJQ67qwovbmPDfKzQtAksYztJDyzS0/LK5mYF6z8miSa2LwijDD9gayAbICecNvR02CAbNlrwILptDz2EqvRYkSofbhJI8UbP98GYBCurmEAwpOsT0t1OtixrYGIeXCdn6dq1bqYha2sH/KDrsyl6GWJaHZrDXRtB+qEtdFZkEU+XkaOTQ7VfnCSs6JoQHGcMBS1TwrqeMxIMpjCdpDrSsxI4Odg7C4AdDW8xmVWPYFXXSU0gTWyl6H5zsjribMm8sGtFcG71GZbn4mMqThwVG2OvLYXsga4syZ5yP/0FbJbr84BoFGlFA4Xm9VLitnFFBJ1zbQWFELfjVuqRVAVEt96JvT0OKay4oJvGwf229sy1SuLw99cUHaNFKtRstjvcVwDuTOhkdeN7O2fChohKhuOg6apQtzlR9ssobAQG0L3cBo5jkN+1xgLlQLUG31JInPz9UxAONQRSEDBxa3rPo10el2CeSrJUlmOKbiioJoYnrm1NCi5C72N0nBaMD5ppDLr4rjbhNwQ0mHYyiut3QPDQkw0hoOMsgCxiePh3WxDishklsZQ15vepOmKE/YEbREqylzDaUyB/ns0Gv6hUZZ5YdjPL9ro66FNDbbl7mFthFElPFFtJjIzS03CuyNqBWnNNIotOR5Ib+tiNihqKMJtfX41cIdtPjsDNhI9gjSokMS+88CnHXgfITFhFWl2kxsMd0NXIybtS3HccMuaK3rPDTq9ZzsdwZlDijOWIWjsjb1SC5H5sT2Qm3AwsBrqZuBnxkCpTpbIKuz1fKcog+XYD6CAuqJnXye4Yt4PCfkDNuXyQZYxLzYpER32JhFOrzJcyA/xfEGp4UwSE8KVtgIlT5Ql0oWd8axeNrtL3PipGObKOgWOI8oSqwCBs0F9t/naonn08KYEQvZ9hBZLWudJwtDEgCzNswjP6gTHknYVWwfc6URHDuFOU8UbKMFhvUaC8cVIZCJglfF/jUyIv+RXWxZ0jc4IJuzGjQsubBlex0wE9MTVEKtkBrxLGlLhFhSRA+Nq7a/ZdaCuQHlRsVEsiZXLb5p1OpCVzCbyC5vuxZHSM2M2Hez2D7kG75KbpdWHRB9moUDkYYeaab1mdgXsjzUyhvF45vdwljU5dEQYYQjnlDZbAFsPQEgO622JFLNeUuApSwDLdSY5yndkV0AqvMSmtfZtUo45LiC9eFMRYtBronnr9QAorjNd+GgJs+3jD5n6wBKOJ7NwDDM87ltZSN2G2UHDdSs7xFLo98VZxvsv09ySyhxDXpWE0elZQvO68wE61NhtAT8pNiDc3qM5XVcx/Fdhuv10DQjVngW6oQvbLfYf+qSi9mWTy9aFVAnxguICHfDc+oQLkW2PBBQm6Ra21CpajNxsFrmkJ1PQ4ubaZsVmEu+BINND1mMZ1gFsVartJBrhU1pozhdBRD6TEPWusHkaUBj/wxONzPo0mTAb1sluiWu2oGFZgwT8LQuyDUQdKkBmtdmGZkeNdZj0GqyNOp2sHxxtcm8IqaFXhMpm8bSo/NluiCY/eYYtqBR2/LGEPvjItOoQ7PhZ7ZU08iZAIQjA5ojxpdQdiNxYo1TqtAClYZEI6eHw59CZQzn+daK39oEy4j0sp7H8aLb9Fg2cDdgUBnXUC4Punk+7NI1cUiHdTw/G5RKbzY4fm55lRYMh4OeFJo11gRDq19Fi6Ga226lokmAaXPtoKmclreretWhQNdcDFCwTi9lFuh57O81dArqXM3dhgZbMoBN1xzYGw0Fa7Uqr7B/06z1oAuJKvafe7QLjHF6gDpZxPH0vFoaioVpQUZ6BZEWle27daClMf1tHdEWPfKqNdCCSh1OBRfJdNP2K4DNzWRYGuWxP9nq9kOQZjJ5OB94yKMoH8e3msHrsA3prUe0hDqOTxh1BTUTMBbmj2yA5VJvN9nZprjl5JVYEZtLrYvmGmpZ7HBmYYdkyOFYH0yxv2V03BpwpVwR9VZ9RqbaDjMTQ1/h0DTIhzKRKxZmnODjeK6zyktYt/MzQRyRmB8DHRNPFXuZGbBYfwixL1yUOcVXdCA7oYxyTQZaxKS0KYlev1SG9YnUkdimzQkiP5pg/SFsFjKpDXItkBXnbYiCFvZ3jPYsJ261YojxNWWLB8VNDYy6MwuNlozr0WOr5ItujR1AY4l0nqDdWQl0M8MqnGmwheeT6VK8ofEWbPU2s+3aIzIdUbNNCqkiclS2mZcMsJqtWTSnHRfrk06nBlB20kdjZ2Kr1Li+bfLzgQxhOShMPSrfmWXEYs3ZQtgfu3JYwWIGpvNqFwa6EVg0HPNLYAWiAf1crZznelnsr9WIoIP6FCGrZK42aIkspDqwz4gezw9qGwW0mA32/ySurtIDjzcF089l0ayVW1pbgL1Wcd3B8Q4aCEaeq7hcC3SAMIWhSfQldqwqFXHWAAR0KmmJZ5xiZgxmlB9ArRfkPH7RXs6BlJ8pSBYmFd6eyHRJFKusi+lrMDI5Mucbvm11NNSkqpk8mZ4UKSCuqgsIW+u5vNHyrS4o1Oh2c1MdMBLZ7aq62G4wElqu1Z7HDSdGBahUcwt7hb6eJ+dqeiNI22YaafmGIJPbEvZlrAIMYN4lsD2cVss1MCCL2F/tM/SWFAMc76yz6S0ajYypzE/qHiNuqq6BTFCubRm/beRAv0sVkR2U0xIhKZYAQjwnUcYYWBY59ashKLsZHH8tJUvm/cwY93caIljL0VOZDmAB2wuSaMAOkHIyz6a9PvbHiBwKqVWeZ6rz6RK02v0Gaks+zLMBMasBzVkt0NweBN5m425CcdsucSg75TWJbsuaK1gOKMLFNABbYkK1DKFULDFwPMuzeUKVqRlo1gsuXA7NmsVYFa4j5jIzBVpq3vV4epbvirVslC+w7IW0pn0s73SnnkZz0rIk1yoLS0DCKtan1aHJs+xQHoJVrTND4YgvedwIVZpiWlbzEBpdnedaRVARh9s5jq/0Uj9PNSxJAN5YDuEATvvqurP06mCNwwvYd7Z1iyh0GiuxVjUraNBuGXksQsociI00iVqVAo4fhdCrgLw5yiBD1nyJUvrMHHDF9hQOc17V49oZYy72iwMNtT2jJTHsatQBvSXWF9OZv9nyNWJkgkwD909FynZL15fjOW/ZuRGc1NZ5lW2vZxTIKoQNFSNXktmuUmmCTWdYQH0i7VvcesW7ILdwIUTF1cTjuA7jAlgtN1DAE3kJ++fpDr/MmiXkYbfTI9mW3uRCWe8jGdtDntnIJR/oBLCgyQA+T67l8hxUkN6FQ7qK9SNSxhWxvJRlpFnlnMTbA3MGamiI/SMtK8sEtVA2olTYNuCAqZdlrk+UFdHaLvPQzvBYvuxSvgTKZcCFId2peDyLYzUwVzomMogM8OhV6JdEmy1Vsb/kZnm2UzLGgG/zY6ROSzVpURAqM1aab/oIdWZunitP3BzgsPpE3Vp2JtP9TrsO6Hk+jwa0Ynt8kZ6tRGI7ryJby+IeF9K4PbKYcaG2VCSeV+ajOpiN5xoMyKXtkSUeFETbZUxYrrQNibewYyys1oKDQqU8tVgByC5vr/hxkxOpjsxnuxNfVCdhBrYVL9zSjC0a4lzMI1TLT2cqDbQCw0pLYYPla05s6UwNl7bnDQ6OavPclqkVgCHiTsvI1omqxJR7nSEojHIu0hi2xlMVxzGFvG4OUVeztnk2LWt9kSLbFTgK18yWngHsXgGvxMOp5Fd4vt5EGVEQNBMOcqyI5UcbLcWypVKou+iJeULLOCbouYEHAyGo5VnTywhAEtJ47vb1Up6FZVgRCpDfwvFq1c7zqxpPgBVaZZAp4HieNcqTgrgAPgvNybzlESFFFEDDZwrQSndcb2MNeiHI5TEDjExF8/iZkm6KXWJDQk2sq1uaatEEyM+qENnbRtqic8VWAeSMchGpLd/askImst95qQANyiryDN1dU6DD5idomClk83xeyRZEqZqtQ69HYP+1H45z3AT0NlCpZS2JVqt9X2xKVBqVspmtxI3wkImr4SaHgpqreVyzPXEFgspl0FSziC2ZrzNdoQaMFnIa7YW1UhvVHJY/TYcuN+xbzECimiIpLqtQrapljyv1sH9iYN8FTs1BkCekkRHy3oAMoQ9ARmU6PcYHK326QBPQL/Jk16ybYjbosMjdjDpbOuRcRjS5qYQ6xTGjks28ORYbtAOhYnUx/Xqj4ANoBQiVmEWDZ5qhkAEdQsH+Y0Ex8nw70+2DoB3geGy1alrrQMvUAeAHTsiy1ZrEqOU2jletUghHY9fNU3BjYP+MHspw0ujmPLY4bjBii6i50JRwOEboJZvh9Sqowj5wPIvw+t4GTHo+NpfeCuuzqei6or/pZpCvbWSeLBb5LtaYNofCYn+j0pq0LgGxutxA25MciV3CeQYsiUwRBWF/KvFdvYDjq3pbw86Fb29Za4nlMVcKFFTr5Pztuu9IvpDtEwryXZ3Nr0QDy2cI5BaqrxxZ5qubtYHtHZmHSPNCixhVACeW2A2E8kZsy0RQzS9BtlzH/lW/P5aZjDcOMT+HNpwF+pCnc/qEEK1Kp4PGmemGJ0hzKoC1YPSwm79YePRsXuoCEQehsOYsIU8P1DUBbK3fR8sivbKojUnpYCHnJtB06VAl+YJAiEG2hp2jNteSiLVACoDzeyZyFt2OxGREPwemojNEak9lJYJVNF3UpuM+6mmWpFJpN1cA61ypDmfzvs5TI3e7BCWnxsH6yMT2jR/zBTHo8nnYM+jmlnE2GQaw5WUNqk6WkXG0485AK+gYcJYd+TxZD/wQ1NuTNYQ57IezXbrui4MiHq9uz63IjO9h5eNvBw04a+pzi+OW/TGY0qsanNm1qcc5VRzfljazIfYncp5ETIorRhhwRYgqtfKY59KrXIdXrIqJo41KzmKWJg7WDUdaoPZ2UuDZnKsPhaDIErC9IeU8UcgJSxEELp4fy9Esv61Xyy1RHTRayGazC4msySUXsKNmHppWH8dTTJ0tiPK6uIWLfFiQmN6CrwPsA62RRzt5j1LmOQGwFikhOVyaKmNLkBCZgEsjm8b4ObJbV8Q1DAbQrqkjHM/3rJlol+ob5JthXuY7VpYAPlGsIGNDmt5GLRp1cdPC+kSZrhsWywSlpii1dAI2++4kzxNl7A/JprOBcnU+lugZjfVHUPSLqIIoW2bZTGsMXHUqY+8gNHD9HtkCPdrro6rXxP5VMag2gSpxFeiow7HE1RzXFOsblWiuu+46z6mZdk7M9rA9zwsLx2NDH9tXsgl6aCGHFsZnFRjQ6W4l1F/qJqZ/KOkidv3LMG/rtLS2FvMu0EzbbtrBaMBzlEDVwbCFfNSV0tig4alGAM7rLtCilQ48jtTFmlhskBDazU7H4/SiYIJ2u7REpj7A/u5UMzkRbHD781md5uniIBSESZuikZYBeZ7XLWEDsKPaRqMSWMo8X9k0QTgrhcibliYeE8xyObEo1HjUlzt9a9VLLzaibhkiDLBzYvHTdgeALMuacE7IorRqUogTp82tATNpVFHJcrXKAQpqBayfJRyf4FB9LvaC1RTOIUfIhJo3umJpCmnUr9Vx/LjSyS6w2nCBOhkxyPMZT8mBzsotoTYTjHm2IrElsSI1M3C2bDgeMeeWTdBvsGVk2a2AZzUcUIvZFruF05EJZbavwbroT1kb2qY4lxCO2zviyMq3Yb8HfImvzZu+CPphFzZKcC7zRIkaijOr0YJ+GRssSlzjeGmpjwGsT9sQx4+1dIUvVhc5OF92FjxZzfQNcUJwAo4nNdfj0r1MCIJNqwu1TaZnsRSz2QhE1R7ASamtSlSt2SiIPMzqMFDqbp5oi1i/NbTpDCGfnno8tgoGqCLFgH2OdCRyyeaagLYbFeTakMnTtXQQitXNIg0DACmLngyILlAarIZCVe3y/LCo4PlFWhq276XZluxL7RxIm8spquBhU2kqIBRgOv0+nNJSTWakIVMTMw2KhdN6ZcrzbF7KAJiuh9g/a809Zmx5oaht2DxczmYTlUnb5Q0Y5uZFmJlwNPY/lEFd4GpdD7bI5Vyi1lPLAEWG3SKk1acqY2iFltgoEGU097h6nuT0WihK7Q2W6IUTbEk7j9Wzv8krsFEt9T1eGBQyIC30N6iuN3JbvgGklTDhlxM41kNdYhVp44o9ZuQioyxqMu1W0r5Yd+cZVKBbJE8u2IoJqhOThL1MAesLhWVyYqOWaSNsz7G+Dls5HbRc2gqZ+ronE+JQGPNIn2ShJ2grlWum3RXIT0MHIk8reuyMS1MirTYGEDvq2H+qV90KMKgaDmRnCya/zZZ1QzSlTAt26sLMY9oq5rffwP7IaF4yZIY3M0vQDRsmWmbTA4snuu4SNCuWiwYT38yvhA7piiuWqCCbmw55jptq2J9Doxy0iWVapfVC28f1l1XUy/k1ldtwjC+MHTuEs7xmqvyW4AyRR0UOtdKduscu+oMaGAvlCsrp7YLKV8R6V1xW5gpSZbvocQtG6Yrr4VyGQ4qb8VtmMy+BXC5bgGroyRZDQ3csODhkD80BQeWp+XZdB0gABtRCo2TRpULPBPqiPIFDw2moPG3zhIhcoosskdmoJDuQFHGaD0zokJaz9e3s2heJEHepW+XCPOlkxznQHTsk6nurusqXm0FOLOTZIgwIO8qfrpySmO5yEszz84LKLSqkAjJBRYHhmLZkVi1xDBBy3gK1ZsE4z+QavZbYbK986HCjQGI3M2zfeao7QkgsZ3ie75cJsVpRVGQRy43lL3RhDIZ1p4LCQUCopNLI9cHWTHdQjtjmt5w9JXTRa2fXaJpZgDydc5mZmPV7dYg2uo/9vw6B7ZmQWWP/fYL9eTBKz8WR0RugKZwuVIJWCKzfYYaGaE32thTB0XURlgIzTFewP7yFXqGL/btaGc5UydhyaNLPgHJQ7KIh49Z4JhisDED7BEAWzE0tmquXdFAkMznoVLk+z24rlRlQcps5lA1zmyfI4rAlWpvqAvpUz5PJVbYCxIW7mMNuuWZ5vFKq1wV/tVDgbDJee7RIDAnQbfN1qK9qOH5aEtZSJLxWD/vrI+zv1zxeF1ZFV4F9HCdtSbfcrotcOYf9HXpc2DJjf4P9sqDVRp1ujciTokUYQEjPcsiuYV+NKyzKJthMAAFdzS2qTCbH1IFWXjKoT8+rFp+bGjnQarll6JJTVeL1kaqLjWp/AecdrJ9WjsiPwYaeKLDFMaZFLnO+Lq42DI38XK8qsbrncgCsqkvkNqnelqONXAi0jZOBrQzj8KxtZgGwQwr7G3qhtuVXvWlBZAoMj5xuGUqMgRW2kPfrBNQW1brKljLYv94GYRHJbDtnkeM1wQjMqlaArVK6sOXbDbcJFukSgMayrXukgMMkIWOVM2hINzMWt3CrBbCV9BqUh9LIYizeN4TZnFRRmJUlHrtj86WAemsHummB8Sh1G/hiWzQZ1GaDTp4kS4qBrWttjhZDrrbl9HDQEVAji+dTls3x/ASNCbDpFUAYNDtDddUmbR37d83orajtmcxPhv5KpFu+CWfFTWdLjNLVirhqIgDHoVmUedRTXTAoazMcz2WbHtkz/BooDWp4PHPFPJaHFtUCo3YZIb2+7ctsfYz9l+JKoZAzRabMYrWugyWL7a8rNnGH0pX6RqQMzoCDWlC2VuMuC4Q5UawjQyXnFlHncXxSteUOam+Wsse0FK4lSn1+BOVusSltdT/ciPVmlYB2TjVVFq0NA6w6igZdsap6vMyz2H/jGBwvN7PLPFXGrheY511sHy1MH6PXCybI5GoNpNCbjMxp2CEVKzQ3glkcvOVplfa74iKc+SExWDgSV5acLnBkPY/10UTn2WLYdHliPjRwAN/rqOw0VFriKiAVNHfNGh7PSbsLyJLcadK+UbTYEuKHoMGTAVxM6uEW91yhALcoZ6COnJnFtybNmaipFglLdEb06GCxMEF/Uhkhs6ZM5Y0zL2TE6SgzgvMcmZGY6VwFoLtlPGgPXUGlqE1Gx/EVtu92jaI9elhbm6DRBXhAAnvp8fO+lcP6oLCBwaodWkx72VTErdbtwWDYMyyeA7AjTtu1Do6f/KXHitWsD+oNfoCq82Do8SNJU4QpYvpw0cHxJqeNKhxQm/UN9IfbqcWNF4YPJty8C5VCZaJSkya2T6vlZo7CuoD120ruE8DKSxXYtFdYHkaL6hCQZpGFy8JoaHE+sTBEDhXn0GYMXeUNe1jA/my9CQ3oGNttEQfIOI7D/q1cBb6F7HJrBarZjI/mC93eclUjH4r9HiHDsSIbEmsK+SGYCc0eDIPyzKNyAfaHJHuE4yte7ef5ohOMBWUF8PiHThb7d735TEyXuDR2sS1jyxcrdAt0cssCtNgsnn/paqsLkF8YIKtGt2SqiP8V1ywzRQuY1aTtGCgAkNW1ihDc+t66ztgVkK5kIR7faSiT9RWOb+FoSEF7ug5UflV1gNAOMv1ocbsss+MW0QeNfp6F+nQCJRZihwOs890ammdXmsQjnh0C3l6NULdf8i1uqo5Nsbs0M3BelfNbVut3S2DdELbQzyxzHrYp7pIl2UUHTQVmiv2FUncF1hL2xwtuZiHRzUkL87/AcNgf0aw81W5ufcAxxQKSq0PdIn47bl/NRztlo92r+x3iUUF8DL6u+LPT3tZMq5D98/fP3z9///z98/fP3z9///z98/fP3/8P/sa+beI9tNh9/f2dQuI/ZOT0EvgDx3zHH08vIsSPvvz+LnoHS+TrYlhcGp9VjL9iqBQZVYoex29gwY8J/Fl3tB2yr/jL5h424hob8TG187SvsdFUjO3i7U8Ryujx+Rvh454d/PfTu+MjxHPTwQ+jk5sEKdAiRXGMwFCiwEdlyjpq8lbR/l4qXLy7EOOdp6zqppNR/bgGQRAky7IcS5MMzTAkTTDcHkhZH4BuoB1jkvN4RKJufBG53StWaAb36AtJM7uvDBl/5TA8zUW/8VcBf+QpXLr7KgoYmMAgPB99p4joOy3if0g2fkBGD3g80JjO+AGFH1AEjR+IMQqKiV72ErVA0TsIgope9xL/s2uEi6qLRFSPiB+Q0UtheCJ+Gj3g2YjkqCUhrkFHHyn6+D1CJjJH8KhFLuo0R8XQGBsTYSTJr18jOTx7HQpmEb8Lwe7dKrYTr8Ow22age4r97lAnlrEosHsX3XrzItiXc5D4Dc1xXIil7iS5/sxcfDvIaXR8ePfkKK2vtYI/v9IMedUMx5w3E8/Xl5v5+np//4eTSv12Qc2u8PvX78ljp/sjpvijZqKrQ8xXJ54vTm6/dLz5P1P5+FLmj6kc7KaIPbK/GcGn263sCIiONcdn+X97t7+y5WN0DFt/lzK16Ii59+14CpbiWUKkdYHTOR1PUFblFTxb8QQYMyxDadyevH/RwfZUDKZrbzngfqNo7M6jqyK+vXQmPC7D4Lay8KPC+Jz8jTG5wPHvN8/K///5TP2LTLkvUP/iI/gxDx+/fPnAPvOiyFDYROgfIg37QXjG9oLmKVLcPUgxzxwnsLTAMbsHz8/PiRu+qGeWEWieIDhcihXoB/IZmwqKZgh+9yDFPgu0QJMsFndc/+ux7hdclyAJkmIFag9KPVMULYoiHyFjYmQELzLYjO2xn7WNSRUEVmR4jtyTitvmBIZg6H3bGEIUBYrlOf66bU6kMGqC2INiYjiOIGhaOBJD4JYZghBvtU0+Y7OJCaX2lEVtMzxuK2ZThJDFjOB4nhHY87aTeL6k+GeS50Rxx79dHyiG4WlCZI+IGe7/be/adtu4geivCO5Li221vA2HRJEHXWxHjm2tZCW2GwTFSispURKvbalVLKD/3iG5K2ttBXpwi6aB3/YyHJ45HHEuArhcGMPNFhw1XVeCIed+vCzENTMCSztkHchUw8CxxKscSIqlbnAhaok/SzNhsfa/yLq0msAosW1uqBNSjmTzem6pjaSp7doZtGGSSSEez22JKvKycqlUHRC1QSyJINyW7izwbX7H60YY5bbO9Xi0CERV6be6TpHbCg3+wTsK6ZnfI4vt4Kev/PCetq+rsdUTMcLUsFRpY1KpMqN4xoeMXAHSf3df/w8PLOG791ZQfmeVwu+sbq8FW+yt1txvrlo9n1byfFrJ82klxT86cHNwkchZszuDaX4+jRrtWCaj5k2/TXFvMI2TP/vtBBqrl211mMuTeNpcLZOTRn43kyefpyzunGKUDKb8sG2W+cXr+M3V6EvCr8fdgV3CTSc6TZr9hC3To7Zh1/llJI7sfjI/m/ZnceeIz+Orl0f9BPvDq5VWB7wR8/Zvq2TZOl7OFvP52Zx2yDcygda+nfGP55cqetUFTNTg6mwQdzAbRfnx6CrptT6+nulG3u+Z7vXpMoGzTrqK8v3WSdz63DhMWHtwsYqWmPWi/a7G5EZd9wbRZQOXkfiYL5O796uDQdzoLI3Fz02VkFWDgd0/k7242/6ySuR7fjmARnTYiVvHvevk9tXkfBX34sU8uvoE3eSu+fJVO9pXnWm0OuhcJMsX38g/ay/+H10Qvl0Z/2d7IJYiJROUT1DOpl0lvm6BPH6zswNC+SblMkwopDQNXKR50P94rPO5/fGdtD94tS3Bn5sf31bzY0gVAU7ADA1SCj9MjZRUv2RCpBNAy5+bH99Ffv5tdz5Y3SghueK1n2uu3SMkGg3uWggqak3xnIprqm+lv7bcUBzA+yqU1VEIxqHmZVGDUqDDOBCgmPL6qIJFpqV/zoSV3HXjN3Qwa5AzUO49+Ja90GFu5epr7p9b0ixYoc8qy0xtU4e2VGOjCDiEYk7GXUuCrJX04wwFCuTK69Yu9mlRwUH31mg0wVaySxd2IRjLgl0GNENAr8OQbgDCtKFDckKtIchyYagcrwXM5E0g/bgyNnscxAy6eTZ0WOKRF7LEv2suef4FUjgHDByE4B3wGUOVtq3oUIKjLPAzokPpgJ8oRNKoAg5LfKDn10qLHvcmDuILBKLHSYBRBFtAEP0i8EsbmCQVXoZAG4QqDkkGAgt+heB6u8LbQs8FV1Z43YSf8Ib1N+SGRlV0MKkIf1hbi0wQjMAvGWAVU4FfJVCGtSWuuSRSK2tLy0kGepw0nSbLgp9qIP82MqwtWlok/1wJS56g1KYOwzSXtLweM6CUEHxaaLrGYAtYTokPl09ptzy5MF74b0h2Mlf8vr2v5RTZLzRkE0wn9GsiX6f8cMgwZWwyGQrjM/FSmGVE72RMZgr6rQyZkQwylaWjoTSp4uOKsDZjk2lhsrHrkqZmSIRYPRYU14Y4sVXhna3/TeGd/aSK5l1x9d2vRWcgH93m+WJrb6As390qFEKF+IOCfa++Xqjf3eu9R0Nbn3J/yHp5Xx+5B34al7S72r58czumjGE0Pv+weP9jZfha6eQ2nRZfcH1wcnW7uD0oJJwNpXTl9ONSsXu/5ePPa7d58PFn/yGYRfl11rVmui3OzG7edUh5Ofrth3c0gRszz/+4HY3blBh9lcMfXGK0V4tqD4aH2wota231yYfbeTm3t4wG3L+975X8VV2JrSTvao38Db1T4Bo=</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_51238e6b592f4415a7fc6fc089ddda0e\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"class NNXOuter(nnx.Module):\\n\",\n    \"  def __init__(self, out_dim: int, rngs: nnx.Rngs):\\n\",\n    \"    self.dot = nnx.bridge.ToNNX(LinenDot(out_dim), rngs=rngs)\\n\",\n    \"    self.b = nnx.Param(jax.random.uniform(rngs.params(), (1, out_dim,)))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    return self.dot(x) + self.b\\n\",\n    \"\\n\",\n    \"x = jax.random.normal(jax.random.key(42), (4, 32))\\n\",\n    \"model = bridge.lazy_init(NNXOuter(64, rngs=nnx.Rngs(0)), x)  # Can fit into one line\\n\",\n    \"nnx.display(model)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The Linen weight is already converted to a typical NNX variable, which is a thin wrapper of the actual JAX array value within. Here, `w` is an `nnx.Param` because it belongs to the `params` collection of `LinenDot` module.\\n\",\n    \"\\n\",\n    \"We will talk more about different collections and types in the [NNX Variable <-> Linen Collections](#variable-types-vs-collections) section. Right now, just know that they are converted to NNX variables like native ones.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"assert isinstance(model.dot.w, nnx.Param)\\n\",\n    \"assert isinstance(model.dot.w.value, jax.Array)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If you create this model witout using `nnx.bridge.lazy_init`, the NNX variables defined outside will be initialized as usual, but the Linen part (wrapped inside `ToNNX`) will not.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_acf55118cb8348a8b9ad55ec072c8c1a\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_acf55118cb8348a8b9ad55ec072c8c1a\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_acf55118cb8348a8b9ad55ec072c8c1a\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_acf55118cb8348a8b9ad55ec072c8c1a\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"partial_model = NNXOuter(64, rngs=nnx.Rngs(0))\\n\",\n    \"nnx.display(partial_model)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_8e27697ae41d4cccae9d79ad4846e0e1\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_8e27697ae41d4cccae9d79ad4846e0e1\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_8e27697ae41d4cccae9d79ad4846e0e1\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_8e27697ae41d4cccae9d79ad4846e0e1\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"full_model = bridge.lazy_init(partial_model, x)\\n\",\n    \"nnx.display(full_model)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### NNX -> Linen\\n\",\n    \"\\n\",\n    \"To convert an NNX module to Linen, you should forward your creation arguments to `bridge.ToLinen` and let it handle the actual creation process.\\n\",\n    \"\\n\",\n    \"This is because NNX module instance initializes all its variables eagerly when it is created, which consumes memory and compute. On the other hand, Linen modules are stateless, and the typical `init` and `apply` process involves multiple creation of them. So `bridge.to_linen` will handle the actual module creation and make sure no memory is allocated twice.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"['params']\\n\",\n      \"(32, 64)\\n\",\n      \"(4, 64)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class NNXDot(nnx.Module):\\n\",\n    \"  def __init__(self, in_dim: int, out_dim: int, rngs: nnx.Rngs):\\n\",\n    \"    self.w = nnx.Param(nnx.initializers.lecun_normal()(\\n\",\n    \"      rngs.params(), (in_dim, out_dim)))\\n\",\n    \"  def __call__(self, x: jax.Array):\\n\",\n    \"    return x @ self.w\\n\",\n    \"\\n\",\n    \"x = jax.random.normal(jax.random.key(42), (4, 32))\\n\",\n    \"# Pass in the arguments, not an actual module\\n\",\n    \"model = bridge.to_linen(NNXDot, 32, out_dim=64)\\n\",\n    \"variables = model.init(jax.random.key(0), x)\\n\",\n    \"y = model.apply(variables, x)\\n\",\n    \"\\n\",\n    \"print(list(variables.keys()))\\n\",\n    \"print(variables['params']['w'].shape)  # => (32, 64)\\n\",\n    \"print(y.shape)                         # => (4, 64)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"`bridge.to_linen` is actually a convenience wrapper around the Linen module `bridge.ToLinen`. Most likely you won't need to use `ToLinen` directly at all, unless you are using one of the built-in arguments of `ToLinen`. For example, if your NNX module doesn't want to be initialized with RNG handling:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class NNXAddConstant(nnx.Module):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.constant = nnx.Variable(jnp.array(1))\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    return x + self.constant\\n\",\n    \"\\n\",\n    \"# You have to use `skip_rng=True` because this module's `__init__` don't\\n\",\n    \"# take `rng` as argument\\n\",\n    \"model = bridge.ToLinen(NNXAddConstant, skip_rng=True)\\n\",\n    \"y, var = model.init_with_output(jax.random.key(0), x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Similar to `ToNNX`, you can use `ToLinen` to create a submodule of another Linen module.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"(32, 64) (1, 64) (4, 64)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class LinenOuter(nn.Module):\\n\",\n    \"  out_dim: int\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    dot = bridge.to_linen(NNXDot, x.shape[-1], self.out_dim)\\n\",\n    \"    b = self.param('b', nn.initializers.lecun_normal(), (1, self.out_dim))\\n\",\n    \"    return dot(x) + b\\n\",\n    \"\\n\",\n    \"x = jax.random.normal(jax.random.key(42), (4, 32))\\n\",\n    \"model = LinenOuter(out_dim=64)\\n\",\n    \"y, variables = model.init_with_output(jax.random.key(0), x)\\n\",\n    \"w, b = variables['params']['ToLinen_0']['w'], variables['params']['b']\\n\",\n    \"print(w.shape, b.shape, y.shape)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Handling RNG keys\\n\",\n    \"\\n\",\n    \"All Flax modules, Linen or NNX, automatically handle the RNG keys for variable creation and random layers like dropouts. However, the specific logics of RNG key splitting are different, so you cannot generate the same params between Linen and NNX modules, even if you pass in same keys.\\n\",\n    \"\\n\",\n    \"Another difference is that NNX modules are stateful, so they can track and update the RNG keys within themselves.\\n\",\n    \"\\n\",\n    \"### Linen to NNX\\n\",\n    \"\\n\",\n    \"If you convert a Linen module to NNX, you enjoy the stateful benefit and don't need to pass in extra RNG keys on every module call. You can use always `nnx.reseed` to reset the RNG state within.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"x = jax.random.normal(jax.random.key(42), (4, 32))\\n\",\n    \"model = bridge.ToNNX(nn.Dropout(rate=0.5, deterministic=False), rngs=nnx.Rngs(dropout=0))\\n\",\n    \"# We don't really need to call lazy_init because no extra params were created here,\\n\",\n    \"# but it's a good practice to always add this line.\\n\",\n    \"bridge.lazy_init(model, x)\\n\",\n    \"y1, y2 = model(x), model(x)\\n\",\n    \"assert not jnp.allclose(y1, y2)  # Two runs yield different outputs!\\n\",\n    \"\\n\",\n    \"# Reset the dropout RNG seed, so that next model run will be the same as the first.\\n\",\n    \"nnx.reseed(model, dropout=0)\\n\",\n    \"y1 = model(x)\\n\",\n    \"nnx.reseed(model, dropout=0)\\n\",\n    \"y2 = model(x)\\n\",\n    \"assert jnp.allclose(y1, y2)  # Two runs yield the same output!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### NNX to Linen\\n\",\n    \"\\n\",\n    \"`to_linen` will automatically take the `rngs` dict argument and create a `Rngs` object that is passed to the underlying NNX module via the `rngs` keyword argument. If the module holds internal `RngState`, `to_linen` will always call reseed using the `rngs` dict to reset the RNG state.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"x = jax.random.normal(jax.random.key(42), (4, 32))\\n\",\n    \"model = bridge.to_linen(nnx.Dropout, rate=0.5)\\n\",\n    \"variables = model.init({'dropout': jax.random.key(0)}, x)\\n\",\n    \"\\n\",\n    \"# Just pass different RNG keys for every `apply()` call.\\n\",\n    \"y1 = model.apply(variables, x, rngs={'dropout': jax.random.key(1)})\\n\",\n    \"y2 = model.apply(variables, x, rngs={'dropout': jax.random.key(2)})\\n\",\n    \"assert not jnp.allclose(y1, y2)  # Every call yields different output!\\n\",\n    \"y3 = model.apply(variables, x, rngs={'dropout': jax.random.key(1)})\\n\",\n    \"assert jnp.allclose(y1, y3)      # When you use same top-level RNG, outputs are same\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## NNX variable types vs. Linen collections\\n\",\n    \"\\n\",\n    \"When you want to group some variables as one category, in Linen you use different collections; in NNX, since all variables shall be top-level Python attributes, you use different variable types.\\n\",\n    \"\\n\",\n    \"Therefore, when mixing Linen and NNX modules, Flax must know the 1-to-1 mapping between Linen collections and NNX variable types, so that `ToNNX` and `ToLinen` can do the conversion automatically.\\n\",\n    \"\\n\",\n    \"Flax keeps a registry for this, and it already covers all Flax's built-in Linen collections. You can register extra mapping of NNX variable type and Linen collection names using `nnx.register_variable_name_type_pair`.\\n\",\n    \"\\n\",\n    \"### Linen to NNX\\n\",\n    \"\\n\",\n    \"For any collection of your Linen module, `ToNNX` will convert all its endpoint arrays (aka. leaves) to a subtype of `nnx.Variable`, either from registry or automatically created on-the-fly.\\n\",\n    \"\\n\",\n    \"(However, we still keep the whole collection as one class attribute, because Linen modules may have duplicated names over different collections.)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[38;2;79;201;177mParam\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 12 (48 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray([[ 0.53824717,  0.7668343 , -0.38585317],\\n\",\n      \"         [-0.35335615, -0.5244857 , -0.43152452],\\n\",\n      \"         [-1.0662307 ,  0.14089198, -0.16519307],\\n\",\n      \"         [ 0.3971692 ,  0.43213558, -0.461545  ]], dtype=float32)\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"\\u001b[38;2;79;201;177mParam\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 3 (12 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray([0., 0., 0.], dtype=float32)\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"\\u001b[38;2;79;201;177mcounter\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray(0, dtype=int32)\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"<class 'flax.nnx.variablelib.counter'>\\n\",\n      \"(Intermediate( # 1 (4 B)\\n\",\n      \"  value=Array(0.5475821, dtype=float32)\\n\",\n      \"),)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class LinenMultiCollections(nn.Module):\\n\",\n    \"  out_dim: int\\n\",\n    \"  def setup(self):\\n\",\n    \"    self.w = self.param('w', nn.initializers.lecun_normal(), (x.shape[-1], self.out_dim))\\n\",\n    \"    self.b = self.param('b', nn.zeros_init(), (self.out_dim,))\\n\",\n    \"    self.count = self.variable('counter', 'count', lambda: jnp.zeros((), jnp.int32))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    if not self.is_initializing():\\n\",\n    \"      self.count.value += 1\\n\",\n    \"    y = x @ self.w + self.b\\n\",\n    \"    self.sow('intermediates', 'dot_sum', jnp.sum(y))\\n\",\n    \"    return y\\n\",\n    \"\\n\",\n    \"x = jax.random.normal(jax.random.key(42), (2, 4))\\n\",\n    \"model = bridge.lazy_init(bridge.ToNNX(LinenMultiCollections(3), rngs=nnx.Rngs(0)), x)\\n\",\n    \"print(model.w)        # Of type `nnx.Param` - note this is still under attribute `params`\\n\",\n    \"print(model.b)        # Of type `nnx.Param`\\n\",\n    \"print(model.count)    # Of type `counter` - auto-created type from the collection name\\n\",\n    \"print(type(model.count))\\n\",\n    \"\\n\",\n    \"y = model(x, mutable=True)  # Linen's `sow()` needs `mutable=True` to trigger\\n\",\n    \"print(model.dot_sum)        # Of type `nnx.Intermediates`\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"You can quickly separate different types of NNX variables apart using `nnx.split`.\\n\",\n    \"\\n\",\n    \"This can be handy when you only want to set some variables as trainable.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"All Params: ['b', 'w']\\n\",\n      \"All Counters: ['count']\\n\",\n      \"All the rest (intermediates and RNG keys): ['dot_sum', 'rngs']\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Separate variables of different types with nnx.split\\n\",\n    \"CountType = type(model.count)\\n\",\n    \"static, params, counter, the_rest = nnx.split(model, nnx.Param, CountType, ...)\\n\",\n    \"print('All Params:', list(params.keys()))\\n\",\n    \"print('All Counters:', list(counter.keys()))\\n\",\n    \"print('All the rest (intermediates and RNG keys):', list(the_rest.keys()))\\n\",\n    \"\\n\",\n    \"model = nnx.merge(static, params, counter, the_rest)  # You can merge them back at any time\\n\",\n    \"y = model(x, mutable=True)  # still works!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"cc9d78ed\",\n   \"metadata\": {},\n   \"source\": [\n    \"    All Params: ['b', 'w']\\n\",\n    \"    All Counters: ['count']\\n\",\n    \"    All the rest (intermediates and RNG keys): ['dot_sum', 'rngs']\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### NNX to Linen\\n\",\n    \"\\n\",\n    \"If you define custom NNX variable types, you should register their names with `nnx.register_variable_name` so that they go to the desired collections.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"All Linen collections: ['nnx', 'LoRAParam', 'params', 'counts']\\n\",\n      \"{'w': Array([[ 0.2916921 ,  0.22780475,  0.06553137],\\n\",\n      \"       [ 0.17487915, -0.34043145,  0.24764155],\\n\",\n      \"       [ 0.6420431 ,  0.6220095 , -0.44769976],\\n\",\n      \"       [ 0.11161668,  0.83873135, -0.7446058 ]], dtype=float32)}\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@nnx.register_variable_name('counts', overwrite=True)\\n\",\n    \"class Count(nnx.Variable): pass\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"class NNXMultiCollections(nnx.Module):\\n\",\n    \"  def __init__(self, din, dout, rngs):\\n\",\n    \"    self.w = nnx.Param(nnx.initializers.lecun_normal()(rngs.params(), (din, dout)))\\n\",\n    \"    self.lora = nnx.LoRA(din, 3, dout, rngs=rngs)\\n\",\n    \"    self.count = Count(jnp.array(0))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    self.count.value += 1\\n\",\n    \"    return (x @ self.w.value) + self.lora(x)\\n\",\n    \"\\n\",\n    \"xkey, pkey, dkey = jax.random.split(jax.random.key(0), 3)\\n\",\n    \"x = jax.random.normal(xkey, (2, 4))\\n\",\n    \"model = bridge.to_linen(NNXMultiCollections, 4, 3)\\n\",\n    \"var = model.init({'params': pkey, 'dropout': dkey}, x)\\n\",\n    \"print('All Linen collections:', list(var.keys()))\\n\",\n    \"print(var['params'])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"    All Linen collections: ['LoRAParam', 'params', 'counts']\\n\",\n    \"    {'w': Array([[ 0.2916921 ,  0.22780475,  0.06553137],\\n\",\n    \"           [ 0.17487915, -0.34043145,  0.24764155],\\n\",\n    \"           [ 0.6420431 ,  0.6220095 , -0.44769976],\\n\",\n    \"           [ 0.11161668,  0.83873135, -0.7446058 ]], dtype=float32)}\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Partition metadata\\n\",\n    \"\\n\",\n    \"Flax uses a metadata wrapper box over the raw JAX array to annotate how a variable should be sharded.\\n\",\n    \"\\n\",\n    \"In Linen, this is an optional feature that triggered by using `nn.with_partitioning` on initializers (see more on [Linen partition metadata guide](https://flax.readthedocs.io/en/latest/guides/parallel_training/flax_on_pjit.html)). In NNX, since all NNX variables are wrapped by `nnx.Variable` class anyway, that class will hold the sharding annotations too.\\n\",\n    \"\\n\",\n    \"The `bridge.ToNNX` and `bridge.ToLinen` API will automatically convert the sharding annotations, if you use the built-in annotation methods (aka. `nn.with_partitioning` for Linen and `nnx.with_partitioning` for NNX).\\n\",\n    \"\\n\",\n    \"### Linen to NNX\\n\",\n    \"\\n\",\n    \"Even if you are not using any partition metadata in your Linen module, the variable JAX arrays will be converted to `nnx.Variable`s that wraps the true JAX array within.\\n\",\n    \"\\n\",\n    \"If you use `nn.with_partitioning` to annotate your Linen module's variables, the annotation will be converted to a `.sharding` field in the corresponding `nnx.Variable`.\\n\",\n    \"\\n\",\n    \"You can then use `nnx.with_sharding_constraint` to explicitly put the arrays into the annotated partitions within a `jax.jit`-compiled function, to initialize the whole model with every array at the right sharding.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"We have 8 fake JAX devices now to partition this model...\\n\",\n      \"<class 'flax.nnx.variables.Param'>\\n\",\n      \"('in', 'out')\\n\",\n      \"GSPMDSharding({devices=[2,4]<=[8]})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class LinenDotWithPartitioning(nn.Module):\\n\",\n    \"  out_dim: int\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    w = self.param('w', nn.with_partitioning(nn.initializers.lecun_normal(),\\n\",\n    \"                                             ('in', 'out')),\\n\",\n    \"                   (x.shape[-1], self.out_dim))\\n\",\n    \"    return x @ w\\n\",\n    \"\\n\",\n    \"@nnx.jit\\n\",\n    \"def create_sharded_nnx_module(x):\\n\",\n    \"  model = bridge.lazy_init(\\n\",\n    \"    bridge.ToNNX(LinenDotWithPartitioning(64), rngs=nnx.Rngs(0)), x)\\n\",\n    \"  state = nnx.state(model)\\n\",\n    \"  sharded_state = nnx.with_sharding_constraint(state, nnx.get_partition_spec(state))\\n\",\n    \"  nnx.update(model, sharded_state)\\n\",\n    \"  return model\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"print(f'We have {len(jax.devices())} fake JAX devices now to partition this model...')\\n\",\n    \"mesh = jax.sharding.Mesh(devices=mesh_utils.create_device_mesh((2, 4)),\\n\",\n    \"                         axis_names=('in', 'out'))\\n\",\n    \"x = jax.random.normal(jax.random.key(42), (4, 32))\\n\",\n    \"with jax.set_mesh(mesh):\\n\",\n    \"  model = create_sharded_nnx_module(x)\\n\",\n    \"\\n\",\n    \"print(type(model.w))           # `nnx.Param`\\n\",\n    \"print(model.w.sharding)        # The partition annotation attached with `w`\\n\",\n    \"print(model.w.value.sharding)  # The underlying JAX array is sharded across the 2x4 mesh\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"08555a06\",\n   \"metadata\": {},\n   \"source\": [\n    \"    We have 8 fake JAX devices now to partition this model...\\n\",\n    \"    <class 'flax.nnx.variables.Param'>\\n\",\n    \"    ('in', 'out')\\n\",\n    \"    GSPMDSharding({devices=[2,4]<=[8]})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### NNX to Linen\\n\",\n    \"\\n\",\n    \"If you are not using any metadata feature of the `nnx.Variable` (i.e., no sharding annotation, no registered hooks), the converted Linen module will not add a metadata wrapper to your NNX variable, and you don't need to worry about it.\\n\",\n    \"\\n\",\n    \"But if you did add sharding annotations to your NNX variables, `ToLinen` will convert them to a default Linen partition metadata class called `bridge.NNXMeta`, retaining all the metadata you put into the NNX variable.\\n\",\n    \"\\n\",\n    \"Like with any Linen metadata wrappers, you can use `linen.unbox()` to get the raw JAX array tree.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 17,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"GSPMDSharding({devices=[2,4]<=[8]})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class NNXDotWithParititioning(nnx.Module):\\n\",\n    \"  def __init__(self, in_dim: int, out_dim: int, rngs: nnx.Rngs):\\n\",\n    \"    init_fn = nnx.with_partitioning(nnx.initializers.lecun_normal(), ('in', 'out'))\\n\",\n    \"    self.w = nnx.Param(init_fn(rngs.params(), (in_dim, out_dim)))\\n\",\n    \"  def __call__(self, x: jax.Array):\\n\",\n    \"    return x @ self.w\\n\",\n    \"\\n\",\n    \"x = jax.random.normal(jax.random.key(42), (4, 32))\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def create_sharded_variables(key, x):\\n\",\n    \"  model = bridge.to_linen(NNXDotWithParititioning, 32, 64)\\n\",\n    \"  variables = model.init(key, x)\\n\",\n    \"  # A `NNXMeta` wrapper of the underlying `nnx.Param`\\n\",\n    \"  assert type(variables['params']['w']) == bridge.NNXMeta\\n\",\n    \"  # The annotation coming from the `nnx.Param` => (in, out)\\n\",\n    \"  assert variables['params']['w'].metadata['sharding'] == ('in', 'out')\\n\",\n    \"\\n\",\n    \"  unboxed_variables = nn.unbox(variables)\\n\",\n    \"  variable_pspecs = nn.get_partition_spec(variables)\\n\",\n    \"  assert isinstance(unboxed_variables['params']['w'], jax.Array)\\n\",\n    \"  assert variable_pspecs['params']['w'] == jax.sharding.PartitionSpec('in', 'out')\\n\",\n    \"\\n\",\n    \"  sharded_vars = jax.tree.map(jax.lax.with_sharding_constraint,\\n\",\n    \"                              nn.unbox(variables),\\n\",\n    \"                              nn.get_partition_spec(variables))\\n\",\n    \"  return sharded_vars\\n\",\n    \"\\n\",\n    \"with jax.set_mesh(mesh):\\n\",\n    \"  variables = create_sharded_variables(jax.random.key(0), x)\\n\",\n    \"\\n\",\n    \"# The underlying JAX array is sharded across the 2x4 mesh\\n\",\n    \"print(variables['params']['w'].sharding)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"    GSPMDSharding({devices=[2,4]<=[8]})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Lifted transforms\\n\",\n    \"\\n\",\n    \"In general, if you want to apply Linen/NNX-style lifted transforms upon an `nnx.bridge`-converted module, just go ahead and do it in the usual Linen/NNX syntax.\\n\",\n    \"\\n\",\n    \"For Linen-style transforms, note that `bridge.ToLinen` is the top level module class, so you may want to just use it as the first argument of your transforms (which needs to be a `linen.Module` class in most cases)\\n\",\n    \"\\n\",\n    \"### Linen to NNX\\n\",\n    \"\\n\",\n    \"NNX style lifted transforms are similar to JAX transforms, and they work on functions.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 18,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"(4, 32, 64)\\n\",\n      \"(4, 64)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class NNXVmapped(nnx.Module):\\n\",\n    \"  def __init__(self, out_dim: int, vmap_axis_size: int, rngs: nnx.Rngs):\\n\",\n    \"    self.linen_dot = nnx.bridge.ToNNX(nn.Dense(out_dim, use_bias=False), rngs=rngs)\\n\",\n    \"    self.vmap_axis_size = vmap_axis_size\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"\\n\",\n    \"    @nnx.split_rngs(splits=self.vmap_axis_size)\\n\",\n    \"    @nnx.vmap(in_axes=(0, 0), axis_size=self.vmap_axis_size)\\n\",\n    \"    def vmap_fn(submodule, x):\\n\",\n    \"      return submodule(x)\\n\",\n    \"\\n\",\n    \"    return vmap_fn(self.linen_dot, x)\\n\",\n    \"\\n\",\n    \"x = jax.random.normal(jax.random.key(0), (4, 32))\\n\",\n    \"model = bridge.lazy_init(NNXVmapped(64, 4, rngs=nnx.Rngs(0)), x)\\n\",\n    \"\\n\",\n    \"print(model.linen_dot.kernel.shape) # (4, 32, 64) - first axis with dim 4 got vmapped\\n\",\n    \"y = model(x)\\n\",\n    \"print(y.shape)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"    (4, 32, 64)\\n\",\n    \"    (4, 64)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"61a1ac21\",\n   \"metadata\": {},\n   \"source\": [\n    \"### NNX to Linen\\n\",\n    \"\\n\",\n    \"Note that `bridge.ToLinen` is the top level module class, so you may want to just use it as the first argument of your transforms (which needs to be a `linen.Module` class in most cases).\\n\",\n    \"\\n\",\n    \"`ToLien` can naturally be used with Linen transforms like `nn.vmap` or `nn.scan`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 19,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"(4, 32, 64)\\n\",\n      \"(4, 64)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class LinenVmapped(nn.Module):\\n\",\n    \"  dout: int\\n\",\n    \"  @nn.compact\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    inner = nn.vmap(bridge.ToLinen, variable_axes={'params': 0}, split_rngs={'params': True}\\n\",\n    \"                    )(nnx.Linear, args=(x.shape[-1], self.dout))\\n\",\n    \"    return inner(x)\\n\",\n    \"\\n\",\n    \"x = jax.random.normal(jax.random.key(42), (4, 32))\\n\",\n    \"model = LinenVmapped(64)\\n\",\n    \"var = model.init(jax.random.key(0), x)\\n\",\n    \"print(var['params']['VmapToLinen_0']['kernel'].shape)  # (4, 32, 64) - leading dim 4 got vmapped\\n\",\n    \"y = model.apply(var, x)\\n\",\n    \"print(y.shape)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"178d2b2f\",\n   \"metadata\": {},\n   \"source\": [\n    \"    (4, 32, 64)\\n\",\n    \"    (4, 64)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"cell_metadata_filter\": \"-all\",\n   \"formats\": \"ipynb,md:myst\",\n   \"main_language\": \"python\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.9\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "docs_nnx/guides/bridge_guide.md",
    "content": "---\njupytext:\n  cell_metadata_filter: -all\n  formats: ipynb,md:myst\n  main_language: python\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Use Flax NNX and Linen together\n\nThis guide is for existing Flax users who want to make their codebase a mixture of Flax Linen and Flax NNX `Module`s, which is made possible thanks to the `flax.nnx.bridge` API.\n\nThis will be helpful if you:\n\n* Want to migrate your codebase to NNX gradually, one module at a time;\n* Have external dependency that already moved to NNX but you haven't, or is still in Linen while you've moved to NNX.\n\nWe hope this allows you to move and try out NNX at your own pace, and leverage the best of both worlds. We will also talk about how to resolve the caveats of interoperating the two APIs, on a few aspects that they are fundamentally different.\n\n**Note**:\n\nThis guide is about glueing Linen and NNX modules. To migrate an existing Linen module to NNX, check out the [Migrate from Flax Linen to Flax NNX](https://flax.readthedocs.io/en/latest/guides/linen_to_nnx.html) guide.\n\nAnd all built-in Linen layers should have equivalent NNX versions! Check out the list of [Built-in NNX layers](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/index.html).\n\n```{code-cell} ipython3\nimport os\nos.environ[\"XLA_FLAGS\"] = '--xla_force_host_platform_device_count=8'\n\nfrom flax import nnx\nfrom flax import linen as nn\nfrom flax.nnx import bridge\nimport jax\nfrom jax import numpy as jnp\nfrom jax.experimental import mesh_utils\nfrom typing import *\n```\n\n## Submodule is all you need\n\nA Flax model is always a tree of modules - either old Linen modules (`flax.linen.Module`, usually written as `nn.Module`) or NNX modules (`nnx.Module`).\n\nAn `nnx.bridge` wrapper glues the two types together, in both ways:\n\n* `nnx.bridge.ToNNX`: Convert a Linen module to NNX, so that it can be a submodule of another NNX module, or stand alone to be trained in NNX-style training loops.\n* `nnx.bridge.ToLinen`: Vice versa, convert a NNX module to Linen.\n\nThis means you can move in either top-down or bottom-up behavior: convert the whole Linen module to NNX, then gradually move down, or convert all the lower level modules to NNX then move up.\n\n+++\n\n## The Basics\n\nThere are two fundamental difference between Linen and NNX modules:\n\n* **Stateless vs. stateful**: Linen module instances are stateless: variables are returned from a purely functional `.init()` call and managed separately. NNX modules, however, owns its variables as instance attributes.\n\n* **Lazy vs. eager**: Linen modules only allocate space to create variables when they actually see their input. Whereas NNX module instances create their variables the moment they are instantiated, without seeing a sample input.\n\nWith that in mind, let's look at how the `nnx.bridge` wrappers tackle the differences.\n\n### Linen -> NNX\n\nSince Linen modules may require an input to create variables, we semi-formally supported lazy initialization in the NNX modules converted from Linen. The Linen variables are created when you give it a sample input.\n\nFor you, it's calling `nnx.bridge.lazy_init()` where you call `module.init()` in Linen code.\n\n(Note: you can call `nnx.display` upon any NNX module to inspect all its variables and state.)\n\n```{code-cell} ipython3\nclass LinenDot(nn.Module):\n  out_dim: int\n  w_init: Callable[..., Any] = nn.initializers.lecun_normal()\n  @nn.compact\n  def __call__(self, x):\n    # Linen might need the input shape to create the weight!\n    w = self.param('w', self.w_init, (x.shape[-1], self.out_dim))\n    return x @ w\n\nx = jax.random.normal(jax.random.key(42), (4, 32))\nmodel = bridge.ToNNX(LinenDot(64),\n                     rngs=nnx.Rngs(0))  # => `model = LinenDot(64)` in Linen\nbridge.lazy_init(model, x)              # => `var = model.init(key, x)` in Linen\ny = model(x)                            # => `y = model.apply(var, x)` in Linen\n\nnnx.display(model)\n\n# In-place swap your weight array and the model still works!\nmodel.w.value = jax.random.normal(jax.random.key(1), (32, 64))\nassert not jnp.allclose(y, model(x))\n```\n\n`nnx.bridge.lazy_init` also works even if the top-level module is a pure-NNX one, so you can do sub-moduling as you wish:\n\n```{code-cell} ipython3\nclass NNXOuter(nnx.Module):\n  def __init__(self, out_dim: int, rngs: nnx.Rngs):\n    self.dot = nnx.bridge.ToNNX(LinenDot(out_dim), rngs=rngs)\n    self.b = nnx.Param(jax.random.uniform(rngs.params(), (1, out_dim,)))\n\n  def __call__(self, x):\n    return self.dot(x) + self.b\n\nx = jax.random.normal(jax.random.key(42), (4, 32))\nmodel = bridge.lazy_init(NNXOuter(64, rngs=nnx.Rngs(0)), x)  # Can fit into one line\nnnx.display(model)\n```\n\nThe Linen weight is already converted to a typical NNX variable, which is a thin wrapper of the actual JAX array value within. Here, `w` is an `nnx.Param` because it belongs to the `params` collection of `LinenDot` module.\n\nWe will talk more about different collections and types in the [NNX Variable <-> Linen Collections](#variable-types-vs-collections) section. Right now, just know that they are converted to NNX variables like native ones.\n\n```{code-cell} ipython3\nassert isinstance(model.dot.w, nnx.Param)\nassert isinstance(model.dot.w.value, jax.Array)\n```\n\nIf you create this model witout using `nnx.bridge.lazy_init`, the NNX variables defined outside will be initialized as usual, but the Linen part (wrapped inside `ToNNX`) will not.\n\n```{code-cell} ipython3\npartial_model = NNXOuter(64, rngs=nnx.Rngs(0))\nnnx.display(partial_model)\n```\n\n```{code-cell} ipython3\nfull_model = bridge.lazy_init(partial_model, x)\nnnx.display(full_model)\n```\n\n### NNX -> Linen\n\nTo convert an NNX module to Linen, you should forward your creation arguments to `bridge.ToLinen` and let it handle the actual creation process.\n\nThis is because NNX module instance initializes all its variables eagerly when it is created, which consumes memory and compute. On the other hand, Linen modules are stateless, and the typical `init` and `apply` process involves multiple creation of them. So `bridge.to_linen` will handle the actual module creation and make sure no memory is allocated twice.\n\n```{code-cell} ipython3\nclass NNXDot(nnx.Module):\n  def __init__(self, in_dim: int, out_dim: int, rngs: nnx.Rngs):\n    self.w = nnx.Param(nnx.initializers.lecun_normal()(\n      rngs.params(), (in_dim, out_dim)))\n  def __call__(self, x: jax.Array):\n    return x @ self.w\n\nx = jax.random.normal(jax.random.key(42), (4, 32))\n# Pass in the arguments, not an actual module\nmodel = bridge.to_linen(NNXDot, 32, out_dim=64)\nvariables = model.init(jax.random.key(0), x)\ny = model.apply(variables, x)\n\nprint(list(variables.keys()))\nprint(variables['params']['w'].shape)  # => (32, 64)\nprint(y.shape)                         # => (4, 64)\n```\n\n`bridge.to_linen` is actually a convenience wrapper around the Linen module `bridge.ToLinen`. Most likely you won't need to use `ToLinen` directly at all, unless you are using one of the built-in arguments of `ToLinen`. For example, if your NNX module doesn't want to be initialized with RNG handling:\n\n```{code-cell} ipython3\nclass NNXAddConstant(nnx.Module):\n  def __init__(self):\n    self.constant = nnx.Variable(jnp.array(1))\n  def __call__(self, x):\n    return x + self.constant\n\n# You have to use `skip_rng=True` because this module's `__init__` don't\n# take `rng` as argument\nmodel = bridge.ToLinen(NNXAddConstant, skip_rng=True)\ny, var = model.init_with_output(jax.random.key(0), x)\n```\n\nSimilar to `ToNNX`, you can use `ToLinen` to create a submodule of another Linen module.\n\n```{code-cell} ipython3\nclass LinenOuter(nn.Module):\n  out_dim: int\n  @nn.compact\n  def __call__(self, x):\n    dot = bridge.to_linen(NNXDot, x.shape[-1], self.out_dim)\n    b = self.param('b', nn.initializers.lecun_normal(), (1, self.out_dim))\n    return dot(x) + b\n\nx = jax.random.normal(jax.random.key(42), (4, 32))\nmodel = LinenOuter(out_dim=64)\ny, variables = model.init_with_output(jax.random.key(0), x)\nw, b = variables['params']['ToLinen_0']['w'], variables['params']['b']\nprint(w.shape, b.shape, y.shape)\n```\n\n## Handling RNG keys\n\nAll Flax modules, Linen or NNX, automatically handle the RNG keys for variable creation and random layers like dropouts. However, the specific logics of RNG key splitting are different, so you cannot generate the same params between Linen and NNX modules, even if you pass in same keys.\n\nAnother difference is that NNX modules are stateful, so they can track and update the RNG keys within themselves.\n\n### Linen to NNX\n\nIf you convert a Linen module to NNX, you enjoy the stateful benefit and don't need to pass in extra RNG keys on every module call. You can use always `nnx.reseed` to reset the RNG state within.\n\n```{code-cell} ipython3\nx = jax.random.normal(jax.random.key(42), (4, 32))\nmodel = bridge.ToNNX(nn.Dropout(rate=0.5, deterministic=False), rngs=nnx.Rngs(dropout=0))\n# We don't really need to call lazy_init because no extra params were created here,\n# but it's a good practice to always add this line.\nbridge.lazy_init(model, x)\ny1, y2 = model(x), model(x)\nassert not jnp.allclose(y1, y2)  # Two runs yield different outputs!\n\n# Reset the dropout RNG seed, so that next model run will be the same as the first.\nnnx.reseed(model, dropout=0)\ny1 = model(x)\nnnx.reseed(model, dropout=0)\ny2 = model(x)\nassert jnp.allclose(y1, y2)  # Two runs yield the same output!\n```\n\n### NNX to Linen\n\n`to_linen` will automatically take the `rngs` dict argument and create a `Rngs` object that is passed to the underlying NNX module via the `rngs` keyword argument. If the module holds internal `RngState`, `to_linen` will always call reseed using the `rngs` dict to reset the RNG state.\n\n```{code-cell} ipython3\nx = jax.random.normal(jax.random.key(42), (4, 32))\nmodel = bridge.to_linen(nnx.Dropout, rate=0.5)\nvariables = model.init({'dropout': jax.random.key(0)}, x)\n\n# Just pass different RNG keys for every `apply()` call.\ny1 = model.apply(variables, x, rngs={'dropout': jax.random.key(1)})\ny2 = model.apply(variables, x, rngs={'dropout': jax.random.key(2)})\nassert not jnp.allclose(y1, y2)  # Every call yields different output!\ny3 = model.apply(variables, x, rngs={'dropout': jax.random.key(1)})\nassert jnp.allclose(y1, y3)      # When you use same top-level RNG, outputs are same\n```\n\n## NNX variable types vs. Linen collections\n\nWhen you want to group some variables as one category, in Linen you use different collections; in NNX, since all variables shall be top-level Python attributes, you use different variable types.\n\nTherefore, when mixing Linen and NNX modules, Flax must know the 1-to-1 mapping between Linen collections and NNX variable types, so that `ToNNX` and `ToLinen` can do the conversion automatically.\n\nFlax keeps a registry for this, and it already covers all Flax's built-in Linen collections. You can register extra mapping of NNX variable type and Linen collection names using `nnx.register_variable_name_type_pair`.\n\n### Linen to NNX\n\nFor any collection of your Linen module, `ToNNX` will convert all its endpoint arrays (aka. leaves) to a subtype of `nnx.Variable`, either from registry or automatically created on-the-fly.\n\n(However, we still keep the whole collection as one class attribute, because Linen modules may have duplicated names over different collections.)\n\n```{code-cell} ipython3\nclass LinenMultiCollections(nn.Module):\n  out_dim: int\n  def setup(self):\n    self.w = self.param('w', nn.initializers.lecun_normal(), (x.shape[-1], self.out_dim))\n    self.b = self.param('b', nn.zeros_init(), (self.out_dim,))\n    self.count = self.variable('counter', 'count', lambda: jnp.zeros((), jnp.int32))\n\n  def __call__(self, x):\n    if not self.is_initializing():\n      self.count.value += 1\n    y = x @ self.w + self.b\n    self.sow('intermediates', 'dot_sum', jnp.sum(y))\n    return y\n\nx = jax.random.normal(jax.random.key(42), (2, 4))\nmodel = bridge.lazy_init(bridge.ToNNX(LinenMultiCollections(3), rngs=nnx.Rngs(0)), x)\nprint(model.w)        # Of type `nnx.Param` - note this is still under attribute `params`\nprint(model.b)        # Of type `nnx.Param`\nprint(model.count)    # Of type `counter` - auto-created type from the collection name\nprint(type(model.count))\n\ny = model(x, mutable=True)  # Linen's `sow()` needs `mutable=True` to trigger\nprint(model.dot_sum)        # Of type `nnx.Intermediates`\n```\n\nYou can quickly separate different types of NNX variables apart using `nnx.split`.\n\nThis can be handy when you only want to set some variables as trainable.\n\n```{code-cell} ipython3\n# Separate variables of different types with nnx.split\nCountType = type(model.count)\nstatic, params, counter, the_rest = nnx.split(model, nnx.Param, CountType, ...)\nprint('All Params:', list(params.keys()))\nprint('All Counters:', list(counter.keys()))\nprint('All the rest (intermediates and RNG keys):', list(the_rest.keys()))\n\nmodel = nnx.merge(static, params, counter, the_rest)  # You can merge them back at any time\ny = model(x, mutable=True)  # still works!\n```\n\n    All Params: ['b', 'w']\n    All Counters: ['count']\n    All the rest (intermediates and RNG keys): ['dot_sum', 'rngs']\n\n+++\n\n### NNX to Linen\n\nIf you define custom NNX variable types, you should register their names with `nnx.register_variable_name` so that they go to the desired collections.\n\n```{code-cell} ipython3\n@nnx.register_variable_name('counts', overwrite=True)\nclass Count(nnx.Variable): pass\n\n\nclass NNXMultiCollections(nnx.Module):\n  def __init__(self, din, dout, rngs):\n    self.w = nnx.Param(nnx.initializers.lecun_normal()(rngs.params(), (din, dout)))\n    self.lora = nnx.LoRA(din, 3, dout, rngs=rngs)\n    self.count = Count(jnp.array(0))\n\n  def __call__(self, x):\n    self.count.value += 1\n    return (x @ self.w.value) + self.lora(x)\n\nxkey, pkey, dkey = jax.random.split(jax.random.key(0), 3)\nx = jax.random.normal(xkey, (2, 4))\nmodel = bridge.to_linen(NNXMultiCollections, 4, 3)\nvar = model.init({'params': pkey, 'dropout': dkey}, x)\nprint('All Linen collections:', list(var.keys()))\nprint(var['params'])\n```\n\n    All Linen collections: ['LoRAParam', 'params', 'counts']\n    {'w': Array([[ 0.2916921 ,  0.22780475,  0.06553137],\n           [ 0.17487915, -0.34043145,  0.24764155],\n           [ 0.6420431 ,  0.6220095 , -0.44769976],\n           [ 0.11161668,  0.83873135, -0.7446058 ]], dtype=float32)}\n\n+++\n\n## Partition metadata\n\nFlax uses a metadata wrapper box over the raw JAX array to annotate how a variable should be sharded.\n\nIn Linen, this is an optional feature that triggered by using `nn.with_partitioning` on initializers (see more on [Linen partition metadata guide](https://flax.readthedocs.io/en/latest/guides/parallel_training/flax_on_pjit.html)). In NNX, since all NNX variables are wrapped by `nnx.Variable` class anyway, that class will hold the sharding annotations too.\n\nThe `bridge.ToNNX` and `bridge.ToLinen` API will automatically convert the sharding annotations, if you use the built-in annotation methods (aka. `nn.with_partitioning` for Linen and `nnx.with_partitioning` for NNX).\n\n### Linen to NNX\n\nEven if you are not using any partition metadata in your Linen module, the variable JAX arrays will be converted to `nnx.Variable`s that wraps the true JAX array within.\n\nIf you use `nn.with_partitioning` to annotate your Linen module's variables, the annotation will be converted to a `.sharding` field in the corresponding `nnx.Variable`.\n\nYou can then use `nnx.with_sharding_constraint` to explicitly put the arrays into the annotated partitions within a `jax.jit`-compiled function, to initialize the whole model with every array at the right sharding.\n\n```{code-cell} ipython3\nclass LinenDotWithPartitioning(nn.Module):\n  out_dim: int\n  @nn.compact\n  def __call__(self, x):\n    w = self.param('w', nn.with_partitioning(nn.initializers.lecun_normal(),\n                                             ('in', 'out')),\n                   (x.shape[-1], self.out_dim))\n    return x @ w\n\n@nnx.jit\ndef create_sharded_nnx_module(x):\n  model = bridge.lazy_init(\n    bridge.ToNNX(LinenDotWithPartitioning(64), rngs=nnx.Rngs(0)), x)\n  state = nnx.state(model)\n  sharded_state = nnx.with_sharding_constraint(state, nnx.get_partition_spec(state))\n  nnx.update(model, sharded_state)\n  return model\n\n\nprint(f'We have {len(jax.devices())} fake JAX devices now to partition this model...')\nmesh = jax.sharding.Mesh(devices=mesh_utils.create_device_mesh((2, 4)),\n                         axis_names=('in', 'out'))\nx = jax.random.normal(jax.random.key(42), (4, 32))\nwith jax.set_mesh(mesh):\n  model = create_sharded_nnx_module(x)\n\nprint(type(model.w))           # `nnx.Param`\nprint(model.w.sharding)        # The partition annotation attached with `w`\nprint(model.w.value.sharding)  # The underlying JAX array is sharded across the 2x4 mesh\n```\n\n    We have 8 fake JAX devices now to partition this model...\n    <class 'flax.nnx.variables.Param'>\n    ('in', 'out')\n    GSPMDSharding({devices=[2,4]<=[8]})\n\n+++\n\n### NNX to Linen\n\nIf you are not using any metadata feature of the `nnx.Variable` (i.e., no sharding annotation, no registered hooks), the converted Linen module will not add a metadata wrapper to your NNX variable, and you don't need to worry about it.\n\nBut if you did add sharding annotations to your NNX variables, `ToLinen` will convert them to a default Linen partition metadata class called `bridge.NNXMeta`, retaining all the metadata you put into the NNX variable.\n\nLike with any Linen metadata wrappers, you can use `linen.unbox()` to get the raw JAX array tree.\n\n```{code-cell} ipython3\nclass NNXDotWithParititioning(nnx.Module):\n  def __init__(self, in_dim: int, out_dim: int, rngs: nnx.Rngs):\n    init_fn = nnx.with_partitioning(nnx.initializers.lecun_normal(), ('in', 'out'))\n    self.w = nnx.Param(init_fn(rngs.params(), (in_dim, out_dim)))\n  def __call__(self, x: jax.Array):\n    return x @ self.w\n\nx = jax.random.normal(jax.random.key(42), (4, 32))\n\n@jax.jit\ndef create_sharded_variables(key, x):\n  model = bridge.to_linen(NNXDotWithParititioning, 32, 64)\n  variables = model.init(key, x)\n  # A `NNXMeta` wrapper of the underlying `nnx.Param`\n  assert type(variables['params']['w']) == bridge.NNXMeta\n  # The annotation coming from the `nnx.Param` => (in, out)\n  assert variables['params']['w'].metadata['sharding'] == ('in', 'out')\n\n  unboxed_variables = nn.unbox(variables)\n  variable_pspecs = nn.get_partition_spec(variables)\n  assert isinstance(unboxed_variables['params']['w'], jax.Array)\n  assert variable_pspecs['params']['w'] == jax.sharding.PartitionSpec('in', 'out')\n\n  sharded_vars = jax.tree.map(jax.lax.with_sharding_constraint,\n                              nn.unbox(variables),\n                              nn.get_partition_spec(variables))\n  return sharded_vars\n\nwith jax.set_mesh(mesh):\n  variables = create_sharded_variables(jax.random.key(0), x)\n\n# The underlying JAX array is sharded across the 2x4 mesh\nprint(variables['params']['w'].sharding)\n```\n\n    GSPMDSharding({devices=[2,4]<=[8]})\n\n+++\n\n## Lifted transforms\n\nIn general, if you want to apply Linen/NNX-style lifted transforms upon an `nnx.bridge`-converted module, just go ahead and do it in the usual Linen/NNX syntax.\n\nFor Linen-style transforms, note that `bridge.ToLinen` is the top level module class, so you may want to just use it as the first argument of your transforms (which needs to be a `linen.Module` class in most cases)\n\n### Linen to NNX\n\nNNX style lifted transforms are similar to JAX transforms, and they work on functions.\n\n```{code-cell} ipython3\nclass NNXVmapped(nnx.Module):\n  def __init__(self, out_dim: int, vmap_axis_size: int, rngs: nnx.Rngs):\n    self.linen_dot = nnx.bridge.ToNNX(nn.Dense(out_dim, use_bias=False), rngs=rngs)\n    self.vmap_axis_size = vmap_axis_size\n\n  def __call__(self, x):\n\n    @nnx.split_rngs(splits=self.vmap_axis_size)\n    @nnx.vmap(in_axes=(0, 0), axis_size=self.vmap_axis_size)\n    def vmap_fn(submodule, x):\n      return submodule(x)\n\n    return vmap_fn(self.linen_dot, x)\n\nx = jax.random.normal(jax.random.key(0), (4, 32))\nmodel = bridge.lazy_init(NNXVmapped(64, 4, rngs=nnx.Rngs(0)), x)\n\nprint(model.linen_dot.kernel.shape) # (4, 32, 64) - first axis with dim 4 got vmapped\ny = model(x)\nprint(y.shape)\n```\n\n    (4, 32, 64)\n    (4, 64)\n\n+++\n\n### NNX to Linen\n\nNote that `bridge.ToLinen` is the top level module class, so you may want to just use it as the first argument of your transforms (which needs to be a `linen.Module` class in most cases).\n\n`ToLien` can naturally be used with Linen transforms like `nn.vmap` or `nn.scan`.\n\n```{code-cell} ipython3\nclass LinenVmapped(nn.Module):\n  dout: int\n  @nn.compact\n  def __call__(self, x):\n    inner = nn.vmap(bridge.ToLinen, variable_axes={'params': 0}, split_rngs={'params': True}\n                    )(nnx.Linear, args=(x.shape[-1], self.dout))\n    return inner(x)\n\nx = jax.random.normal(jax.random.key(42), (4, 32))\nmodel = LinenVmapped(64)\nvar = model.init(jax.random.key(0), x)\nprint(var['params']['VmapToLinen_0']['kernel'].shape)  # (4, 32, 64) - leading dim 4 got vmapped\ny = model.apply(var, x)\nprint(y.shape)\n```\n\n    (4, 32, 64)\n    (4, 64)\n"
  },
  {
    "path": "docs_nnx/guides/checkpointing.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Save and load checkpoints\\n\",\n    \"\\n\",\n    \"This guide demonstrates how to save and load Flax NNX model checkpoints with [Orbax](https://orbax.readthedocs.io/).\\n\",\n    \"\\n\",\n    \"> **Note:** The Flax team does not actively maintain a library for saving and loading model checkpoints to disk. Therefore, it is recommended you use external libraries like [Orbax](https://orbax.readthedocs.io/en/latest/index.html) to do it.\\n\",\n    \"\\n\",\n    \"In this guide you will learn how to:\\n\",\n    \"\\n\",\n    \"* Save checkpoints.\\n\",\n    \"* Restore checkpoints.\\n\",\n    \"* Restore checkpoints if checkpoint structures differ. \\n\",\n    \"* Perform multi-process checkpointing. \\n\",\n    \"\\n\",\n    \"The Orbax API examples used throughout the guide are for demonstration purposes, and for the most up-to-date recommended APIs refer to the [Orbax website](https://orbax.readthedocs.io/).\\n\",\n    \"\\n\",\n    \"> **Note:** The Flax team recommends using [Orbax](https://orbax.readthedocs.io/en/latest/index.html) for saving and loading checkpoints to disk, as we do not actively maintain a library for these functionalities.\\n\",\n    \"\\n\",\n    \"> **Note:** If you are looking for Flax Linen's legacy `flax.training.checkpoints` package, it was deprecated in 2023 in favor of Orbax. The documentation resides on the [Flax Linen site](https://flax-linen.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html).\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Setup\\n\",\n    \"\\n\",\n    \"Import the necessary dependencies, set up a checkpoint directory and an example Flax NNX model - `TwoLayerMLP` - by subclassing [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from flax import nnx\\n\",\n    \"import orbax.checkpoint as ocp\\n\",\n    \"import jax\\n\",\n    \"from jax import numpy as jnp\\n\",\n    \"import numpy as np\\n\",\n    \"\\n\",\n    \"ckpt_dir = ocp.test_utils.erase_and_create_empty('/tmp/my-checkpoints/')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class TwoLayerMLP(nnx.Module):\\n\",\n    \"  def __init__(self, dim, rngs: nnx.Rngs):\\n\",\n    \"    self.linear1 = nnx.Linear(dim, dim, rngs=rngs, use_bias=False)\\n\",\n    \"    self.linear2 = nnx.Linear(dim, dim, rngs=rngs, use_bias=False)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x = self.linear1(x)\\n\",\n    \"    return self.linear2(x)\\n\",\n    \"\\n\",\n    \"# Instantiate the model and show we can run it.\\n\",\n    \"model = TwoLayerMLP(4, rngs=nnx.Rngs(0))\\n\",\n    \"x = jax.random.normal(jax.random.key(42), (3, 4))\\n\",\n    \"assert model(x).shape == (3, 4)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Save checkpoints\\n\",\n    \"\\n\",\n    \"JAX checkpointing libraries, such as Orbax, can save and load any given JAX [pytree](https://jax.readthedocs.io/en/latest/pytrees.html), which is a pure, possibly nested container of [`jax.Array`s)](https://jax.readthedocs.io/en/latest/key-concepts.html#jax-arrays-jax-array) (or, \\\"tensors\\\" as some other frameworks would put it). In the context of machine learning, the checkpoint is usually a pytree of model parameters and other data, such as optimizer states.\\n\",\n    \"\\n\",\n    \"In Flax NNX, you can obtain such a pytree from an [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html) by calling [`nnx.split`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.split), and picking up the returned [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_4f82ceeefe1c4ecd9cc4f9e1f20d1deb\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_4f82ceeefe1c4ecd9cc4f9e1f20d1deb\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNrtWY1X2sgW/1em6XkLPCWG8CWonBeQL1u1iq3W3T3skEzCSJjEZABxj//7u5OE72ht67bd3eI5ApM79/veub9h3+dTm1Rk7hHi645Lup7jcPQnch2fcuqwMvKIjTkdkz1kOoynTTyk9rSMhg5zfBfrsD7pU07SwZcycj1YsanP0wHrNJ+6sMocBss9rA8szxkxI607tuOVw617KPrWs4EA+FGD98vIpBzIGCeM76EhZeloPaMo/wFezl3ap/eUWbDP8QzipWFpD7nYMGAxbROTl5Gq94U2jKT7hFp9WMnIeSGPcUzBuDn/6EN6TH3aozblYCIecWdOm6aMe5T5VBdiSfg0suthfyf04/7cj2lvxECmB2u+7lGXI+GIgwR2XZvqWLh2x9E5EW7yCB4mKslk6qACngd5PkcGMZmPDhDvU1+2CD+HsJw4Bkmm5L7jczl4DqYRjrouYcJkTRdcxaZff4970sLMsAk8ZiPb3gslyKBmx3EYrCYnjjdIoWUdnEtYEo9WljnVxaJLPNPxhpjpRGbOJJkKEgEEJDeeoHS4aR9l1RTwoSZKrmkt24RZvI8ODpAiSJ5U3SN85DHwOyK2TxaK9UdMaLbO2u9Tkwv9AgLx4QH+HpGQhPRjhjORPXI7Ij7XGB0G4Wp4eEiSoU9SgsfehiB35PdDN+7F2DgTcRCa8YSVz9dBaBEGkjuWZYfl2w1KDLLVFbzECrH5NiJjSPAokkK74Ls8IFPhdMmThEIRsazb2PffQhVHfJPSnGd3CGkozYQ/pMCfkP5Bjlf2d+IKwKBjFDA8kFb7jIQ47oGl5O5AUiQoXY9vkjgMVARnMHj0VDHEeyAp9sxsl6AYw34XNJwu7vU8Mg7yJ+g/rwu7KlYUsCoi0J3hEDYuUeDgJYxfI8Fl5vBkue+MiZeKoY/IfdFErGWG2Uw+kxcEYAXxPGJ0XWiDpO/Y0NOWCYviL+rCgRllRDmGbiI2m0COe2A+A7880r9B5xWybugxoDaoD0Knsz69TogqyMY9YpfLPQKVTZa00oPXXqy8sAWnM6IHR70bfDuXRVnQmHu2I5r+ozIDj25KNrA38Am2IFvY5u4gGGtLfewnKwHPSqwfwgDqfaIPiJFKof+mFjqIrfGbZvQrGgZnSRklflPzPT3xPdVb3fSokoVvoKSIoxA88nwRQNeBE5V4MXKp/3Jig1IIBKWDHuA/luMvI3VhHid3fFOKTP2uST2fdx3WFekfU1pPlZKs5kU1xYYKfbX6YcTXVRRWDbFnwQwUqhEU9MNXSoN+6E57I85h+IhrQIvHcUkrIWmNChwJE2g88W8kkzOktQE2cYwhKyi2UWc67Dm2j05HXNhroFq4E97dKRRGekJ6AxhGw847hPOlH4ydmHHYTrFPjPkI+5oo4m9vM83D3cHoqMglMly3MqyPGCvi291ipzzBfleHcwAcO9+PTb5yesz69FMy1/asilx2PRpjL5lOG5jjNGYQ2GA4SS0vCyFi4vIwm2VzwBZlfETAYzBOp50R/zxT5hpAYCgxXq1qEohEr+jQdTyO2QbvnucMCOuKlUUz+rR3l7Yt+XMW5gdZzC+gmNHVYfo1PMIiVVfxBfBcJXyh7rpUOtFBulKqOrb1JIAgmMAz7l0wtMk+x2L/XN+/TJMIloWaGA4H24UWy867HWGbwUzbBdho0jtgslImu0GZwACPxVw0wR6DwuvOGvssFqaJ9Uw2htCFCfjPOSL0IgAoulfkpGgprchBW13g03IAGrGXtjxsUAhbEmWyeYNY28iBlLYIUkC9gt7fDlMcRlfRMIIlFLl5Q5eNzvoybRttNOiZPQ/ypA+pK8bcuB4rjjxoxLE04XC2RAVSH2MUcoBo2NiFXvjpefLzD4vHJSwUDYnIHTQN41Gal9AjTsSKKxb4K84VqwBHXgVW6AkO66ayFagXT/gcYY/yecHbDQE40SvN8/BUNj1nCCBWHwngJIuC9+UxtgHpJlMp2XcA4gZtQEBV8S6HR7aAqc88tKUEFEVqfjHg9wnh4vaATFCt0+kIazpiTdwFBA8BaQeoqzNlevKP/0WDgk5mDenzh4ZlmMbEbYgdrU2ii6icAJq+p5fRyLOT4gQri+c7E8c01b0enJGF3LahlJrHllbVglf7TNOc4FP1fAL/Ww1Nq2tPvapDTbMGzhujXa/WJh817eJj7Ug7bldrWsO6a7fe9rlfPabEyjYOr9S37cLHcccd0XfH+YvM0VX7/MPx+PL4nr+bNhq1rUtrcEGrh0qfHp6NjupG80Zp9XbMcdtwb98U+reXlJ6Njlmz3zLfc+19oXri5bRGmw3qBf39aMS2zvO3uj+YjM2GvXN7Z9WdXat3NGnuZlraDtPO82897yhzvmXdK+eGoh2ZGeukWJs0b1RLcaaj82JxWM8UJq2r0qllueRiMM2Rdu8+r/e80ybHmnXWPpkcYn/qn43a7avLemOivTtz2x+N9zs7W1bxoniV5Yr55t2tNs4Dz7faSVE7nmhD6/68szW67pD61Z1qFvT7k9x5a5ofVbU399Ubt+FmaeusVleuR+9ynSIzq2/rrcbxUKNbu+O62meZfnGr92FydTNpeePD5vsauzHrdYtvnerXtl3Ml2pHk+puv5Q7Pm52ss1rzRq28zfVsxK/aJJWqV6ttpvZQyt3vvNRn/a0JsT0w5sd7ayJNXJcs7XWff3UuuZWofrOOj1tH1YH9CxPGtWrWrWhU8Xte47LIDfc6/ph5j4z6Jg1k/enb1jLwA2/ZSonw2b9pFA1tNsPH1zM/c710DAwLanmfSn3nt7cFtyhVzh1PtY61GsOx0fNbOeyk23UVb16Zl5stWzHbeYa/iSPrdvCLr0mnRPbvWTVVpsYxx4ZXd42a8PMZcMbdDp3ebVweelPNNAohYKbQJ5MBGmdEEfjH/BvXv3YcFyYCxYlGdxfyrL8BMV2WLO/A6+nb4T6wYVaMLqFUyXwhvRgOkqGw93qdSeU4IUjyhfIouFPrPnQHgQLMc2KERBPMOWI4TG1MHc8GTi7PQd7hjzxKCcXAPySC15gbMRrcacGA0lSWhp1xW0aSLmgQwIzcXJ23bqxzyNDmFs3tj5sI1VRlGDsgOYLE0gyAG3xcpfmWWmhnICrsw4mLiAl9Bo1MLWhsXEHCeJXQWeDQYPBzAbdmILPCDbEOL617LvoZvATd4JitJ9dCq7e+6wPSVJlPzyh9ylzR9FJIwVncs+5k2KZRMc3PAyPblAi2Lwqd/WklSq/2FyoDRRP0608XJtgpYpp4zuZsTsxZ3Ni05484xm+dcRy8s/9nciqJWaJ2ZCVWF0WS117bK8uSxvIQ3rq+drD5etIMP31nVrcC2feTPglUreMHo/V14RmHpJ4TywAWAI5rCaS/SDxmdUe3P2mEmiODg+kXxORjYnfJRSczgfSEnwso19uRw7fWyILF/bQBtyFYSDId+inffgcubeyGuwfKboD4jFif9vgfst6G2MYzECTzZJ7hz08TH6nPPs1ETr+2Rm32PByuSetAJfYZIl+yJAq0PkzBZQs5FA19d2yOcADB89IC2ocSPNfTkw9a2Z7u/lSLpPJKbt5XCgoekYxYZTOkpK6bnjcLy5RWsZbegOpFmAYFOeROZAOCGckpu1gnlWTuW2US/1iLVL+E8FaQWWx1haKWDVVvKtk9EyuoCslRclnMwWzmNNzWC3pUuWvru6YivqhnPrpQtj+3GJ5qjl9024SQubP7inhthfsLOvdA9rHB+pDl6b3AUfUpwbUNsyLaHHJIG4fvqgA/mWOfv7bIzXzVKgeO3+XfxyXKoDFnhH2zRpMPX32b9bsC1bpP/pY/57JkEGAcIdfmhIP0fv2Zhznd7vQQX68YH8brPB4OXwhklP/BUhOfV501J9I7ieS+8o8+7yWr/5Ecl+B5IiuFIy8ulvcLRZzRZWUstlizlALar6kArBT/1lILkswINc8MbJKKZfdJT1DJ4XsbiZbVBQFb4T5J5L7uyM59csAhvoTyf1dHf0Tyf2QSO67Hut/fyT3Nwv1t0EK3zOsahBW/4vj+pK1Pqc06Ljyf6THF/o=</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_4f82ceeefe1c4ecd9cc4f9e1f20d1deb\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_4f82ceeefe1c4ecd9cc4f9e1f20d1deb\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"_, state = nnx.split(model)\\n\",\n    \"nnx.display(state)\\n\",\n    \"\\n\",\n    \"checkpointer = ocp.StandardCheckpointer()\\n\",\n    \"checkpointer.save(ckpt_dir / 'state', state)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_5ecb69e45849446495cac4e3367e5c4f\\\" ></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5ecb69e45849446495cac4e3367e5c4f\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5ecb69e45849446495cac4e3367e5c4f\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5ecb69e45849446495cac4e3367e5c4f\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"## Restore checkpoints\\n\",\n    \"\\n\",\n    \"Note that you saved the checkpoint as a Flax class of [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State), which is also nested with [`nnx.Variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Variable) and [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param) classes.\\n\",\n    \"\\n\",\n    \"At checkpoint restoration time, you need to have these classes ready in your runtime, and instruct the checkpointing library (Orbax) to restore your pytree back to that structure. This can be achieved as follows:\\n\",\n    \"- First, create an abstract Flax NNX model (without allocating any memory for arrays), and show its abstract variable state to the checkpointing library.\\n\",\n    \"- Once you have the state, use [`nnx.merge`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.merge) to obtain your Flax NNX model, and use it as usual.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"The abstract NNX state (all leaves are abstract arrays):\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_0a649299f88b4449b5c992f7be3625cf\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_0a649299f88b4449b5c992f7be3625cf\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_0a649299f88b4449b5c992f7be3625cf\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"NNX State restored: \\n\"\n     ]\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"/Users/cgarciae/repos/flax/.venv/lib/python3.11/site-packages/orbax/checkpoint/_src/serialization/type_handlers.py:1251: UserWarning: Sharding info not provided when restoring. Populating sharding info from sharding file. Please note restoration time will be slightly increased due to reading from file. Note also that this option is unsafe when restoring on a different topology than the checkpoint was saved with.\\n\",\n      \"  warnings.warn(\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_a0dfa5fe30174a9c9b95b75954d2484b\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_a0dfa5fe30174a9c9b95b75954d2484b\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_a0dfa5fe30174a9c9b95b75954d2484b\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_a0dfa5fe30174a9c9b95b75954d2484b\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Restore the checkpoint back to its `nnx.State` structure - need an abstract reference.\\n\",\n    \"abstract_model = nnx.eval_shape(lambda: TwoLayerMLP(4, rngs=nnx.Rngs(0)))\\n\",\n    \"graphdef, abstract_state = nnx.split(abstract_model)\\n\",\n    \"print('The abstract NNX state (all leaves are abstract arrays):')\\n\",\n    \"nnx.display(abstract_state)\\n\",\n    \"\\n\",\n    \"state_restored = checkpointer.restore(ckpt_dir / 'state', abstract_state)\\n\",\n    \"jax.tree.map(np.testing.assert_array_equal, state, state_restored)\\n\",\n    \"print('NNX State restored: ')\\n\",\n    \"nnx.display(state_restored)\\n\",\n    \"\\n\",\n    \"# The model is now good to use!\\n\",\n    \"model = nnx.merge(graphdef, state_restored)\\n\",\n    \"assert model(x).shape == (3, 4)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"    The abstract NNX state (all leaves are abstract arrays):\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_5eb8fb6116ef450e8276128216cc9262\\\" ></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5eb8fb6116ef450e8276128216cc9262\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5eb8fb6116ef450e8276128216cc9262\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"    NNX State restored: \\n\",\n    \"\\n\",\n    \"\\n\",\n    \"    /Users/ivyzheng/envs/flax-head/lib/python3.11/site-packages/orbax/checkpoint/type_handlers.py:1439: UserWarning: Couldn't find sharding info under RestoreArgs. Populating sharding info from sharding file. Please note restoration time will be slightly increased due to reading from file instead of directly from RestoreArgs. Note also that this option is unsafe when restoring on a different topology than the checkpoint was saved with.\\n\",\n    \"      warnings.warn(\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_16a3675ef842438cb85db5d1411974db\\\" ></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_16a3675ef842438cb85db5d1411974db\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_16a3675ef842438cb85db5d1411974db\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_16a3675ef842438cb85db5d1411974db\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"## Save and restore as pure dictionaries\\n\",\n    \"\\n\",\n    \"When interacting with checkpoint libraries (like Orbax), you may prefer to work with Python built-in container types. In this case, you can use the [`nnx.State.to_pure_dict`](https://github.com/google/flax/blob/764e1732dcd3b8bf178b9ba73ddecf125709b5d7/flax/nnx/statelib.py#L170) and [`nnx.State.replace_by_pure_dict`](https://github.com/google/flax/blob/764e1732dcd3b8bf178b9ba73ddecf125709b5d7/flax/nnx/statelib.py#L179) API to convert an [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State) to and from pure nested dictionaries.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_f7edccc33c5b43588628e4ee5c70feb6\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_f7edccc33c5b43588628e4ee5c70feb6\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_f7edccc33c5b43588628e4ee5c70feb6\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_f7edccc33c5b43588628e4ee5c70feb6\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:absl:`StandardCheckpointHandler` expects a target tree to be provided for restore. Not doing so is generally UNSAFE unless you know the present topology to be the same one as the checkpoint was saved under.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Save as pure dict\\n\",\n    \"pure_dict_state = nnx.to_pure_dict(state)\\n\",\n    \"nnx.display(pure_dict_state)\\n\",\n    \"checkpointer.save(ckpt_dir / 'pure_dict', pure_dict_state)\\n\",\n    \"\\n\",\n    \"# Restore as a pure dictionary.\\n\",\n    \"restored_pure_dict = checkpointer.restore(ckpt_dir / 'pure_dict')\\n\",\n    \"abstract_model = nnx.eval_shape(lambda: TwoLayerMLP(4, rngs=nnx.Rngs(0)))\\n\",\n    \"graphdef, abstract_state = nnx.split(abstract_model)\\n\",\n    \"nnx.replace_by_pure_dict(abstract_state, restored_pure_dict)\\n\",\n    \"model = nnx.merge(graphdef, abstract_state)\\n\",\n    \"assert model(x).shape == (3, 4)  # The model still works!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_277cbde407034bf9addfa8e4969bce7e\\\" ></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_277cbde407034bf9addfa8e4969bce7e\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_277cbde407034bf9addfa8e4969bce7e\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_277cbde407034bf9addfa8e4969bce7e\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"    WARNING:absl:`StandardCheckpointHandler` expects a target tree to be provided for restore. Not doing so is generally UNSAFE unless you know the present topology to be the same one as the checkpoint was saved under.\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"## Restore when checkpoint structures differ\\n\",\n    \"\\n\",\n    \"The ability to load a checkpoint as a pure nested dictionary can come in handy when you want to load some outdated checkpoints that no longer match with your current model code. Check out this simple example below.\\n\",\n    \"\\n\",\n    \"This pattern also works if you save the checkpoint as an [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State) instead of a pure dictionary. Check out the [Checkpoint surgery section](https://flax.readthedocs.io/en/latest/guides/surgery.html#checkpoint-surgery) of the [Model Surgery](https://flax.readthedocs.io/en/latest/guides/surgery.html) guide for an example with code. The only difference is you need to reprocess your raw dictionary a bit before calling [`nnx.State.replace_by_pure_dict`](https://github.com/google/flax/blob/764e1732dcd3b8bf178b9ba73ddecf125709b5d7/flax/nnx/statelib.py#L179).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:absl:`StandardCheckpointHandler` expects a target tree to be provided for restore. Not doing so is generally UNSAFE unless you know the present topology to be the same one as the checkpoint was saved under.\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_fdd68f8c1cf8422aae61f18e8de4a99e\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_fdd68f8c1cf8422aae61f18e8de4a99e\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_fdd68f8c1cf8422aae61f18e8de4a99e\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_fdd68f8c1cf8422aae61f18e8de4a99e\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"class ModifiedTwoLayerMLP(nnx.Module):\\n\",\n    \"  \\\"\\\"\\\"A modified version of TwoLayerMLP, which requires bias arrays.\\\"\\\"\\\"\\n\",\n    \"  def __init__(self, dim, rngs: nnx.Rngs):\\n\",\n    \"    self.linear1 = nnx.Linear(dim, dim, rngs=rngs, use_bias=True)  # We need bias now!\\n\",\n    \"    self.linear2 = nnx.Linear(dim, dim, rngs=rngs, use_bias=True)  # We need bias now!\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x = self.linear1(x)\\n\",\n    \"    return self.linear2(x)\\n\",\n    \"\\n\",\n    \"# Accommodate your old checkpoint to the new code.\\n\",\n    \"restored_pure_dict = checkpointer.restore(ckpt_dir / 'pure_dict')\\n\",\n    \"restored_pure_dict['linear1']['bias'] = jnp.zeros((4,))\\n\",\n    \"restored_pure_dict['linear2']['bias'] = jnp.zeros((4,))\\n\",\n    \"\\n\",\n    \"# Same restore code as above.\\n\",\n    \"abstract_model = nnx.eval_shape(lambda: ModifiedTwoLayerMLP(4, rngs=nnx.Rngs(0)))\\n\",\n    \"graphdef, abstract_state = nnx.split(abstract_model)\\n\",\n    \"nnx.replace_by_pure_dict(abstract_state, restored_pure_dict)\\n\",\n    \"model = nnx.merge(graphdef, abstract_state)\\n\",\n    \"assert model(x).shape == (3, 4)  # The new model works!\\n\",\n    \"\\n\",\n    \"nnx.display(model.linear1)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"    WARNING:absl:`StandardCheckpointHandler` expects a target tree to be provided for restore. Not doing so is generally UNSAFE unless you know the present topology to be the same one as the checkpoint was saved under.\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_9572198988a54a0b9731fab9e0c97e51\\\" ></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_9572198988a54a0b9731fab9e0c97e51\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_9572198988a54a0b9731fab9e0c97e51\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_9572198988a54a0b9731fab9e0c97e51\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"## Multi-process checkpointing\\n\",\n    \"\\n\",\n    \"In a multi-host/multi-process environment, you would want to restore your checkpoint as sharded across multiple devices. Check out the [Load sharded model from a checkpoint](https://flax.readthedocs.io/en/latest/guides/flax_gspmd.html#load-sharded-model-from-a-checkpoint) section in the Flax [Scale up on multiple devices](https://flax.readthedocs.io/en/latest/guides/flax_gspmd.html) guide to learn how to derive a sharding pytree and use it to load your checkpoint.\\n\",\n    \"\\n\",\n    \"> **Note:** JAX provides several ways to scale up your code on multiple hosts at the same time. This usually happens when the number of devices (CPU/GPU/TPU) is so large that different devices are managed by different hosts (CPU). Check out JAX’s [Introduction to parallel programming](https://jax.readthedocs.io/en/latest/sharded-computation.html), [Using JAX in multi-host and multi-process environments](https://jax.readthedocs.io/en/latest/multi_process.html), [Distributed arrays and automatic parallelization](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html), and [Manual parallelism with `shard_map`](https://jax.readthedocs.io/en/latest/notebooks/shard_map.html).\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Other checkpointing features\\n\",\n    \"\\n\",\n    \"This guide only uses the simplest [`orbax.checkpoint.StandardCheckpointer`](https://orbax.readthedocs.io/en/latest/api_reference/checkpoint.checkpointers.html#standardcheckpointer) API to show how to save and load on the Flax modeling side. Feel free to use other tools or libraries as you see fit.\\n\",\n    \"\\n\",\n    \"In addition, check out [the Orbax website](https://orbax.readthedocs.io/en/latest/index.html) for other commonly used features, such as:\\n\",\n    \"\\n\",\n    \"* [`CheckpointManager`](https://orbax.readthedocs.io/en/latest/guides/checkpoint/api_refactor.html) to track checkpoints from different steps.\\n\",\n    \"\\n\",\n    \"* [Asynchronous checkpointing](https://orbax.readthedocs.io/en/latest/guides/checkpoint/async_checkpointing.html).\\n\",\n    \"\\n\",\n    \"* [Orbax transformations](https://orbax.readthedocs.io/en/latest/guides/checkpoint/transformations.html): A way to modify pytree structure during loading time, instead of after loading time, which is demonstrated in this guide.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.9\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "docs_nnx/guides/checkpointing.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Save and load checkpoints\n\nThis guide demonstrates how to save and load Flax NNX model checkpoints with [Orbax](https://orbax.readthedocs.io/).\n\n> **Note:** The Flax team does not actively maintain a library for saving and loading model checkpoints to disk. Therefore, it is recommended you use external libraries like [Orbax](https://orbax.readthedocs.io/en/latest/index.html) to do it.\n\nIn this guide you will learn how to:\n\n* Save checkpoints.\n* Restore checkpoints.\n* Restore checkpoints if checkpoint structures differ. \n* Perform multi-process checkpointing. \n\nThe Orbax API examples used throughout the guide are for demonstration purposes, and for the most up-to-date recommended APIs refer to the [Orbax website](https://orbax.readthedocs.io/).\n\n> **Note:** The Flax team recommends using [Orbax](https://orbax.readthedocs.io/en/latest/index.html) for saving and loading checkpoints to disk, as we do not actively maintain a library for these functionalities.\n\n> **Note:** If you are looking for Flax Linen's legacy `flax.training.checkpoints` package, it was deprecated in 2023 in favor of Orbax. The documentation resides on the [Flax Linen site](https://flax-linen.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html).\n\n+++\n\n### Setup\n\nImport the necessary dependencies, set up a checkpoint directory and an example Flax NNX model - `TwoLayerMLP` - by subclassing [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html).\n\n```{code-cell} ipython3\nfrom flax import nnx\nimport orbax.checkpoint as ocp\nimport jax\nfrom jax import numpy as jnp\nimport numpy as np\n\nckpt_dir = ocp.test_utils.erase_and_create_empty('/tmp/my-checkpoints/')\n```\n\n```{code-cell} ipython3\nclass TwoLayerMLP(nnx.Module):\n  def __init__(self, dim, rngs: nnx.Rngs):\n    self.linear1 = nnx.Linear(dim, dim, rngs=rngs, use_bias=False)\n    self.linear2 = nnx.Linear(dim, dim, rngs=rngs, use_bias=False)\n\n  def __call__(self, x):\n    x = self.linear1(x)\n    return self.linear2(x)\n\n# Instantiate the model and show we can run it.\nmodel = TwoLayerMLP(4, rngs=nnx.Rngs(0))\nx = jax.random.normal(jax.random.key(42), (3, 4))\nassert model(x).shape == (3, 4)\n```\n\n## Save checkpoints\n\nJAX checkpointing libraries, such as Orbax, can save and load any given JAX [pytree](https://jax.readthedocs.io/en/latest/pytrees.html), which is a pure, possibly nested container of [`jax.Array`s)](https://jax.readthedocs.io/en/latest/key-concepts.html#jax-arrays-jax-array) (or, \"tensors\" as some other frameworks would put it). In the context of machine learning, the checkpoint is usually a pytree of model parameters and other data, such as optimizer states.\n\nIn Flax NNX, you can obtain such a pytree from an [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html) by calling [`nnx.split`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.split), and picking up the returned [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State).\n\n```{code-cell} ipython3\n_, state = nnx.split(model)\nnnx.display(state)\n\ncheckpointer = ocp.StandardCheckpointer()\ncheckpointer.save(ckpt_dir / 'state', state)\n```\n\n<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \"open\"}); this.defns = {}; this.state = {}; } } customElements.define(\"treescope-container\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \"\"; fn.call(this); this.remove(); }; const child = this.querySelector(\"script\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\"script\")); }).observe(this, {childList: true}); } } } customElements.define(\"treescope-run-here\", RunHere); } })(); </script> <treescope-container class=\"treescope_out_5ecb69e45849446495cac4e3367e5c4f\" ></treescope-container> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_5ecb69e45849446495cac4e3367e5c4f\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\"span\"); msg.style = \"color: #cccccc; font-family: monospace;\"; msg.textContent = \"(Loading...)\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \"1000px\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\"script\")) { let newScript = document.createElement(\"script\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\"deflate\") ).pipeThrough( new TextDecoderStream(\"utf-8\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\"\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\"display:none\"> <script type=\"application/octet-stream\" >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</script> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_5ecb69e45849446495cac4e3367e5c4f\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\"application/octet-stream\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\n\n\n\n<div style=\"display:none\"> <script type=\"application/octet-stream\" >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</script> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_5ecb69e45849446495cac4e3367e5c4f\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\"application/octet-stream\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\n\n\n## Restore checkpoints\n\nNote that you saved the checkpoint as a Flax class of [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State), which is also nested with [`nnx.Variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Variable) and [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param) classes.\n\nAt checkpoint restoration time, you need to have these classes ready in your runtime, and instruct the checkpointing library (Orbax) to restore your pytree back to that structure. This can be achieved as follows:\n- First, create an abstract Flax NNX model (without allocating any memory for arrays), and show its abstract variable state to the checkpointing library.\n- Once you have the state, use [`nnx.merge`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.merge) to obtain your Flax NNX model, and use it as usual.\n\n```{code-cell} ipython3\n# Restore the checkpoint back to its `nnx.State` structure - need an abstract reference.\nabstract_model = nnx.eval_shape(lambda: TwoLayerMLP(4, rngs=nnx.Rngs(0)))\ngraphdef, abstract_state = nnx.split(abstract_model)\nprint('The abstract NNX state (all leaves are abstract arrays):')\nnnx.display(abstract_state)\n\nstate_restored = checkpointer.restore(ckpt_dir / 'state', abstract_state)\njax.tree.map(np.testing.assert_array_equal, state, state_restored)\nprint('NNX State restored: ')\nnnx.display(state_restored)\n\n# The model is now good to use!\nmodel = nnx.merge(graphdef, state_restored)\nassert model(x).shape == (3, 4)\n```\n\n    The abstract NNX state (all leaves are abstract arrays):\n\n\n\n<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \"open\"}); this.defns = {}; this.state = {}; } } customElements.define(\"treescope-container\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \"\"; fn.call(this); this.remove(); }; const child = this.querySelector(\"script\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\"script\")); }).observe(this, {childList: true}); } } } customElements.define(\"treescope-run-here\", RunHere); } })(); </script> <treescope-container class=\"treescope_out_5eb8fb6116ef450e8276128216cc9262\" ></treescope-container> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_5eb8fb6116ef450e8276128216cc9262\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\"span\"); msg.style = \"color: #cccccc; font-family: monospace;\"; msg.textContent = \"(Loading...)\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \"1000px\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\"script\")) { let newScript = document.createElement(\"script\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\"deflate\") ).pipeThrough( new TextDecoderStream(\"utf-8\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\"\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\"display:none\"> <script type=\"application/octet-stream\" >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</script> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_5eb8fb6116ef450e8276128216cc9262\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\"application/octet-stream\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\n\n\n    NNX State restored: \n\n\n    /Users/ivyzheng/envs/flax-head/lib/python3.11/site-packages/orbax/checkpoint/type_handlers.py:1439: UserWarning: Couldn't find sharding info under RestoreArgs. Populating sharding info from sharding file. Please note restoration time will be slightly increased due to reading from file instead of directly from RestoreArgs. Note also that this option is unsafe when restoring on a different topology than the checkpoint was saved with.\n      warnings.warn(\n\n\n\n<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \"open\"}); this.defns = {}; this.state = {}; } } customElements.define(\"treescope-container\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \"\"; fn.call(this); this.remove(); }; const child = this.querySelector(\"script\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\"script\")); }).observe(this, {childList: true}); } } } customElements.define(\"treescope-run-here\", RunHere); } })(); </script> <treescope-container class=\"treescope_out_16a3675ef842438cb85db5d1411974db\" ></treescope-container> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_16a3675ef842438cb85db5d1411974db\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\"span\"); msg.style = \"color: #cccccc; font-family: monospace;\"; msg.textContent = \"(Loading...)\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \"1000px\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\"script\")) { let newScript = document.createElement(\"script\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\"deflate\") ).pipeThrough( new TextDecoderStream(\"utf-8\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\"\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\"display:none\"> <script type=\"application/octet-stream\" >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</script> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_16a3675ef842438cb85db5d1411974db\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\"application/octet-stream\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\n\n\n\n<div style=\"display:none\"> <script type=\"application/octet-stream\" >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</script> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_16a3675ef842438cb85db5d1411974db\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\"application/octet-stream\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\n\n\n## Save and restore as pure dictionaries\n\nWhen interacting with checkpoint libraries (like Orbax), you may prefer to work with Python built-in container types. In this case, you can use the [`nnx.State.to_pure_dict`](https://github.com/google/flax/blob/764e1732dcd3b8bf178b9ba73ddecf125709b5d7/flax/nnx/statelib.py#L170) and [`nnx.State.replace_by_pure_dict`](https://github.com/google/flax/blob/764e1732dcd3b8bf178b9ba73ddecf125709b5d7/flax/nnx/statelib.py#L179) API to convert an [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State) to and from pure nested dictionaries.\n\n```{code-cell} ipython3\n# Save as pure dict\npure_dict_state = nnx.to_pure_dict(state)\nnnx.display(pure_dict_state)\ncheckpointer.save(ckpt_dir / 'pure_dict', pure_dict_state)\n\n# Restore as a pure dictionary.\nrestored_pure_dict = checkpointer.restore(ckpt_dir / 'pure_dict')\nabstract_model = nnx.eval_shape(lambda: TwoLayerMLP(4, rngs=nnx.Rngs(0)))\ngraphdef, abstract_state = nnx.split(abstract_model)\nnnx.replace_by_pure_dict(abstract_state, restored_pure_dict)\nmodel = nnx.merge(graphdef, abstract_state)\nassert model(x).shape == (3, 4)  # The model still works!\n```\n\n<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \"open\"}); this.defns = {}; this.state = {}; } } customElements.define(\"treescope-container\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \"\"; fn.call(this); this.remove(); }; const child = this.querySelector(\"script\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\"script\")); }).observe(this, {childList: true}); } } } customElements.define(\"treescope-run-here\", RunHere); } })(); </script> <treescope-container class=\"treescope_out_277cbde407034bf9addfa8e4969bce7e\" ></treescope-container> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_277cbde407034bf9addfa8e4969bce7e\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\"span\"); msg.style = \"color: #cccccc; font-family: monospace;\"; msg.textContent = \"(Loading...)\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \"1000px\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\"script\")) { let newScript = document.createElement(\"script\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\"deflate\") ).pipeThrough( new TextDecoderStream(\"utf-8\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\"\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\"display:none\"> <script type=\"application/octet-stream\" >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</script> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_277cbde407034bf9addfa8e4969bce7e\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\"application/octet-stream\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\n\n\n\n<div style=\"display:none\"> <script type=\"application/octet-stream\" >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</script> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_277cbde407034bf9addfa8e4969bce7e\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\"application/octet-stream\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\n\n\n    WARNING:absl:`StandardCheckpointHandler` expects a target tree to be provided for restore. Not doing so is generally UNSAFE unless you know the present topology to be the same one as the checkpoint was saved under.\n\n\n## Restore when checkpoint structures differ\n\nThe ability to load a checkpoint as a pure nested dictionary can come in handy when you want to load some outdated checkpoints that no longer match with your current model code. Check out this simple example below.\n\nThis pattern also works if you save the checkpoint as an [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State) instead of a pure dictionary. Check out the [Checkpoint surgery section](https://flax.readthedocs.io/en/latest/guides/surgery.html#checkpoint-surgery) of the [Model Surgery](https://flax.readthedocs.io/en/latest/guides/surgery.html) guide for an example with code. The only difference is you need to reprocess your raw dictionary a bit before calling [`nnx.State.replace_by_pure_dict`](https://github.com/google/flax/blob/764e1732dcd3b8bf178b9ba73ddecf125709b5d7/flax/nnx/statelib.py#L179).\n\n```{code-cell} ipython3\nclass ModifiedTwoLayerMLP(nnx.Module):\n  \"\"\"A modified version of TwoLayerMLP, which requires bias arrays.\"\"\"\n  def __init__(self, dim, rngs: nnx.Rngs):\n    self.linear1 = nnx.Linear(dim, dim, rngs=rngs, use_bias=True)  # We need bias now!\n    self.linear2 = nnx.Linear(dim, dim, rngs=rngs, use_bias=True)  # We need bias now!\n\n  def __call__(self, x):\n    x = self.linear1(x)\n    return self.linear2(x)\n\n# Accommodate your old checkpoint to the new code.\nrestored_pure_dict = checkpointer.restore(ckpt_dir / 'pure_dict')\nrestored_pure_dict['linear1']['bias'] = jnp.zeros((4,))\nrestored_pure_dict['linear2']['bias'] = jnp.zeros((4,))\n\n# Same restore code as above.\nabstract_model = nnx.eval_shape(lambda: ModifiedTwoLayerMLP(4, rngs=nnx.Rngs(0)))\ngraphdef, abstract_state = nnx.split(abstract_model)\nnnx.replace_by_pure_dict(abstract_state, restored_pure_dict)\nmodel = nnx.merge(graphdef, abstract_state)\nassert model(x).shape == (3, 4)  # The new model works!\n\nnnx.display(model.linear1)\n```\n\n    WARNING:absl:`StandardCheckpointHandler` expects a target tree to be provided for restore. Not doing so is generally UNSAFE unless you know the present topology to be the same one as the checkpoint was saved under.\n\n\n\n<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \"open\"}); this.defns = {}; this.state = {}; } } customElements.define(\"treescope-container\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \"\"; fn.call(this); this.remove(); }; const child = this.querySelector(\"script\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\"script\")); }).observe(this, {childList: true}); } } } customElements.define(\"treescope-run-here\", RunHere); } })(); </script> <treescope-container class=\"treescope_out_9572198988a54a0b9731fab9e0c97e51\" ></treescope-container> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_9572198988a54a0b9731fab9e0c97e51\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\"span\"); msg.style = \"color: #cccccc; font-family: monospace;\"; msg.textContent = \"(Loading...)\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \"1000px\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\"script\")) { let newScript = document.createElement(\"script\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\"deflate\") ).pipeThrough( new TextDecoderStream(\"utf-8\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\"\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\"display:none\"> <script type=\"application/octet-stream\" >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</script> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_9572198988a54a0b9731fab9e0c97e51\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\"application/octet-stream\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\n\n\n\n<div style=\"display:none\"> <script type=\"application/octet-stream\" >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</script> <treescope-run-here><script type=\"application/octet-stream\"> const root = ( Array.from(document.getElementsByClassName( \"treescope_out_9572198988a54a0b9731fab9e0c97e51\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\"application/octet-stream\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\n\n\n## Multi-process checkpointing\n\nIn a multi-host/multi-process environment, you would want to restore your checkpoint as sharded across multiple devices. Check out the [Load sharded model from a checkpoint](https://flax.readthedocs.io/en/latest/guides/flax_gspmd.html#load-sharded-model-from-a-checkpoint) section in the Flax [Scale up on multiple devices](https://flax.readthedocs.io/en/latest/guides/flax_gspmd.html) guide to learn how to derive a sharding pytree and use it to load your checkpoint.\n\n> **Note:** JAX provides several ways to scale up your code on multiple hosts at the same time. This usually happens when the number of devices (CPU/GPU/TPU) is so large that different devices are managed by different hosts (CPU). Check out JAX’s [Introduction to parallel programming](https://jax.readthedocs.io/en/latest/sharded-computation.html), [Using JAX in multi-host and multi-process environments](https://jax.readthedocs.io/en/latest/multi_process.html), [Distributed arrays and automatic parallelization](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html), and [Manual parallelism with `shard_map`](https://jax.readthedocs.io/en/latest/notebooks/shard_map.html).\n\n+++\n\n## Other checkpointing features\n\nThis guide only uses the simplest [`orbax.checkpoint.StandardCheckpointer`](https://orbax.readthedocs.io/en/latest/api_reference/checkpoint.checkpointers.html#standardcheckpointer) API to show how to save and load on the Flax modeling side. Feel free to use other tools or libraries as you see fit.\n\nIn addition, check out [the Orbax website](https://orbax.readthedocs.io/en/latest/index.html) for other commonly used features, such as:\n\n* [`CheckpointManager`](https://orbax.readthedocs.io/en/latest/guides/checkpoint/api_refactor.html) to track checkpoints from different steps.\n\n* [Asynchronous checkpointing](https://orbax.readthedocs.io/en/latest/guides/checkpoint/async_checkpointing.html).\n\n* [Orbax transformations](https://orbax.readthedocs.io/en/latest/guides/checkpoint/transformations.html): A way to modify pytree structure during loading time, instead of after loading time, which is demonstrated in this guide.\n"
  },
  {
    "path": "docs_nnx/guides/demo.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a1b37dff\",\n   \"metadata\": {},\n   \"source\": [\n    \"# NNX Demo\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"id\": \"e8099a6f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import jax\\n\",\n    \"from jax import numpy as jnp\\n\",\n    \"from flax import nnx\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"bcc5cffe\",\n   \"metadata\": {},\n   \"source\": [\n    \"### [1] NNX is Pythonic\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"id\": \"d99b73af\",\n   \"metadata\": {\n    \"outputId\": \"d8ef66d5-6866-4d5c-94c2-d22512bfe718\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"model = MLP(\\n\",\n      \"  blocks=[Block(\\n\",\n      \"      linear=Linear(\\n\",\n      \"            in_features=4,\\n\",\n      \"            out_features=4,\\n\",\n      \"            use_bias=True,\\n\",\n      \"            dtype=None,\\n\",\n      \"            param_dtype=<class 'jax.numpy.float32'>,\\n\",\n      \"            precision=None,\\n\",\n      \"            kernel_init=<function variance_scaling.<locals>.init at 0x28ae86dc0>,\\n\",\n      \"            bias_init=<function zeros at 0x122d39f70>,\\n\",\n      \"            dot_general=<function dot_general at 0x1218459d0>\\n\",\n      \"          ),\\n\",\n      \"      bn=BatchNorm(\\n\",\n      \"            num_features=4,\\n\",\n      \"  \\n\",\n      \"...\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"\\n\",\n    \"class Block(nnx.Module):\\n\",\n    \"  def __init__(self, din, dout, *, rngs):\\n\",\n    \"    self.linear = nnx.Linear(din, dout, rngs=rngs)\\n\",\n    \"    self.bn = nnx.BatchNorm(dout, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    return nnx.relu(self.bn(self.linear(x)))\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"class MLP(nnx.Module):\\n\",\n    \"  def __init__(self, nlayers, dim, *, rngs): # explicit RNG threading\\n\",\n    \"    self.blocks = [\\n\",\n    \"      Block(dim, dim, rngs=rngs) for _ in range(nlayers)\\n\",\n    \"    ]\\n\",\n    \"    self.count = Count(0)  # stateful variables are defined as attributes\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    self.count.value += 1  # in-place stateful updates\\n\",\n    \"    for block in self.blocks:\\n\",\n    \"      x = block(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"class Count(nnx.Variable):   # custom Variable types define the \\\"collections\\\"\\n\",\n    \"  pass\\n\",\n    \"\\n\",\n    \"model = MLP(5, 4, rngs=nnx.Rngs(0))  # no special `init` method\\n\",\n    \"model.set_attributes(use_running_average=False)  # set flags\\n\",\n    \"y = model(jnp.ones((2, 4)))  # call methods directly\\n\",\n    \"\\n\",\n    \"print(f'{model = }'[:500] + '\\\\n...')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"523aa27c\",\n   \"metadata\": {},\n   \"source\": [\n    \"Because NNX Modules contain their own state, they are very easily to inspect:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"id\": \"6f278ec4\",\n   \"metadata\": {\n    \"outputId\": \"10a46b0f-2993-4677-c26d-36a4ddf33449\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"model.count = Count(\\n\",\n      \"  raw_value=1\\n\",\n      \")\\n\",\n      \"model.blocks[0].linear.kernel = Param(\\n\",\n      \"  raw_value=Array([[-0.80345297, -0.34071913, -0.9408296 ,  0.01005968],\\n\",\n      \"         [ 0.26146442,  1.1247735 ,  0.54563737, -0.374164  ],\\n\",\n      \"         [ 1.0281805 , -0.6798804 , -0.1488401 ,  0.05694951],\\n\",\n      \"         [-0.44308168, -0.60587114,  0.434087  , -0.40541083]],      dtype=float32)\\n\",\n      \")\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(f'{model.count = }')\\n\",\n    \"print(f'{model.blocks[0].linear.kernel = }')\\n\",\n    \"# print(f'{model.blocks.sdf.kernel = }') # typesafe inspection\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"95f389f2\",\n   \"metadata\": {},\n   \"source\": [\n    \"### [2] Model Surgery is Intuitive\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"id\": \"96f61108\",\n   \"metadata\": {\n    \"outputId\": \"e6f86be8-3537-4c48-f471-316ee0fb6c45\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"y.shape = (2, 4)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Module sharing\\n\",\n    \"model.blocks[1] = model.blocks[3]\\n\",\n    \"# Weight tying\\n\",\n    \"model.blocks[0].linear.kernel = model.blocks[-1].linear.kernel\\n\",\n    \"# Monkey patching\\n\",\n    \"def my_optimized_layer(x): return x\\n\",\n    \"model.blocks[2] = my_optimized_layer\\n\",\n    \"\\n\",\n    \"y = model(jnp.ones((2, 4)))  # still works\\n\",\n    \"print(f'{y.shape = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"aca5a6cd\",\n   \"metadata\": {},\n   \"source\": [\n    \"### [3] Interacting with JAX is easy\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"id\": \"c166dcc7\",\n   \"metadata\": {\n    \"outputId\": \"9a3f378b-739e-4f45-9968-574651200ede\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"state = State({\\n\",\n      \"  'blocks': {\\n\",\n      \"    '0': {\\n\",\n      \"      'linear': {\\n\",\n      \"        'kernel': Param(\\n\",\n      \"          raw_value=Array([[-0.33095378,  0.67149884,  0.33700302,  0.30972847],\\n\",\n      \"                 [ 0.8662822 , -0.11225506, -1.0820619 , -0.9906892 ],\\n\",\n      \"                 [ 0.88298297, -0.2143851 ,  0.48143268,  0.6474548 ],\\n\",\n      \"                 [-0.7710582 ,  0.3372276 ,  0.15487202,  0.6219269 ]],      dtype=float32)\\n\",\n      \"        ),\\n\",\n      \"        'bias': Param(\\n\",\n      \"          raw_value=Array([0., 0., 0., 0.], dtype=float32)\\n\",\n      \"        \\n\",\n      \"...\\n\",\n      \"\\n\",\n      \"graphdef = GraphDef(\\n\",\n      \"  type=MLP,\\n\",\n      \"  index=0,\\n\",\n      \"  attributes=('blocks', 'count'),\\n\",\n      \"  subgraphs={\\n\",\n      \"    'blocks': GraphDef(\\n\",\n      \"      type=list,\\n\",\n      \"      index=1,\\n\",\n      \"      attributes=('0', '1', '2', '3', '4'),\\n\",\n      \"      subgraphs={\\n\",\n      \"        '0': GraphDef(\\n\",\n      \"          type=Block,\\n\",\n      \"          index=2,\\n\",\n      \"          attributes=('line\\n\",\n      \"...\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"graphdef, state = model.split()\\n\",\n    \"\\n\",\n    \"# state is a dictionary-like JAX pytree\\n\",\n    \"print(f'{state = }'[:500] + '\\\\n...')\\n\",\n    \"\\n\",\n    \"# graphdef is also a JAX pytree, but just metadata\\n\",\n    \"print(f'\\\\n{graphdefefefefefef = }'[:300] + '\\\\n...')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"id\": \"9f03e3af\",\n   \"metadata\": {\n    \"outputId\": \"0007d357-152a-449e-bcb9-b1b5a91d2d8d\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"y.shape = (2, 4)\\n\",\n      \"model.count.value = Array(3, dtype=int32, weak_type=True)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"graphdef, state = model.split()\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def forward(graphdef: nnx.GraphDef, state: nnx.State, x: jax.Array):\\n\",\n    \"  model = graphdef.merge(state)\\n\",\n    \"  y = model(x)\\n\",\n    \"  state, _ = model.split()\\n\",\n    \"  return y, state\\n\",\n    \"\\n\",\n    \"x = jnp.ones((2, 4))\\n\",\n    \"y, state = forward(graphdef,state, x)\\n\",\n    \"\\n\",\n    \"model.update(state)\\n\",\n    \"\\n\",\n    \"print(f'{y.shape = }')\\n\",\n    \"print(f'{model.count.value = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"id\": \"9e23dbb4\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"y.shape = (2, 4)\\n\",\n      \"model.count = Array(4, dtype=int32, weak_type=True)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"params, batch_stats, counts, graphdef = model.split(nnx.Param, nnx.BatchStat, Count)\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def forward(graphdef: nnx.GraphDef, params, batch_stats, counts, x: jax.Array):\\n\",\n    \"  model = graphdef.merge(params, batch_stats, counts)\\n\",\n    \"  y = model(x, train=True)\\n\",\n    \"  params, batch_stats, counts, _ = model.split(nnx.Param, nnx.BatchStat, Count)\\n\",\n    \"  return y, params, batch_stats, counts\\n\",\n    \"\\n\",\n    \"x = jnp.ones((2, 4))\\n\",\n    \"y, params, batch_stats, counts = forward(graphdef, params, batch_stats, counts, x)\\n\",\n    \"\\n\",\n    \"model.update(params, batch_stats, counts)\\n\",\n    \"\\n\",\n    \"print(f'{y.shape = }')\\n\",\n    \"print(f'{model.count = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"id\": \"2461bfe8\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"y.shape = (2, 4)\\n\",\n      \"parent.model.count.value = Array(4, dtype=int32, weak_type=True)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Parent(nnx.Module):\\n\",\n    \"    def __init__(self, model: MLP):\\n\",\n    \"        self.model = model\\n\",\n    \"\\n\",\n    \"    def __call__(self, x):\\n\",\n    \"        params, batch_stats, counts, graphdef = self.model.split(nnx.Param, nnx.BatchStat, Count)\\n\",\n    \"\\n\",\n    \"        @jax.jit\\n\",\n    \"        def forward(graphdef: nnx.GraphDef, params, batch_stats, counts, x: jax.Array):\\n\",\n    \"            model = graphdef.merge(params, batch_stats, counts)\\n\",\n    \"            y = model(x)\\n\",\n    \"            params, batch_stats, counts, _ = model.split(nnx.Param, nnx.BatchStat, Count)\\n\",\n    \"            return y, params, batch_stats, counts\\n\",\n    \"\\n\",\n    \"        y, params, batch_stats, counts = forward(graphdef, params, batch_stats, counts, x)\\n\",\n    \"\\n\",\n    \"        self.model.update(params, batch_stats, counts)\\n\",\n    \"        return y\\n\",\n    \"\\n\",\n    \"parent = Parent(model)\\n\",\n    \"\\n\",\n    \"y = parent(jnp.ones((2, 4)))\\n\",\n    \"\\n\",\n    \"print(f'{y.shape = }')\\n\",\n    \"print(f'{parent.model.count.value = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"2e340bcb\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.9.18\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs_nnx/guides/demo.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# NNX Demo\n\n```{code-cell} ipython3\nimport jax\nfrom jax import numpy as jnp\nfrom flax import nnx\n```\n\n### [1] NNX is Pythonic\n\n```{code-cell} ipython3\n:outputId: d8ef66d5-6866-4d5c-94c2-d22512bfe718\n\n\nclass Block(nnx.Module):\n  def __init__(self, din, dout, *, rngs):\n    self.linear = nnx.Linear(din, dout, rngs=rngs)\n    self.bn = nnx.BatchNorm(dout, rngs=rngs)\n\n  def __call__(self, x):\n    return nnx.relu(self.bn(self.linear(x)))\n\n\nclass MLP(nnx.Module):\n  def __init__(self, nlayers, dim, *, rngs): # explicit RNG threading\n    self.blocks = [\n      Block(dim, dim, rngs=rngs) for _ in range(nlayers)\n    ]\n    self.count = Count(0)  # stateful variables are defined as attributes\n\n  def __call__(self, x):\n    self.count.value += 1  # in-place stateful updates\n    for block in self.blocks:\n      x = block(x)\n    return x\n\nclass Count(nnx.Variable):   # custom Variable types define the \"collections\"\n  pass\n\nmodel = MLP(5, 4, rngs=nnx.Rngs(0))  # no special `init` method\nmodel.set_attributes(use_running_average=False)  # set flags\ny = model(jnp.ones((2, 4)))  # call methods directly\n\nprint(f'{model = }'[:500] + '\\n...')\n```\n\nBecause NNX Modules contain their own state, they are very easily to inspect:\n\n```{code-cell} ipython3\n:outputId: 10a46b0f-2993-4677-c26d-36a4ddf33449\n\nprint(f'{model.count = }')\nprint(f'{model.blocks[0].linear.kernel = }')\n# print(f'{model.blocks.sdf.kernel = }') # typesafe inspection\n```\n\n### [2] Model Surgery is Intuitive\n\n```{code-cell} ipython3\n:outputId: e6f86be8-3537-4c48-f471-316ee0fb6c45\n\n# Module sharing\nmodel.blocks[1] = model.blocks[3]\n# Weight tying\nmodel.blocks[0].linear.kernel = model.blocks[-1].linear.kernel\n# Monkey patching\ndef my_optimized_layer(x): return x\nmodel.blocks[2] = my_optimized_layer\n\ny = model(jnp.ones((2, 4)))  # still works\nprint(f'{y.shape = }')\n```\n\n### [3] Interacting with JAX is easy\n\n```{code-cell} ipython3\n:outputId: 9a3f378b-739e-4f45-9968-574651200ede\n\ngraphdef, state = model.split()\n\n# state is a dictionary-like JAX pytree\nprint(f'{state = }'[:500] + '\\n...')\n\n# graphdef is also a JAX pytree, but just metadata\nprint(f'\\n{graphdefefefefefef = }'[:300] + '\\n...')\n```\n\n```{code-cell} ipython3\n:outputId: 0007d357-152a-449e-bcb9-b1b5a91d2d8d\n\ngraphdef, state = model.split()\n\n@jax.jit\ndef forward(graphdef: nnx.GraphDef, state: nnx.State, x: jax.Array):\n  model = graphdef.merge(state)\n  y = model(x)\n  state, _ = model.split()\n  return y, state\n\nx = jnp.ones((2, 4))\ny, state = forward(graphdef,state, x)\n\nmodel.update(state)\n\nprint(f'{y.shape = }')\nprint(f'{model.count.value = }')\n```\n\n```{code-cell} ipython3\nparams, batch_stats, counts, graphdef = model.split(nnx.Param, nnx.BatchStat, Count)\n\n@jax.jit\ndef forward(graphdef: nnx.GraphDef, params, batch_stats, counts, x: jax.Array):\n  model = graphdef.merge(params, batch_stats, counts)\n  y = model(x, train=True)\n  params, batch_stats, counts, _ = model.split(nnx.Param, nnx.BatchStat, Count)\n  return y, params, batch_stats, counts\n\nx = jnp.ones((2, 4))\ny, params, batch_stats, counts = forward(graphdef, params, batch_stats, counts, x)\n\nmodel.update(params, batch_stats, counts)\n\nprint(f'{y.shape = }')\nprint(f'{model.count = }')\n```\n\n```{code-cell} ipython3\nclass Parent(nnx.Module):\n    def __init__(self, model: MLP):\n        self.model = model\n\n    def __call__(self, x):\n        params, batch_stats, counts, graphdef = self.model.split(nnx.Param, nnx.BatchStat, Count)\n\n        @jax.jit\n        def forward(graphdef: nnx.GraphDef, params, batch_stats, counts, x: jax.Array):\n            model = graphdef.merge(params, batch_stats, counts)\n            y = model(x)\n            params, batch_stats, counts, _ = model.split(nnx.Param, nnx.BatchStat, Count)\n            return y, params, batch_stats, counts\n\n        y, params, batch_stats, counts = forward(graphdef, params, batch_stats, counts, x)\n\n        self.model.update(params, batch_stats, counts)\n        return y\n\nparent = Parent(model)\n\ny = parent(jnp.ones((2, 4)))\n\nprint(f'{y.shape = }')\nprint(f'{parent.model.count.value = }')\n```\n\n```{code-cell} ipython3\n\n```\n"
  },
  {
    "path": "docs_nnx/guides/extracting_intermediates.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"0fa991e8\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Extracting intermediate values\\n\",\n    \"\\n\",\n    \"This guide will show you how to extract intermediate values from a module.\\n\",\n    \"Consider a toy neural network with two pieces: a \\\"feature\\\" component that embeds\\n\",\n    \"inputs in some feature space, and a \\\"loss\\\" component that operates on those features.\\n\",\n    \"We'll want to log these feature components during training to identify any issues with\\n\",\n    \"the feature extraction. To do this, we can use the `Module.sow` method.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"id\": \"e4c7c65b\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"W0317 18:04:12.704562 2028538 cpp_gen_intrinsics.cc:74] Empty bitcode string provided for eigen. Optimizations relying on this IR will be disabled.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from flax import nnx\\n\",\n    \"import jax\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"from functools import partial\\n\",\n    \"\\n\",\n    \"class Foo(nnx.Module):\\n\",\n    \"  def __init__(self, *, rngs: nnx.Rngs):\\n\",\n    \"    self.dense1 = nnx.Linear(8, 32, rngs=rngs)\\n\",\n    \"    self.dense2 = nnx.Linear(32, 1, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def features(self, x, rngs= None):\\n\",\n    \"      feature = nnx.relu(self.dense1(x))\\n\",\n    \"      self.sow(nnx.Intermediate, 'features', feature)\\n\",\n    \"      return feature\\n\",\n    \"\\n\",\n    \"  def loss(self, x_features, y_features):\\n\",\n    \"    return jnp.sum((x_features - y_features)**2)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x, y):\\n\",\n    \"    return self.loss(self.features(x), self.features(y))\\n\",\n    \"\\n\",\n    \"# Instantiate the model.\\n\",\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"model = Foo(rngs=rngs)\\n\",\n    \"\\n\",\n    \"# Dummy input for testing\\n\",\n    \"x, y = rngs.normal((2,8))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"c56dd826\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here, `self.sow` will store intermediate values under the key `'features'` in a collection associated with the\\n\",\n    \"`nnx.Intermediate` type. If you want to log values to multiple different collections, you can use different subclasses of `nnx.Intermediate`\\n\",\n    \"for each collection.\\n\",\n    \"\\n\",\n    \"Now, we can wrap the module with the `nnx.capture` decorator, which wraps any `Callable` accepting a module as its argument (which includes `nnx.Module`s, their methods, or ordinary functions) to return both the resulting loss as well as any intermediate values stored to the `nnx.Intermediate` collection:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"id\": \"c508f8f3\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"State({\\n\",\n       \"  'features': Intermediate(\\n\",\n       \"    value=((32,), (32,))\\n\",\n       \"  )\\n\",\n       \"})\"\n      ]\n     },\n     \"execution_count\": 2,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"capturing_model = nnx.capture(model, nnx.Intermediate)\\n\",\n    \"result, intms = capturing_model(x, y)\\n\",\n    \"jax.tree.map(lambda a: a.shape, intms)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"d2f2f609\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that, by default, sow appends values every time it is called. We can see\\n\",\n    \"this in the *features* intermediate values logged above. It contains a tuple with one element for the call on `x` and one for the call on `y`. To override the default append behavior, specify `init_fn` and `reduce_fn` - see `Module.sow()`.\\n\",\n    \"\\n\",\n    \"## How `nnx.capture` Works\\n\",\n    \"\\n\",\n    \"`nnx.capture` works by temporarily installing a set of mutable capture buffers on every module in the graph before calling the wrapped function, then harvesting those buffers afterward. Before calling the wrapped function, `capture` walks the entire module graph with `iter_modules`. For each module it sets a `__captures__` attribute: a tuple of Variable instances, one per requested `var_type`. Each Variable holds a plain `dict` that maps sow-key → accumulated value.\\n\",\n    \"\\n\",\n    \"We can see this `__captures__` tuple by printing out the module contents during a `nnx.capture` call:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"id\": \"1e5c2ae7\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Captures: (Intermediate(\\n\",\n      \"  value={}\\n\",\n      \"),)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@nnx.capture(nnx.Intermediate)\\n\",\n    \"def print_captures(model):\\n\",\n    \"      print(\\\"Captures:\\\", model.__captures__)\\n\",\n    \"_, intms = print_captures(nnx.Module())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"2daab2c9\",\n   \"metadata\": {},\n   \"source\": [\n    \"`Module.sow` looks for the Variable in the `__captures__` tuple whose type matches `variable_type`, then writes its value into that dict using `reduce_fn`.\\n\",\n    \"\\n\",\n    \"If no matching type is found, `sow` silently returns `False` without logging the value. This can be used to capture only a subset of the sown values. For example:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"id\": \"159a909b-0c3a-411e-9ccb-98ddecd5720e\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"State({\\n\",\n       \"  'gets_sown': Metric1(\\n\",\n       \"    value=((2,),)\\n\",\n       \"  )\\n\",\n       \"})\"\n      ]\n     },\n     \"execution_count\": 16,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"class Metric1(nnx.Intermediate):\\n\",\n    \"    pass\\n\",\n    \"\\n\",\n    \"class Metric2(nnx.Intermediate):\\n\",\n    \"    pass\\n\",\n    \"\\n\",\n    \"@nnx.capture(Metric1)\\n\",\n    \"def get_captures(model):\\n\",\n    \"    model.sow(Metric1, 'gets_sown', jnp.ones(2))\\n\",\n    \"    model.sow(Metric2, 'gets_ignored', jnp.ones(2))\\n\",\n    \"_, intms = get_captures(nnx.Module())\\n\",\n    \"jax.tree.map(lambda a: a.shape, intms)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1328ce66\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Capturing all intermediate values\\n\",\n    \"\\n\",\n    \"To observe the output of each method without manually adding calls to `sow`, we can call `nnx.capture` with the `method_outputs` argument. This will automatically `sow` the output of each method using the given variable type, including methods of sub-modules.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"id\": \"47781215\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"State({\\n\",\n       \"  '__call__': Intermediate(\\n\",\n       \"    value=((),)\\n\",\n       \"  ),\\n\",\n       \"  'dense1': {\\n\",\n       \"    '__call__': Intermediate(\\n\",\n       \"      value=((32,), (32,))\\n\",\n       \"    )\\n\",\n       \"  },\\n\",\n       \"  'features': Intermediate(\\n\",\n       \"    value=((32,), (32,))\\n\",\n       \"  ),\\n\",\n       \"  'loss': Intermediate(\\n\",\n       \"    value=((),)\\n\",\n       \"  )\\n\",\n       \"})\"\n      ]\n     },\n     \"execution_count\": 7,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"class Foo(nnx.Module):\\n\",\n    \"  def __init__(self, *, rngs: nnx.Rngs):\\n\",\n    \"    self.dense1 = nnx.Linear(8, 32, rngs=rngs)\\n\",\n    \"    self.dense2 = nnx.Linear(32, 1, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def features(self, x, rngs= None):\\n\",\n    \"      feature = nnx.relu(self.dense1(x))\\n\",\n    \"      return feature\\n\",\n    \"\\n\",\n    \"  def loss(self, x_features, y_features):\\n\",\n    \"    return jnp.sum((x_features - y_features)**2)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x, y):\\n\",\n    \"    return self.loss(self.features(x), self.features(y))\\n\",\n    \"\\n\",\n    \"model = Foo(rngs=nnx.Rngs(0))\\n\",\n    \"capturing_model = nnx.capture(model, nnx.Intermediate, method_outputs=nnx.Intermediate)\\n\",\n    \"result, intms = capturing_model(x, y)\\n\",\n    \"jax.tree.map(lambda a: a.shape, intms)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"eee2809a\",\n   \"metadata\": {},\n   \"source\": [\n    \"This pattern should be considered the \\\"sledge hammer\\\" approach to capturing intermediates. As a debugging and inspection tool it is very useful, but using the other patterns described in this guide will give you more fine-grained control over what intermediates you want to extract. We can also combine the `method_output_type` argument with manual calls to sow to capture both layer outputs and computations mid-layer.\\n\",\n    \"\\n\",\n    \"## Extracting gradients of intermediate values\\n\",\n    \"\\n\",\n    \"For debugging purposes, it can be useful to extract the gradients of intermediate values. This is a little tricky: jax doesn't have a stable mechanism for sowing information from the backward pass into to objects from the forward pass. Instead, we record the gradients of intermediate values using the `Module.perturb` method as follows:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 30,\n   \"id\": \"84911b66\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"State({\\n\",\n       \"  'grad_of_x': Perturbation( # 1 (4 B)\\n\",\n       \"    value=Array(3., dtype=float32, weak_type=True)\\n\",\n       \"  ),\\n\",\n       \"  'activations': Intermediate( # 1 (4 B)\\n\",\n       \"    value=(Array(1., dtype=float32, weak_type=True),)\\n\",\n       \"  )\\n\",\n       \"})\"\n      ]\n     },\n     \"execution_count\": 30,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"class Model(nnx.Module):\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x2 = self.perturb('grad_of_x', x)\\n\",\n    \"    self.sow(nnx.Intermediate, 'activations', x2)\\n\",\n    \"    return 3 * x2\\n\",\n    \"\\n\",\n    \"model = Model()\\n\",\n    \"\\n\",\n    \"def train_step(model, x):\\n\",\n    \"    _, perturbations = nnx.capture(model, nnx.Perturbation)(x)\\n\",\n    \"    def loss(model, perturbations, x):\\n\",\n    \"        return nnx.capture(model, nnx.Intermediate, init=perturbations)(x)\\n\",\n    \"\\n\",\n    \"    (grads, perturb_grads), sowed = nnx.grad(loss, argnums=(0, 1), has_aux=True)(model, perturbations, x)\\n\",\n    \"    return nnx.merge_state(perturb_grads, sowed)\\n\",\n    \"\\n\",\n    \"train_step(model, 1.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"7dc978af\",\n   \"metadata\": {},\n   \"source\": [\n    \"There are four steps:\\n\",\n    \"\\n\",\n    \"**Step One: Initialize *perturbations* of the model**.\\n\",\n    \"\\n\",\n    \"We do this with a call to `nnx.capture(model, nnx.Perturbation)`. Before the call, `capture` installs `__captures__` on the module — a tuple containing one empty `Perturbation` buffer (as described in \\\"How `nnx.capture` Works\\\" above). When `self.perturb` runs, it checks `__captures__` for a matching `Perturbation` Variable, initialises the slot to `zeros_like(value)`, and returns `zeros + x`. After the call, `__captures__` is removed and the filled buffer is returned as `perturbations`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"id\": \"5f4fda80\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"before perturb: (Perturbation(\\n\",\n      \"  value={}\\n\",\n      \"),)\\n\",\n      \"after  perturb: (Perturbation( # 1 (4 B)\\n\",\n      \"  value={'grad_of_x': Array(0., dtype=float32, weak_type=True)}\\n\",\n      \"),)\\n\",\n      \"\\u001b[38;2;79;201;177mState\\u001b[0m\\u001b[38;2;255;213;3m({\\u001b[0m\\u001b[38;2;105;105;105m\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254m'grad_of_x'\\u001b[0m\\u001b[38;2;212;212;212m: \\u001b[0m\\u001b[38;2;79;201;177mPerturbation\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray(0., dtype=float32, weak_type=True)\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m})\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Model(nnx.Module):\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    print(\\\"before perturb:\\\", self.__captures__)\\n\",\n    \"    x2 = self.perturb('grad_of_x', x)\\n\",\n    \"    print(\\\"after  perturb:\\\", self.__captures__)\\n\",\n    \"    self.sow(nnx.Intermediate, 'activations', x2)\\n\",\n    \"    # sow is a no-op: Intermediate is not in __captures__, so it returns False silently\\n\",\n    \"    return 3 * x2\\n\",\n    \"\\n\",\n    \"model = Model()\\n\",\n    \"_, perturbations = nnx.capture(model, nnx.Perturbation)(1.0)\\n\",\n    \"print(perturbations)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"2b4e1d98\",\n   \"metadata\": {},\n   \"source\": [\n    \"There are only two differences between `sow` and `perturb`:\\n\",\n    \"\\n\",\n    \"- The `nnx.Variable` tag used for values written with `self.perturb` is `nnx.Perturbation` rather than `nnx.Intermediate`.\\n\",\n    \"    \\n\",\n    \"- `perturb` returns the logged value. You must use this returned value rather than the original value for the gradient capturing machinery to work.\\n\",\n    \"\\n\",\n    \"The `var_types` argument to `capture` restricts which of the logged values we want to return. Because we only want the intermediates logged with `self.perturb` statements, we only capture `nnx.Perturbation` types.\\n\",\n    \"\\n\",\n    \"**Step Two: Run the model again, but add in these perturbations**.\\n\",\n    \"\\n\",\n    \"Call `capture` again with `init=perturbations`. `capture` first builds a mapping from module path to the Variables in `init`, then uses it to pre-populate `__captures__`. Now `__captures__` has *two* buffers: an empty `Intermediate` buffer (from `var_types`) and a `Perturbation` buffer pre-populated from `init`. `self.perturb` finds the pre-populated buffer and returns `x + perturbation`; `self.sow` writes into the `Intermediate` buffer as normal.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"id\": \"a4087d73\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"before perturb: (Intermediate(\\n\",\n      \"  value={}\\n\",\n      \"), Perturbation( # 1 (4 B)\\n\",\n      \"  value={'grad_of_x': Array(0., dtype=float32, weak_type=True)}\\n\",\n      \"))\\n\",\n      \"after  sow:     (Intermediate( # 1 (4 B)\\n\",\n      \"  value={'activations': (Array(1., dtype=float32, weak_type=True),)}\\n\",\n      \"), Perturbation( # 1 (4 B)\\n\",\n      \"  value={'grad_of_x': Array(0., dtype=float32, weak_type=True)}\\n\",\n      \"))\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Model(nnx.Module):\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    print(\\\"before perturb:\\\", self.__captures__)\\n\",\n    \"    x2 = self.perturb('grad_of_x', x)\\n\",\n    \"    self.sow(nnx.Intermediate, 'activations', x2)\\n\",\n    \"    print(\\\"after  sow:    \\\", self.__captures__)\\n\",\n    \"    return 3 * x2\\n\",\n    \"\\n\",\n    \"model = Model()\\n\",\n    \"_, interms = nnx.capture(model, nnx.Intermediate, init=perturbations)(1.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"63e2ba59\",\n   \"metadata\": {},\n   \"source\": [\n    \"This changes the behavior of `x2 = self.perturb('name', x)` to essentially be `x2 = x + perturbations['name']`. The gradient of our output with respect to `x` will be the same as the gradient with respect to the perturbation, because JAX can differentiate through the addition with respect to the perturbation value stored in the capture dict.\\n\",\n    \"\\n\",\n    \"**Step Three: Take gradients**.\\n\",\n    \"\\n\",\n    \"Specifically, take the gradient of this second `capture` call with respect to the perturbation arguments. JAX traces through exactly the same `__captures__` setup as Step Two, but with abstract (traced) array values instead of concrete ones. This will give us the same values as the gradients with respect to the intermediate variables. If we want to track intermediate variables in the forward pass at the same time, we'll need to return the intermediate values output of the `capture` call as well, so we'll need to pass `has_aux=True` to `nnx.grad`.\\n\",\n    \"\\n\",\n    \"**Step Four: Combine intermediate states**\\n\",\n    \"\\n\",\n    \"Merge the `State` object we get from the perturbation gradients with the `State` object for forward intermediates with `nnx.merge_state(perturb_grads, sowed)`. At this point `__captures__` no longer exists on any module — it was cleaned up at the end of the `capture` call in Step Three.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"23ccf952\",\n   \"metadata\": {},\n   \"source\": [\n    \"## NNX Transforms and Capturing\\n\",\n    \"\\n\",\n    \"`nnx.capture` composes with NNX transforms such as `nnx.vmap`. The main thing to keep in mind is that perturbations must be initialized with a run that has the same batch structure as the training step that will consume them.\\n\",\n    \"\\n\",\n    \"Consider a model that calls both `sow` and `perturb`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 34,\n   \"id\": \"7c8f8d83\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class Foo(nnx.Module):\\n\",\n    \"  def __init__(self, dim):\\n\",\n    \"    self.w = nnx.Param(jax.random.normal(jax.random.key(0), dim))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x = self.perturb('grad_of_x', x)\\n\",\n    \"    y = jnp.dot(x, self.w)\\n\",\n    \"    self.sow(nnx.Intermediate, 'y', y)\\n\",\n    \"    return y\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"786278eb\",\n   \"metadata\": {},\n   \"source\": [\n    \"The training step vmaps `loss_grad` over a batch of inputs and perturbations, while the model weights are shared across the batch (`in_axes=None`):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 35,\n   \"id\": \"8ac86ed1\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"@nnx.jit\\n\",\n    \"def train_step(model, x):\\n\",\n    \"  _, perturbations = init_perturbations(model, x)\\n\",\n    \"  def loss_grad(model, perturbations, x):\\n\",\n    \"    def loss(model, perturbations, x):\\n\",\n    \"      loss, interms = nnx.capture(model, nnx.Intermediate, init=perturbations)(x)\\n\",\n    \"      return loss, interms\\n\",\n    \"    (grads, perturb_grads), sowed = nnx.grad(loss, argnums=(0, 1), has_aux=True)(model, perturbations, x)\\n\",\n    \"    return grads, nnx.merge_state(perturb_grads, sowed)\\n\",\n    \"  return nnx.vmap(loss_grad, in_axes=(None, 0, 0))(model, perturbations, x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"3de42b97-9923-436f-9db0-d9aeedc259ad\",\n   \"metadata\": {},\n   \"source\": [\n    \"After every training step, we can sum the gradients and pass them to an `Optimizer` to adjust the model, as usual. But we can also look at the full batch of sown values and perturbations.\\n\",\n    \"\\n\",\n    \"Because `train_step` expects `perturbations` to have a leading batch axis (axis 0), the perturbation initialization run must also produce a batched `perturbations` state. We do this inside an `init_perturbations` method that splits the model and vmaps the run with `in_axes=(0, None, 0)` for `(intermediates, params, x)`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 36,\n   \"id\": \"76c291c8\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"@nnx.capture(nnx.Perturbation)\\n\",\n    \"def init_perturbations(model, x):\\n\",\n    \"    graphdef, intms, params = nnx.split(model, nnx.Intermediate, nnx.Param)\\n\",\n    \"    def forward(intms, params, x):\\n\",\n    \"      return nnx.merge(graphdef, intms, params)(x)\\n\",\n    \"    return nnx.vmap(forward, in_axes=(0, None, 0))(intms, params, x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"981642f6\",\n   \"metadata\": {},\n   \"source\": [\n    \"Putting it together:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 37,\n   \"id\": \"7a741ca4\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"State({\\n\",\n       \"  'grad_of_x': Perturbation(\\n\",\n       \"    value=(3, 4)\\n\",\n       \"  ),\\n\",\n       \"  'y': Intermediate(\\n\",\n       \"    value=((3,),)\\n\",\n       \"  )\\n\",\n       \"})\"\n      ]\n     },\n     \"execution_count\": 37,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"model, x = Foo(4), jnp.ones((3, 4))\\n\",\n    \"_, intermediates = train_step(model, x)\\n\",\n    \"jax.tree.map(lambda a: a.shape, intermediates)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"027216c7\",\n   \"metadata\": {},\n   \"source\": [\n    \"The pattern generalises: whenever a transform introduces a new batch axis over which `capture` runs, initialize perturbations with a matching vmapped pre-run so that the `init=perturbations` argument inside the transform has the correct shape.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md\",\n   \"main_language\": \"python\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.13.5\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs_nnx/guides/extracting_intermediates.md",
    "content": "---\njupyter:\n  jupytext:\n    formats: ipynb,md\n    main_language: python\n    text_representation:\n      extension: .md\n      format_name: markdown\n      format_version: '1.3'\n      jupytext_version: 1.13.8\n---\n\n# Extracting intermediate values\n\nThis guide will show you how to extract intermediate values from a module.\nConsider a toy neural network with two pieces: a \"feature\" component that embeds\ninputs in some feature space, and a \"loss\" component that operates on those features.\nWe'll want to log these feature components during training to identify any issues with\nthe feature extraction. To do this, we can use the `Module.sow` method.\n\n```python\nfrom flax import nnx\nimport jax\nimport jax.numpy as jnp\nfrom functools import partial\n\nclass Foo(nnx.Module):\n  def __init__(self, *, rngs: nnx.Rngs):\n    self.dense1 = nnx.Linear(8, 32, rngs=rngs)\n    self.dense2 = nnx.Linear(32, 1, rngs=rngs)\n\n  def features(self, x, rngs= None):\n      feature = nnx.relu(self.dense1(x))\n      self.sow(nnx.Intermediate, 'features', feature)\n      return feature\n\n  def loss(self, x_features, y_features):\n    return jnp.sum((x_features - y_features)**2)\n\n  def __call__(self, x, y):\n    return self.loss(self.features(x), self.features(y))\n\n# Instantiate the model.\nrngs = nnx.Rngs(0)\nmodel = Foo(rngs=rngs)\n\n# Dummy input for testing\nx, y = rngs.normal((2,8))\n```\n\nHere, `self.sow` will store intermediate values under the key `'features'` in a collection associated with the\n`nnx.Intermediate` type. If you want to log values to multiple different collections, you can use different subclasses of `nnx.Intermediate`\nfor each collection.\n\nNow, we can wrap the module with the `nnx.capture` decorator, which wraps any `Callable` accepting a module as its argument (which includes `nnx.Module`s, their methods, or ordinary functions) to return both the resulting loss as well as any intermediate values stored to the `nnx.Intermediate` collection:\n\n```python\ncapturing_model = nnx.capture(model, nnx.Intermediate)\nresult, intms = capturing_model(x, y)\njax.tree.map(lambda a: a.shape, intms)\n```\n\nNote that, by default, sow appends values every time it is called. We can see\nthis in the *features* intermediate values logged above. It contains a tuple with one element for the call on `x` and one for the call on `y`. To override the default append behavior, specify `init_fn` and `reduce_fn` - see `Module.sow()`.\n\n## How `nnx.capture` Works\n\n`nnx.capture` works by temporarily installing a set of mutable capture buffers on every module in the graph before calling the wrapped function, then harvesting those buffers afterward. Before calling the wrapped function, `capture` walks the entire module graph with `iter_modules`. For each module it sets a `__captures__` attribute: a tuple of Variable instances, one per requested `var_type`. Each Variable holds a plain `dict` that maps sow-key → accumulated value.\n\nWe can see this `__captures__` tuple by printing out the module contents during a `nnx.capture` call:\n\n```python\n@nnx.capture(nnx.Intermediate)\ndef print_captures(model):\n      print(\"Captures:\", model.__captures__)\n_, intms = print_captures(nnx.Module())\n```\n\n`Module.sow` looks for the Variable in the `__captures__` tuple whose type matches `variable_type`, then writes its value into that dict using `reduce_fn`.\n\nIf no matching type is found, `sow` silently returns `False` without logging the value. This can be used to capture only a subset of the sown values. For example:\n\n```python\nclass Metric1(nnx.Intermediate):\n    pass\n\nclass Metric2(nnx.Intermediate):\n    pass\n\n@nnx.capture(Metric1)\ndef get_captures(model):\n    model.sow(Metric1, 'gets_sown', jnp.ones(2))\n    model.sow(Metric2, 'gets_ignored', jnp.ones(2))\n_, intms = get_captures(nnx.Module())\njax.tree.map(lambda a: a.shape, intms)\n```\n\n## Capturing all intermediate values\n\nTo observe the output of each method without manually adding calls to `sow`, we can call `nnx.capture` with the `method_outputs` argument. This will automatically `sow` the output of each method using the given variable type, including methods of sub-modules.\n\n```python\nclass Foo(nnx.Module):\n  def __init__(self, *, rngs: nnx.Rngs):\n    self.dense1 = nnx.Linear(8, 32, rngs=rngs)\n    self.dense2 = nnx.Linear(32, 1, rngs=rngs)\n\n  def features(self, x, rngs= None):\n      feature = nnx.relu(self.dense1(x))\n      return feature\n\n  def loss(self, x_features, y_features):\n    return jnp.sum((x_features - y_features)**2)\n\n  def __call__(self, x, y):\n    return self.loss(self.features(x), self.features(y))\n\nmodel = Foo(rngs=nnx.Rngs(0))\ncapturing_model = nnx.capture(model, nnx.Intermediate, method_outputs=nnx.Intermediate)\nresult, intms = capturing_model(x, y)\njax.tree.map(lambda a: a.shape, intms)\n```\n\nThis pattern should be considered the \"sledge hammer\" approach to capturing intermediates. As a debugging and inspection tool it is very useful, but using the other patterns described in this guide will give you more fine-grained control over what intermediates you want to extract. We can also combine the `method_output_type` argument with manual calls to sow to capture both layer outputs and computations mid-layer.\n\n## Extracting gradients of intermediate values\n\nFor debugging purposes, it can be useful to extract the gradients of intermediate values. This is a little tricky: jax doesn't have a stable mechanism for sowing information from the backward pass into to objects from the forward pass. Instead, we record the gradients of intermediate values using the `Module.perturb` method as follows:\n\n```python\nclass Model(nnx.Module):\n  def __call__(self, x):\n    x2 = self.perturb('grad_of_x', x)\n    self.sow(nnx.Intermediate, 'activations', x2)\n    return 3 * x2\n\nmodel = Model()\n\ndef train_step(model, x):\n    _, perturbations = nnx.capture(model, nnx.Perturbation)(x)\n    def loss(model, perturbations, x):\n        return nnx.capture(model, nnx.Intermediate, init=perturbations)(x)\n\n    (grads, perturb_grads), sowed = nnx.grad(loss, argnums=(0, 1), has_aux=True)(model, perturbations, x)\n    return nnx.merge_state(perturb_grads, sowed)\n\ntrain_step(model, 1.0)\n```\n\nThere are four steps:\n\n**Step One: Initialize *perturbations* of the model**.\n\nWe do this with a call to `nnx.capture(model, nnx.Perturbation)`. Before the call, `capture` installs `__captures__` on the module — a tuple containing one empty `Perturbation` buffer (as described in \"How `nnx.capture` Works\" above). When `self.perturb` runs, it checks `__captures__` for a matching `Perturbation` Variable, initialises the slot to `zeros_like(value)`, and returns `zeros + x`. After the call, `__captures__` is removed and the filled buffer is returned as `perturbations`.\n\n```python\nclass Model(nnx.Module):\n  def __call__(self, x):\n    print(\"before perturb:\", self.__captures__)\n    x2 = self.perturb('grad_of_x', x)\n    print(\"after  perturb:\", self.__captures__)\n    self.sow(nnx.Intermediate, 'activations', x2)\n    # sow is a no-op: Intermediate is not in __captures__, so it returns False silently\n    return 3 * x2\n\nmodel = Model()\n_, perturbations = nnx.capture(model, nnx.Perturbation)(1.0)\nprint(perturbations)\n```\n\nThere are only two differences between `sow` and `perturb`:\n\n- The `nnx.Variable` tag used for values written with `self.perturb` is `nnx.Perturbation` rather than `nnx.Intermediate`.\n    \n- `perturb` returns the logged value. You must use this returned value rather than the original value for the gradient capturing machinery to work.\n\nThe `var_types` argument to `capture` restricts which of the logged values we want to return. Because we only want the intermediates logged with `self.perturb` statements, we only capture `nnx.Perturbation` types.\n\n**Step Two: Run the model again, but add in these perturbations**.\n\nCall `capture` again with `init=perturbations`. `capture` first builds a mapping from module path to the Variables in `init`, then uses it to pre-populate `__captures__`. Now `__captures__` has *two* buffers: an empty `Intermediate` buffer (from `var_types`) and a `Perturbation` buffer pre-populated from `init`. `self.perturb` finds the pre-populated buffer and returns `x + perturbation`; `self.sow` writes into the `Intermediate` buffer as normal.\n\n```python\nclass Model(nnx.Module):\n  def __call__(self, x):\n    print(\"before perturb:\", self.__captures__)\n    x2 = self.perturb('grad_of_x', x)\n    self.sow(nnx.Intermediate, 'activations', x2)\n    print(\"after  sow:    \", self.__captures__)\n    return 3 * x2\n\nmodel = Model()\n_, interms = nnx.capture(model, nnx.Intermediate, init=perturbations)(1.0)\n```\n\nThis changes the behavior of `x2 = self.perturb('name', x)` to essentially be `x2 = x + perturbations['name']`. The gradient of our output with respect to `x` will be the same as the gradient with respect to the perturbation, because JAX can differentiate through the addition with respect to the perturbation value stored in the capture dict.\n\n**Step Three: Take gradients**.\n\nSpecifically, take the gradient of this second `capture` call with respect to the perturbation arguments. JAX traces through exactly the same `__captures__` setup as Step Two, but with abstract (traced) array values instead of concrete ones. This will give us the same values as the gradients with respect to the intermediate variables. If we want to track intermediate variables in the forward pass at the same time, we'll need to return the intermediate values output of the `capture` call as well, so we'll need to pass `has_aux=True` to `nnx.grad`.\n\n**Step Four: Combine intermediate states**\n\nMerge the `State` object we get from the perturbation gradients with the `State` object for forward intermediates with `nnx.merge_state(perturb_grads, sowed)`. At this point `__captures__` no longer exists on any module — it was cleaned up at the end of the `capture` call in Step Three.\n\n\n## NNX Transforms and Capturing\n\n`nnx.capture` composes with NNX transforms such as `nnx.vmap`. The main thing to keep in mind is that perturbations must be initialized with a run that has the same batch structure as the training step that will consume them.\n\nConsider a model that calls both `sow` and `perturb`:\n\n```python\nclass Foo(nnx.Module):\n  def __init__(self, dim):\n    self.w = nnx.Param(jax.random.normal(jax.random.key(0), dim))\n\n  def __call__(self, x):\n    x = self.perturb('grad_of_x', x)\n    y = jnp.dot(x, self.w)\n    self.sow(nnx.Intermediate, 'y', y)\n    return y\n```\n\nThe training step vmaps `loss_grad` over a batch of inputs and perturbations, while the model weights are shared across the batch (`in_axes=None`):\n\n```python\n@nnx.jit\ndef train_step(model, x):\n  _, perturbations = init_perturbations(model, x)\n  def loss_grad(model, perturbations, x):\n    def loss(model, perturbations, x):\n      loss, interms = nnx.capture(model, nnx.Intermediate, init=perturbations)(x)\n      return loss, interms\n    (grads, perturb_grads), sowed = nnx.grad(loss, argnums=(0, 1), has_aux=True)(model, perturbations, x)\n    return grads, nnx.merge_state(perturb_grads, sowed)\n  return nnx.vmap(loss_grad, in_axes=(None, 0, 0))(model, perturbations, x)\n```\n\nAfter every training step, we can sum the gradients and pass them to an `Optimizer` to adjust the model, as usual. But we can also look at the full batch of sown values and perturbations.\n\nBecause `train_step` expects `perturbations` to have a leading batch axis (axis 0), the perturbation initialization run must also produce a batched `perturbations` state. We do this inside an `init_perturbations` method that splits the model and vmaps the run with `in_axes=(0, None, 0)` for `(intermediates, params, x)`.\n\n```python\n@nnx.capture(nnx.Perturbation)\ndef init_perturbations(model, x):\n    graphdef, intms, params = nnx.split(model, nnx.Intermediate, nnx.Param)\n    def forward(intms, params, x):\n      return nnx.merge(graphdef, intms, params)(x)\n    return nnx.vmap(forward, in_axes=(0, None, 0))(intms, params, x)\n```\n\nPutting it together:\n\n```python\nmodel, x = Foo(4), jnp.ones((3, 4))\n_, intermediates = train_step(model, x)\njax.tree.map(lambda a: a.shape, intermediates)\n```\n\nThe pattern generalises: whenever a transform introduces a new batch axis over which `capture` runs, initialize perturbations with a matching vmapped pre-run so that the `init=perturbations` argument inside the transform has the correct shape.\n"
  },
  {
    "path": "docs_nnx/guides/filters_guide.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"95b08e64\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Filters\\n\",\n    \"\\n\",\n    \"Flax NNX uses [`Filter`s](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/filterlib.html) extensively as a way to create [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State) groups in APIs, such as [`nnx.split`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.split), [`nnx.state()`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.state), and many of the [Flax NNX transformations (transforms)](https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html).\\n\",\n    \"\\n\",\n    \"In this guide you will learn how to:\\n\",\n    \"\\n\",\n    \"* Use [`Filter`s](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/filterlib.html) to group Flax NNX variables and states into subgroups;\\n\",\n    \"* Understand relationships between types, such as [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param) or [`nnx.BatchStat`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.BatchStat), and [`Filter`s](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/filterlib.html);\\n\",\n    \"* Express your `Filter`s flexibly with [`nnx.filterlib.Filter`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/filterlib.html) language.\\n\",\n    \"\\n\",\n    \"In the following example [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param) and [`nnx.BatchStat`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.BatchStat) are used as `Filter`s to split the model into two groups: one with the parameters and the other with the batch statistics:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"id\": \"45485345\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"params = State({\\n\",\n      \"  'a': Param(\\n\",\n      \"    value=0\\n\",\n      \"  )\\n\",\n      \"})\\n\",\n      \"batch_stats = State({\\n\",\n      \"  'b': BatchStat(\\n\",\n      \"    value=True\\n\",\n      \"  )\\n\",\n      \"})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from flax import nnx\\n\",\n    \"\\n\",\n    \"class Foo(nnx.Module):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.a = nnx.Param(0)\\n\",\n    \"    self.b = nnx.BatchStat(True)\\n\",\n    \"\\n\",\n    \"foo = Foo()\\n\",\n    \"\\n\",\n    \"graphdef, params, batch_stats = nnx.split(foo, nnx.Param, nnx.BatchStat)\\n\",\n    \"\\n\",\n    \"print(f'{params = }')\\n\",\n    \"print(f'{batch_stats = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"8f77e99a\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's dive deeper into `Filter`s.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a0413d64\",\n   \"metadata\": {},\n   \"source\": [\n    \"## The `Filter` Protocol\\n\",\n    \"\\n\",\n    \"In general, Flax `Filter`s are predicate functions of the form:\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"\\n\",\n    \"(path: tuple[Key, ...], value: Any) -> bool\\n\",\n    \"\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"where:\\n\",\n    \"\\n\",\n    \"- `Key` is a hashable and comparable type;\\n\",\n    \"- `path` is a tuple of `Key`s representing the path to the value in a nested structure; and\\n\",\n    \"- `value` is the value at the path.\\n\",\n    \"\\n\",\n    \"The function returns `True` if the value should be included in the group, and `False` otherwise.\\n\",\n    \"\\n\",\n    \"Types are not functions of this form. They are treated as `Filter`s because, as you will learn in the next section, types and some other literals are converted to _predicates_. For example, [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param) is roughly converted to a predicate like this:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"id\": \"30f4c868\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"is_param((), nnx.Param(0)) = True\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"def is_param(path, value) -> bool:\\n\",\n    \"  return isinstance(value, nnx.Param)\\n\",\n    \"\\n\",\n    \"print(f'{is_param((), nnx.Param(0)) = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a8a2641e\",\n   \"metadata\": {},\n   \"source\": [\n    \"Such function matches any value that is an instance of [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param). Internally Flax NNX uses `OfType` which defines a callable of this form for a given type:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"id\": \"b3095221\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"is_param((), nnx.Param(0)) = True\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"is_param = nnx.OfType(nnx.Param)\\n\",\n    \"\\n\",\n    \"print(f'{is_param((), nnx.Param(0)) = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"87c06e39\",\n   \"metadata\": {},\n   \"source\": [\n    \"## The `Filter` DSL\\n\",\n    \"\\n\",\n    \"Flax NNX exposes a small domain specific language ([DSL](https://en.wikipedia.org/wiki/Domain-specific_language)), formalized as the [`nnx.filterlib.Filter`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/filterlib.html) type. This means users don't have to create functions like in the previous section.\\n\",\n    \"\\n\",\n    \"Here is a list of all the callable `Filter`s included in Flax NNX, and their corresponding DSL literals (when available):\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"| Literal | Callable | Description |\\n\",\n    \"|--------|----------------------|-------------|\\n\",\n    \"| `...` or `True` | `Everything()` | Matches all values |\\n\",\n    \"| `None` or `False` | `Nothing()` | Matches no values |\\n\",\n    \"| `type` | `OfType(type)` | Matches values that are instances of `type` or have a `type` attribute that is an instance of `type` |\\n\",\n    \"| | `PathContains(key)` | Matches values that have an associated `path` that contains the given `key` |\\n\",\n    \"| `'{filter}'` <span style=\\\"color:gray\\\">str</span> | `WithTag('{filter}')` | Matches values that have string `tag` attribute equal to `'{filter}'`. Used by `RngKey` and `RngCount`. |\\n\",\n    \"| `(*filters)` <span style=\\\"color:gray\\\">tuple</span> or `[*filters]` <span style=\\\"color:gray\\\">list</span> | `Any(*filters)` | Matches values that match any of the inner `filters` |\\n\",\n    \"| | `All(*filters)` | Matches values that match all of the inner `filters` |\\n\",\n    \"| | `Not(filter)` | Matches values that do not match the inner `filter` |\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"Let's check out the DSL in action by using [`nnx.vmap`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.vmap) as an example. Consider the following:\\n\",\n    \"\\n\",\n    \"1) You want to vectorize all parameters;\\n\",\n    \"2) Apply `'dropout'` `Rng(Keys|Counts)` on the `0`th axis; and\\n\",\n    \"3) Broadcast the rest.\\n\",\n    \"\\n\",\n    \"To do this, you can use the following `Filter`s to define a `nnx.StateAxes` object that you can pass to `nnx.vmap`'s `in_axes` to specify how the `model`'s various sub-states should be vectorized:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"id\": \"d38b7694\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"state_axes = nnx.StateAxes({(nnx.Param, 'dropout'): 0, ...: None})\\n\",\n    \"\\n\",\n    \"@nnx.vmap(in_axes=(state_axes, 0))\\n\",\n    \"def forward(model, x):\\n\",\n    \"  ...\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"bd60f0e1\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here `(nnx.Param, 'dropout')` expands to `Any(OfType(nnx.Param), WithTag('dropout'))` and `...` expands to `Everything()`.\\n\",\n    \"\\n\",\n    \"If you wish to manually convert literal into a predicate, you can use [`nnx.filterlib.to_predicate`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/filterlib.html#flax.nnx.filterlib.to_predicate):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"id\": \"7e065fa9\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"is_param = OfType(<class 'flax.nnx.variablelib.Param'>)\\n\",\n      \"everything = Everything()\\n\",\n      \"nothing = Nothing()\\n\",\n      \"params_or_dropout = Any(OfType(<class 'flax.nnx.variablelib.Param'>), WithTag('dropout'))\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"is_param = nnx.filterlib.to_predicate(nnx.Param)\\n\",\n    \"everything = nnx.filterlib.to_predicate(...)\\n\",\n    \"nothing = nnx.filterlib.to_predicate(False)\\n\",\n    \"params_or_dropout = nnx.filterlib.to_predicate((nnx.Param, 'dropout'))\\n\",\n    \"\\n\",\n    \"print(f'{is_param = }')\\n\",\n    \"print(f'{everything = }')\\n\",\n    \"print(f'{nothing = }')\\n\",\n    \"print(f'{params_or_dropout = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"db9b4cf3\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Grouping `State`s\\n\",\n    \"\\n\",\n    \"With the knowledge of `Filter`s from previous sections at hand, let's learn how to roughly implement [`nnx.split`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.split). Here are the key ideas:\\n\",\n    \"\\n\",\n    \"* Use `nnx.graph.flatten` to get the [`GraphDef`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.GraphDef) and [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State) representation of the node.\\n\",\n    \"* Convert all the `Filter`s to predicates.\\n\",\n    \"* Use `State.flat_state` to get the flat representation of the state.\\n\",\n    \"* Traverse all the `(path, value)` pairs in the flat state and group them according to the predicates.\\n\",\n    \"* Use `State.from_flat_state` to convert the flat states to nested [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State)s.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"id\": \"068208fc\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"params = State({\\n\",\n      \"  'a': Param(\\n\",\n      \"    value=0\\n\",\n      \"  )\\n\",\n      \"})\\n\",\n      \"batch_stats = State({\\n\",\n      \"  'b': BatchStat(\\n\",\n      \"    value=True\\n\",\n      \"  )\\n\",\n      \"})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from typing import Any\\n\",\n    \"KeyPath = tuple[nnx.graph.Key, ...]\\n\",\n    \"\\n\",\n    \"def split(node, *filters):\\n\",\n    \"  graphdef, state = nnx.graph.flatten(node)\\n\",\n    \"  predicates = [nnx.filterlib.to_predicate(f) for f in filters]\\n\",\n    \"  flat_states: list[dict[KeyPath, Any]] = [{} for p in predicates]\\n\",\n    \"\\n\",\n    \"  for path, value in state:\\n\",\n    \"    for i, predicate in enumerate(predicates):\\n\",\n    \"      if predicate(path, value):\\n\",\n    \"        flat_states[i][path] = value\\n\",\n    \"        break\\n\",\n    \"    else:\\n\",\n    \"      raise ValueError(f'No filter matched {path = } {value = }')\\n\",\n    \"\\n\",\n    \"  states: tuple[nnx.GraphState, ...] = tuple(\\n\",\n    \"    nnx.State.from_flat_path(flat_state) for flat_state in flat_states\\n\",\n    \"  )\\n\",\n    \"  return graphdef, *states\\n\",\n    \"\\n\",\n    \"# Let's test it.\\n\",\n    \"foo = Foo()\\n\",\n    \"\\n\",\n    \"graphdef, params, batch_stats = split(foo, nnx.Param, nnx.BatchStat)\\n\",\n    \"\\n\",\n    \"print(f'{params = }')\\n\",\n    \"print(f'{batch_stats = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"7b3aeac8\",\n   \"metadata\": {},\n   \"source\": [\n    \"**Note:*** It's very important to know that **filtering is order-dependent**. The first `Filter` that matches a value will keep it, and therefore you should place more specific `Filter`s before more general `Filter`s.\\n\",\n    \"\\n\",\n    \"For example, as demonstrated below, if you:\\n\",\n    \"\\n\",\n    \"1) Create a `SpecialParam` type that is a subclass of [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param), and a `Bar` object (subclassing [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html)) that contains both types of parameters; and\\n\",\n    \"2) Try to split the [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param)s before the `SpecialParam`s\\n\",\n    \"\\n\",\n    \"then all the values will be placed in the [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param) group, and the `SpecialParam` group will be empty because all `SpecialParam`s are also [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param)s:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"id\": \"014da4d4\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"params = State({\\n\",\n      \"  'a': Param(\\n\",\n      \"    value=0\\n\",\n      \"  ),\\n\",\n      \"  'b': SpecialParam(\\n\",\n      \"    value=0\\n\",\n      \"  )\\n\",\n      \"})\\n\",\n      \"special_params = State({})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class SpecialParam(nnx.Param):\\n\",\n    \"  pass\\n\",\n    \"\\n\",\n    \"class Bar(nnx.Module):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.a = nnx.Param(0)\\n\",\n    \"    self.b = SpecialParam(0)\\n\",\n    \"\\n\",\n    \"bar = Bar()\\n\",\n    \"\\n\",\n    \"graphdef, params, special_params = split(bar, nnx.Param, SpecialParam) # wrong!\\n\",\n    \"print(f'{params = }')\\n\",\n    \"print(f'{special_params = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a9f0b7b8\",\n   \"metadata\": {},\n   \"source\": [\n    \"And reversing the order will ensure that the `SpecialParam` are captured first:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"id\": \"a2ebf5b2\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"params = State({\\n\",\n      \"  'a': Param(\\n\",\n      \"    value=0\\n\",\n      \"  )\\n\",\n      \"})\\n\",\n      \"special_params = State({\\n\",\n      \"  'b': SpecialParam(\\n\",\n      \"    value=0\\n\",\n      \"  )\\n\",\n      \"})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"graphdef, special_params, params = split(bar, SpecialParam, nnx.Param) # correct!\\n\",\n    \"print(f'{params = }')\\n\",\n    \"print(f'{special_params = }')\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.9\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs_nnx/guides/filters_guide.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Filters\n\nFlax NNX uses [`Filter`s](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/filterlib.html) extensively as a way to create [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State) groups in APIs, such as [`nnx.split`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.split), [`nnx.state()`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.state), and many of the [Flax NNX transformations (transforms)](https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html).\n\nIn this guide you will learn how to:\n\n* Use [`Filter`s](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/filterlib.html) to group Flax NNX variables and states into subgroups;\n* Understand relationships between types, such as [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param) or [`nnx.BatchStat`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.BatchStat), and [`Filter`s](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/filterlib.html);\n* Express your `Filter`s flexibly with [`nnx.filterlib.Filter`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/filterlib.html) language.\n\nIn the following example [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param) and [`nnx.BatchStat`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.BatchStat) are used as `Filter`s to split the model into two groups: one with the parameters and the other with the batch statistics:\n\n```{code-cell} ipython3\nfrom flax import nnx\n\nclass Foo(nnx.Module):\n  def __init__(self):\n    self.a = nnx.Param(0)\n    self.b = nnx.BatchStat(True)\n\nfoo = Foo()\n\ngraphdef, params, batch_stats = nnx.split(foo, nnx.Param, nnx.BatchStat)\n\nprint(f'{params = }')\nprint(f'{batch_stats = }')\n```\n\nLet's dive deeper into `Filter`s.\n\n+++\n\n## The `Filter` Protocol\n\nIn general, Flax `Filter`s are predicate functions of the form:\n\n```python\n\n(path: tuple[Key, ...], value: Any) -> bool\n\n```\n\nwhere:\n\n- `Key` is a hashable and comparable type;\n- `path` is a tuple of `Key`s representing the path to the value in a nested structure; and\n- `value` is the value at the path.\n\nThe function returns `True` if the value should be included in the group, and `False` otherwise.\n\nTypes are not functions of this form. They are treated as `Filter`s because, as you will learn in the next section, types and some other literals are converted to _predicates_. For example, [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param) is roughly converted to a predicate like this:\n\n```{code-cell} ipython3\ndef is_param(path, value) -> bool:\n  return isinstance(value, nnx.Param)\n\nprint(f'{is_param((), nnx.Param(0)) = }')\n```\n\nSuch function matches any value that is an instance of [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param). Internally Flax NNX uses `OfType` which defines a callable of this form for a given type:\n\n```{code-cell} ipython3\nis_param = nnx.OfType(nnx.Param)\n\nprint(f'{is_param((), nnx.Param(0)) = }')\n```\n\n## The `Filter` DSL\n\nFlax NNX exposes a small domain specific language ([DSL](https://en.wikipedia.org/wiki/Domain-specific_language)), formalized as the [`nnx.filterlib.Filter`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/filterlib.html) type. This means users don't have to create functions like in the previous section.\n\nHere is a list of all the callable `Filter`s included in Flax NNX, and their corresponding DSL literals (when available):\n\n\n| Literal | Callable | Description |\n|--------|----------------------|-------------|\n| `...` or `True` | `Everything()` | Matches all values |\n| `None` or `False` | `Nothing()` | Matches no values |\n| `type` | `OfType(type)` | Matches values that are instances of `type` or have a `type` attribute that is an instance of `type` |\n| | `PathContains(key)` | Matches values that have an associated `path` that contains the given `key` |\n| `'{filter}'` <span style=\"color:gray\">str</span> | `WithTag('{filter}')` | Matches values that have string `tag` attribute equal to `'{filter}'`. Used by `RngKey` and `RngCount`. |\n| `(*filters)` <span style=\"color:gray\">tuple</span> or `[*filters]` <span style=\"color:gray\">list</span> | `Any(*filters)` | Matches values that match any of the inner `filters` |\n| | `All(*filters)` | Matches values that match all of the inner `filters` |\n| | `Not(filter)` | Matches values that do not match the inner `filter` |\n\n\nLet's check out the DSL in action by using [`nnx.vmap`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.vmap) as an example. Consider the following:\n\n1) You want to vectorize all parameters;\n2) Apply `'dropout'` `Rng(Keys|Counts)` on the `0`th axis; and\n3) Broadcast the rest.\n\nTo do this, you can use the following `Filter`s to define a `nnx.StateAxes` object that you can pass to `nnx.vmap`'s `in_axes` to specify how the `model`'s various sub-states should be vectorized:\n\n```{code-cell} ipython3\nstate_axes = nnx.StateAxes({(nnx.Param, 'dropout'): 0, ...: None})\n\n@nnx.vmap(in_axes=(state_axes, 0))\ndef forward(model, x):\n  ...\n```\n\nHere `(nnx.Param, 'dropout')` expands to `Any(OfType(nnx.Param), WithTag('dropout'))` and `...` expands to `Everything()`.\n\nIf you wish to manually convert literal into a predicate, you can use [`nnx.filterlib.to_predicate`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/filterlib.html#flax.nnx.filterlib.to_predicate):\n\n```{code-cell} ipython3\nis_param = nnx.filterlib.to_predicate(nnx.Param)\neverything = nnx.filterlib.to_predicate(...)\nnothing = nnx.filterlib.to_predicate(False)\nparams_or_dropout = nnx.filterlib.to_predicate((nnx.Param, 'dropout'))\n\nprint(f'{is_param = }')\nprint(f'{everything = }')\nprint(f'{nothing = }')\nprint(f'{params_or_dropout = }')\n```\n\n## Grouping `State`s\n\nWith the knowledge of `Filter`s from previous sections at hand, let's learn how to roughly implement [`nnx.split`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.split). Here are the key ideas:\n\n* Use `nnx.graph.flatten` to get the [`GraphDef`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.GraphDef) and [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State) representation of the node.\n* Convert all the `Filter`s to predicates.\n* Use `State.flat_state` to get the flat representation of the state.\n* Traverse all the `(path, value)` pairs in the flat state and group them according to the predicates.\n* Use `State.from_flat_state` to convert the flat states to nested [`nnx.State`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/state.html#flax.nnx.State)s.\n\n```{code-cell} ipython3\nfrom typing import Any\nKeyPath = tuple[nnx.graph.Key, ...]\n\ndef split(node, *filters):\n  graphdef, state = nnx.graph.flatten(node)\n  predicates = [nnx.filterlib.to_predicate(f) for f in filters]\n  flat_states: list[dict[KeyPath, Any]] = [{} for p in predicates]\n\n  for path, value in state:\n    for i, predicate in enumerate(predicates):\n      if predicate(path, value):\n        flat_states[i][path] = value\n        break\n    else:\n      raise ValueError(f'No filter matched {path = } {value = }')\n\n  states: tuple[nnx.GraphState, ...] = tuple(\n    nnx.State.from_flat_path(flat_state) for flat_state in flat_states\n  )\n  return graphdef, *states\n\n# Let's test it.\nfoo = Foo()\n\ngraphdef, params, batch_stats = split(foo, nnx.Param, nnx.BatchStat)\n\nprint(f'{params = }')\nprint(f'{batch_stats = }')\n```\n\n**Note:*** It's very important to know that **filtering is order-dependent**. The first `Filter` that matches a value will keep it, and therefore you should place more specific `Filter`s before more general `Filter`s.\n\nFor example, as demonstrated below, if you:\n\n1) Create a `SpecialParam` type that is a subclass of [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param), and a `Bar` object (subclassing [`nnx.Module`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html)) that contains both types of parameters; and\n2) Try to split the [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param)s before the `SpecialParam`s\n\nthen all the values will be placed in the [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param) group, and the `SpecialParam` group will be empty because all `SpecialParam`s are also [`nnx.Param`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Param)s:\n\n```{code-cell} ipython3\nclass SpecialParam(nnx.Param):\n  pass\n\nclass Bar(nnx.Module):\n  def __init__(self):\n    self.a = nnx.Param(0)\n    self.b = SpecialParam(0)\n\nbar = Bar()\n\ngraphdef, params, special_params = split(bar, nnx.Param, SpecialParam) # wrong!\nprint(f'{params = }')\nprint(f'{special_params = }')\n```\n\nAnd reversing the order will ensure that the `SpecialParam` are captured first:\n\n```{code-cell} ipython3\ngraphdef, special_params, params = split(bar, SpecialParam, nnx.Param) # correct!\nprint(f'{params = }')\nprint(f'{special_params = }')\n```\n"
  },
  {
    "path": "docs_nnx/guides/flax_gspmd.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Scale up on multiple devices\\n\",\n    \"\\n\",\n    \"This guide demonstrates how to scale up a Flax NNX model on multiple accelerators (GPUs or Google TPUs) using JAX's parallel programming APIs.\\n\",\n    \"\\n\",\n    \"[Introduction to Parallel Programming](https://docs.jax.dev/en/latest/sharded-computation.html) is a fantastic guide to learn about the distributed programming essentials of JAX. It describes three parallelism APIs - automatic, explicit and manual - for different levels of control.\\n\",\n    \"\\n\",\n    \"This guide will primarily cover the automatic scenario, which use the [`jax.jit`](https://jax.readthedocs.io/en/latest/jit-compilation.html) to compile your single-device code as multi-device. You will use [`flax.nnx.spmd`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/spmd.html) APIs to annotate your model variables with how it should be sharded.\\n\",\n    \"\\n\",\n    \"If you want to follow explicit sharding style, follow [JAX Explicit Sharding](https://docs.jax.dev/en/latest/notebooks/explicit-sharding.html) guide and use JAX's relevant APIs. No API on Flax side is needed.\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Setup\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"You have 8 “fake” JAX devices now: [CpuDevice(id=0), CpuDevice(id=1), CpuDevice(id=2), CpuDevice(id=3), CpuDevice(id=4), CpuDevice(id=5), CpuDevice(id=6), CpuDevice(id=7)]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from functools import partial\\n\",\n    \"\\n\",\n    \"import jax\\n\",\n    \"from jax import numpy as jnp\\n\",\n    \"from jax.sharding import PartitionSpec as P, NamedSharding, AxisType\\n\",\n    \"import optax\\n\",\n    \"import flax\\n\",\n    \"from flax import nnx\\n\",\n    \"\\n\",\n    \"# Ignore this if you are already running on a TPU or GPU\\n\",\n    \"if not jax._src.xla_bridge.backends_are_initialized():\\n\",\n    \"  jax.config.update('jax_num_cpu_devices', 8)\\n\",\n    \"print(f'You have 8 “fake” JAX devices now: {jax.devices()}')\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Set up a `2x4` device mesh as the [JAX data sharding tutorial](https://docs.jax.dev/en/latest/sharded-computation.html#key-concept-data-sharding) instructs.\\n\",\n    \"\\n\",\n    \"In this guide we use a standard FSDP layout and shard our devices on two axes - `data` and `model`, for doing batch data parallelism and tensor parallelism.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Create an auto-mode mesh of two dimensions and annotate each axis with a name.\\n\",\n    \"auto_mesh = jax.make_mesh((2, 4), ('data', 'model'))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"> Compatibility Note: This guide covers the [eager sharding feature](https://flax.readthedocs.io/en/latest/flip/4844-var-eager-sharding.html) that greatly simplifies creating sharded model. If your project already used Flax GSPMD API on version `flax<0.12`, you might have turned the feature off to keep your code working. Users can toggle this feature using the `nnx.use_eager_sharding` function.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"nnx.use_eager_sharding(True)\\n\",\n    \"assert nnx.using_eager_sharding()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"c24144d8\",\n   \"metadata\": {},\n   \"source\": [\n    \"The `nnx.use_eager_sharding` function can also be used as a context manager to toggle the eager sharding feature within a specific scope.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"2d849e2e\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"with nnx.use_eager_sharding(False):\\n\",\n    \"  assert not nnx.using_eager_sharding()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"c9f808ec\",\n   \"metadata\": {},\n   \"source\": [\n    \"You can also enable eager sharding on a per-variable basis by passing `eager_sharding=False` during variable initialization. The mesh can also be passed this way.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"67bbd440\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"nnx.Param(jnp.ones(4,4), out_sharding=(None, 'model'), eager_sharding=True, mesh=auto_mesh)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Shard a single-array model\\n\",\n    \"\\n\",\n    \"Let's begin by sharding the simplest component possible - a Flax variable.\\n\",\n    \"\\n\",\n    \"When you define a Flax variable, you can pass in a metadata field called `out_sharding`, to specify how the underlying JAX array should be sharded. This field should be a tuple of names, each of which refer to how an axis of the array should be sharded.\\n\",\n    \"\\n\",\n    \"**You must have an existing device mesh** and create a sharding-annotated `nnx.Variable` within its scope. This allows the result variable to be sharded accordingly on those devices. The device mesh can be your actual accelerator mesh, or a dummy fake CPU mesh like in this notebook.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"PartitionSpec(None, 'model')\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\"><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #ad494a\\\">            </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #b5cf6b\\\">            </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #ad494a\\\">            </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #b5cf6b\\\">            </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #ad494a\\\">            </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #b5cf6b\\\">            </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #ad494a\\\">            </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #b5cf6b\\\">            </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #ad494a\\\">            </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #b5cf6b\\\">            </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">  CPU 0,4   </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">  CPU 1,5   </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #ad494a\\\">  CPU 2,6   </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #b5cf6b\\\">  CPU 3,7   </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #ad494a\\\">            </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #b5cf6b\\\">            </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #ad494a\\\">            </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #b5cf6b\\\">            </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #ad494a\\\">            </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #b5cf6b\\\">            </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #ad494a\\\">            </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #b5cf6b\\\">            </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">            </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #ad494a\\\">            </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #b5cf6b\\\">            </span>\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74m            \\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107m            \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74m            \\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107m            \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74m            \\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107m            \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74m            \\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107m            \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74m            \\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107m            \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;57;59;121mCPU 0,4\\u001b[0m\\u001b[38;2;255;255;255;48;2;57;59;121m   \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214mCPU 1,5\\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m   \\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74mCPU 2,6\\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74m   \\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107m  \\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107mCPU 3,7\\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107m   \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74m            \\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107m            \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74m            \\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107m            \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74m            \\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107m            \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74m            \\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107m            \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m            \\u001b[0m\\u001b[38;2;255;255;255;48;2;173;73;74m            \\u001b[0m\\u001b[38;2;0;0;0;48;2;181;207;107m            \\u001b[0m\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"\\n\",\n    \"with jax.set_mesh(auto_mesh):\\n\",\n    \"  w = nnx.Param(\\n\",\n    \"    rngs.lecun_normal()((4, 8)),\\n\",\n    \"    out_sharding=(None, 'model')\\n\",\n    \"  )\\n\",\n    \"  print(w.sharding.spec)\\n\",\n    \"  jax.debug.visualize_array_sharding(w)  # already sharded!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Initialize with style\\n\",\n    \"\\n\",\n    \"When using existing modules, you can apply [`flax.nnx.with_partitioning`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/spmd.html#flax.nnx.with_partitioning) on initializers to achieve the same effect. Here we create a sharded `nnx.Linear` module with only the kernel weight.\\n\",\n    \"\\n\",\n    \"Also, you should use `jax.jit` for the whole initialization for maximum performance. This is because without `jax.jit`, a single-device variable must be created first before we apply sharding constraints and then make it sharded, which is wasteful. `jax.jit` will automatically optimize this out.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"@jax.jit\\n\",\n    \"def init_sharded_linear(key):\\n\",\n    \"  init_fn = nnx.nn.linear.default_kernel_init\\n\",\n    \"  # Shard your parameter along `model` dimension, as in model/tensor parallelism\\n\",\n    \"  return nnx.Linear(4, 8, use_bias=False, rngs=nnx.Rngs(key),\\n\",\n    \"                    kernel_init=nnx.with_partitioning(init_fn, (None, 'model')))\\n\",\n    \"\\n\",\n    \"with jax.set_mesh(auto_mesh):\\n\",\n    \"  key= rngs()\\n\",\n    \"  linear = init_sharded_linear(key)\\n\",\n    \"  assert linear.kernel.sharding.spec == P(None, 'model') # already sharded!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Run the model\\n\",\n    \"\\n\",\n    \"If you also shard your input correctly, JAX would be able to carry out the most natural and optimized computation and produce your output as sharded.\\n\",\n    \"\\n\",\n    \"You should still make sure to `jax.jit` for maximum performance, and also to explicitly control how each array is sharded when you want to. We will give an example of that control in the next section.\\n\",\n    \"\\n\",\n    \"> Note: You need to `jax.jit` a pure function that takes the model as an argument, instead of jitting the callable model directly.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"PartitionSpec('data', 'model')\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\"><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">  CPU 0  </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">  CPU 1  </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">  CPU 2  </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">  CPU 3  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span>\\n\",\n       \"<span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">  CPU 4  </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">  CPU 5  </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">  CPU 6  </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">  CPU 7  </span>\\n\",\n       \"<span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;57;59;121mCPU 0\\u001b[0m\\u001b[38;2;255;255;255;48;2;57;59;121m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107mCPU 1\\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82mCPU 2\\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214mCPU 3\\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;0;0;0;48;2;231;203;148m  \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148mCPU 4\\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207mCPU 5\\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148mCPU 6\\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49mCPU 7\\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# For simple computations, you can get correctly-sharded output without jitting\\n\",\n    \"# In this case, ('data', None) @ (None, 'model') = ('data', 'model')\\n\",\n    \"with jax.set_mesh(auto_mesh):\\n\",\n    \"  # Create your input data, sharded along `data` dimension, as in data parallelism\\n\",\n    \"  x = jax.device_put(jnp.ones((16, 4)), P('data', None))\\n\",\n    \"\\n\",\n    \"  # Run the model forward function, jitted\\n\",\n    \"  y = jax.jit(lambda m, x: m(x))(linear, x)\\n\",\n    \"  print(y.sharding.spec)                       # sharded: ('data', 'model')\\n\",\n    \"  jax.debug.visualize_array_sharding(y)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Shard a wholesome model\\n\",\n    \"\\n\",\n    \"Now we construct a more wholesome model to show a few advanced tricks. Check out this simple `DotReluDot` module that does two matmuls, and the `MultiDotReluDot` module that creates an arbitrary stack of `DotReluDot` sublayers.\\n\",\n    \"\\n\",\n    \"Make note of the following:\\n\",\n    \"\\n\",\n    \"* **Additional axis annotation**: Transforms like `vmap` and `scan` will add additional dimensions to the JAX arrays. Unfortunately, in auto sharding mode you will need to use `nnx.vmap` and `nnx.scan` instead of raw JAX transforms, so that both JAX and Flax knows how to shard this dimension. You won't need this in [explicit sharding mode](#explicit-sharding).\\n\",\n    \"\\n\",\n    \"* [`jax.lax.with_sharding_constraint`](https://docs.jax.dev/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html#constraining-shardings-of-intermediates-in-jitted-code): They can help you to enforce specific shardings on intermediate activations. Only works under an auto mode mesh context.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class DotReluDot(nnx.Module):\\n\",\n    \"  def __init__(self, depth: int, rngs: nnx.Rngs):\\n\",\n    \"    init_fn = nnx.initializers.lecun_normal()\\n\",\n    \"    self.dot1 = nnx.Linear(\\n\",\n    \"      depth, depth,\\n\",\n    \"      kernel_init=nnx.with_partitioning(init_fn, (None, 'model')),\\n\",\n    \"      use_bias=False,  # or use `bias_init` to give it annotation too\\n\",\n    \"      rngs=rngs)\\n\",\n    \"    self.w2 = nnx.Param(\\n\",\n    \"      init_fn(rngs.params(), (depth, depth)),  # RNG key and shape for W2 creation\\n\",\n    \"      sharding=('model', None),\\n\",\n    \"    )\\n\",\n    \"\\n\",\n    \"  def __call__(self, x: jax.Array):\\n\",\n    \"    y = self.dot1(x)\\n\",\n    \"    y = jax.nn.relu(y)\\n\",\n    \"    y = jax.lax.with_sharding_constraint(y, P('data', 'model'))\\n\",\n    \"    z = jnp.dot(y, self.w2[...])\\n\",\n    \"    return z\\n\",\n    \"\\n\",\n    \"class MultiDotReluDot(nnx.Module):\\n\",\n    \"  def __init__(self, depth: int, num_layers: int, rngs: nnx.Rngs):\\n\",\n    \"    # Annotate the additional axis with sharding=None, meaning it will be\\n\",\n    \"    # replicated across all devices.\\n\",\n    \"    @nnx.vmap(transform_metadata={nnx.PARTITION_NAME: None})\\n\",\n    \"    def create_sublayers(r):\\n\",\n    \"      return DotReluDot(depth, r)\\n\",\n    \"    self.layers = create_sublayers(rngs.fork(split=num_layers))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    def scan_over_layers(x, layer):\\n\",\n    \"      return layer(x), None\\n\",\n    \"    x, _ = jax.lax.scan(scan_over_layers, x, self.layers)\\n\",\n    \"    return x\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now a sample training loop, using `jax.jit`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"1.251457\\n\",\n      \"0.8495563\\n\",\n      \"0.6590716\\n\",\n      \"0.5399748\\n\",\n      \"0.39150265\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@jax.jit\\n\",\n    \"def train_step(model, optimizer, x, y):\\n\",\n    \"  def loss_fn(model: DotReluDot):\\n\",\n    \"    y_pred = model(x)\\n\",\n    \"    return jnp.mean((y_pred - y) ** 2)\\n\",\n    \"\\n\",\n    \"  loss, grads = jax.value_and_grad(loss_fn)(model)\\n\",\n    \"  optimizer.update(model, grads)\\n\",\n    \"  return model, loss\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"with jax.set_mesh(auto_mesh):\\n\",\n    \"  # Training data\\n\",\n    \"  input = jax.device_put(rngs.normal((8, 1024)), P('data', None))\\n\",\n    \"  label = jax.device_put(rngs.normal((8, 1024)), P('data', None))\\n\",\n    \"  # Model and optimizer\\n\",\n    \"  model = MultiDotReluDot(1024, 2, rngs=nnx.Rngs(0))\\n\",\n    \"  optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\\n\",\n    \"\\n\",\n    \"  # The loop\\n\",\n    \"  for i in range(5):\\n\",\n    \"    model, loss = train_step(model, optimizer, input, label)\\n\",\n    \"    print(loss)    # Model (over-)fitting to the labels quickly.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Profiling\\n\",\n    \"\\n\",\n    \"If you are using a Google TPU pod or a pod slice, you can create a custom `block_all()` utility function, as defined below, to measure the performance:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"13 ms ± 588 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"%%timeit\\n\",\n    \"\\n\",\n    \"def block_all(xs):\\n\",\n    \"  jax.tree_util.tree_map(lambda x: x.block_until_ready(), xs)\\n\",\n    \"  return xs\\n\",\n    \"\\n\",\n    \"with jax.set_mesh(auto_mesh):\\n\",\n    \"  new_state = block_all(train_step(model, optimizer, input, label))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Load a sharded model from a checkpoint\\n\",\n    \"\\n\",\n    \"Now you learned how to initialize a sharded model without OOM, but what about saving and loading it from a checkpoint on disk? JAX checkpointing libraries, such as [Orbax](https://orbax.readthedocs.io/en/latest/), support loading a model distributedly if a sharding pytree is provided. Below is an example that uses Orbax's `StandardCheckpointer` API.\\n\",\n    \"\\n\",\n    \"Make sure you save a model's state, especially if your model shares some variables across modules. Given a You can generate an identical abstract pytree with shardings using Flax’s `nnx.get_abstract_model`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"PartitionSpec(None, None, 'model')\\n\",\n      \"(2, 1024, 1024)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"import orbax.checkpoint as ocp\\n\",\n    \"\\n\",\n    \"# Save the sharded state.\\n\",\n    \"sharded_state = nnx.state(model)\\n\",\n    \"path = ocp.test_utils.erase_and_create_empty('/tmp/my-checkpoints/')\\n\",\n    \"checkpointer = ocp.StandardCheckpointer()\\n\",\n    \"checkpointer.save(path / 'checkpoint_name', sharded_state)\\n\",\n    \"\\n\",\n    \"# Load a sharded state from the checkpoint.\\n\",\n    \"graphdef, abs_state = nnx.get_abstract_model(\\n\",\n    \"  lambda: MultiDotReluDot(1024, 2, rngs=nnx.Rngs(0)), auto_mesh)\\n\",\n    \"restored_state = checkpointer.restore(path / 'checkpoint_name',\\n\",\n    \"                                      target=abs_state)\\n\",\n    \"restored_model = nnx.merge(graphdef, abs_state)\\n\",\n    \"print(restored_model.layers.dot1.kernel.sharding.spec)\\n\",\n    \"print(restored_model.layers.dot1.kernel.shape)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Logical axis annotation\\n\",\n    \"\\n\",\n    \"JAX's [automatic](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html) [SPMD](https://jax.readthedocs.io/en/latest/glossary.html#term-SPMD) encourages users to explore different sharding layouts to find the optimal one. To this end, in Flax you have the option to annotate with more descriptive axis names (not just device mesh axis names like `'data'` and `'model'`), as long as you provide a mapping from your alias to the device mesh axes.\\n\",\n    \"\\n\",\n    \"You can provide the mapping along with the annotation as another metadata of the corresponding [`nnx.Variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Variable), or overwrite it at top-level. Check out the `LogicalDotReluDot` example below.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# The mapping from alias annotation to the device mesh.\\n\",\n    \"sharding_rules = (('batch', 'data'), ('hidden', 'model'), ('embed', None))\\n\",\n    \"\\n\",\n    \"class LogicalDotReluDot(nnx.Module):\\n\",\n    \"  def __init__(self, depth: int, rngs: nnx.Rngs):\\n\",\n    \"    init_fn = nnx.initializers.lecun_normal()\\n\",\n    \"    self.dot1 = nnx.Linear(\\n\",\n    \"      depth, depth,\\n\",\n    \"      kernel_init=nnx.with_partitioning(init_fn, ('embed', 'hidden')),\\n\",\n    \"      use_bias=False,  # or use `bias_init` to give it annotation too\\n\",\n    \"      rngs=rngs)\\n\",\n    \"    self.w2 = nnx.Param(\\n\",\n    \"      init_fn(rngs.params(), (depth, depth)),  # RNG key and shape for W2 creation\\n\",\n    \"      sharding=('hidden', 'embed'),\\n\",\n    \"    )\\n\",\n    \"\\n\",\n    \"  def __call__(self, x: jax.Array):\\n\",\n    \"    y = self.dot1(x)\\n\",\n    \"    y = jax.nn.relu(y)\\n\",\n    \"    # Unfortunately the logical aliasing doesn't work on lower-level JAX calls.\\n\",\n    \"    y = jax.lax.with_sharding_constraint(y, P('data', None))\\n\",\n    \"    z = jnp.dot(y, self.w2[...])\\n\",\n    \"    return z\\n\",\n    \"\\n\",\n    \"class LogicalMultiDotReluDot(nnx.Module):\\n\",\n    \"  def __init__(self, depth: int, num_layers: int, rngs: nnx.Rngs):\\n\",\n    \"    @nnx.vmap(transform_metadata={nnx.PARTITION_NAME: None})\\n\",\n    \"    def create_sublayers(r):\\n\",\n    \"      return LogicalDotReluDot(depth, r)\\n\",\n    \"    self.layers = create_sublayers(rngs.fork(split=num_layers))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    def scan_over_layers(x, layer):\\n\",\n    \"      return layer(x), None\\n\",\n    \"    x, _ = jax.lax.scan(scan_over_layers, x, self.layers)\\n\",\n    \"    return x\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If you didn't provide all `sharding_rule` annotations in the model definition, you can apply them at top level by put them into the context via `nnx.logical_axis_rules`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"with jax.set_mesh(auto_mesh), nnx.logical_axis_rules(sharding_rules):\\n\",\n    \"  # Model and optimizer\\n\",\n    \"  logical_model = LogicalMultiDotReluDot(1024, 2, rngs=nnx.Rngs(0))\\n\",\n    \"  logical_output = logical_model(input)\\n\",\n    \"\\n\",\n    \"# Check out their equivalency with some easier-to-read sharding descriptions.\\n\",\n    \"assert logical_model.layers.dot1.kernel.sharding.is_equivalent_to(\\n\",\n    \"  NamedSharding(auto_mesh, P(None, None, 'model')), ndim=3\\n\",\n    \")\\n\",\n    \"assert logical_model.layers.w2.sharding.is_equivalent_to(\\n\",\n    \"  NamedSharding(auto_mesh, P(None, 'model', None)), ndim=3\\n\",\n    \")\\n\",\n    \"assert logical_output.sharding.is_equivalent_to(\\n\",\n    \"  NamedSharding(auto_mesh, P('data', None)), ndim=2\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### When to use device axis / logical axis\\n\",\n    \"\\n\",\n    \"Choosing when to use a device or logical axis depends on how much you want to control the partitioning of your model:\\n\",\n    \"\\n\",\n    \"* **Device mesh axis**:\\n\",\n    \"\\n\",\n    \"  * For a simpler model, this can save you a few extra lines of code of converting the logical naming back to the device naming.\\n\",\n    \"\\n\",\n    \"  * Shardings of intermediate *activation* values can only be done via [`jax.lax.with_sharding_constraint`](https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.with_sharding_constraint.html) and device mesh axis. Therefore, if you want super fine-grained control over your model's sharding, directly using device mesh axis names everywhere might be less confusing.\\n\",\n    \"\\n\",\n    \"* **Logical naming**: This is helpful if you want to experiment around and find the most optimal partition layout for your *model weights*.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Explicit sharding\\n\",\n    \"\\n\",\n    \"[Explicit sharding](https://docs.jax.dev/en/latest/notebooks/explicit-sharding.html), also called \\\"sharding-in-types\\\", is a new JAX sharding feature that allows every sharding of every array to be deterministic and explicit. Instead of letting XLA compiler figure out the shardings, you as user would explicitly state the shardings via JAX APIs.\\n\",\n    \"\\n\",\n    \"For education purposes, we provide a simple Flax model example using explicit sharding. Note how you specify shardings for this model:\\n\",\n    \"\\n\",\n    \"* Parameters: `out_sharding` argument passed into JAX initializers.\\n\",\n    \"\\n\",\n    \"* Ambigious computations like `jnp.dot`: provide `out_sharding` argument to specify the output sharding.\\n\",\n    \"\\n\",\n    \"* Additional dimension from transforms: use `jax.vmap`'s argument `spmd_axis_name`, instead of Flax lifted transforms.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"PartitionSpec(None, None, 'model')\\n\",\n      \"PartitionSpec(None, 'model', None)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Explicit axis mesh\\n\",\n    \"explicit_mesh = jax.make_mesh((2, 4), ('data', 'model'),\\n\",\n    \"                              axis_types=(AxisType.Explicit, AxisType.Explicit))\\n\",\n    \"\\n\",\n    \"class ExplicitDotReluDot(nnx.Module):\\n\",\n    \"  def __init__(self, depth: int, rngs: nnx.Rngs):\\n\",\n    \"    init_fn = nnx.initializers.lecun_normal()\\n\",\n    \"    self.dot1 = nnx.Linear(\\n\",\n    \"      depth, depth,\\n\",\n    \"      kernel_init=partial(init_fn, out_sharding=P(None, 'model')),\\n\",\n    \"      use_bias=False,\\n\",\n    \"      rngs=rngs)\\n\",\n    \"    self.w2 = nnx.Param(\\n\",\n    \"      init_fn(rngs.params(), (depth, depth), out_sharding=P('model', None)),\\n\",\n    \"    )\\n\",\n    \"    self.b2 = nnx.Param(jnp.zeros((depth, ), out_sharding=P(None,)))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x: jax.Array):\\n\",\n    \"    y = self.dot1(x)\\n\",\n    \"    y = jax.nn.relu(y)\\n\",\n    \"    z = jnp.dot(y, self.w2[...], out_sharding=P('data', None))\\n\",\n    \"    z = z + self.b2\\n\",\n    \"    return z\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"class ExplicitMultiDotReluDot(nnx.Module):\\n\",\n    \"  def __init__(self, depth: int, num_layers: int, rngs: nnx.Rngs):\\n\",\n    \"    # Annotate the additional axis with sharding=None, meaning it will be\\n\",\n    \"    # replicated across all devices.\\n\",\n    \"    @partial(jax.vmap, spmd_axis_name=None)\\n\",\n    \"    def create_sublayers(r):\\n\",\n    \"      return ExplicitDotReluDot(depth, r)\\n\",\n    \"    self.layers = create_sublayers(rngs.fork(split=num_layers))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    def scan_over_layers(x, layer):\\n\",\n    \"      return layer(x), None\\n\",\n    \"    x, _ = jax.lax.scan(scan_over_layers, x, self.layers)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"with jax.set_mesh(explicit_mesh):\\n\",\n    \"  model = ExplicitMultiDotReluDot(1024, 2, rngs=nnx.Rngs(0))\\n\",\n    \"  x = jax.device_put(rngs.normal((8, 1024)),\\n\",\n    \"                     NamedSharding(explicit_mesh, P('data', None)))\\n\",\n    \"  y = model(x)\\n\",\n    \"\\n\",\n    \"print(model.layers.dot1.kernel.sharding.spec)\\n\",\n    \"print(model.layers.w2.sharding.spec)\\n\",\n    \"assert x.sharding.is_equivalent_to(y.sharding, ndim=2)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"One thing easier in explicit mode is that you can obtain the abstract array tree with shardings via `jax.eval_shape`, instead of calling `nnx.get_abstract_sharding`. This is not possible in auto mode.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"PartitionSpec(None, None, 'model')\\n\",\n      \"PartitionSpec(None, 'model', None)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Get the sharding tree to load checkpoint with\\n\",\n    \"with jax.set_mesh(explicit_mesh):\\n\",\n    \"  abs_model = jax.eval_shape(\\n\",\n    \"    lambda: ExplicitMultiDotReluDot(1024, 2, rngs=nnx.Rngs(0)))\\n\",\n    \"  print(abs_model.layers.dot1.kernel.sharding.spec)\\n\",\n    \"  print(abs_model.layers.w2.sharding.spec)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Further readings\\n\",\n    \"\\n\",\n    \"JAX has abundant documentation on scaled computing.\\n\",\n    \"\\n\",\n    \"- [Introduction to parallel programming](https://jax.readthedocs.io/en/latest/sharded-computation.html): A 101 level tutorial covering the basics of automatic parallelization with [`jax.jit`](https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html#jax.jit), semi-automatic parallelization with [`jax.jit`](https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html) and [`jax.lax.with_sharding_constraint`](https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.with_sharding_constraint.html), and manual sharding with [`shard_map`](https://jax.readthedocs.io/en/latest/_autosummary/jax.experimental.shard_map.shard_map.html#jax.experimental.shard_map.shard_map).\\n\",\n    \"- [JAX in multi-process environments](https://jax.readthedocs.io/en/latest/multi_process.html).\\n\",\n    \"- [Distributed arrays and automatic parallelization](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html): A more detailed tutorial about parallelization with [`jax.jit`](https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html#jax.jit) and [`jax.lax.with_sharding_constraint`](https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.with_sharding_constraint.html).\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.9\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "docs_nnx/guides/flax_gspmd.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Scale up on multiple devices\n\nThis guide demonstrates how to scale up a Flax NNX model on multiple accelerators (GPUs or Google TPUs) using JAX's parallel programming APIs.\n\n[Introduction to Parallel Programming](https://docs.jax.dev/en/latest/sharded-computation.html) is a fantastic guide to learn about the distributed programming essentials of JAX. It describes three parallelism APIs - automatic, explicit and manual - for different levels of control.\n\nThis guide will primarily cover the automatic scenario, which use the [`jax.jit`](https://jax.readthedocs.io/en/latest/jit-compilation.html) to compile your single-device code as multi-device. You will use [`flax.nnx.spmd`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/spmd.html) APIs to annotate your model variables with how it should be sharded.\n\nIf you want to follow explicit sharding style, follow [JAX Explicit Sharding](https://docs.jax.dev/en/latest/notebooks/explicit-sharding.html) guide and use JAX's relevant APIs. No API on Flax side is needed.\n\n+++\n\n### Setup\n\n```{code-cell} ipython3\nfrom functools import partial\n\nimport jax\nfrom jax import numpy as jnp\nfrom jax.sharding import PartitionSpec as P, NamedSharding, AxisType\nimport optax\nimport flax\nfrom flax import nnx\n\n# Ignore this if you are already running on a TPU or GPU\nif not jax._src.xla_bridge.backends_are_initialized():\n  jax.config.update('jax_num_cpu_devices', 8)\nprint(f'You have 8 “fake” JAX devices now: {jax.devices()}')\n```\n\nSet up a `2x4` device mesh as the [JAX data sharding tutorial](https://docs.jax.dev/en/latest/sharded-computation.html#key-concept-data-sharding) instructs.\n\nIn this guide we use a standard FSDP layout and shard our devices on two axes - `data` and `model`, for doing batch data parallelism and tensor parallelism.\n\n```{code-cell} ipython3\n# Create an auto-mode mesh of two dimensions and annotate each axis with a name.\nauto_mesh = jax.make_mesh((2, 4), ('data', 'model'))\n```\n\n> Compatibility Note: This guide covers the [eager sharding feature](https://flax.readthedocs.io/en/latest/flip/4844-var-eager-sharding.html) that greatly simplifies creating sharded model. If your project already used Flax GSPMD API on version `flax<0.12`, you might have turned the feature off to keep your code working. Users can toggle this feature using the `nnx.use_eager_sharding` function.\n\n```{code-cell} ipython3\nnnx.use_eager_sharding(True)\nassert nnx.using_eager_sharding()\n```\n\nThe `nnx.use_eager_sharding` function can also be used as a context manager to toggle the eager sharding feature within a specific scope.\n\n```{code-cell} ipython3\nwith nnx.use_eager_sharding(False):\n  assert not nnx.using_eager_sharding()\n```\n\nYou can also enable eager sharding on a per-variable basis by passing `eager_sharding=False` during variable initialization. The mesh can also be passed this way.\n\n```{code-cell} ipython3\nnnx.Param(jnp.ones(4,4), out_sharding=(None, 'model'), eager_sharding=True, mesh=auto_mesh)\n```\n\n## Shard a single-array model\n\nLet's begin by sharding the simplest component possible - a Flax variable.\n\nWhen you define a Flax variable, you can pass in a metadata field called `out_sharding`, to specify how the underlying JAX array should be sharded. This field should be a tuple of names, each of which refer to how an axis of the array should be sharded.\n\n**You must have an existing device mesh** and create a sharding-annotated `nnx.Variable` within its scope. This allows the result variable to be sharded accordingly on those devices. The device mesh can be your actual accelerator mesh, or a dummy fake CPU mesh like in this notebook.\n\n```{code-cell} ipython3\nrngs = nnx.Rngs(0)\n\nwith jax.set_mesh(auto_mesh):\n  w = nnx.Param(\n    rngs.lecun_normal()((4, 8)),\n    out_sharding=(None, 'model')\n  )\n  print(w.sharding.spec)\n  jax.debug.visualize_array_sharding(w)  # already sharded!\n```\n\n### Initialize with style\n\nWhen using existing modules, you can apply [`flax.nnx.with_partitioning`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/spmd.html#flax.nnx.with_partitioning) on initializers to achieve the same effect. Here we create a sharded `nnx.Linear` module with only the kernel weight.\n\nAlso, you should use `jax.jit` for the whole initialization for maximum performance. This is because without `jax.jit`, a single-device variable must be created first before we apply sharding constraints and then make it sharded, which is wasteful. `jax.jit` will automatically optimize this out.\n\n```{code-cell} ipython3\n@jax.jit\ndef init_sharded_linear(key):\n  init_fn = nnx.nn.linear.default_kernel_init\n  # Shard your parameter along `model` dimension, as in model/tensor parallelism\n  return nnx.Linear(4, 8, use_bias=False, rngs=nnx.Rngs(key),\n                    kernel_init=nnx.with_partitioning(init_fn, (None, 'model')))\n\nwith jax.set_mesh(auto_mesh):\n  key= rngs()\n  linear = init_sharded_linear(key)\n  assert linear.kernel.sharding.spec == P(None, 'model') # already sharded!\n```\n\n### Run the model\n\nIf you also shard your input correctly, JAX would be able to carry out the most natural and optimized computation and produce your output as sharded.\n\nYou should still make sure to `jax.jit` for maximum performance, and also to explicitly control how each array is sharded when you want to. We will give an example of that control in the next section.\n\n> Note: You need to `jax.jit` a pure function that takes the model as an argument, instead of jitting the callable model directly.\n\n```{code-cell} ipython3\n# For simple computations, you can get correctly-sharded output without jitting\n# In this case, ('data', None) @ (None, 'model') = ('data', 'model')\nwith jax.set_mesh(auto_mesh):\n  # Create your input data, sharded along `data` dimension, as in data parallelism\n  x = jax.device_put(jnp.ones((16, 4)), P('data', None))\n\n  # Run the model forward function, jitted\n  y = jax.jit(lambda m, x: m(x))(linear, x)\n  print(y.sharding.spec)                       # sharded: ('data', 'model')\n  jax.debug.visualize_array_sharding(y)\n```\n\n## Shard a wholesome model\n\nNow we construct a more wholesome model to show a few advanced tricks. Check out this simple `DotReluDot` module that does two matmuls, and the `MultiDotReluDot` module that creates an arbitrary stack of `DotReluDot` sublayers.\n\nMake note of the following:\n\n* **Additional axis annotation**: Transforms like `vmap` and `scan` will add additional dimensions to the JAX arrays. Unfortunately, in auto sharding mode you will need to use `nnx.vmap` and `nnx.scan` instead of raw JAX transforms, so that both JAX and Flax knows how to shard this dimension. You won't need this in [explicit sharding mode](#explicit-sharding).\n\n* [`jax.lax.with_sharding_constraint`](https://docs.jax.dev/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html#constraining-shardings-of-intermediates-in-jitted-code): They can help you to enforce specific shardings on intermediate activations. Only works under an auto mode mesh context.\n\n```{code-cell} ipython3\nclass DotReluDot(nnx.Module):\n  def __init__(self, depth: int, rngs: nnx.Rngs):\n    init_fn = nnx.initializers.lecun_normal()\n    self.dot1 = nnx.Linear(\n      depth, depth,\n      kernel_init=nnx.with_partitioning(init_fn, (None, 'model')),\n      use_bias=False,  # or use `bias_init` to give it annotation too\n      rngs=rngs)\n    self.w2 = nnx.Param(\n      init_fn(rngs.params(), (depth, depth)),  # RNG key and shape for W2 creation\n      sharding=('model', None),\n    )\n\n  def __call__(self, x: jax.Array):\n    y = self.dot1(x)\n    y = jax.nn.relu(y)\n    y = jax.lax.with_sharding_constraint(y, P('data', 'model'))\n    z = jnp.dot(y, self.w2[...])\n    return z\n\nclass MultiDotReluDot(nnx.Module):\n  def __init__(self, depth: int, num_layers: int, rngs: nnx.Rngs):\n    # Annotate the additional axis with sharding=None, meaning it will be\n    # replicated across all devices.\n    @nnx.vmap(transform_metadata={nnx.PARTITION_NAME: None})\n    def create_sublayers(r):\n      return DotReluDot(depth, r)\n    self.layers = create_sublayers(rngs.fork(split=num_layers))\n\n  def __call__(self, x):\n    def scan_over_layers(x, layer):\n      return layer(x), None\n    x, _ = jax.lax.scan(scan_over_layers, x, self.layers)\n    return x\n```\n\nNow a sample training loop, using `jax.jit`.\n\n```{code-cell} ipython3\n@jax.jit\ndef train_step(model, optimizer, x, y):\n  def loss_fn(model: DotReluDot):\n    y_pred = model(x)\n    return jnp.mean((y_pred - y) ** 2)\n\n  loss, grads = jax.value_and_grad(loss_fn)(model)\n  optimizer.update(model, grads)\n  return model, loss\n\n\nwith jax.set_mesh(auto_mesh):\n  # Training data\n  input = jax.device_put(rngs.normal((8, 1024)), P('data', None))\n  label = jax.device_put(rngs.normal((8, 1024)), P('data', None))\n  # Model and optimizer\n  model = MultiDotReluDot(1024, 2, rngs=nnx.Rngs(0))\n  optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\n\n  # The loop\n  for i in range(5):\n    model, loss = train_step(model, optimizer, input, label)\n    print(loss)    # Model (over-)fitting to the labels quickly.\n```\n\n## Profiling\n\nIf you are using a Google TPU pod or a pod slice, you can create a custom `block_all()` utility function, as defined below, to measure the performance:\n\n```{code-cell} ipython3\n%%timeit\n\ndef block_all(xs):\n  jax.tree_util.tree_map(lambda x: x.block_until_ready(), xs)\n  return xs\n\nwith jax.set_mesh(auto_mesh):\n  new_state = block_all(train_step(model, optimizer, input, label))\n```\n\n## Load a sharded model from a checkpoint\n\nNow you learned how to initialize a sharded model without OOM, but what about saving and loading it from a checkpoint on disk? JAX checkpointing libraries, such as [Orbax](https://orbax.readthedocs.io/en/latest/), support loading a model distributedly if a sharding pytree is provided. Below is an example that uses Orbax's `StandardCheckpointer` API.\n\nMake sure you save a model's state, especially if your model shares some variables across modules. Given a You can generate an identical abstract pytree with shardings using Flax’s `nnx.get_abstract_model`.\n\n```{code-cell} ipython3\nimport orbax.checkpoint as ocp\n\n# Save the sharded state.\nsharded_state = nnx.state(model)\npath = ocp.test_utils.erase_and_create_empty('/tmp/my-checkpoints/')\ncheckpointer = ocp.StandardCheckpointer()\ncheckpointer.save(path / 'checkpoint_name', sharded_state)\n\n# Load a sharded state from the checkpoint.\ngraphdef, abs_state = nnx.get_abstract_model(\n  lambda: MultiDotReluDot(1024, 2, rngs=nnx.Rngs(0)), auto_mesh)\nrestored_state = checkpointer.restore(path / 'checkpoint_name',\n                                      target=abs_state)\nrestored_model = nnx.merge(graphdef, abs_state)\nprint(restored_model.layers.dot1.kernel.sharding.spec)\nprint(restored_model.layers.dot1.kernel.shape)\n```\n\n## Logical axis annotation\n\nJAX's [automatic](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html) [SPMD](https://jax.readthedocs.io/en/latest/glossary.html#term-SPMD) encourages users to explore different sharding layouts to find the optimal one. To this end, in Flax you have the option to annotate with more descriptive axis names (not just device mesh axis names like `'data'` and `'model'`), as long as you provide a mapping from your alias to the device mesh axes.\n\nYou can provide the mapping along with the annotation as another metadata of the corresponding [`nnx.Variable`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/variables.html#flax.nnx.Variable), or overwrite it at top-level. Check out the `LogicalDotReluDot` example below.\n\n```{code-cell} ipython3\n# The mapping from alias annotation to the device mesh.\nsharding_rules = (('batch', 'data'), ('hidden', 'model'), ('embed', None))\n\nclass LogicalDotReluDot(nnx.Module):\n  def __init__(self, depth: int, rngs: nnx.Rngs):\n    init_fn = nnx.initializers.lecun_normal()\n    self.dot1 = nnx.Linear(\n      depth, depth,\n      kernel_init=nnx.with_partitioning(init_fn, ('embed', 'hidden')),\n      use_bias=False,  # or use `bias_init` to give it annotation too\n      rngs=rngs)\n    self.w2 = nnx.Param(\n      init_fn(rngs.params(), (depth, depth)),  # RNG key and shape for W2 creation\n      sharding=('hidden', 'embed'),\n    )\n\n  def __call__(self, x: jax.Array):\n    y = self.dot1(x)\n    y = jax.nn.relu(y)\n    # Unfortunately the logical aliasing doesn't work on lower-level JAX calls.\n    y = jax.lax.with_sharding_constraint(y, P('data', None))\n    z = jnp.dot(y, self.w2[...])\n    return z\n\nclass LogicalMultiDotReluDot(nnx.Module):\n  def __init__(self, depth: int, num_layers: int, rngs: nnx.Rngs):\n    @nnx.vmap(transform_metadata={nnx.PARTITION_NAME: None})\n    def create_sublayers(r):\n      return LogicalDotReluDot(depth, r)\n    self.layers = create_sublayers(rngs.fork(split=num_layers))\n\n  def __call__(self, x):\n    def scan_over_layers(x, layer):\n      return layer(x), None\n    x, _ = jax.lax.scan(scan_over_layers, x, self.layers)\n    return x\n```\n\nIf you didn't provide all `sharding_rule` annotations in the model definition, you can apply them at top level by put them into the context via `nnx.logical_axis_rules`.\n\n```{code-cell} ipython3\nwith jax.set_mesh(auto_mesh), nnx.logical_axis_rules(sharding_rules):\n  # Model and optimizer\n  logical_model = LogicalMultiDotReluDot(1024, 2, rngs=nnx.Rngs(0))\n  logical_output = logical_model(input)\n\n# Check out their equivalency with some easier-to-read sharding descriptions.\nassert logical_model.layers.dot1.kernel.sharding.is_equivalent_to(\n  NamedSharding(auto_mesh, P(None, None, 'model')), ndim=3\n)\nassert logical_model.layers.w2.sharding.is_equivalent_to(\n  NamedSharding(auto_mesh, P(None, 'model', None)), ndim=3\n)\nassert logical_output.sharding.is_equivalent_to(\n  NamedSharding(auto_mesh, P('data', None)), ndim=2\n)\n```\n\n### When to use device axis / logical axis\n\nChoosing when to use a device or logical axis depends on how much you want to control the partitioning of your model:\n\n* **Device mesh axis**:\n\n  * For a simpler model, this can save you a few extra lines of code of converting the logical naming back to the device naming.\n\n  * Shardings of intermediate *activation* values can only be done via [`jax.lax.with_sharding_constraint`](https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.with_sharding_constraint.html) and device mesh axis. Therefore, if you want super fine-grained control over your model's sharding, directly using device mesh axis names everywhere might be less confusing.\n\n* **Logical naming**: This is helpful if you want to experiment around and find the most optimal partition layout for your *model weights*.\n\n+++\n\n## Explicit sharding\n\n[Explicit sharding](https://docs.jax.dev/en/latest/notebooks/explicit-sharding.html), also called \"sharding-in-types\", is a new JAX sharding feature that allows every sharding of every array to be deterministic and explicit. Instead of letting XLA compiler figure out the shardings, you as user would explicitly state the shardings via JAX APIs.\n\nFor education purposes, we provide a simple Flax model example using explicit sharding. Note how you specify shardings for this model:\n\n* Parameters: `out_sharding` argument passed into JAX initializers.\n\n* Ambigious computations like `jnp.dot`: provide `out_sharding` argument to specify the output sharding.\n\n* Additional dimension from transforms: use `jax.vmap`'s argument `spmd_axis_name`, instead of Flax lifted transforms.\n\n```{code-cell} ipython3\n# Explicit axis mesh\nexplicit_mesh = jax.make_mesh((2, 4), ('data', 'model'),\n                              axis_types=(AxisType.Explicit, AxisType.Explicit))\n\nclass ExplicitDotReluDot(nnx.Module):\n  def __init__(self, depth: int, rngs: nnx.Rngs):\n    init_fn = nnx.initializers.lecun_normal()\n    self.dot1 = nnx.Linear(\n      depth, depth,\n      kernel_init=partial(init_fn, out_sharding=P(None, 'model')),\n      use_bias=False,\n      rngs=rngs)\n    self.w2 = nnx.Param(\n      init_fn(rngs.params(), (depth, depth), out_sharding=P('model', None)),\n    )\n    self.b2 = nnx.Param(jnp.zeros((depth, ), out_sharding=P(None,)))\n\n  def __call__(self, x: jax.Array):\n    y = self.dot1(x)\n    y = jax.nn.relu(y)\n    z = jnp.dot(y, self.w2[...], out_sharding=P('data', None))\n    z = z + self.b2\n    return z\n\n\nclass ExplicitMultiDotReluDot(nnx.Module):\n  def __init__(self, depth: int, num_layers: int, rngs: nnx.Rngs):\n    # Annotate the additional axis with sharding=None, meaning it will be\n    # replicated across all devices.\n    @partial(jax.vmap, spmd_axis_name=None)\n    def create_sublayers(r):\n      return ExplicitDotReluDot(depth, r)\n    self.layers = create_sublayers(rngs.fork(split=num_layers))\n\n  def __call__(self, x):\n    def scan_over_layers(x, layer):\n      return layer(x), None\n    x, _ = jax.lax.scan(scan_over_layers, x, self.layers)\n    return x\n\n\nwith jax.set_mesh(explicit_mesh):\n  model = ExplicitMultiDotReluDot(1024, 2, rngs=nnx.Rngs(0))\n  x = jax.device_put(rngs.normal((8, 1024)),\n                     NamedSharding(explicit_mesh, P('data', None)))\n  y = model(x)\n\nprint(model.layers.dot1.kernel.sharding.spec)\nprint(model.layers.w2.sharding.spec)\nassert x.sharding.is_equivalent_to(y.sharding, ndim=2)\n```\n\nOne thing easier in explicit mode is that you can obtain the abstract array tree with shardings via `jax.eval_shape`, instead of calling `nnx.get_abstract_sharding`. This is not possible in auto mode.\n\n```{code-cell} ipython3\n# Get the sharding tree to load checkpoint with\nwith jax.set_mesh(explicit_mesh):\n  abs_model = jax.eval_shape(\n    lambda: ExplicitMultiDotReluDot(1024, 2, rngs=nnx.Rngs(0)))\n  print(abs_model.layers.dot1.kernel.sharding.spec)\n  print(abs_model.layers.w2.sharding.spec)\n```\n\n## Further readings\n\nJAX has abundant documentation on scaled computing.\n\n- [Introduction to parallel programming](https://jax.readthedocs.io/en/latest/sharded-computation.html): A 101 level tutorial covering the basics of automatic parallelization with [`jax.jit`](https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html#jax.jit), semi-automatic parallelization with [`jax.jit`](https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html) and [`jax.lax.with_sharding_constraint`](https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.with_sharding_constraint.html), and manual sharding with [`shard_map`](https://jax.readthedocs.io/en/latest/_autosummary/jax.experimental.shard_map.shard_map.html#jax.experimental.shard_map.shard_map).\n- [JAX in multi-process environments](https://jax.readthedocs.io/en/latest/multi_process.html).\n- [Distributed arrays and automatic parallelization](https://jax.readthedocs.io/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html): A more detailed tutorial about parallelization with [`jax.jit`](https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html#jax.jit) and [`jax.lax.with_sharding_constraint`](https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.with_sharding_constraint.html).\n"
  },
  {
    "path": "docs_nnx/guides/index.rst",
    "content": "Guides\n======\n\n.. toctree::\n   :maxdepth: 2\n\n   ../guides_basic\n   ../guides_advanced\n"
  },
  {
    "path": "docs_nnx/guides/jax_and_nnx_transforms.rst",
    "content": "Flax NNX vs JAX transformations\n===============================\n\nThis guide describes the differences between\n`Flax NNX transformations <https://flax.readthedocs.io/en/latest/guides/transforms.html>`__\nand `JAX transformations <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`__,\nand how to seamlessly switch between them or use them side-by-side. The examples here will focus on\n``nnx.jit``, ``jax.jit``, ``nnx.grad`` and ``jax.grad`` function transformations (transforms).\n\nFirst, let's set up imports and generate some dummy data:\n\n.. testcode:: Flax NNX, JAX\n\n  from flax import nnx\n  import jax\n\n  x = jax.random.normal(jax.random.key(0), (1, 2))\n  y = jax.random.normal(jax.random.key(1), (1, 3))\n\nDifferences\n***********\n\nFlax NNX transformations can transform functions that are not pure and make mutations and\nside-effects:\n- Flax NNX transforms enable you to transform functions that take in Flax NNX graph objects as\narguments - such as ``nnx.Module``, ``nnx.Rngs``, ``nnx.Optimizer``, and so on - even those whose state\nwill be mutated.\n- In comparison, these kinds of objects aren't recognized in JAX transformations.\n\nThe Flax NNX `Functional API <https://flax.readthedocs.io/en/latest/nnx_basics.html#the-flax-functional-api>`_\nprovides a way to convert graph structures to `pytrees <https://jax.readthedocs.io/en/latest/working-with-pytrees.html>`__\nand back. By doing this at every function boundary you can effectively use graph structures with any\nJAX transforms and propagate state updates in a way consistent with functional purity.\n\nFlax NNX custom transforms, such as ``nnx.jit`` and ``nnx.grad``, simply remove the boilerplate, and\nas a result the code looks stateful.\n\nBelow is an example of using the ``nnx.jit`` and ``nnx.grad`` transforms compared to the\nthe code that uses ``jax.jit`` and ``jax.grad`` transforms.\n\nNotice that:\n\n- The function signature of Flax NNX-transformed functions can accept the ``nnx.Linear``\n  ``nnx.Module`` instances directly and make stateful updates to the ``Module``.\n- The function signature of JAX-transformed functions can only accept the pytree-registered\n  ``nnx.State`` and ``nnx.GraphDef`` objects, and must return an updated copy of them to maintain the\n  purity of the transformed function.\n\n.. codediff::\n  :title: Flax NNX transforms, JAX transforms\n  :groups: Flax NNX, JAX\n  :sync:\n\n  @nnx.jit\n  def train_step(model, x, y):\n    def loss_fn(model):\n      return ((model(x) - y) ** 2).mean()\n    grads = nnx.grad(loss_fn)(model)\n    params = nnx.state(model, nnx.Param)\n    params = jax.tree_util.tree_map(\n      lambda p, g: p - 0.1 * g, params, grads\n    )\n    nnx.update(model, params)\n\n  model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n  train_step(model, x, y)\n\n  ---\n  @jax.jit #!\n  def train_step(graphdef, state, x, y): #!\n    def loss_fn(graphdef, state): #!\n      model = nnx.merge(graphdef, state) #!\n      return ((model(x) - y) ** 2).mean()\n    grads = jax.grad(loss_fn, argnums=1)(graphdef, state) #!\n\n    model = nnx.merge(graphdef, state) #!\n    params = nnx.state(model, nnx.Param)\n    params = jax.tree_util.tree_map(\n      lambda p, g: p - 0.1 * g, params, grads\n    )\n    nnx.update(model, params)\n    return nnx.split(model) #!\n\n  graphdef, state = nnx.split(nnx.Linear(2, 3, rngs=nnx.Rngs(0))) #!\n  graphdef, state = train_step(graphdef, state, x, y) #!\n\n\nMixing Flax NNX and JAX transforms\n**********************************\n\nBoth Flax NNX transforms and JAX transforms can be mixed together, so long as the JAX-transformed function\nin your code is pure and has valid argument types that are recognized by JAX.\n\n.. codediff::\n  :title: Using ``nnx.jit`` with ``jax.grad``, Using ``jax.jit`` with ``nnx.grad``\n  :groups: Flax NNX, JAX\n  :sync:\n\n  @nnx.jit\n  def train_step(model, x, y):\n    def loss_fn(graphdef, state): #!\n      model = nnx.merge(graphdef, state)\n      return ((model(x) - y) ** 2).mean()\n    grads = jax.grad(loss_fn, 1)(*nnx.split(model)) #!\n    params = nnx.state(model, nnx.Param)\n    params = jax.tree_util.tree_map(\n      lambda p, g: p - 0.1 * g, params, grads\n    )\n    nnx.update(model, params)\n\n  model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n  train_step(model, x, y)\n\n  ---\n  @jax.jit #!\n  def train_step(graphdef, state, x, y): #!\n    model = nnx.merge(graphdef, state)\n    def loss_fn(model):\n      return ((model(x) - y) ** 2).mean()\n    grads = nnx.grad(loss_fn)(model)\n    params = nnx.state(model, nnx.Param)\n    params = jax.tree_util.tree_map(\n      lambda p, g: p - 0.1 * g, params, grads\n    )\n    nnx.update(model, params)\n    return nnx.split(model)\n\n  graphdef, state = nnx.split(nnx.Linear(2, 3, rngs=nnx.Rngs(0)))\n  graphdef, state = train_step(graphdef, state, x, y)\n"
  },
  {
    "path": "docs_nnx/guides/performance.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Performance considerations\\n\",\n    \"\\n\",\n    \"Currently, Flax [`nnx.jit`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.jit) traverses the object graph in pure Python which can add overhead. This overhead mostly affects small to medium models and can be mitigated in the following ways:\\n\",\n    \"* By leveraging JAX's [Asynchronous dispatch](#asynchronous-dispatch).\\n\",\n    \"* By using [nnx.cached_partial](#caching-graph-node-traversals) to cache the graph node traversals.\\n\",\n    \"* By using a [Functional training loop](#functional-training-loop) which stages out the graph traversals.\\n\",\n    \"\\n\",\n    \"A full resolution _might_ involve developing a C extension (e.g. `flaxlib`) to speed up some of the traversal logic in [`graph.py`](https://github.com/google/flax/blob/main/flax/nnx/graph.py). Before we continue lets an example of a model and a simple training loop:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from flax import nnx\\n\",\n    \"import jax\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"import optax\\n\",\n    \"\\n\",\n    \"class Model(nnx.Module):\\n\",\n    \"  def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\\n\",\n    \"    self.linear = nnx.Linear(din, dmid, rngs=rngs)\\n\",\n    \"    self.bn = nnx.BatchNorm(dmid, rngs=rngs)\\n\",\n    \"    self.dropout = nnx.Dropout(0.2, rngs=rngs)\\n\",\n    \"    self.linear_out = nnx.Linear(dmid, dout, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x = nnx.relu(self.dropout(self.bn(self.linear(x))))\\n\",\n    \"    return self.linear_out(x)\\n\",\n    \"  \\n\",\n    \"model = Model(2, 64, 3, rngs=nnx.Rngs(0))  # eager initialization\\n\",\n    \"optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\\n\",\n    \"metrics = nnx.MultiMetric(\\n\",\n    \"  loss=nnx.metrics.Average('loss'),\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"@nnx.jit  # <== currently slow\\n\",\n    \"def train_step(model, optimizer, metrics, x, y):\\n\",\n    \"  def loss_fn(model):\\n\",\n    \"    y_pred = model(x)  # call methods directly\\n\",\n    \"    return ((y_pred - y) ** 2).mean()\\n\",\n    \"\\n\",\n    \"  loss, grads = nnx.value_and_grad(loss_fn)(model)\\n\",\n    \"  optimizer.update(model, grads)  # in-place updates\\n\",\n    \"  metrics.update(loss=loss)\\n\",\n    \"\\n\",\n    \"  return loss\\n\",\n    \"  \\n\",\n    \"for _ in range(10):\\n\",\n    \"  x, y = jnp.ones((32, 2)), jnp.zeros((32, 3))\\n\",\n    \"  loss = train_step(model, optimizer, metrics, x, y)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Important thing here is that we created a `train_step()` function that uses `nnx.jit` and takes in a `model`, `optimizer`, and `metrics` arguments, all of which are Flax NNX objects. We'll later see how to improve this.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Asynchronous dispatch\\n\",\n    \"\\n\",\n    \"Asynchronous dispatch is a feature of JAX where it runs operations in the background whenever possible so Python can continue executing other code. This can be use to absorb the cost of data loading and in this case the overhead of `nnx.jit` and similar transforms. In general, as the amount of computation JAX has to perform per iteration increases the more it is able to absorb the python overhead since eventually the JAX computation will be the main blocker and programs with different overhead will have the same performance. This could be achieved in a couple of ways:\\n\",\n    \"\\n\",\n    \"* Increasing the batch size.\\n\",\n    \"* Increasing the model size.\\n\",\n    \"* Performing more JAX steps per python step if data loading is fast enough.\\n\",\n    \"\\n\",\n    \"To demonstrate this, the graph below which shows total time of running [benchmarks/nnx_simple_training.py](https://github.com/google/flax/blob/main/benchmarks/nnx_simple_training.py) for both `jax.jit` and `nnx.jit` with different model sizes:\\n\",\n    \"\\n\",\n    \"![performance-graph](images/performance-graph.png)\\n\",\n    \"\\n\",\n    \"As we can observe, after a certain model size both `jax.jit` and `nnx.jit` converge to the same runtime cost. This means we don't have to modify our training loop above.\\n\",\n    \"\\n\",\n    \"## Caching graph node traversals\\n\",\n    \"\\n\",\n    \"The simplest way to get rid of the traversal overhead entirely is by using `nnx.cached_partial` to convert a transformed function and the input graph objects into a partial function which caches the graph object and just expects the remaining arguments. In this example we use `nnx.cached_partial` over `train_step` and partially apply `model`, `optimizer`, and `metrics`, to create `cached_train_step`. Then we simply update our training loop to use `cached_train_step` which only expects the `x` and `y` inputs:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"cached_train_step = nnx.cached_partial(train_step, model, optimizer, metrics)\\n\",\n    \"\\n\",\n    \"for _ in range(10):\\n\",\n    \"  x, y = jnp.ones((32, 2)), jnp.zeros((32, 3))\\n\",\n    \"  loss = cached_train_step(x, y)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that `cached_partial` will enforce that the structure of the graph nodes doesn't change during `train_step` (no mutations except for `Variable` state update) so the cache is guaranteed to be up-to-date and we can avoid costly checks which require traversals. This is actually what is expected for most step functions as making any change here would imply costly recompilation, so enforcing this might be a secondary feature that could be useful for this purpose.\\n\",\n    \"\\n\",\n    \"Similarly, to prevent the user from mutating the cached objects outside, `cached_partial` creates a copy of all the graph nodes but, to allow state to be propagated to the original objects, they share references to the same `Variable`s.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Functional training loop\\n\",\n    \"\\n\",\n    \"To remove the Python overhead we can create a functional training loop that uses regular `jax.jit` in combination with `nnx.split` and `nnx.merge` to stage out the traversal logic. Concretely we can use [`nnx.split`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.split) before the training loop to create a single `graphdef` and `state` pytrees for all the graph nodes. Then we change `train_step()` to accept `graphdef` and `state`, and use [`nnx.merge`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.merge) to recreate the objects inside, and either `nnx.split` or `nnx.state` at the end to get the output `state`. At the end of the training loop or whenever needed we can use [`nnx.update`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.update) to update the objects to the current `state`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# split before training loop\\n\",\n    \"graphdef, state = nnx.split((model, optimizer, metrics))\\n\",\n    \"\\n\",\n    \"@jax.jit  # regular JAX\\n\",\n    \"def jax_train_step(graphdef, state, x, y):\\n\",\n    \"  # merge at the beginning of the function\\n\",\n    \"  model, optimizer, metrics = nnx.merge(graphdef, state)\\n\",\n    \"\\n\",\n    \"  def loss_fn(model):\\n\",\n    \"    y_pred = model(x)  # call methods directly\\n\",\n    \"    return ((y_pred - y) ** 2).mean()\\n\",\n    \"\\n\",\n    \"  loss, grads = nnx.value_and_grad(loss_fn)(model)\\n\",\n    \"  optimizer.update(model, grads)\\n\",\n    \"  metrics.update(loss=loss)\\n\",\n    \"\\n\",\n    \"  state = nnx.state((model, optimizer, metrics))\\n\",\n    \"  return loss, state\\n\",\n    \"\\n\",\n    \"for _ in range(10):\\n\",\n    \"  x, y = jnp.ones((32, 2)), jnp.zeros((32, 3))\\n\",\n    \"  loss, state = jax_train_step(graphdef, state, x, y)\\n\",\n    \"\\n\",\n    \"# update objects after training\\n\",\n    \"nnx.update((model, optimizer, metrics), state)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Notice that we only need to do this for `jit`, the use of other Flax transforms like [`nnx.value_and_grad`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.value_and_grad) inside `train_step` doesn't have any performance cost since `jit` will make sure this only traced once.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"name\": \"python\",\n   \"version\": \"3.11.9\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "docs_nnx/guides/performance.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Performance considerations\n\nCurrently, Flax [`nnx.jit`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.jit) traverses the object graph in pure Python which can add overhead. This overhead mostly affects small to medium models and can be mitigated in the following ways:\n* By leveraging JAX's [Asynchronous dispatch](#asynchronous-dispatch).\n* By using [nnx.cached_partial](#caching-graph-node-traversals) to cache the graph node traversals.\n* By using a [Functional training loop](#functional-training-loop) which stages out the graph traversals.\n\nA full resolution _might_ involve developing a C extension (e.g. `flaxlib`) to speed up some of the traversal logic in [`graph.py`](https://github.com/google/flax/blob/main/flax/nnx/graph.py). Before we continue lets an example of a model and a simple training loop:\n\n```{code-cell}\nfrom flax import nnx\nimport jax\nimport jax.numpy as jnp\nimport optax\n\nclass Model(nnx.Module):\n  def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n    self.linear = nnx.Linear(din, dmid, rngs=rngs)\n    self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n    self.dropout = nnx.Dropout(0.2, rngs=rngs)\n    self.linear_out = nnx.Linear(dmid, dout, rngs=rngs)\n\n  def __call__(self, x):\n    x = nnx.relu(self.dropout(self.bn(self.linear(x))))\n    return self.linear_out(x)\n  \nmodel = Model(2, 64, 3, rngs=nnx.Rngs(0))  # eager initialization\noptimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\nmetrics = nnx.MultiMetric(\n  loss=nnx.metrics.Average('loss'),\n)\n\n@nnx.jit  # <== currently slow\ndef train_step(model, optimizer, metrics, x, y):\n  def loss_fn(model):\n    y_pred = model(x)  # call methods directly\n    return ((y_pred - y) ** 2).mean()\n\n  loss, grads = nnx.value_and_grad(loss_fn)(model)\n  optimizer.update(model, grads)  # in-place updates\n  metrics.update(loss=loss)\n\n  return loss\n  \nfor _ in range(10):\n  x, y = jnp.ones((32, 2)), jnp.zeros((32, 3))\n  loss = train_step(model, optimizer, metrics, x, y)\n```\n\nImportant thing here is that we created a `train_step()` function that uses `nnx.jit` and takes in a `model`, `optimizer`, and `metrics` arguments, all of which are Flax NNX objects. We'll later see how to improve this.\n\n+++\n\n## Asynchronous dispatch\n\nAsynchronous dispatch is a feature of JAX where it runs operations in the background whenever possible so Python can continue executing other code. This can be use to absorb the cost of data loading and in this case the overhead of `nnx.jit` and similar transforms. In general, as the amount of computation JAX has to perform per iteration increases the more it is able to absorb the python overhead since eventually the JAX computation will be the main blocker and programs with different overhead will have the same performance. This could be achieved in a couple of ways:\n\n* Increasing the batch size.\n* Increasing the model size.\n* Performing more JAX steps per python step if data loading is fast enough.\n\nTo demonstrate this, the graph below which shows total time of running [benchmarks/nnx_simple_training.py](https://github.com/google/flax/blob/main/benchmarks/nnx_simple_training.py) for both `jax.jit` and `nnx.jit` with different model sizes:\n\n![performance-graph](images/performance-graph.png)\n\nAs we can observe, after a certain model size both `jax.jit` and `nnx.jit` converge to the same runtime cost. This means we don't have to modify our training loop above.\n\n## Caching graph node traversals\n\nThe simplest way to get rid of the traversal overhead entirely is by using `nnx.cached_partial` to convert a transformed function and the input graph objects into a partial function which caches the graph object and just expects the remaining arguments. In this example we use `nnx.cached_partial` over `train_step` and partially apply `model`, `optimizer`, and `metrics`, to create `cached_train_step`. Then we simply update our training loop to use `cached_train_step` which only expects the `x` and `y` inputs:\n\n```{code-cell}\ncached_train_step = nnx.cached_partial(train_step, model, optimizer, metrics)\n\nfor _ in range(10):\n  x, y = jnp.ones((32, 2)), jnp.zeros((32, 3))\n  loss = cached_train_step(x, y)\n```\n\nNote that `cached_partial` will enforce that the structure of the graph nodes doesn't change during `train_step` (no mutations except for `Variable` state update) so the cache is guaranteed to be up-to-date and we can avoid costly checks which require traversals. This is actually what is expected for most step functions as making any change here would imply costly recompilation, so enforcing this might be a secondary feature that could be useful for this purpose.\n\nSimilarly, to prevent the user from mutating the cached objects outside, `cached_partial` creates a copy of all the graph nodes but, to allow state to be propagated to the original objects, they share references to the same `Variable`s.\n\n+++\n\n## Functional training loop\n\nTo remove the Python overhead we can create a functional training loop that uses regular `jax.jit` in combination with `nnx.split` and `nnx.merge` to stage out the traversal logic. Concretely we can use [`nnx.split`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.split) before the training loop to create a single `graphdef` and `state` pytrees for all the graph nodes. Then we change `train_step()` to accept `graphdef` and `state`, and use [`nnx.merge`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.merge) to recreate the objects inside, and either `nnx.split` or `nnx.state` at the end to get the output `state`. At the end of the training loop or whenever needed we can use [`nnx.update`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/graph.html#flax.nnx.update) to update the objects to the current `state`.\n\n```{code-cell}\n# split before training loop\ngraphdef, state = nnx.split((model, optimizer, metrics))\n\n@jax.jit  # regular JAX\ndef jax_train_step(graphdef, state, x, y):\n  # merge at the beginning of the function\n  model, optimizer, metrics = nnx.merge(graphdef, state)\n\n  def loss_fn(model):\n    y_pred = model(x)  # call methods directly\n    return ((y_pred - y) ** 2).mean()\n\n  loss, grads = nnx.value_and_grad(loss_fn)(model)\n  optimizer.update(model, grads)\n  metrics.update(loss=loss)\n\n  state = nnx.state((model, optimizer, metrics))\n  return loss, state\n\nfor _ in range(10):\n  x, y = jnp.ones((32, 2)), jnp.zeros((32, 3))\n  loss, state = jax_train_step(graphdef, state, x, y)\n\n# update objects after training\nnnx.update((model, optimizer, metrics), state)\n```\n\nNotice that we only need to do this for `jit`, the use of other Flax transforms like [`nnx.value_and_grad`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.value_and_grad) inside `train_step` doesn't have any performance cost since `jit` will make sure this only traced once.\n"
  },
  {
    "path": "docs_nnx/guides/pytree.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"90ad74ee\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Module & Pytree\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"5c0691d0\",\n   \"metadata\": {},\n   \"source\": [\n    \"Flax NNX's Modules are by default registered as JAX Pytrees, this allows using them throughout most of JAX APIs but in particular JAX transforms and the `jax.tree.*` functions. Thanks to the pytree protocol a simple NNX program might look like this:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"id\": \"9b2b929d\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"y.shape = (5, 3)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"from flax import nnx\\n\",\n    \"import jax\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"\\n\",\n    \"class Linear(nnx.Module):\\n\",\n    \"  def __init__(self, din, dout, rngs: nnx.Rngs):\\n\",\n    \"    self.din, self.dout = din, dout\\n\",\n    \"    self.kernel = nnx.Param(rngs.normal((din, dout)))\\n\",\n    \"\\n\",\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"weights = Linear(2, 3, rngs=rngs)\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def forward(weights, x):\\n\",\n    \"  return x @ weights.kernel\\n\",\n    \"\\n\",\n    \"y = forward(weights, x=rngs.uniform((5, 2)))\\n\",\n    \"print(f\\\"{y.shape = }\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"bcfbbdb2\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here `weights`, of type `Linear`, was able to be passed directly to the `jit`-ed function `forward`. Throughout the rest of this guide we will try to answer the questions:\\n\",\n    \"1. What are pytrees? \\n\",\n    \"2. How does NNX implement pytrees?\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"4f610bb3\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Pytrees 101\\n\",\n    \"Most modern ML models have too many Arrays for users to pass around individually, to deal with this JAX developed a way to track Array data in nested structures that still allowed caching for compilation: Pytrees. JAX pytrees are tree structures made of python objects that can be recursively traversed in order to collect an ordered list of leaves and a definition of the tree structure, this is done via the `jax.tree.flatten` function. Most common pytrees are native python containers like `list`, `dict`, and `tuple`, but interestingly it also include `None`. The example bellow shows how to collect all the integer leaves from a nested structure using `flatten`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"id\": \"c3529274\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"leaves = [1, 2, 3, 4]\\n\",\n      \"treedef = PyTreeDef([{'a': *}, {'b': *, 'c': (*, *), 'd': None}])\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"pytree = [\\n\",\n    \"  {'a': 1},\\n\",\n    \"  {\\n\",\n    \"    'b': 2,\\n\",\n    \"    'c': (3, 4),\\n\",\n    \"    'd': None,\\n\",\n    \"  }\\n\",\n    \"]\\n\",\n    \"\\n\",\n    \"leaves, treedef = jax.tree.flatten(pytree)\\n\",\n    \"print(f\\\"leaves = {leaves}\\\")\\n\",\n    \"print(f\\\"treedef = {treedef}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"9c037b5e\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that `None` is not a leaf because its defined as a pytree with no children. The main purpose of being able to flatten, apart from collecting the leaves, is being able reconstruct the pytree structure from the tree definition from any sequence of leaves of the same length via the `jax.tree.unflatten` function:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"id\": \"d8237524\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"old pytree = [{'a': 1}, {'b': 2, 'c': (3, 4), 'd': None}]\\n\",\n      \"new pytree = [{'a': 10}, {'b': 20, 'c': (30, 40), 'd': None}]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"new_leaves = [x * 10 for x in leaves]\\n\",\n    \"new_pytree = jax.tree.unflatten(treedef, new_leaves)\\n\",\n    \"\\n\",\n    \"print(f\\\"old pytree = {pytree}\\\")\\n\",\n    \"print(f\\\"new pytree = {new_pytree}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"117c8b2d\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Custom Pytrees\\n\",\n    \"JAX allows us to register custom pytree node type by using the `jax.tree_util.register_pytree_node` utility. For any type we are able to define a flatten that decomposes the object into a a sequence of nodes / children and a static (hashable) structure, and a unflatten function which takes the sequence of nodes and the static structure and creates a new instance. In the following example we create a simple type `Foo` with the attributes `a`, `b`, and `c`, and define `a` and `b` as nodes and `c` as static.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"id\": \"0c46905c\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"leaves = [1, 2]\\n\",\n      \"treedef = PyTreeDef(CustomNode(Foo[('hi',)], [*, *]))\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Foo:\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.a = 1\\n\",\n    \"    self.b = 2\\n\",\n    \"    self.c = \\\"hi\\\"\\n\",\n    \"\\n\",\n    \"def flatten_foo(foo: Foo):\\n\",\n    \"  nodes = [foo.a, foo.b]  # sequence of nodes\\n\",\n    \"  static = (foo.c,) # hashable & equatable structure\\n\",\n    \"  return nodes, static\\n\",\n    \"\\n\",\n    \"def unflatten_foo(static, nodes):\\n\",\n    \"  foo = object.__new__(Foo)  # create uninitialized instance\\n\",\n    \"  foo.a = nodes[0]\\n\",\n    \"  foo.b = nodes[1]\\n\",\n    \"  foo.c = static[0]\\n\",\n    \"  return foo\\n\",\n    \"\\n\",\n    \"jax.tree_util.register_pytree_node(Foo, flatten_foo, unflatten_foo)\\n\",\n    \"\\n\",\n    \"foo = Foo()\\n\",\n    \"leaves, treedef = jax.tree.flatten(foo)\\n\",\n    \"print(f\\\"leaves = {leaves}\\\")\\n\",\n    \"print(f\\\"treedef = {treedef}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"15647b13\",\n   \"metadata\": {},\n   \"source\": [\n    \"Notice that `'hi'` does not appear in the leaves because `c` is defined as static, but you can see it as part of the `PyTreeDef` structure.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"d603fa09\",\n   \"metadata\": {},\n   \"source\": [\n    \"## nnx.Pytree\\n\",\n    \"In general it would be cumbersome for users to manually register the pytree definition for every type they create. To automate this process NNX provides the `nnx.Pytree` base type that offers a simple API: users annotate attributes using either `nnx.static` or `nnx.data`, and Pytree will register some flatten and unflatten functions that will take the annotations into account. The `nnx.data` and `nnx.static` annotations must only be assigned to `Pytree` attributes directly.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"id\": \"95016a94\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"pytree structure:\\n\",\n      \" - pytree.layers[0].b = Array([0.], dtype=float32)\\n\",\n      \" - pytree.layers[0].w = Array([[1.]], dtype=float32)\\n\",\n      \" - pytree.layers[1].b = Array([0.], dtype=float32)\\n\",\n      \" - pytree.layers[1].w = Array([[1.]], dtype=float32)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Linear(nnx.Pytree):\\n\",\n    \"  def __init__(self, din: int, dout: int):\\n\",\n    \"    self.din = nnx.static(din)\\n\",\n    \"    self.dout = nnx.static(dout)\\n\",\n    \"    self.w = nnx.data(jnp.ones((din, dout)))\\n\",\n    \"    self.b = nnx.data(jnp.zeros((dout,)))\\n\",\n    \"\\n\",\n    \"class MLP(nnx.Pytree):\\n\",\n    \"  def __init__(self, num_layers, dim):\\n\",\n    \"    self.num_layers = nnx.static(num_layers)\\n\",\n    \"    self.layers = nnx.data([\\n\",\n    \"      Linear(dim, dim) for _ in range(num_layers)\\n\",\n    \"    ])\\n\",\n    \"\\n\",\n    \"pytree = MLP(num_layers=2, dim=1)\\n\",\n    \"\\n\",\n    \"def pytree_structure(pytree, title='pytree structure'):\\n\",\n    \"  print(f\\\"{title}:\\\")\\n\",\n    \"  path_leaves, treedef = jax.tree.flatten_with_path(pytree)\\n\",\n    \"  for path, value in path_leaves:\\n\",\n    \"    print(f\\\" - pytree{jax.tree_util.keystr(path)} = {value!r}\\\")\\n\",\n    \"\\n\",\n    \"pytree_structure(pytree)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"3a2db214\",\n   \"metadata\": {},\n   \"source\": [\n    \"As you can see above, only the `data` paths appear in the leaves. However, its very verbose to have to define `static` and `data` for each attribute, so `Pytree` has sensible defaults. You can remove most of them and it will just work:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"id\": \"a8665146\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"pytree structure:\\n\",\n      \" - pytree.layers[0].b = Array([0.], dtype=float32)\\n\",\n      \" - pytree.layers[0].w = Array([[1.]], dtype=float32)\\n\",\n      \" - pytree.layers[1].b = Array([0.], dtype=float32)\\n\",\n      \" - pytree.layers[1].w = Array([[1.]], dtype=float32)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Linear(nnx.Pytree):\\n\",\n    \"  def __init__(self, din: int, dout: int):\\n\",\n    \"    self.din = din # static\\n\",\n    \"    self.dout = dout # static\\n\",\n    \"    self.w = jnp.ones((din, dout)) # data\\n\",\n    \"    self.b = jnp.zeros((dout,)) # data\\n\",\n    \"\\n\",\n    \"class MLP(nnx.Pytree):\\n\",\n    \"  def __init__(self, num_layers, dim):\\n\",\n    \"    self.num_layers = num_layers # static\\n\",\n    \"    self.layers = nnx.List([ # data\\n\",\n    \"      Linear(dim, dim) for _ in range(num_layers)\\n\",\n    \"    ])\\n\",\n    \"\\n\",\n    \"pytree = MLP(num_layers=2, dim=1)\\n\",\n    \"pytree_structure(pytree)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a249c5f0\",\n   \"metadata\": {},\n   \"source\": [\n    \"The only change we had to do here is use `nnx.List` to signal that `layers` contains `data`, the status of the rest of the attributes can be correctly inferred. The rules that determine if a value is data or not are the following:\\n\",\n    \"\\n\",\n    \"* `Array`s, `Variable`s, `ArrayRef`s, and `nnx.Pytree`s are data.\\n\",\n    \"* Types registered using `nnx.register_data_type` are data.\\n\",\n    \"* All other types are static.\\n\",\n    \"\\n\",\n    \"To check if a value is data use the `nnx.is_data` function which will return its status:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"id\": \"27682936\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\n\",\n      \"# ------ DATA ------------\\n\",\n      \"nnx.is_data( jnp.array(0) ) = True                   # Arrays are data\\n\",\n      \"nnx.is_data( nnx.Param(1) ) = True                   # Variables are data\\n\",\n      \"nnx.is_data( nnx.Rngs(2) ) = True                    # nnx.Pytrees are data\\n\",\n      \"\\n\",\n      \"# ------ STATIC ------------\\n\",\n      \"nnx.is_data( 'hello' ) = False                       # strings, arbitrary objects\\n\",\n      \"nnx.is_data( 42 ) = False                            # int, float, bool, complex, etc.\\n\",\n      \"nnx.is_data( [1, 2.0, 3j, jnp.array(1)] ) = False    # list, dict, tuple, regular pytrees\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(f\\\"\\\"\\\"\\n\",\n    \"# ------ DATA ------------\\n\",\n    \"{nnx.is_data( jnp.array(0) ) = }                   # Arrays are data\\n\",\n    \"{nnx.is_data( nnx.Param(1) ) = }                   # Variables are data\\n\",\n    \"{nnx.is_data( nnx.Rngs(2) ) = }                    # nnx.Pytrees are data\\n\",\n    \"\\n\",\n    \"# ------ STATIC ------------\\n\",\n    \"{nnx.is_data( 'hello' ) = }                       # strings, arbitrary objects\\n\",\n    \"{nnx.is_data( 42 ) = }                            # int, float, bool, complex, etc.\\n\",\n    \"{nnx.is_data( [1, 2.0, 3j, jnp.array(1)] ) = }    # list, dict, tuple, regular pytrees\\n\",\n    \"\\\"\\\"\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"5ab06e79\",\n   \"metadata\": {},\n   \"source\": [\n    \"### When to use explicit annotations?\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"9dead73d\",\n   \"metadata\": {},\n   \"source\": [\n    \"There are cases were you do want to explicitely annotate the attributes to avoid ambiguity or protect yourself against possible edge cases. These include constraining input arguments which might have unexpected types, forcing attributes as data when their type is not treated as data by default, or using `nnx.static` as a way to assert the attribute should not contain data.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"id\": \"9e064461\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"pytree structure:\\n\",\n      \" - pytree.bias.value = Array(0., dtype=float32, weak_type=True)\\n\",\n      \" - pytree.ls[0] = Array(0, dtype=int32, weak_type=True)\\n\",\n      \" - pytree.ls[1] = Array(1, dtype=int32, weak_type=True)\\n\",\n      \" - pytree.ls[2] = Array(2, dtype=int32, weak_type=True)\\n\",\n      \" - pytree.x = 1.0\\n\",\n      \" - pytree.y = 42\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Bar(nnx.Pytree):\\n\",\n    \"  def __init__(self, x, use_bias: bool):\\n\",\n    \"    self.x = nnx.data(x)  # constrain inputs (e.g. user could pass Array or float)\\n\",\n    \"    self.y = nnx.data(42)  # force types that are not data by default\\n\",\n    \"    self.ls = nnx.List([jnp.array(i) for i in range(3)]) # use nnx.List for lists of data\\n\",\n    \"    self.bias = nnx.data(None)  # optional values that can be data\\n\",\n    \"    if use_bias:\\n\",\n    \"      self.bias = nnx.Param(jnp.array(0.0))\\n\",\n    \"\\n\",\n    \"pytree = Bar(1.0, True)\\n\",\n    \"pytree_structure(pytree)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"8055b72c\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Dataclasses\\n\",\n    \"`nnx.Pytree` dataclasses can be created by using the `nnx.dataclass` decorator. To control the status of each field, `nnx.static` and `nnx.data` can be used as `field` specifiers.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"id\": \"9ca77d65\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"pytree structure:\\n\",\n      \" - pytree.ls[0].i = 0\\n\",\n      \" - pytree.ls[0].x = Array(0, dtype=int32, weak_type=True)\\n\",\n      \" - pytree.ls[1].i = 1\\n\",\n      \" - pytree.ls[1].x = Array(42, dtype=int32, weak_type=True)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"import dataclasses\\n\",\n    \"\\n\",\n    \"@nnx.dataclass\\n\",\n    \"class Foo(nnx.Pytree):\\n\",\n    \"  i: int = nnx.data()\\n\",\n    \"  x: jax.Array\\n\",\n    \"  a: int\\n\",\n    \"  s: str = nnx.static(default='hi', kw_only=True)\\n\",\n    \"\\n\",\n    \"@nnx.dataclass\\n\",\n    \"class Bar(nnx.Pytree):\\n\",\n    \"  ls: list[Foo] = nnx.data()\\n\",\n    \"  shapes: list[int]\\n\",\n    \"\\n\",\n    \"pytree = Bar(\\n\",\n    \"  ls=[Foo(i, jnp.array(42 * i), hash(i)) for i in range(2)],\\n\",\n    \"  shapes=[8, 16, 32]\\n\",\n    \")\\n\",\n    \"pytree_structure(pytree)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"fca51f65\",\n   \"metadata\": {},\n   \"source\": [\n    \"`dataclasses.dataclass` can also be used directly, however type checkers will not handle `nnx.static` and `nnx.data` correctly. To solve this `dataclasses.field` can be used by setting `metadata` with the appropriate entry for `static`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"id\": \"ff54e732\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"dataclass pytree structure:\\n\",\n      \" - pytree.a = 10\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@dataclasses.dataclass\\n\",\n    \"class Bar(nnx.Pytree):\\n\",\n    \"  a: int = dataclasses.field(metadata={'static': False}) # data\\n\",\n    \"  b: str = dataclasses.field(metadata={'static': True})  # static\\n\",\n    \"\\n\",\n    \"pytree = Bar(a=10, b=\\\"hello\\\")\\n\",\n    \"pytree_structure(pytree, title='dataclass pytree structure')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"d6036a0e\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Attribute Updates\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"4e0a1f63\",\n   \"metadata\": {},\n   \"source\": [\n    \"The status of an attribute is defined during its first assignment and will not change upon reassignment. However, it is possible to override the status by explicitly using `nnx.data` or `nnx.static` on reassignment.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"id\": \"509a517e\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"original:\\n\",\n      \" - pytree.a = Array(1., dtype=float32, weak_type=True)\\n\",\n      \" - pytree.c = 3.14\\n\",\n      \"updated:\\n\",\n      \" - pytree.a = '🤔'\\n\",\n      \" - pytree.b = 42\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Foo(nnx.Pytree):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.a = jnp.array(1.0)  # data\\n\",\n    \"    self.b = \\\"Hello, world!\\\"               # static\\n\",\n    \"    self.c = nnx.data(3.14)         # data\\n\",\n    \"\\n\",\n    \"pytree = Foo()\\n\",\n    \"pytree_structure(pytree, \\\"original\\\")\\n\",\n    \"\\n\",\n    \"pytree.a = \\\"🤔\\\"  # data status doesn't change\\n\",\n    \"pytree.b = nnx.data(42)     # explicit annotation overrides status to data\\n\",\n    \"pytree.c = nnx.static(0.5)  # explicit annotation overrides status to static\\n\",\n    \"pytree_structure(pytree, \\\"updated\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"17fd41f5\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Attribute checks\\n\",\n    \"`Pytree` has a variety of checks to prevent a common class of errors in JAX. This includes checking for Arrays being assigned to new `static` attributes:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"id\": \"98e04ff9\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"ValueError: Found Arrays in value of type '<class 'jaxlib._jax.ArrayImpl'>' annotated with nnx.static(...) when setting attribute 'name' of Pytree type '<class '__main__.Foo'>'.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Foo(nnx.Pytree):\\n\",\n    \"  def __init__(self, name):\\n\",\n    \"    self.name = nnx.static(name)\\n\",\n    \"\\n\",\n    \"try:\\n\",\n    \"  foo = Foo(name=jnp.array(123))\\n\",\n    \"except ValueError as e:\\n\",\n    \"  print(\\\"ValueError:\\\", e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"070a07f0\",\n   \"metadata\": {},\n   \"source\": [\n    \"Checking for Arrays being assigned to known `static` attributes:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"id\": \"c864d5b1\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"ValueError: Cannot assign data value of type '<class 'jaxlib._jax.ArrayImpl'>' to static attribute 'name' of Pytree type '<class '__main__.Foo'>'. To override the status explicitly wrap the value with nnx.data on assignment:\\n\",\n      \"\\n\",\n      \"  _.name = nnx.data(...)\\n\",\n      \"\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"try:\\n\",\n    \"  foo = Foo(name=\\\"mattjj\\\")\\n\",\n    \"  foo.name = jnp.array(123)\\n\",\n    \"except ValueError as e:\\n\",\n    \"  print(\\\"ValueError:\\\", e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"c3b17f07\",\n   \"metadata\": {},\n   \"source\": [\n    \"Checking for Arrays after `__init__` on `static` attributes that could've been inserted via mutation. This check can be manually trigger via `nnx.check_pytree` at any time.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"id\": \"628698dd\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"ValueError: Found unexpected Arrays on value of type <class 'list'> in static attribute 'ls' of Pytree type '<class '__main__.Foo'>'. This is an error starting from Flax version 0.12.0.\\n\",\n      \"Consider one of the following options:\\n\",\n      \"\\n\",\n      \"1. If the attribute is meant to be static, either remove the Array value or wrap it in a static container.\\n\",\n      \"2. Wrap the value with nnx.data on assignment:\\n\",\n      \"\\n\",\n      \"  _.ls = nnx.data(...)\\n\",\n      \"\\n\",\n      \"3. Annotate the class attribute with nnx.Data:\\n\",\n      \"\\n\",\n      \"  class Foo(Pytree):\\n\",\n      \"    ls: nnx.Data[list]\\n\",\n      \"\\n\",\n      \"4. If the container is a list or dict, try using nnx.List(...) or nnx.Dict(...) instead.\\n\",\n      \"5. Disable pytree for this class:\\n\",\n      \"\\n\",\n      \"  class Foo(Pytree, pytree=False):\\n\",\n      \"\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Foo(nnx.Pytree):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.ls = []  # treated as static\\n\",\n    \"    for i in range(5):\\n\",\n    \"      self.ls.append(jnp.array(i))  # append arrays into static attribute\\n\",\n    \"\\n\",\n    \"try:\\n\",\n    \"  foo = Foo()  # error: Array found in static attribute after `__init__`\\n\",\n    \"except ValueError as e:\\n\",\n    \"  print(\\\"ValueError:\\\", e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"37ee2429\",\n   \"metadata\": {},\n   \"source\": [\n    \"Checking for `nnx.data` or `nnx.static` annotations stored inside nested structures that are not `nnx.Pytree` instances:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"id\": \"f9d69634\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"ValueError: Found unexpected tags {'static', 'data'} on attribute 'Foo.a'. Values from nnx.data(...) and\\n\",\n      \"nnx.static(...) should be assigned to nnx.Pytree attributes directly, they should not be inside other structures. Got value of type '<class 'list'>' on Pytree of type '<class '__main__.Foo'>'.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Foo(nnx.Pytree):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.a = [nnx.data(1), nnx.static(2)]  # annotations in sub-pytree\\n\",\n    \"\\n\",\n    \"try:\\n\",\n    \"  foo = Foo()\\n\",\n    \"except ValueError as e:\\n\",\n    \"  print(\\\"ValueError:\\\", e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"452915ac\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Trace-level awareness\\n\",\n    \"To prevent tracer leakage NNX will raise an error when trying to update the attribute of a `Pytree` or the value of a `Variable` on instances that are passed as captures to functions called by JAX transforms:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"id\": \"668db479\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Error: Cannot mutate 'Foo' from different trace level (https://flax.readthedocs.io/en/latest/api_reference/flax.errors.html#flax.errors.TraceContextError)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Foo(nnx.Pytree):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.count = nnx.data(0)\\n\",\n    \"\\n\",\n    \"foo = Foo()\\n\",\n    \"\\n\",\n    \"@jax.vmap  # or jit, grad, shard_map, pmap, scan, etc.\\n\",\n    \"def increment(n):\\n\",\n    \"  # foo passed as capture\\n\",\n    \"  foo.count += 1  # error!\\n\",\n    \"\\n\",\n    \"try:\\n\",\n    \"  increment(jnp.arange(5))\\n\",\n    \"except Exception as e:\\n\",\n    \"  print(f\\\"Error: {e}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"536d77b0\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Reference Sharing\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"2449a3c5\",\n   \"metadata\": {},\n   \"source\": [\n    \"As the name implies Pytrees should be trees. To check if a structure is a well-defined tree you can use the `nnx.find_duplicates` functions which will return a list of duplicates, where each duplicate is a list of path tuples. In the example below we see that `left` and `right` are shared references therefore `find_duplicates` returns a non-empty list with the paths:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 17,\n   \"id\": \"32c46ce8\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"nnx.find_duplicates(m) = [[('left',), ('right',)]]  # not a tree\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Shared(nnx.Pytree):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.x = jnp.array(1.0)\\n\",\n    \"\\n\",\n    \"class Parent(nnx.Pytree):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.left = Shared()\\n\",\n    \"    self.right = self.left  # reference sharing\\n\",\n    \"\\n\",\n    \"m = Parent()\\n\",\n    \"\\n\",\n    \"print(f\\\"{nnx.find_duplicates(m) = }  # not a tree\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"49b2e5f0\",\n   \"metadata\": {},\n   \"source\": [\n    \"The main issue is that sharing is not preserved across pytree operations including JAX transforms, and this results in unintended state duplication:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 18,\n   \"id\": \"c33e4862\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Before: m.left is m.right = True\\n\",\n      \"Inside: m.left is m.right = False\\n\",\n      \"After:  m.left is m.right = False\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"m = Parent()\\n\",\n    \"print(f\\\"Before: {m.left is m.right = }\\\")\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def f(m):\\n\",\n    \"  print(f\\\"Inside: {m.left is m.right = }\\\")\\n\",\n    \"  return m\\n\",\n    \"\\n\",\n    \"m = f(m)\\n\",\n    \"print(f\\\"After:  {m.left is m.right = }\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"d60953f4\",\n   \"metadata\": {},\n   \"source\": [\n    \"Reference sharing is rare in most Machine Learning applications, however if it is required you can either use the `nnx.{split, merge, state, update}` APIs to move the deduplicated state and graph definiton across the JAX transforms:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 19,\n   \"id\": \"dda51b67\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Before: m.left is m.right = True\\n\",\n      \"Inside: m.left is m.right = True\\n\",\n      \"After:  m.left is m.right = True\\n\",\n      \"state = State({\\n\",\n      \"  'left': {\\n\",\n      \"    'x': Array(1., dtype=float32, weak_type=True)\\n\",\n      \"  }\\n\",\n      \"})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"m = Parent()\\n\",\n    \"print(f\\\"Before: {m.left is m.right = }\\\")\\n\",\n    \"graphdef, state = nnx.split(m)\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def f(graphdef, state):\\n\",\n    \"  m = nnx.merge(graphdef, state)\\n\",\n    \"  print(f\\\"Inside: {m.left is m.right = }\\\")\\n\",\n    \"  return nnx.state(m)\\n\",\n    \"\\n\",\n    \"state = f(graphdef, state)\\n\",\n    \"nnx.update(m, state)\\n\",\n    \"\\n\",\n    \"print(f\\\"After:  {m.left is m.right = }\\\")\\n\",\n    \"print(f\\\"{state = }\\\") # deduplicated state\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"0afee781\",\n   \"metadata\": {},\n   \"source\": [\n    \"Or alternatively you can use the NNX transforms which preserve shared references:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 20,\n   \"id\": \"caa01e3b\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Before: m.left is m.right = True\\n\",\n      \"Inside: m.left is m.right = True\\n\",\n      \"After:  m.left is m.right = True\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"m = Parent()\\n\",\n    \"print(f\\\"Before: {m.left is m.right = }\\\")\\n\",\n    \"\\n\",\n    \"@nnx.jit\\n\",\n    \"def f(m):\\n\",\n    \"  print(f\\\"Inside: {m.left is m.right = }\\\")\\n\",\n    \"  return m\\n\",\n    \"\\n\",\n    \"m = f(m)\\n\",\n    \"\\n\",\n    \"print(f\\\"After:  {m.left is m.right = }\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"734aa5b3\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Turning off pytree registration\\n\",\n    \"`nnx.Pytree` allows you to turn off the pytree registration along with the attribute checks for subtypes by setting `pytree` type attribute option to `False`. This can be useful when upgrading to previous NNX code to newer Flax verions as you will still be able to use the NNX APIs or when creating types that should not be treated as pytree because e.g. they shared references.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 21,\n   \"id\": \"d2e03753\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \" nnx.state(foo) = State({\\n\",\n      \"  'a': {\\n\",\n      \"    0: Array(2, dtype=int32, weak_type=True),\\n\",\n      \"    1: Array(4, dtype=int32, weak_type=True)\\n\",\n      \"  },\\n\",\n      \"  'b': Array(6, dtype=int32, weak_type=True)\\n\",\n      \"})\\n\",\n      \" jax.tree_util.all_leaves([foo]) = True\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Foo(nnx.Pytree, pytree=False):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.a = [jnp.array(1), jnp.array(2)]  # no checks\\n\",\n    \"    self.b = \\\"hello\\\" \\n\",\n    \"    self.b = jnp.array(3) # no checks\\n\",\n    \"\\n\",\n    \"foo = Foo()\\n\",\n    \"\\n\",\n    \"@nnx.jit # can use in NNX transformations\\n\",\n    \"def double(foo: Foo):\\n\",\n    \"  foo.a = [x * 2 for x in foo.a]\\n\",\n    \"  foo.b *= 2\\n\",\n    \"\\n\",\n    \"double(foo)\\n\",\n    \"print(f\\\"{ nnx.state(foo) = }\\\")  # can be used with NNX APIs\\n\",\n    \"print(f\\\"{ jax.tree_util.all_leaves([foo]) = }\\\")  # not a pytree\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"bfaf17de\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Module\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"67000b88\",\n   \"metadata\": {},\n   \"source\": [\n    \"NNX Modules are `Pytree`s that have two additional methods for traking intermediate values: `sow` and `perturb`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"cc5afc70\",\n   \"metadata\": {},\n   \"source\": [\n    \"### sow\\n\",\n    \"`sow` receives a `Variable` type, a `name`, and a `value`, and stores it in the `Module` so it can be retrieved at a later time. As the following example shows, NNX APIs such as `nnx.state` or `nnx.pop` are a good way of retrieving the sowed state, however `pop` is recommended because it explicitly removes the temporary state from the Module.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 22,\n   \"id\": \"ca9f58a2\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[38;2;79;201;177mState\\u001b[0m\\u001b[38;2;255;213;3m({\\u001b[0m\\u001b[38;2;105;105;105m\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254m'blocks'\\u001b[0m\\u001b[38;2;212;212;212m: \\u001b[0m\\u001b[38;2;255;213;3m{\\u001b[0m\\u001b[38;2;105;105;105m\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254m0\\u001b[0m\\u001b[38;2;212;212;212m: \\u001b[0m\\u001b[38;2;255;213;3m{\\u001b[0m\\u001b[38;2;105;105;105m\\u001b[0m\\n\",\n      \"      \\u001b[38;2;156;220;254m'y_mean'\\u001b[0m\\u001b[38;2;212;212;212m: \\u001b[0m\\u001b[38;2;79;201;177mIntermediate\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"        \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0mArray(4.659754e-06, dtype=float32),\\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"      \\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;255;213;3m}\\u001b[0m,\\n\",\n      \"    \\u001b[38;2;156;220;254m1\\u001b[0m\\u001b[38;2;212;212;212m: \\u001b[0m\\u001b[38;2;255;213;3m{\\u001b[0m\\u001b[38;2;105;105;105m\\u001b[0m\\n\",\n      \"      \\u001b[38;2;156;220;254m'y_mean'\\u001b[0m\\u001b[38;2;212;212;212m: \\u001b[0m\\u001b[38;2;79;201;177mIntermediate\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"        \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0mArray(0.00025933, dtype=float32),\\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"      \\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;255;213;3m}\\u001b[0m,\\n\",\n      \"    \\u001b[38;2;156;220;254m2\\u001b[0m\\u001b[38;2;212;212;212m: \\u001b[0m\\u001b[38;2;255;213;3m{\\u001b[0m\\u001b[38;2;105;105;105m\\u001b[0m\\n\",\n      \"      \\u001b[38;2;156;220;254m'y_mean'\\u001b[0m\\u001b[38;2;212;212;212m: \\u001b[0m\\u001b[38;2;79;201;177mIntermediate\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"        \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0mArray(0.05561922, dtype=float32),\\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"      \\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;255;213;3m}\\u001b[0m\\n\",\n      \"  \\u001b[38;2;255;213;3m}\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m})\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Block(nnx.Module):\\n\",\n    \"  def __init__(self, din: int, dout: int, rngs: nnx.Rngs):\\n\",\n    \"    self.linear = nnx.Linear(din, dout, rngs=rngs)\\n\",\n    \"    self.bn = nnx.BatchNorm(dout, rngs=rngs)\\n\",\n    \"    self.dropout = nnx.Dropout(0.1, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    y = nnx.relu(self.dropout(self.bn(self.linear(x))))\\n\",\n    \"    self.sow(nnx.Intermediate, \\\"y_mean\\\", jnp.mean(y))\\n\",\n    \"    return y\\n\",\n    \"\\n\",\n    \"class MLP(nnx.Module):\\n\",\n    \"  def __init__(self, num_layers, dim, rngs: nnx.Rngs):\\n\",\n    \"    self.blocks = nnx.List([Block(dim, dim, rngs) for _ in range(num_layers)])\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    for block in self.blocks:\\n\",\n    \"      x = block(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"model = MLP(num_layers=3, dim=20, rngs=nnx.Rngs(0))\\n\",\n    \"x = jnp.ones((10, 20))\\n\",\n    \"y = model(x)\\n\",\n    \"intermediates = nnx.pop(model, nnx.Intermediate) # extract intermediate values\\n\",\n    \"print(intermediates)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"6e266e5f\",\n   \"metadata\": {},\n   \"source\": [\n    \"### perturb\\n\",\n    \"`perturb` is similar to `sow` but it aims to capture the gradient of a value, currently this is a two step process although it might be simplified in the future:\\n\",\n    \"1. Initialize the pertubation state by running the model once.\\n\",\n    \"2. Pass the perturbation state as a differentiable target to `grad`.\\n\",\n    \"\\n\",\n    \"As an example lets create a simple model and use `perturb` to get the intermediate gradient `xgrad` for the variable `x`, and initialize the perturbations:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 23,\n   \"id\": \"41398e14\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"nnx.state(model, nnx.Perturbation) = \\u001b[38;2;79;201;177mState\\u001b[0m\\u001b[38;2;255;213;3m({\\u001b[0m\\u001b[38;2;105;105;105m\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254m'xgrad'\\u001b[0m\\u001b[38;2;212;212;212m: \\u001b[0m\\u001b[38;2;79;201;177mPerturbation\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 3 (12 B)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray([[0., 0., 0.]], dtype=float32)\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m})\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"import optax\\n\",\n    \"\\n\",\n    \"class Model(nnx.Module):\\n\",\n    \"  def __init__(self, rngs):\\n\",\n    \"    self.linear1 = nnx.Linear(2, 3, rngs=rngs)\\n\",\n    \"    self.linear2 = nnx.Linear(3, 4, rngs=rngs)\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x = nnx.gelu(self.linear1(x))\\n\",\n    \"    x = self.perturb('xgrad', x)\\n\",\n    \"    x = self.linear2(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"model = Model(rngs)\\n\",\n    \"optimizer = nnx.Optimizer(model, tx=optax.sgd(1e-1), wrt=nnx.Param)\\n\",\n    \"x, y = rngs.uniform((1, 2)), rngs.uniform((1, 4))\\n\",\n    \"_ = model(x) # initialize perturbations\\n\",\n    \"print(f\\\"{nnx.state(model, nnx.Perturbation) = !s}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"c9221005\",\n   \"metadata\": {},\n   \"source\": [\n    \"Next we'll create a training step function that differentiates w.r.t. both the parameters of the model and the perturbations, the later will be the gradients for the intermediate values. `nnx.jit` and `nnx.value_and_grad` will be use to automatically propagate state updates. We'll return the `loss` function and the itermediate gradients.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 24,\n   \"id\": \"d10effba\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"step = 0, loss = Array(0.7326511, dtype=float32), iterm_grads = \\u001b[38;2;79;201;177mState\\u001b[0m\\u001b[38;2;255;213;3m({\\u001b[0m\\u001b[38;2;105;105;105m\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254m'xgrad'\\u001b[0m\\u001b[38;2;212;212;212m: \\u001b[0m\\u001b[38;2;79;201;177mPerturbation\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 3 (12 B)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray([[-0.430146  , -0.14356601,  0.2935633 ]], dtype=float32)\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m})\\u001b[0m\\n\",\n      \"step = 1, loss = Array(0.65039134, dtype=float32), iterm_grads = \\u001b[38;2;79;201;177mState\\u001b[0m\\u001b[38;2;255;213;3m({\\u001b[0m\\u001b[38;2;105;105;105m\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254m'xgrad'\\u001b[0m\\u001b[38;2;212;212;212m: \\u001b[0m\\u001b[38;2;79;201;177mPerturbation\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 3 (12 B)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray([[-0.38535568, -0.11745065,  0.24441527]], dtype=float32)\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m})\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@nnx.jit\\n\",\n    \"def train_step(model, optimizer, x, y):\\n\",\n    \"  graphdef, params, perturbations = nnx.split(model, nnx.Param, nnx.Perturbation)\\n\",\n    \"\\n\",\n    \"  def loss_fn(params, perturbations):\\n\",\n    \"    model = nnx.merge(graphdef, params, perturbations)\\n\",\n    \"    return jnp.mean((model(x) - y) ** 2)\\n\",\n    \"\\n\",\n    \"  loss, (grads, iterm_grads) = nnx.value_and_grad(loss_fn, argnums=(0, 1))(params, perturbations)\\n\",\n    \"  optimizer.update(model, grads)\\n\",\n    \"\\n\",\n    \"  return loss, iterm_grads\\n\",\n    \"\\n\",\n    \"for step in range(2):\\n\",\n    \"  loss, iterm_grads = train_step(model, optimizer, x, y)\\n\",\n    \"  print(f\\\"{step = }, {loss = }, {iterm_grads = !s}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"d8511c3d\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Object\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1c2fd61c\",\n   \"metadata\": {},\n   \"source\": [\n    \"`Object` are NNX types that are **not** registered as JAX pytrees. Formally, any `Object` subclass is a `nnx.Pytree` with `pytree=False`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 25,\n   \"id\": \"a9cab639\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \" nnx.state(foo) = State({\\n\",\n      \"  'a': {\\n\",\n      \"    0: Array(2, dtype=int32, weak_type=True),\\n\",\n      \"    1: Array(4, dtype=int32, weak_type=True)\\n\",\n      \"  },\\n\",\n      \"  'b': Array(6, dtype=int32, weak_type=True)\\n\",\n      \"})\\n\",\n      \" jax.tree_util.all_leaves([foo]) = True\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Foo(nnx.Object): # instead of Foo(nnx.Pytree, pytree=False)\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.a = [jnp.array(1), jnp.array(2)]  # no checks\\n\",\n    \"    self.b = \\\"hello\\\" \\n\",\n    \"    self.b = jnp.array(3) # no checks\\n\",\n    \"\\n\",\n    \"foo = Foo()\\n\",\n    \"\\n\",\n    \"@nnx.jit # can use in NNX transformations\\n\",\n    \"def double(foo: Foo):\\n\",\n    \"  foo.a = [x * 2 for x in foo.a]\\n\",\n    \"  foo.b *= 2\\n\",\n    \"\\n\",\n    \"double(foo)\\n\",\n    \"print(f\\\"{ nnx.state(foo) = }\\\")  # can be used with NNX APIs\\n\",\n    \"print(f\\\"{ jax.tree_util.all_leaves([foo]) = }\\\")  # not a pytree\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.9\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs_nnx/guides/pytree.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Module & Pytree\n\n+++\n\nFlax NNX's Modules are by default registered as JAX Pytrees, this allows using them throughout most of JAX APIs but in particular JAX transforms and the `jax.tree.*` functions. Thanks to the pytree protocol a simple NNX program might look like this:\n\n```{code-cell} ipython3\nfrom flax import nnx\nimport jax\nimport jax.numpy as jnp\n\nclass Linear(nnx.Module):\n  def __init__(self, din, dout, rngs: nnx.Rngs):\n    self.din, self.dout = din, dout\n    self.kernel = nnx.Param(rngs.normal((din, dout)))\n\nrngs = nnx.Rngs(0)\nweights = Linear(2, 3, rngs=rngs)\n\n@jax.jit\ndef forward(weights, x):\n  return x @ weights.kernel\n\ny = forward(weights, x=rngs.uniform((5, 2)))\nprint(f\"{y.shape = }\")\n```\n\nHere `weights`, of type `Linear`, was able to be passed directly to the `jit`-ed function `forward`. Throughout the rest of this guide we will try to answer the questions:\n1. What are pytrees? \n2. How does NNX implement pytrees?\n\n+++\n\n## Pytrees 101\nMost modern ML models have too many Arrays for users to pass around individually, to deal with this JAX developed a way to track Array data in nested structures that still allowed caching for compilation: Pytrees. JAX pytrees are tree structures made of python objects that can be recursively traversed in order to collect an ordered list of leaves and a definition of the tree structure, this is done via the `jax.tree.flatten` function. Most common pytrees are native python containers like `list`, `dict`, and `tuple`, but interestingly it also include `None`. The example bellow shows how to collect all the integer leaves from a nested structure using `flatten`:\n\n```{code-cell} ipython3\npytree = [\n  {'a': 1},\n  {\n    'b': 2,\n    'c': (3, 4),\n    'd': None,\n  }\n]\n\nleaves, treedef = jax.tree.flatten(pytree)\nprint(f\"leaves = {leaves}\")\nprint(f\"treedef = {treedef}\")\n```\n\nNote that `None` is not a leaf because its defined as a pytree with no children. The main purpose of being able to flatten, apart from collecting the leaves, is being able reconstruct the pytree structure from the tree definition from any sequence of leaves of the same length via the `jax.tree.unflatten` function:\n\n```{code-cell} ipython3\nnew_leaves = [x * 10 for x in leaves]\nnew_pytree = jax.tree.unflatten(treedef, new_leaves)\n\nprint(f\"old pytree = {pytree}\")\nprint(f\"new pytree = {new_pytree}\")\n```\n\n### Custom Pytrees\nJAX allows us to register custom pytree node type by using the `jax.tree_util.register_pytree_node` utility. For any type we are able to define a flatten that decomposes the object into a a sequence of nodes / children and a static (hashable) structure, and a unflatten function which takes the sequence of nodes and the static structure and creates a new instance. In the following example we create a simple type `Foo` with the attributes `a`, `b`, and `c`, and define `a` and `b` as nodes and `c` as static.\n\n```{code-cell} ipython3\nclass Foo:\n  def __init__(self):\n    self.a = 1\n    self.b = 2\n    self.c = \"hi\"\n\ndef flatten_foo(foo: Foo):\n  nodes = [foo.a, foo.b]  # sequence of nodes\n  static = (foo.c,) # hashable & equatable structure\n  return nodes, static\n\ndef unflatten_foo(static, nodes):\n  foo = object.__new__(Foo)  # create uninitialized instance\n  foo.a = nodes[0]\n  foo.b = nodes[1]\n  foo.c = static[0]\n  return foo\n\njax.tree_util.register_pytree_node(Foo, flatten_foo, unflatten_foo)\n\nfoo = Foo()\nleaves, treedef = jax.tree.flatten(foo)\nprint(f\"leaves = {leaves}\")\nprint(f\"treedef = {treedef}\")\n```\n\nNotice that `'hi'` does not appear in the leaves because `c` is defined as static, but you can see it as part of the `PyTreeDef` structure.\n\n+++\n\n## nnx.Pytree\nIn general it would be cumbersome for users to manually register the pytree definition for every type they create. To automate this process NNX provides the `nnx.Pytree` base type that offers a simple API: users annotate attributes using either `nnx.static` or `nnx.data`, and Pytree will register some flatten and unflatten functions that will take the annotations into account. The `nnx.data` and `nnx.static` annotations must only be assigned to `Pytree` attributes directly.\n\n```{code-cell} ipython3\nclass Linear(nnx.Pytree):\n  def __init__(self, din: int, dout: int):\n    self.din = nnx.static(din)\n    self.dout = nnx.static(dout)\n    self.w = nnx.data(jnp.ones((din, dout)))\n    self.b = nnx.data(jnp.zeros((dout,)))\n\nclass MLP(nnx.Pytree):\n  def __init__(self, num_layers, dim):\n    self.num_layers = nnx.static(num_layers)\n    self.layers = nnx.data([\n      Linear(dim, dim) for _ in range(num_layers)\n    ])\n\npytree = MLP(num_layers=2, dim=1)\n\ndef pytree_structure(pytree, title='pytree structure'):\n  print(f\"{title}:\")\n  path_leaves, treedef = jax.tree.flatten_with_path(pytree)\n  for path, value in path_leaves:\n    print(f\" - pytree{jax.tree_util.keystr(path)} = {value!r}\")\n\npytree_structure(pytree)\n```\n\nAs you can see above, only the `data` paths appear in the leaves. However, its very verbose to have to define `static` and `data` for each attribute, so `Pytree` has sensible defaults. You can remove most of them and it will just work:\n\n```{code-cell} ipython3\nclass Linear(nnx.Pytree):\n  def __init__(self, din: int, dout: int):\n    self.din = din # static\n    self.dout = dout # static\n    self.w = jnp.ones((din, dout)) # data\n    self.b = jnp.zeros((dout,)) # data\n\nclass MLP(nnx.Pytree):\n  def __init__(self, num_layers, dim):\n    self.num_layers = num_layers # static\n    self.layers = nnx.List([ # data\n      Linear(dim, dim) for _ in range(num_layers)\n    ])\n\npytree = MLP(num_layers=2, dim=1)\npytree_structure(pytree)\n```\n\nThe only change we had to do here is use `nnx.List` to signal that `layers` contains `data`, the status of the rest of the attributes can be correctly inferred. The rules that determine if a value is data or not are the following:\n\n* `Array`s, `Variable`s, `ArrayRef`s, and `nnx.Pytree`s are data.\n* Types registered using `nnx.register_data_type` are data.\n* All other types are static.\n\nTo check if a value is data use the `nnx.is_data` function which will return its status:\n\n```{code-cell} ipython3\nprint(f\"\"\"\n# ------ DATA ------------\n{nnx.is_data( jnp.array(0) ) = }                   # Arrays are data\n{nnx.is_data( nnx.Param(1) ) = }                   # Variables are data\n{nnx.is_data( nnx.Rngs(2) ) = }                    # nnx.Pytrees are data\n\n# ------ STATIC ------------\n{nnx.is_data( 'hello' ) = }                       # strings, arbitrary objects\n{nnx.is_data( 42 ) = }                            # int, float, bool, complex, etc.\n{nnx.is_data( [1, 2.0, 3j, jnp.array(1)] ) = }    # list, dict, tuple, regular pytrees\n\"\"\")\n```\n\n### When to use explicit annotations?\n\n+++\n\nThere are cases were you do want to explicitely annotate the attributes to avoid ambiguity or protect yourself against possible edge cases. These include constraining input arguments which might have unexpected types, forcing attributes as data when their type is not treated as data by default, or using `nnx.static` as a way to assert the attribute should not contain data.\n\n```{code-cell} ipython3\nclass Bar(nnx.Pytree):\n  def __init__(self, x, use_bias: bool):\n    self.x = nnx.data(x)  # constrain inputs (e.g. user could pass Array or float)\n    self.y = nnx.data(42)  # force types that are not data by default\n    self.ls = nnx.List([jnp.array(i) for i in range(3)]) # use nnx.List for lists of data\n    self.bias = nnx.data(None)  # optional values that can be data\n    if use_bias:\n      self.bias = nnx.Param(jnp.array(0.0))\n\npytree = Bar(1.0, True)\npytree_structure(pytree)\n```\n\n### Dataclasses\n`nnx.Pytree` dataclasses can be created by using the `nnx.dataclass` decorator. To control the status of each field, `nnx.static` and `nnx.data` can be used as `field` specifiers.\n\n```{code-cell} ipython3\nimport dataclasses\n\n@nnx.dataclass\nclass Foo(nnx.Pytree):\n  i: int = nnx.data()\n  x: jax.Array\n  a: int\n  s: str = nnx.static(default='hi', kw_only=True)\n\n@nnx.dataclass\nclass Bar(nnx.Pytree):\n  ls: list[Foo] = nnx.data()\n  shapes: list[int]\n\npytree = Bar(\n  ls=[Foo(i, jnp.array(42 * i), hash(i)) for i in range(2)],\n  shapes=[8, 16, 32]\n)\npytree_structure(pytree)\n```\n\n`dataclasses.dataclass` can also be used directly, however type checkers will not handle `nnx.static` and `nnx.data` correctly. To solve this `dataclasses.field` can be used by setting `metadata` with the appropriate entry for `static`.\n\n```{code-cell} ipython3\n@dataclasses.dataclass\nclass Bar(nnx.Pytree):\n  a: int = dataclasses.field(metadata={'static': False}) # data\n  b: str = dataclasses.field(metadata={'static': True})  # static\n\npytree = Bar(a=10, b=\"hello\")\npytree_structure(pytree, title='dataclass pytree structure')\n```\n\n### Attribute Updates\n\n+++\n\nThe status of an attribute is defined during its first assignment and will not change upon reassignment. However, it is possible to override the status by explicitly using `nnx.data` or `nnx.static` on reassignment.\n\n```{code-cell} ipython3\nclass Foo(nnx.Pytree):\n  def __init__(self):\n    self.a = jnp.array(1.0)  # data\n    self.b = \"Hello, world!\"               # static\n    self.c = nnx.data(3.14)         # data\n\npytree = Foo()\npytree_structure(pytree, \"original\")\n\npytree.a = \"🤔\"  # data status doesn't change\npytree.b = nnx.data(42)     # explicit annotation overrides status to data\npytree.c = nnx.static(0.5)  # explicit annotation overrides status to static\npytree_structure(pytree, \"updated\")\n```\n\n### Attribute checks\n`Pytree` has a variety of checks to prevent a common class of errors in JAX. This includes checking for Arrays being assigned to new `static` attributes:\n\n```{code-cell} ipython3\nclass Foo(nnx.Pytree):\n  def __init__(self, name):\n    self.name = nnx.static(name)\n\ntry:\n  foo = Foo(name=jnp.array(123))\nexcept ValueError as e:\n  print(\"ValueError:\", e)\n```\n\nChecking for Arrays being assigned to known `static` attributes:\n\n```{code-cell} ipython3\ntry:\n  foo = Foo(name=\"mattjj\")\n  foo.name = jnp.array(123)\nexcept ValueError as e:\n  print(\"ValueError:\", e)\n```\n\nChecking for Arrays after `__init__` on `static` attributes that could've been inserted via mutation. This check can be manually trigger via `nnx.check_pytree` at any time.\n\n```{code-cell} ipython3\nclass Foo(nnx.Pytree):\n  def __init__(self):\n    self.ls = []  # treated as static\n    for i in range(5):\n      self.ls.append(jnp.array(i))  # append arrays into static attribute\n\ntry:\n  foo = Foo()  # error: Array found in static attribute after `__init__`\nexcept ValueError as e:\n  print(\"ValueError:\", e)\n```\n\nChecking for `nnx.data` or `nnx.static` annotations stored inside nested structures that are not `nnx.Pytree` instances:\n\n```{code-cell} ipython3\nclass Foo(nnx.Pytree):\n  def __init__(self):\n    self.a = [nnx.data(1), nnx.static(2)]  # annotations in sub-pytree\n\ntry:\n  foo = Foo()\nexcept ValueError as e:\n  print(\"ValueError:\", e)\n```\n\n### Trace-level awareness\nTo prevent tracer leakage NNX will raise an error when trying to update the attribute of a `Pytree` or the value of a `Variable` on instances that are passed as captures to functions called by JAX transforms:\n\n```{code-cell} ipython3\nclass Foo(nnx.Pytree):\n  def __init__(self):\n    self.count = nnx.data(0)\n\nfoo = Foo()\n\n@jax.vmap  # or jit, grad, shard_map, pmap, scan, etc.\ndef increment(n):\n  # foo passed as capture\n  foo.count += 1  # error!\n\ntry:\n  increment(jnp.arange(5))\nexcept Exception as e:\n  print(f\"Error: {e}\")\n```\n\n### Reference Sharing\n\n+++\n\nAs the name implies Pytrees should be trees. To check if a structure is a well-defined tree you can use the `nnx.find_duplicates` functions which will return a list of duplicates, where each duplicate is a list of path tuples. In the example below we see that `left` and `right` are shared references therefore `find_duplicates` returns a non-empty list with the paths:\n\n```{code-cell} ipython3\nclass Shared(nnx.Pytree):\n  def __init__(self):\n    self.x = jnp.array(1.0)\n\nclass Parent(nnx.Pytree):\n  def __init__(self):\n    self.left = Shared()\n    self.right = self.left  # reference sharing\n\nm = Parent()\n\nprint(f\"{nnx.find_duplicates(m) = }  # not a tree\")\n```\n\nThe main issue is that sharing is not preserved across pytree operations including JAX transforms, and this results in unintended state duplication:\n\n```{code-cell} ipython3\nm = Parent()\nprint(f\"Before: {m.left is m.right = }\")\n\n@jax.jit\ndef f(m):\n  print(f\"Inside: {m.left is m.right = }\")\n  return m\n\nm = f(m)\nprint(f\"After:  {m.left is m.right = }\")\n```\n\nReference sharing is rare in most Machine Learning applications, however if it is required you can either use the `nnx.{split, merge, state, update}` APIs to move the deduplicated state and graph definiton across the JAX transforms:\n\n```{code-cell} ipython3\nm = Parent()\nprint(f\"Before: {m.left is m.right = }\")\ngraphdef, state = nnx.split(m)\n\n@jax.jit\ndef f(graphdef, state):\n  m = nnx.merge(graphdef, state)\n  print(f\"Inside: {m.left is m.right = }\")\n  return nnx.state(m)\n\nstate = f(graphdef, state)\nnnx.update(m, state)\n\nprint(f\"After:  {m.left is m.right = }\")\nprint(f\"{state = }\") # deduplicated state\n```\n\nOr alternatively you can use the NNX transforms which preserve shared references:\n\n```{code-cell} ipython3\nm = Parent()\nprint(f\"Before: {m.left is m.right = }\")\n\n@nnx.jit\ndef f(m):\n  print(f\"Inside: {m.left is m.right = }\")\n  return m\n\nm = f(m)\n\nprint(f\"After:  {m.left is m.right = }\")\n```\n\n### Turning off pytree registration\n`nnx.Pytree` allows you to turn off the pytree registration along with the attribute checks for subtypes by setting `pytree` type attribute option to `False`. This can be useful when upgrading to previous NNX code to newer Flax verions as you will still be able to use the NNX APIs or when creating types that should not be treated as pytree because e.g. they shared references.\n\n```{code-cell} ipython3\nclass Foo(nnx.Pytree, pytree=False):\n  def __init__(self):\n    self.a = [jnp.array(1), jnp.array(2)]  # no checks\n    self.b = \"hello\" \n    self.b = jnp.array(3) # no checks\n\nfoo = Foo()\n\n@nnx.jit # can use in NNX transformations\ndef double(foo: Foo):\n  foo.a = [x * 2 for x in foo.a]\n  foo.b *= 2\n\ndouble(foo)\nprint(f\"{ nnx.state(foo) = }\")  # can be used with NNX APIs\nprint(f\"{ jax.tree_util.all_leaves([foo]) = }\")  # not a pytree\n```\n\n## Module\n\n+++\n\nNNX Modules are `Pytree`s that have two additional methods for traking intermediate values: `sow` and `perturb`.\n\n+++\n\n### sow\n`sow` receives a `Variable` type, a `name`, and a `value`, and stores it in the `Module` so it can be retrieved at a later time. As the following example shows, NNX APIs such as `nnx.state` or `nnx.pop` are a good way of retrieving the sowed state, however `pop` is recommended because it explicitly removes the temporary state from the Module.\n\n```{code-cell} ipython3\nclass Block(nnx.Module):\n  def __init__(self, din: int, dout: int, rngs: nnx.Rngs):\n    self.linear = nnx.Linear(din, dout, rngs=rngs)\n    self.bn = nnx.BatchNorm(dout, rngs=rngs)\n    self.dropout = nnx.Dropout(0.1, rngs=rngs)\n\n  def __call__(self, x):\n    y = nnx.relu(self.dropout(self.bn(self.linear(x))))\n    self.sow(nnx.Intermediate, \"y_mean\", jnp.mean(y))\n    return y\n\nclass MLP(nnx.Module):\n  def __init__(self, num_layers, dim, rngs: nnx.Rngs):\n    self.blocks = nnx.List([Block(dim, dim, rngs) for _ in range(num_layers)])\n\n  def __call__(self, x):\n    for block in self.blocks:\n      x = block(x)\n    return x\n\n\nmodel = MLP(num_layers=3, dim=20, rngs=nnx.Rngs(0))\nx = jnp.ones((10, 20))\ny = model(x)\nintermediates = nnx.pop(model, nnx.Intermediate) # extract intermediate values\nprint(intermediates)\n```\n\n### perturb\n`perturb` is similar to `sow` but it aims to capture the gradient of a value, currently this is a two step process although it might be simplified in the future:\n1. Initialize the pertubation state by running the model once.\n2. Pass the perturbation state as a differentiable target to `grad`.\n\nAs an example lets create a simple model and use `perturb` to get the intermediate gradient `xgrad` for the variable `x`, and initialize the perturbations:\n\n```{code-cell} ipython3\nimport optax\n\nclass Model(nnx.Module):\n  def __init__(self, rngs):\n    self.linear1 = nnx.Linear(2, 3, rngs=rngs)\n    self.linear2 = nnx.Linear(3, 4, rngs=rngs)\n  def __call__(self, x):\n    x = nnx.gelu(self.linear1(x))\n    x = self.perturb('xgrad', x)\n    x = self.linear2(x)\n    return x\n\nrngs = nnx.Rngs(0)\nmodel = Model(rngs)\noptimizer = nnx.Optimizer(model, tx=optax.sgd(1e-1), wrt=nnx.Param)\nx, y = rngs.uniform((1, 2)), rngs.uniform((1, 4))\n_ = model(x) # initialize perturbations\nprint(f\"{nnx.state(model, nnx.Perturbation) = !s}\")\n```\n\nNext we'll create a training step function that differentiates w.r.t. both the parameters of the model and the perturbations, the later will be the gradients for the intermediate values. `nnx.jit` and `nnx.value_and_grad` will be use to automatically propagate state updates. We'll return the `loss` function and the itermediate gradients.\n\n```{code-cell} ipython3\n@nnx.jit\ndef train_step(model, optimizer, x, y):\n  graphdef, params, perturbations = nnx.split(model, nnx.Param, nnx.Perturbation)\n\n  def loss_fn(params, perturbations):\n    model = nnx.merge(graphdef, params, perturbations)\n    return jnp.mean((model(x) - y) ** 2)\n\n  loss, (grads, iterm_grads) = nnx.value_and_grad(loss_fn, argnums=(0, 1))(params, perturbations)\n  optimizer.update(model, grads)\n\n  return loss, iterm_grads\n\nfor step in range(2):\n  loss, iterm_grads = train_step(model, optimizer, x, y)\n  print(f\"{step = }, {loss = }, {iterm_grads = !s}\")\n```\n\n## Object\n\n+++\n\n`Object` are NNX types that are **not** registered as JAX pytrees. Formally, any `Object` subclass is a `nnx.Pytree` with `pytree=False`.\n\n```{code-cell} ipython3\nclass Foo(nnx.Object): # instead of Foo(nnx.Pytree, pytree=False)\n  def __init__(self):\n    self.a = [jnp.array(1), jnp.array(2)]  # no checks\n    self.b = \"hello\" \n    self.b = jnp.array(3) # no checks\n\nfoo = Foo()\n\n@nnx.jit # can use in NNX transformations\ndef double(foo: Foo):\n  foo.a = [x * 2 for x in foo.a]\n  foo.b *= 2\n\ndouble(foo)\nprint(f\"{ nnx.state(foo) = }\")  # can be used with NNX APIs\nprint(f\"{ jax.tree_util.all_leaves([foo]) = }\")  # not a pytree\n```\n"
  },
  {
    "path": "docs_nnx/guides/randomness.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Randomness\\n\",\n    \"\\n\",\n    \"Flax NNX uses the stateful `nnx.Rngs` class to simplify Jax's handling of random states. For example, the code below uses a `nnx.Rngs` object to define a simple linear model with dropout:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from flax import nnx\\n\",\n    \"\\n\",\n    \"class Model(nnx.Module):\\n\",\n    \"  def __init__(self, *, rngs: nnx.Rngs):\\n\",\n    \"    self.linear = nnx.Linear(20, 10, rngs=rngs)\\n\",\n    \"    self.drop = nnx.Dropout(0.1)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x, *, rngs):\\n\",\n    \"    return nnx.relu(self.drop(self.linear(x), rngs=rngs))\\n\",\n    \"\\n\",\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"model = Model(rngs=rngs)  # pass rngs to initialize parameters\\n\",\n    \"x = rngs.normal((32, 20))  # convenient jax.random methods\\n\",\n    \"y = model(x, rngs=rngs)  # pass rngs for dropout masks\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We always pass `nnx.Rngs` objects to models at initialization (to initialize parameters). For models with nondeterministic outputs like the one above, we also pass `nnx.Rngs` objects to the model's `__call__` method.\\n\",\n    \"\\n\",\n    \"The Flax NNX [pseudorandom number generator (PRNG)](https://flax.readthedocs.io/en/latest/glossary.html#term-RNG-sequences) system has the following main characteristics:\\n\",\n    \"\\n\",\n    \"- It is **explicit**.\\n\",\n    \"- It is **order-based**.\\n\",\n    \"- It uses **dynamic counters**.\\n\",\n    \"\\n\",\n    \"> **Note:** To learn more about random number generation in JAX, the `jax.random` API, and PRNG-generated sequences, check out this [JAX PRNG tutorial](https://jax.readthedocs.io/en/latest/random-numbers.html).\\n\",\n    \"\\n\",\n    \"## `Rngs`, `RngStream`, and `RngState`\\n\",\n    \"\\n\",\n    \"In Flax NNX, the `nnx.Rngs` type is the primary convenience API for managing the random state(s). Following Flax Linen's footsteps, `nnx.Rngs` have the ability to create multiple named PRNG key [streams](https://jax.readthedocs.io/en/latest/jep/263-prng.html), each with its own state, for the purpose of having tight control over randomness in the context of [JAX transformations (transforms)](https://jax.readthedocs.io/en/latest/key-concepts.html#transformations).\\n\",\n    \"\\n\",\n    \"Here are the main PRNG-related types in Flax NNX:\\n\",\n    \"\\n\",\n    \"* **`nnx.Rngs`**: The main user interface. It defines a set of named `nnx.RngStream` objects.\\n\",\n    \"* **`nnx.RngStream`**: An object that can generate a stream of PRNG keys. It holds a root `key` and a `count` inside an `nnx.RngKey` and `nnx.RngCount` `nnx.Variable`s, respectively. When a new key is generated, the count is incremented.\\n\",\n    \"* **`nnx.RngState`**: The base type for all RNG-related states.\\n\",\n    \"  * **`nnx.RngKey`**: NNX Variable type for holding PRNG keys. It includes a `tag` attribute containing the name of the PRNG key stream.\\n\",\n    \"  * **`nnx.RngCount`**: NNX Variable type for holding PRNG counts. It includes a `tag` attribute containing the PRNG key stream name.\\n\",\n    \"\\n\",\n    \"To create an `nnx.Rngs` object you can simply pass an integer seed or `jax.random.key` instance to any keyword argument of your choice in the constructor.\\n\",\n    \"\\n\",\n    \"Here's an example:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_7724a6e7f26b4b71a5626ec2b9a9a689\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_7724a6e7f26b4b71a5626ec2b9a9a689\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNrtGQlX4kj6r1TTb0dYJYYbvN4G5LJbbcVubXfmsZWkEkpCJVYKEOf1f9+vkoBcoj2jo86o7ylWffXdtzu+GDlkTxGcEN9wPdLmrivQ78hzfSqoy7YQJw4WdEC2keUykbRwjzqjLdRzmet72IDzYYcKkgz+2EIehxOH+iIZoE6KkQenzGVwrGOja3O3z8yk4Tou3wqfbqPoL90BAMBHTdHZQhYVAMYEYWIb9ShLRucpVf0X4HJvkj69pcyGdy43CU/C0TbysGnCYdIhlthCaaMjuWEk2SHU7sBJSslJekxgCsJN8EcfkgPqU506VICIuC/cCWySMsEp86khyZLwNpLrx85mqMediR6TvM+AJocz3+DUE0gqYncNe55DDSxVu+kagkg1cYJ7a3vxeGJ3DzQP9HyBTGIxH+0i0aG+YhNxCmY5ck0STygd1xdKcA+iEYHaHmFSZM2QWOWj//627KaBmekQuGZ9x9kOKSjAZst1GZzGhy7vJtA0D+45HMmrmWNBDXnoEW65vIeZQRTmDuOJwBGAQHzhBiXDRzsok04AHmqh+BzXikOYLTpodxepEmQl65yIPmegd0Qcn9wx1ukzydk8ar9DLSH5CwDkhx/wfQ+FOLgfM92hwsl1n/hCY7QXmKvGcY/EQ50kJI7tBUJe3++EatxeIuOYxG4oxgopH8+D5CI0pHBt2wnDtx2EGHirJ3HJE+KIDUQG4OCRJSV3wd9Kl4yk0mM8JhmKgBXDwb7/GaI4whuPTXC2e+CGsTHxHwnQJ7h/4ON7O5vLAsCkAxQg3I3N5pkYElgHScnNbkyNQehysQjiMmARlMHgalUwLNdAXL4Zyx6DYAzzXZBw2ljXORkE/hPkn4/5YhqrKkgVARhurwcPpyBw8CWFnwPBW8wV8a2OOyA8sQQ+AvdlErGnEWZSuVROAoAUhHNitj1Ig6TjOpDTpgEL8jvKwoEYW4gKDNlEPrYAHOsgPgO93JO/gecZsHaoMYA2qQ9ER+M8PQ+I9pCDdeJsbekEIptMcWUEX9tL6YUpOJmSOTjK3aDbCS3KgsSsO65M+vfSDDS6SNnEvOsTbIO3sMXXgTHmjjrYj+8FOPeW6iE0oNEhRpeYiQT6d+KOB/l0+aMx/AyHQS3ZQmu/pnO6sfaS7M0+upfJ/F/ApLSjJNznvjSg50JFJXwJXeo/HdkgFAJCySAH+Pf5+NNQvRNPkBuxSEWhftui3Bdtl7Wl+y8JrVWhpKRzMpqWmgr9afZDi8+zKKXqYW5DDxSyEQT0jz9JDfKhN9L7QkDzsSwB3V0vc9oYis1BgSKhA10O/CtJZc3YXAO7dojBKyh2UGvU013HR8d9IeU1USV8Cb+9EQRGckj0LjSjYebtQX3pBG0nZgKeU+wTc9LCfiSq/N5edPPwddA6qkqJ9OalDONjiRTL093dS2WI/bYBdQAUO3mPLTFTPcZ5ehXNuTezJKdVjwaYx5NJEwucxAwMGzQnieljSUR2XByzsTcHaFHKRwQ0Bu100u2LnxNlwgEYhhLzwywnAUn0gfY8lwvMFnDr3O0S1pYnd8noYe1OPZvS59jMPxTZvwBjZtuA7tfkhEWszs4XgHMW8Imy61ToRIV0JlQN7BhxGIKgA095N0HTpvgCy/cTfp+Nk2gsCzkxXQGySy6mlXfdxw6DnrYNY6NFbwDJTJgUgzCBBh7LvmiIOYPAa48T+9gWloWNVGYJoAcd8O+TiZBHA6DMXpGSoqOkqgRp9W4+3QqGRsyTNscmBbPFUSqTM4m9gVxwaZsgFdjLG52N0MWhdZUJIzhCkZoXeFnIrE+TttFCgh7L80MZdsB1ZZu7LMfKkgeJeClM2JxNQQHV+xCFGMAaDvYgFz7cT/58sbifwh2jIRC5gaRh3gvzFHwsIzGjirv5a5kqZgccZXawQiswzIvKZka95YCPIXYvnifcbsiBE33QOMcjxeJuD4ZYoy8HJ0UGvK8MsAOTbjyRUHwXRtwgDchRVf5WwpItx9RHFu3YGgRFYrIY8DuECLk9IENUabVaUpqWPJO7gOASJu1g6mqNmBH/33+iRsEg44T0803D9JjG5DbEic6G0SIqKwdNnxtbqM+duKxgW/J+c+haVnpbhxqZz26Yaql+aGtlLfhqnmiaG3wqnw7hZ6OmaVVt1Ve5p2l21/1kNqvlyvC7pp19rxxoh81yRavZN83G547wy4eU2Jna/kX6czP/fdDy+vTLYe4sdXDRPP12ODg/vBVfRrVaZf3c7p7R8r7aofsn/YOqWb9SG/qmNWia3vWnfOf6nNKT/iGrdxrWV6F9zZePeFarNVm3mje+9vts/TR3bfjd4cCqOZvXN3bVLdr6wbBeTDW0Taad5j5zfpA6Xbdv1VNT1Q6slH1UqAzrV2lbdUf900KhV03lh42L0rFte+SsO8qSpn6bM3R+XBdYs0+aR8N97I/8k36zeXFerQ21Lyde87v5dXNz3S6cFS4yQrU+fbnWBjnA+Vk7KmiHQ61n35621vuXLVK9uElbeeP2KHvaGOX6Ze3TbfnKq3kZ2jipVNXL/pdsq8Cs8udqo3bY0+h6cVBNd1iqU1jXvw0vroYNPtivf62wK6tatcX6sXHpOIVcqXIwLBc7pezhYb2VqV9qdq+ZuyqflMRZnTRK1XK5Wc/s29nTze/GSNfqYNNvnza1kzrWyGHF0Rq31WP7Utj58hf7+Li5X+7SkxyplS8q5ZpBVa/DXY+Bb3iX1f3UbarbsiqW6Iw+sYaJa37DUo969epRvmxq19++eVj4rcueaWJaSlu3pexXenWd93o8f+x+r7Qor/cGB/VM67yVqVXTRvnEOltvOK5Xz9b8YQ7b1/kivSStI8c7Z+VGk5iHnPTPr+uVXuq8xrut1k0unT8/94cacJRAwSZQxNcCt16TpfF/8GMS/dh0PegL7kIy2F8qirICYiOM2d8A1+qNUCdYqAWtW9hVAm5wD2ageNjcza47IQTPXBm+ABY1f/LMh/QgUchuVraAeIipQAwPqI2FyxXA7Oku5qYy5FSQMxj84ne4QNgI191ODRqSeGyq1ZXbNKByRnsEeuL4eN268I6THvStC09/bKC0qqpB2wHJFzqQeDC0Lac71c/G7piT4+o4g8kFZAx9RDVMHUhswkUS+EOQ2aDRYNCzQTamoDOCTdmOr0/rLtoMPrATlK39eCk4u/eZb5Jiezthhd6hzOtHlSYW1GTdvYktRRKVb7gMSzcwETyepTtbaWN7vzhCsg0Qq+FmLuc62Nie5eAbhbEbhTPboboyxhj+OmW2H9/ZjCRaoDJuLeaIzKwaY3tgG8DTgrELiksWxdNZVE7M0pl+vjbu3dZmj+VR2xk4s8exhYEmtuo+tge9L+75u/eb9G9nwVbQ1Cw149rd5LiGXFaRUbq79pNpKlhaJ9bQZKzdjSmhlmMoaCl2Y1MzL3Ql0a3cVcxP5XAbhCWk/Q58jmTfu99b/rAjplE8lX5JRxTY3l3CbbhyB+f4eJMubIeaCj9HLG4sij8ZNUDGn9TRM3uAAlKu9gIJ8cc94X6/mFd3l4yeKehfVbB/IqOXiHT5j7kH7AwQLxHxKRQvvmSYB6Ni5HnU3I1N/m+GjUxOzZsmSWVy2YxFinoJF41cumTqVsrI4zm8y/7fFnnVcmGuwFOCCXZZSlibrFECwDEI2EhitPjoF1tsxxPy56zm3lb2AXnCWf1h3wzh/opM9EcT/yqlbrxCxT+c+iOop1L6Q5aYdvpVWeO+VD39b/jYHkx99wPd4VqIoMTqMvH2I+4568Djo8wAnYp/QsWvSEFfpOYHKn7A2gHMC9X97Ous+/lMqZQtWYZhqdmMqmI1Y6pmVs1a0AYYmeJL1P0+ZSKTfvv1PvC2R1X8Kcj3mv+Uyn+46k/g3uv+qrr/5kz/vJXgvdV7Zcn2uZZ7j0+qJnc9ty/ed7nPa+5IzffZO7p+3+Y+XOAjVb3FdW7E+qryPgXyvtB9uwvdsRlXTPJTIO8r3akRrZgz04WUWkgXc5lsERt6gZRyxDKwlSvkVPN9pftEfrl6wlsAfDUD3rL8/yba/GmVPqICvO91/z7N/l9VDt43u69hszs25cqBfgbofbc7VcjNtK5bejFfKOWK2VwxXcoWcDadLaRzJT1b1NPvu90/65OPK/2vcbv75ov/g/vdBcD3BuDNL3j/yoLwbvTXZfRn2/j90w09gTTpYO//+7bsXA==</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_7724a6e7f26b4b71a5626ec2b9a9a689\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNrtVk1P20AQvfdXrIxE7QLG+IPYSbDUQitx6aE99IBQtN4dJy7LrrveUKKK/95ZOx+EOqWV2hs5xFnPmzfzZvfZGfPqjjRmIeDM4VVTC7oYEqkkOKTiZ06p9IRDCVoDn1AWJcEp53ASJXFUQlpkNGVJmPGiPGGn1MnHTU0lflu+3GdKYDpTt7cgzeT7DOQE7hHAgQ+lMq5fKsFpIWAiFYfhjDZuLmgBIt+OTIyaTgW0OUM2A3YD3PPIG4/8IG2NIdmj7WdEHsbHXfW2FcIEbRor4xEdttlVGVeynhtiFjWKb3kLde/05ixbwGBXPh8ft8nbZTrBtCg03Dl9od5ZPEG2MYQLWjc2uC/MCEV1g32K23DskR7QaxxZ28/r/Cu9999qTRe9uJZsDVxBbmBhq5d6sT81I9fbQf0M4c7mH4ucbkQ+uSz36jfSt2IV3pMGTyubVYJrkL+NO3k7E9f1DglvD8K2Zo+oO9BoikpOh6+uAhJc72hz+4Kuysd/Y63TKMvirGSsDOIoCGgQ8YDHQVyi01iUbqy187jZrteb7AYrPfNKmij0+gb8t02mCQ8HJ8EgTJMoTikrBpAlUDJaJoMk4LS3yRfrvVjv31jv5P9Yj4dFURbp6SBL0jhJwywe0DiMB2GSFXFahP/PekYDNEzVcKTn8mgG2r61mK7qlTFoXYuKUVMpeayYAXPUYA69dXKmZIMoqqdgLnlDzsiV8+fv6UPi/PmT5zH42SfAY/Czg70edTK4YlopgyLMrGp8lPQJlx/R+643IkupOKslaAn3v81BLz6DAGaUdh1/Pc6JDTu/pJ4LPAC2yHLtM3ujLWP0HBC/jmjAI8PgS2Vm7lb6mrTUdGqfJ10/c/vTZ7g3Bi6Wyw9LhNWwQvu4oyD5uT3/a2IbV5q4AgypkC8Y4WW82VxfgJyaGd49OLB/eSyuCyJ4zYzL9wLsz3eLSyRfZV9V11jA5jRqrhlcoDN2znDPOsMhB+RJerfcGsuazS8r3axqt8owYRPVcIs+tkN42N6J3iF71imtBdAkPf74CfEZuR0=</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_7724a6e7f26b4b71a5626ec2b9a9a689\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"rngs = nnx.Rngs(params=0, dropout=random.key(1))\\n\",\n    \"nnx.display(rngs)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Notice that the `key` and `count` `nnx.Variable`s contain the PRNG key stream name in a `tag` attribute. This is primarily used for filtering as we'll see later.\\n\",\n    \"\\n\",\n    \"To generate new keys, you can access one of the streams and use its `__call__` method with no arguments. This will return a new key by using `random.fold_in` with the current `key` and `count`. The `count` is then incremented so that subsequent calls will return new keys.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_3e29bf34baa14c71bab5dc5adaaef4ee\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_3e29bf34baa14c71bab5dc5adaaef4ee\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_3e29bf34baa14c71bab5dc5adaaef4ee\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNrtVslu2zAQvfcrCAVopaZRbJm2JdsR0BXIpYf20EMQGBQ5stXSpErRaYwi/96h5CVK7SxAc6sPlqh5s7zhPEoTUVyRyq4knHmiqErJViOitAKPFOLMy7WZCsjBGBDTbh5FlCddyvo57Q+zOOtT6A/jJIt6Oc0HXjqpSqbw38VLQ64lunO9WICy019zUFO4RoAAMVLa+mGupWCZhKnSAkZzVvmpZBnItG2ZWj2bSah9RnwO/AeIICCvA/Kb1DlG5IjVvzG5mZw22etSCJesqhyNW+GwzCbLpFDl0hK7KpF8HTfT195en3UJaGzSp5PT2rmdpiHMsszAlbfPtLcXd5C1DeGSlZUzvpR2jKSaxt7F7WIckT2gV9iyup5X6Xd2Hb41hq324upgW+AG8gNWLntuVi9nduwHB0I/EPBg8bdJznYk71zWe3UP9ZatwGfK4rTyeSGFAXWv3Uvrnvh+8IaIehDanAOir8CgKAo1G7246JDO5YEy2xdUVTp5grTiKIlzGkFP5AmNkk6MSos7HS54ZxglADtpHRw3V/V2k/3uhs+yULYXBfsa/NQihwntRjRLhgxi2qVxMsyA9fuDXAg6OFTkf+n9l96/kV73eaQ3jAaik+MEUwpU9GjcR+nFgvZokvRjzp9PetYAVFyXcGKW6mQOxr21uCnKjTBYWcqCM1todaq5BXtSoQ9beCnXqkIUMzOw56IiZ+TCe/x7+g3xHn/y3AY/eAK0wA819nLc0BCaG60tkrDzogqR0hdcfkbt+8GYrKlir9agNTz8uQSz+goSuNXG98JtO6fO7P3l+l7iALgk63XI3YM6jTVLQPzWYgBHhsO3ws79lvs2aG7YzJ0nTT1Ldxty3BsLH9bLT2uE47BBh7ijoMR7N//bwM6uDfElWFJgvM4YL5Pd5oYS1MzO8enxsfvkcbjGiOBtZFx+lOBu363OMfjG+6K4xATOp9JLw+EDKuNgD4+cMjxyTO64N8tWW7bRwrww1SZ3zQwddlYDC9Sxa8JNeyf2NjlwSqklgCLZo48/gOe2/Q==</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_3e29bf34baa14c71bab5dc5adaaef4ee\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"params_key = rngs.params()\\n\",\n    \"dropout_key = rngs.dropout()\\n\",\n    \"\\n\",\n    \"nnx.display(rngs)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that the `key` attribute does not change when new PRNG keys are generated.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Using random state with flax Modules.\\n\",\n    \"\\n\",\n    \"Almost all flax Modules require a random state for initialization. In a `Linear` layer, for example, we need to sample the weights and biases from the appropriate Normal distribution. Random state is provided using the `rngs` keyword argument at initialization.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"linear = nnx.Linear(20, 10, rngs=rngs)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Specifically, this will use the RngSteam `rngs.params` for weight initialization. The `params` stream is also used for initialization of `nnx.Conv`, `nnx.ConvTranspose`, and `nnx.Embed`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The `nnx.Dropout` module also requires a random state, but it requires this state at *call* time rather than initialization. Once again, we can pass it random state using the `rngs` keyword argument.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"dropout = nnx.Dropout(0.5)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Array([2., 0., 2., 2.], dtype=float32)\"\n      ]\n     },\n     \"execution_count\": 7,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"import jax.numpy as jnp\\n\",\n    \"dropout(jnp.ones(4), rngs=rngs)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The `nnx.Dropout` layer will use the rng's `dropout` stream. This also applies to Modules that use `Dropout` as a sub-Module, like `nnx.MultiHeadAttention`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To summarize, there are only two standard PRNG key stream names used by Flax NNX's built-in layers, shown in the table below:\\n\",\n    \"\\n\",\n    \"| PRNG key stream name | Description                                   |\\n\",\n    \"|----------------------|-----------------------------------------------|\\n\",\n    \"| `params`             | Used for parameter initialization             |\\n\",\n    \"| `dropout`            | Used by `nnx.Dropout` to create dropout masks |\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Default PRNG key stream\\n\",\n    \"\\n\",\n    \"One of the downsides of having named streams is that the user needs to know all the possible names that a model will use when creating the `nnx.Rngs` object. While this could be solved with some documentation, Flax NNX provides a `default` stream that can be\\n\",\n    \"be used as a fallback when a stream is not found. To use the default PRNG key stream, you can simply pass an integer seed or `jax.random.key` as the first positional argument.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_0ecc6278c22b4db19a08abab42e8069a\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_0ecc6278c22b4db19a08abab42e8069a\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_0ecc6278c22b4db19a08abab42e8069a\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNrtVk1vm0AQvfdXrIjUQpMQMDjGH0Hqp5RLD+2hhyiylt3Bpt3s0mWdxqry3zsL2A4pzofU3uqD8TJv3syb3Qee8eKaVGYt4MzhRVUKup4QqSQ4pOBnTq70nEMOWgOfx8GQZ4MgiPnoNAYaUYii8TAIYx4mYZhTJ51VJZX4bflSnymB6UxdXYE0859LkHO4QQAHPpHKuH6uBKeZgLlUHCZLWrmpoBmItBuZG7VYCKhzJmwJ7DtwzyOvPfKL1DUm5IDWnym5nZ001etWCBO0qqyMO3TYZlNlVshyZYhZlyi+5s3UjdOb07aAwaZ8Ojupk7tlGsE0yzRcO32h3lncQ9YxhAtaVjb4UpgpimoGex+34zggPaBXOLK6n1fpN3rjv9GarntxNdkWuIF8h7Wtnuv1y4WZut4e6kcI9zZ/V+RiJ/Lepd2rB6R3YgXekwZPK1sWgmuQD8adtJ6J63pHhNcHoavZI+oaNJqikIvJi4uAhJd72uxe0FXp7BnWCvMkjhnkpxEL43DIkzCm4+FgAMMxHVEW7ay197jZrreb7IYbPatCmmjg9Q34uU3ycTKiSTyOxgziPOEJj/IBizkbD0ejLE96m/xvvf/W+zvWC/6R9YI4yoajPAggiFkYZKdxAIxmbDxiNEyyZ1tv8FTrGQ1QMVXCsV7J4yVo+9Ziuig3xqBlKQpGTaHkiWIGzHGFOfTKSZmSFaKoXoA55xU5IxfO09/TR8R5+pPnLvjRJ0CH+bHBXk4bGVwxrZRBEWZZVD5K+ozLT+h915uSVirOqgW1cP/HCvT6CwhgRmnX8bfjnNuw80fqO4EHwBZp1z6zN+oyRq8A8duIBjwyDL4WZul20rekuaYL+zxp+lnZnz7DvTHwvl1+bBFWwwbt446C5O/s+d8S27jSxBVgSIF8wRQvs93m+gLkwizx7uGh/ctjcU0QwVtmXH4QYH++XZ8j+Sb7orjEAjanUivN4D06Y+8MD6wzHHJI7qU3y85Ytmx+XuhqU7tWhgm7qIYr9LEdwm13J3qH7Fmn1BZAk/T44zetCbmg</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_0ecc6278c22b4db19a08abab42e8069a\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"rngs = nnx.Rngs(0, params=1)\\n\",\n    \"\\n\",\n    \"key1 = rngs.params() # Call params.\\n\",\n    \"key2 = rngs.dropout() # Fallback to the default stream.\\n\",\n    \"key3 = rngs() # Call the default stream directly.\\n\",\n    \"\\n\",\n    \"nnx.display(rngs)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"As shown above, a PRNG key from the `default` stream can also be generated by calling the `nnx.Rngs` object itself.\\n\",\n    \"\\n\",\n    \"> **Note**\\n\",\n    \"> <br> For large projects it is recommended to use named streams to avoid potential conflicts. For small projects or quick prototyping just using the `default` stream is a good choice.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### jax.random shorthand methods\\n\",\n    \"Since a very common pattern is to sample a key and immediately pass it to a function from `jax.random`, both `Rngs` and `RngStream` expose the same functions as methods with the same signature except they don't require a key:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import jax\\n\",\n    \"rngs = nnx.Rngs(0, params=1)\\n\",\n    \"\\n\",\n    \"# using jax.random\\n\",\n    \"z1 = jax.random.normal(rngs(), (2, 3))\\n\",\n    \"z2 = jax.random.bernoulli(rngs.params(), 0.5, (10,))\\n\",\n    \"\\n\",\n    \"# shorthand methods\\n\",\n    \"z1 = rngs.normal((2, 3))                 # generates key from rngs.default\\n\",\n    \"z2 = rngs.params.bernoulli(0.5, (10,)) # generates key from rngs.params\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Forking random state\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Say you want to train a model that uses dropout on a batch of data. You don't want to use the same random state for every dropout mask in your batch.  Instead, you want to fork the random state into separate pieces for each layer. This can be accomplished with the `fork` method, as shown below.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class Model(nnx.Module):\\n\",\n    \"  def __init__(self, rngs: nnx.Rngs):\\n\",\n    \"    self.linear = nnx.Linear(20, 10, rngs=rngs)\\n\",\n    \"    self.drop = nnx.Dropout(0.1)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x, rngs):\\n\",\n    \"    return nnx.relu(self.drop(self.linear(x), rngs=rngs))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"model =  Model(rngs=nnx.Rngs(0))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"@nnx.vmap(in_axes=(None, 0, 0), out_axes=0)\\n\",\n    \"def model_forward(model, x, rngs):\\n\",\n    \"  return model(x, rngs=rngs)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"(Rngs( # RngState: 2 (12 B)\\n\",\n       \"   default=RngStream( # RngState: 2 (12 B)\\n\",\n       \"     tag='default',\\n\",\n       \"     key=RngKey( # 1 (8 B)\\n\",\n       \"       value=Array((), dtype=key<fry>) overlaying:\\n\",\n       \"       [0 1],\\n\",\n       \"       tag='default'\\n\",\n       \"     ),\\n\",\n       \"     count=RngCount( # 1 (4 B)\\n\",\n       \"       value=Array(1, dtype=uint32),\\n\",\n       \"       tag='default'\\n\",\n       \"     )\\n\",\n       \"   )\\n\",\n       \" ),\\n\",\n       \" Rngs( # RngState: 10 (60 B)\\n\",\n       \"   default=RngStream( # RngState: 10 (60 B)\\n\",\n       \"     tag='default',\\n\",\n       \"     key=RngKey( # 5 (40 B)\\n\",\n       \"       value=Array(shape=(5,), dtype=key<fry>),\\n\",\n       \"       tag='default'\\n\",\n       \"     ),\\n\",\n       \"     count=RngCount( # 5 (20 B)\\n\",\n       \"       value=Array(shape=(5,), dtype=dtype('uint32')),\\n\",\n       \"       tag='default'\\n\",\n       \"     )\\n\",\n       \"   )\\n\",\n       \" ))\"\n      ]\n     },\n     \"execution_count\": 13,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"dropout_rngs = nnx.Rngs(1)\\n\",\n    \"forked_rngs = dropout_rngs.fork(split=5)\\n\",\n    \"(dropout_rngs, forked_rngs)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"(5, 10)\"\n      ]\n     },\n     \"execution_count\": 14,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"model_forward(model, jnp.ones((5, 20)), forked_rngs).shape\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The output of `rng.fork` is another `Rng` with keys and counts that have an expanded shape. In the example above, the `RngKey` and `RngCount` of `dropout_rngs` have shape `()`, but in `forked_rngs` they have shape `(5,)`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Implicit Random State\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"So far, we have looked at passing random state directly to each Module when it gets called. But there's another way to handle call-time randomness in flax: we can bundle the random state into the Module itself. This makes the random state is just another type of Module state. Using implicit random state requires passing the `rngs` keyward argument when initializing the module rather than when calling it. For example, here is how we might construct the simple `Module` we defined earlier using an implicit style.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 18,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"y.shape = (1, 10)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Model(nnx.Module):\\n\",\n    \"  def __init__(self, rngs: nnx.Rngs):\\n\",\n    \"    self.linear = nnx.Linear(20, 10, rngs=rngs)\\n\",\n    \"    self.drop = nnx.Dropout(0.1, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    return nnx.relu(self.drop(self.linear(x)))\\n\",\n    \"\\n\",\n    \"model = Model(nnx.Rngs(params=0, dropout=1))\\n\",\n    \"\\n\",\n    \"y = model(x=jnp.ones((1, 20)))\\n\",\n    \"print(f'{y.shape = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This implicit state handling style is less verbose than passing RNGs explicitly, and more closely resembles code in other deep learning frameworks like PyTorch. However, as we'll see in the following sections, using implicit state makes it less obvious how to apply jax transformations to your Modules. With explicit state, you can usually use tranforms like `jax.vmap` directly. With implicit state, you'll need to some extra tricks with `nnx.vmap` to make everything work. Because of this additional complexity, we recommend that new flax projects stick to the explicit style.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Filtering random state\\n\",\n    \"\\n\",\n    \"Implicit random state can be manipulated using [Filters](https://flax.readthedocs.io/en/latest/guides/filters_guide.html) just like any other type of state. It can be filtered using types (`nnx.RngState`, `nnx.RngKey`, `nnx.RngCount`) or using strings corresponding to the stream names (refer to [the Flax NNX `Filter` DSL](https://flax.readthedocs.io/en/latest/guides/filters_guide.html#the-filter-dsl)). Here's an example using `nnx.state` with various filters to select different substates of the `Rngs` inside a `Model`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 21,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_c0d7ce35e60448b08f3721b83bd92f1a\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_c0d7ce35e60448b08f3721b83bd92f1a\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_c0d7ce35e60448b08f3721b83bd92f1a\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNqVVEtT2zAQvvdXaMwM2AVMHnZCXp5poZ3h0kN76IFhMoq0jl0UyZUViqfDf+/KdpwaHEpziCztt9++d87TB5KbQsDC4WmeCVpMiVQSHJLyhRMrveQQg9bAl2E/HsOox1lIecDpYBVPLilnEEz4JUAITjTPMyqrf8IEzfOFw5RADrpaaXhwomNhZj/oo/9Ba1q4vTPCTZHBYptKMxx4x2szm19UHLsD/Yvm/+HkpD8IKASjcTzsBzEfToZjFg/okI8GKxoG472Tli/yK/+Y2mxAmuWvBOQSHhHAgU+lMq4fK8HpSsBSKg7ThOZuJOgKRNSWLI1arwWUOlOWALsH7nnkvUd+k9LGlBzR8jcjTxhdab2VqRYdullZmacy2xpSpskpeVfq0enUqV1AYWUes1cqv1aQDlFnLp4hSxnCBc1yK7R13ZXsBW7PcUQ6QCeYstKfk6hpjU5cSdYAd5B7KKz1WBe2fVzvAPU/CA86/3eQHe1ZH3WtXgm9JUvxTRrsVpakgmuQr8qdqBoX19vNSztmj6gH0DgUqVxP392SsDcOwn4QhKR/GQ77o8l4EtwdcLxj2IwGyJnK4Fxv5XkC2vYp02m260KaZSJl1KRKXihmwJznqEM3TsSUzBFF9RrMDc/Jgtw6b18fZ8R5+xjfzSpjXDGtlEFTJklzHw1/xesXHAfXm5HaIYyoBtVw/+cWdPENBDCjtOv4TdBLK3ZeqF4JXDfWSH33mX0ozRi9BcQ3Eg24oBh8T03ittQb0ljTtR2xyp+t/fQZZtDAdX39XCNsDDu0j3kHya9sSzTEVq40cQUYkiJfb4bHfF8CX4BcmwRfT0/tKrK4SojghhmvnwTYz4/FDZLvtG/TOzRgdXK11QyucQ8fzOGR3cMOOSXP1KtrKy0Nmx+nOt/ZLiNDhb1UwwZb2ybhqV2JziR7djrLRsVW7ujiP6tNb5M=</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_c0d7ce35e60448b08f3721b83bd92f1a\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"model = Model(nnx.Rngs(params=0, dropout=1))\\n\",\n    \"\\n\",\n    \"rng_state = nnx.state(model, nnx.RngState) # All random states.\\n\",\n    \"key_state = nnx.state(model, nnx.RngKey) # Only PRNG keys.\\n\",\n    \"count_state = nnx.state(model, nnx.RngCount) # Only counts.\\n\",\n    \"rng_dropout_state = nnx.state(model, 'dropout') # Only `dropout`.\\n\",\n    \"\\n\",\n    \"nnx.display(rng_dropout_state)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Reseeding\\n\",\n    \"\\n\",\n    \"In Haiku and Flax Linen, random states are explicitly passed to `Module.apply` each time before you call the model. This makes it easy to control the randomness of the model when needed (for example, for reproducibility).\\n\",\n    \"\\n\",\n    \"In Flax NNX, there are two ways to approach this:\\n\",\n    \"\\n\",\n    \"1. By passing an `nnx.Rngs` object through the `__call__` stack manually, as shown previously.\\n\",\n    \"2. By using `nnx.reseed` to set the random state of the model to a specific configuration. This option is less intrusive and can be used even if the model is not designed to enable manual control over the random state.\\n\",\n    \"\\n\",\n    \"`nnx.reseed` is a function that accepts an arbitrary graph node (this includes [pytrees](https://jax.readthedocs.io/en/latest/working-with-pytrees.html#working-with-pytrees) of `nnx.Module`s) and some keyword arguments containing the new seed or key value for the `nnx.RngStream`s specified by the argument names. `nnx.reseed` will then traverse the graph and update the random state of the matching `nnx.RngStream`s, this includes both setting the `key` to a possibly new value and resetting the `count` to zero.\\n\",\n    \"\\n\",\n    \"Here's an example of how to use `nnx.reseed` to reset the random state of the `nnx.Dropout` layer and verify that the computation is identical to the first time the model was called:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 22,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"model = Model(nnx.Rngs(params=0, dropout=1))\\n\",\n    \"x = jnp.ones((1, 20))\\n\",\n    \"\\n\",\n    \"y1 = model(x)\\n\",\n    \"y2 = model(x)\\n\",\n    \"\\n\",\n    \"nnx.reseed(model, dropout=1) # reset dropout RngState\\n\",\n    \"y3 = model(x)\\n\",\n    \"\\n\",\n    \"assert not jnp.allclose(y1, y2) # different\\n\",\n    \"assert jnp.allclose(y1, y3)     # same\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Forking implicit random state\\n\",\n    \"\\n\",\n    \"We saw above how to use `rng.fork` when passing explicit random state through [Flax NNX transforms](https://flax.readthedocs.io/en/latest/guides/transforms.html) like `nnx.vmap` or `nnx.pmap`. The decorator `nnx.fork_rngs` allows this for implicit random state. Consider the example below, which generates a batch of samples from the nondeterministic model we defined above.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 23,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"(5, 1, 10)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"rng_axes = nnx.StateAxes({'dropout': 0, ...: None})\\n\",\n    \"\\n\",\n    \"@nnx.fork_rngs(split={'dropout': 5})\\n\",\n    \"@nnx.vmap(in_axes=(rng_axes, None), out_axes=0)\\n\",\n    \"def sample_from_model(model, x):\\n\",\n    \"    return model(x)\\n\",\n    \"\\n\",\n    \"print(sample_from_model(model, x).shape)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here `sample_from_model` is modified by two decorators:\\n\",\n    \"- The function we get from the `nnx.vmap` decorator expects that the random state of the `model` argument has already been split into 5 pieces. It runs the model once for each random key.\\n\",\n    \"- The function we get from the `nnx.fork_rngs` decorator splits the random state of its `model` argument into five pieces before passing it on to the inner function.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Transforming implicit state\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In the previous section, we showed how to use `nnx.vmap` with a module that contained implicit random state. But we can use other `nnx` transformations too! Remember: implicit random state isn't different from any other type of Model state, and this applies to Flax NNX transforms too. This means you can use the Flax NNX state handling APIs of each transform to get the results you want. For a more involved example, let’s explore how to implement recurrent dropout on an `RNNCell` using `nnx.scan`.\\n\",\n    \"\\n\",\n    \"We'll start by constructing the `RNNCell` class:\\n\",\n    \"\\n\",\n    \"- First, create an `nnx.Dropout` layer that will sample PRNG keys from a custom `recurrent_dropout` stream.\\n\",\n    \"- Apply dropout (`drop`) to the hidden state `h` of the `RNNCell`.\\n\",\n    \"- Then, define an `initial_state` function to create the initial state of the `RNNCell`.\\n\",\n    \"- Finally, instantiate `RNNCell`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 24,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class Count(nnx.Variable): pass\\n\",\n    \"\\n\",\n    \"class RNNCell(nnx.Module):\\n\",\n    \"  def __init__(self, din, dout, rngs):\\n\",\n    \"    self.linear = nnx.Linear(dout + din, dout, rngs=rngs)\\n\",\n    \"    self.drop = nnx.Dropout(0.1, rngs=rngs, rng_collection='recurrent_dropout')\\n\",\n    \"    self.dout = dout\\n\",\n    \"    self.count = Count(jnp.array(0, jnp.uint32))\\n\",\n    \"\\n\",\n    \"  def __call__(self, h, x) -> tuple[jax.Array, jax.Array]:\\n\",\n    \"    h = self.drop(h) # Recurrent dropout.\\n\",\n    \"    y = nnx.relu(self.linear(jnp.concatenate([h, x], axis=-1)))\\n\",\n    \"    self.count.value += 1\\n\",\n    \"    return y, y\\n\",\n    \"\\n\",\n    \"  def initial_state(self, batch_size: int):\\n\",\n    \"    return jnp.zeros((batch_size, self.dout))\\n\",\n    \"\\n\",\n    \"cell = RNNCell(8, 16, nnx.Rngs(params=0, recurrent_dropout=1))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Next, use `nnx.scan` over an `unroll` function to implement the `rnn_forward` operation:\\n\",\n    \"- The key ingredient of recurrent dropout is to apply the same dropout mask across all time steps. Therefore, to achieve this you will pass `nnx.StateAxes` to `nnx.scan`'s `in_axes`, specifying that the `cell`'s `recurrent_dropout` PRNG stream will be broadcast, and the rest of the `RNNCell`'s state will be carried over.\\n\",\n    \"- Also, the hidden state `h` will be the `nnx.scan`'s `Carry` variable, and the sequence `x` will be `scan`ned over its axis `1`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 25,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"y.shape = (4, 20, 16)\\n\",\n      \"cell.count.value = Array(20, dtype=uint32)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@nnx.jit\\n\",\n    \"def rnn_forward(cell: RNNCell, x: jax.Array):\\n\",\n    \"  h = cell.initial_state(batch_size=x.shape[0])\\n\",\n    \"\\n\",\n    \"  # Broadcast the 'recurrent_dropout' PRNG state to have the same mask on every step.\\n\",\n    \"  state_axes = nnx.StateAxes({'recurrent_dropout': None, ...: nnx.Carry})\\n\",\n    \"  @nnx.scan(in_axes=(state_axes, nnx.Carry, 1), out_axes=(nnx.Carry, 1))\\n\",\n    \"  def unroll(cell: RNNCell, h, x) -> tuple[jax.Array, jax.Array]:\\n\",\n    \"    h, y = cell(h, x)\\n\",\n    \"    return h, y\\n\",\n    \"\\n\",\n    \"  h, y = unroll(cell, h, x)\\n\",\n    \"  return y\\n\",\n    \"\\n\",\n    \"x = jnp.ones((4, 20, 8))\\n\",\n    \"y = rnn_forward(cell, x)\\n\",\n    \"\\n\",\n    \"print(f'{y.shape = }')\\n\",\n    \"print(f'{cell.count.value = }')\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.6\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "docs_nnx/guides/randomness.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Randomness\n\nFlax NNX uses the stateful `nnx.Rngs` class to simplify Jax's handling of random states. For example, the code below uses a `nnx.Rngs` object to define a simple linear model with dropout:\n\n```{code-cell} ipython3\nfrom flax import nnx\n\nclass Model(nnx.Module):\n  def __init__(self, *, rngs: nnx.Rngs):\n    self.linear = nnx.Linear(20, 10, rngs=rngs)\n    self.drop = nnx.Dropout(0.1)\n\n  def __call__(self, x, *, rngs):\n    return nnx.relu(self.drop(self.linear(x), rngs=rngs))\n\nrngs = nnx.Rngs(0)\nmodel = Model(rngs=rngs)  # pass rngs to initialize parameters\nx = rngs.normal((32, 20))  # convenient jax.random methods\ny = model(x, rngs=rngs)  # pass rngs for dropout masks\n```\n\nWe always pass `nnx.Rngs` objects to models at initialization (to initialize parameters). For models with nondeterministic outputs like the one above, we also pass `nnx.Rngs` objects to the model's `__call__` method.\n\nThe Flax NNX [pseudorandom number generator (PRNG)](https://flax.readthedocs.io/en/latest/glossary.html#term-RNG-sequences) system has the following main characteristics:\n\n- It is **explicit**.\n- It is **order-based**.\n- It uses **dynamic counters**.\n\n> **Note:** To learn more about random number generation in JAX, the `jax.random` API, and PRNG-generated sequences, check out this [JAX PRNG tutorial](https://jax.readthedocs.io/en/latest/random-numbers.html).\n\n## `Rngs`, `RngStream`, and `RngState`\n\nIn Flax NNX, the `nnx.Rngs` type is the primary convenience API for managing the random state(s). Following Flax Linen's footsteps, `nnx.Rngs` have the ability to create multiple named PRNG key [streams](https://jax.readthedocs.io/en/latest/jep/263-prng.html), each with its own state, for the purpose of having tight control over randomness in the context of [JAX transformations (transforms)](https://jax.readthedocs.io/en/latest/key-concepts.html#transformations).\n\nHere are the main PRNG-related types in Flax NNX:\n\n* **`nnx.Rngs`**: The main user interface. It defines a set of named `nnx.RngStream` objects.\n* **`nnx.RngStream`**: An object that can generate a stream of PRNG keys. It holds a root `key` and a `count` inside an `nnx.RngKey` and `nnx.RngCount` `nnx.Variable`s, respectively. When a new key is generated, the count is incremented.\n* **`nnx.RngState`**: The base type for all RNG-related states.\n  * **`nnx.RngKey`**: NNX Variable type for holding PRNG keys. It includes a `tag` attribute containing the name of the PRNG key stream.\n  * **`nnx.RngCount`**: NNX Variable type for holding PRNG counts. It includes a `tag` attribute containing the PRNG key stream name.\n\nTo create an `nnx.Rngs` object you can simply pass an integer seed or `jax.random.key` instance to any keyword argument of your choice in the constructor.\n\nHere's an example:\n\n```{code-cell} ipython3\nrngs = nnx.Rngs(params=0, dropout=random.key(1))\nnnx.display(rngs)\n```\n\nNotice that the `key` and `count` `nnx.Variable`s contain the PRNG key stream name in a `tag` attribute. This is primarily used for filtering as we'll see later.\n\nTo generate new keys, you can access one of the streams and use its `__call__` method with no arguments. This will return a new key by using `random.fold_in` with the current `key` and `count`. The `count` is then incremented so that subsequent calls will return new keys.\n\n```{code-cell} ipython3\nparams_key = rngs.params()\ndropout_key = rngs.dropout()\n\nnnx.display(rngs)\n```\n\nNote that the `key` attribute does not change when new PRNG keys are generated.\n\n+++\n\n### Using random state with flax Modules.\n\nAlmost all flax Modules require a random state for initialization. In a `Linear` layer, for example, we need to sample the weights and biases from the appropriate Normal distribution. Random state is provided using the `rngs` keyword argument at initialization.\n\n```{code-cell} ipython3\nlinear = nnx.Linear(20, 10, rngs=rngs)\n```\n\nSpecifically, this will use the RngSteam `rngs.params` for weight initialization. The `params` stream is also used for initialization of `nnx.Conv`, `nnx.ConvTranspose`, and `nnx.Embed`.\n\n+++\n\nThe `nnx.Dropout` module also requires a random state, but it requires this state at *call* time rather than initialization. Once again, we can pass it random state using the `rngs` keyword argument.\n\n```{code-cell} ipython3\ndropout = nnx.Dropout(0.5)\n```\n\n```{code-cell} ipython3\nimport jax.numpy as jnp\ndropout(jnp.ones(4), rngs=rngs)\n```\n\nThe `nnx.Dropout` layer will use the rng's `dropout` stream. This also applies to Modules that use `Dropout` as a sub-Module, like `nnx.MultiHeadAttention`.\n\n+++\n\nTo summarize, there are only two standard PRNG key stream names used by Flax NNX's built-in layers, shown in the table below:\n\n| PRNG key stream name | Description                                   |\n|----------------------|-----------------------------------------------|\n| `params`             | Used for parameter initialization             |\n| `dropout`            | Used by `nnx.Dropout` to create dropout masks |\n\n+++\n\n### Default PRNG key stream\n\nOne of the downsides of having named streams is that the user needs to know all the possible names that a model will use when creating the `nnx.Rngs` object. While this could be solved with some documentation, Flax NNX provides a `default` stream that can be\nbe used as a fallback when a stream is not found. To use the default PRNG key stream, you can simply pass an integer seed or `jax.random.key` as the first positional argument.\n\n```{code-cell} ipython3\nrngs = nnx.Rngs(0, params=1)\n\nkey1 = rngs.params() # Call params.\nkey2 = rngs.dropout() # Fallback to the default stream.\nkey3 = rngs() # Call the default stream directly.\n\nnnx.display(rngs)\n```\n\nAs shown above, a PRNG key from the `default` stream can also be generated by calling the `nnx.Rngs` object itself.\n\n> **Note**\n> <br> For large projects it is recommended to use named streams to avoid potential conflicts. For small projects or quick prototyping just using the `default` stream is a good choice.\n\n+++\n\n### jax.random shorthand methods\nSince a very common pattern is to sample a key and immediately pass it to a function from `jax.random`, both `Rngs` and `RngStream` expose the same functions as methods with the same signature except they don't require a key:\n\n```{code-cell} ipython3\nimport jax\nrngs = nnx.Rngs(0, params=1)\n\n# using jax.random\nz1 = jax.random.normal(rngs(), (2, 3))\nz2 = jax.random.bernoulli(rngs.params(), 0.5, (10,))\n\n# shorthand methods\nz1 = rngs.normal((2, 3))                 # generates key from rngs.default\nz2 = rngs.params.bernoulli(0.5, (10,)) # generates key from rngs.params\n```\n\n## Forking random state\n\n+++\n\nSay you want to train a model that uses dropout on a batch of data. You don't want to use the same random state for every dropout mask in your batch.  Instead, you want to fork the random state into separate pieces for each layer. This can be accomplished with the `fork` method, as shown below.\n\n```{code-cell} ipython3\nclass Model(nnx.Module):\n  def __init__(self, rngs: nnx.Rngs):\n    self.linear = nnx.Linear(20, 10, rngs=rngs)\n    self.drop = nnx.Dropout(0.1)\n\n  def __call__(self, x, rngs):\n    return nnx.relu(self.drop(self.linear(x), rngs=rngs))\n```\n\n```{code-cell} ipython3\nmodel =  Model(rngs=nnx.Rngs(0))\n```\n\n```{code-cell} ipython3\n@nnx.vmap(in_axes=(None, 0, 0), out_axes=0)\ndef model_forward(model, x, rngs):\n  return model(x, rngs=rngs)\n```\n\n```{code-cell} ipython3\ndropout_rngs = nnx.Rngs(1)\nforked_rngs = dropout_rngs.fork(split=5)\n(dropout_rngs, forked_rngs)\n```\n\n```{code-cell} ipython3\nmodel_forward(model, jnp.ones((5, 20)), forked_rngs).shape\n```\n\nThe output of `rng.fork` is another `Rng` with keys and counts that have an expanded shape. In the example above, the `RngKey` and `RngCount` of `dropout_rngs` have shape `()`, but in `forked_rngs` they have shape `(5,)`.\n\n+++\n\n# Implicit Random State\n\n+++\n\nSo far, we have looked at passing random state directly to each Module when it gets called. But there's another way to handle call-time randomness in flax: we can bundle the random state into the Module itself. This makes the random state is just another type of Module state. Using implicit random state requires passing the `rngs` keyward argument when initializing the module rather than when calling it. For example, here is how we might construct the simple `Module` we defined earlier using an implicit style.\n\n```{code-cell} ipython3\nclass Model(nnx.Module):\n  def __init__(self, rngs: nnx.Rngs):\n    self.linear = nnx.Linear(20, 10, rngs=rngs)\n    self.drop = nnx.Dropout(0.1, rngs=rngs)\n\n  def __call__(self, x):\n    return nnx.relu(self.drop(self.linear(x)))\n\nmodel = Model(nnx.Rngs(params=0, dropout=1))\n\ny = model(x=jnp.ones((1, 20)))\nprint(f'{y.shape = }')\n```\n\nThis implicit state handling style is less verbose than passing RNGs explicitly, and more closely resembles code in other deep learning frameworks like PyTorch. However, as we'll see in the following sections, using implicit state makes it less obvious how to apply jax transformations to your Modules. With explicit state, you can usually use tranforms like `jax.vmap` directly. With implicit state, you'll need to some extra tricks with `nnx.vmap` to make everything work. Because of this additional complexity, we recommend that new flax projects stick to the explicit style.\n\n+++\n\n## Filtering random state\n\nImplicit random state can be manipulated using [Filters](https://flax.readthedocs.io/en/latest/guides/filters_guide.html) just like any other type of state. It can be filtered using types (`nnx.RngState`, `nnx.RngKey`, `nnx.RngCount`) or using strings corresponding to the stream names (refer to [the Flax NNX `Filter` DSL](https://flax.readthedocs.io/en/latest/guides/filters_guide.html#the-filter-dsl)). Here's an example using `nnx.state` with various filters to select different substates of the `Rngs` inside a `Model`:\n\n```{code-cell} ipython3\nmodel = Model(nnx.Rngs(params=0, dropout=1))\n\nrng_state = nnx.state(model, nnx.RngState) # All random states.\nkey_state = nnx.state(model, nnx.RngKey) # Only PRNG keys.\ncount_state = nnx.state(model, nnx.RngCount) # Only counts.\nrng_dropout_state = nnx.state(model, 'dropout') # Only `dropout`.\n\nnnx.display(rng_dropout_state)\n```\n\n## Reseeding\n\nIn Haiku and Flax Linen, random states are explicitly passed to `Module.apply` each time before you call the model. This makes it easy to control the randomness of the model when needed (for example, for reproducibility).\n\nIn Flax NNX, there are two ways to approach this:\n\n1. By passing an `nnx.Rngs` object through the `__call__` stack manually, as shown previously.\n2. By using `nnx.reseed` to set the random state of the model to a specific configuration. This option is less intrusive and can be used even if the model is not designed to enable manual control over the random state.\n\n`nnx.reseed` is a function that accepts an arbitrary graph node (this includes [pytrees](https://jax.readthedocs.io/en/latest/working-with-pytrees.html#working-with-pytrees) of `nnx.Module`s) and some keyword arguments containing the new seed or key value for the `nnx.RngStream`s specified by the argument names. `nnx.reseed` will then traverse the graph and update the random state of the matching `nnx.RngStream`s, this includes both setting the `key` to a possibly new value and resetting the `count` to zero.\n\nHere's an example of how to use `nnx.reseed` to reset the random state of the `nnx.Dropout` layer and verify that the computation is identical to the first time the model was called:\n\n```{code-cell} ipython3\nmodel = Model(nnx.Rngs(params=0, dropout=1))\nx = jnp.ones((1, 20))\n\ny1 = model(x)\ny2 = model(x)\n\nnnx.reseed(model, dropout=1) # reset dropout RngState\ny3 = model(x)\n\nassert not jnp.allclose(y1, y2) # different\nassert jnp.allclose(y1, y3)     # same\n```\n\n## Forking implicit random state\n\nWe saw above how to use `rng.fork` when passing explicit random state through [Flax NNX transforms](https://flax.readthedocs.io/en/latest/guides/transforms.html) like `nnx.vmap` or `nnx.pmap`. The decorator `nnx.fork_rngs` allows this for implicit random state. Consider the example below, which generates a batch of samples from the nondeterministic model we defined above.\n\n```{code-cell} ipython3\nrng_axes = nnx.StateAxes({'dropout': 0, ...: None})\n\n@nnx.fork_rngs(split={'dropout': 5})\n@nnx.vmap(in_axes=(rng_axes, None), out_axes=0)\ndef sample_from_model(model, x):\n    return model(x)\n\nprint(sample_from_model(model, x).shape)\n```\n\nHere `sample_from_model` is modified by two decorators:\n- The function we get from the `nnx.vmap` decorator expects that the random state of the `model` argument has already been split into 5 pieces. It runs the model once for each random key.\n- The function we get from the `nnx.fork_rngs` decorator splits the random state of its `model` argument into five pieces before passing it on to the inner function.\n\n+++\n\n## Transforming implicit state\n\n+++\n\nIn the previous section, we showed how to use `nnx.vmap` with a module that contained implicit random state. But we can use other `nnx` transformations too! Remember: implicit random state isn't different from any other type of Model state, and this applies to Flax NNX transforms too. This means you can use the Flax NNX state handling APIs of each transform to get the results you want. For a more involved example, let’s explore how to implement recurrent dropout on an `RNNCell` using `nnx.scan`.\n\nWe'll start by constructing the `RNNCell` class:\n\n- First, create an `nnx.Dropout` layer that will sample PRNG keys from a custom `recurrent_dropout` stream.\n- Apply dropout (`drop`) to the hidden state `h` of the `RNNCell`.\n- Then, define an `initial_state` function to create the initial state of the `RNNCell`.\n- Finally, instantiate `RNNCell`.\n\n```{code-cell} ipython3\nclass Count(nnx.Variable): pass\n\nclass RNNCell(nnx.Module):\n  def __init__(self, din, dout, rngs):\n    self.linear = nnx.Linear(dout + din, dout, rngs=rngs)\n    self.drop = nnx.Dropout(0.1, rngs=rngs, rng_collection='recurrent_dropout')\n    self.dout = dout\n    self.count = Count(jnp.array(0, jnp.uint32))\n\n  def __call__(self, h, x) -> tuple[jax.Array, jax.Array]:\n    h = self.drop(h) # Recurrent dropout.\n    y = nnx.relu(self.linear(jnp.concatenate([h, x], axis=-1)))\n    self.count.value += 1\n    return y, y\n\n  def initial_state(self, batch_size: int):\n    return jnp.zeros((batch_size, self.dout))\n\ncell = RNNCell(8, 16, nnx.Rngs(params=0, recurrent_dropout=1))\n```\n\nNext, use `nnx.scan` over an `unroll` function to implement the `rnn_forward` operation:\n- The key ingredient of recurrent dropout is to apply the same dropout mask across all time steps. Therefore, to achieve this you will pass `nnx.StateAxes` to `nnx.scan`'s `in_axes`, specifying that the `cell`'s `recurrent_dropout` PRNG stream will be broadcast, and the rest of the `RNNCell`'s state will be carried over.\n- Also, the hidden state `h` will be the `nnx.scan`'s `Carry` variable, and the sequence `x` will be `scan`ned over its axis `1`.\n\n```{code-cell} ipython3\n@nnx.jit\ndef rnn_forward(cell: RNNCell, x: jax.Array):\n  h = cell.initial_state(batch_size=x.shape[0])\n\n  # Broadcast the 'recurrent_dropout' PRNG state to have the same mask on every step.\n  state_axes = nnx.StateAxes({'recurrent_dropout': None, ...: nnx.Carry})\n  @nnx.scan(in_axes=(state_axes, nnx.Carry, 1), out_axes=(nnx.Carry, 1))\n  def unroll(cell: RNNCell, h, x) -> tuple[jax.Array, jax.Array]:\n    h, y = cell(h, x)\n    return h, y\n\n  h, y = unroll(cell, h, x)\n  return y\n\nx = jnp.ones((4, 20, 8))\ny = rnn_forward(cell, x)\n\nprint(f'{y.shape = }')\nprint(f'{cell.count.value = }')\n```\n"
  },
  {
    "path": "docs_nnx/guides/surgery.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Model surgery\\n\",\n    \"\\n\",\n    \"Model surgery is an act of making modifications on an existing neural network's building blocks and parameters, such as layer replacement, parameter or state manipulation, or even \\\"monkey patching\\\". In this guide, you will learn how to perform model surgery in Flax NNX using several real-world scenarios:\\n\",\n    \"\\n\",\n    \"* __Pythonic `nnx.Module` manipulation__: Using Pythonic ways to manipulate sub-`Module`s given a model.\\n\",\n    \"\\n\",\n    \"* __Manipulation of an abstract model or state__: A key trick for playing with `flax.nnx.Module`s and states without memory allocation.\\n\",\n    \"\\n\",\n    \"* __Checkpoint surgery from a raw state to model__: How to manipulate parameter states when they are incompatible with existing model code.\\n\",\n    \"\\n\",\n    \"* __Partial initialization__: How to initialize only a part of the model from scratch using a naive method or a memory-efficient method.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from typing import *\\n\",\n    \"from pprint import pprint\\n\",\n    \"import functools\\n\",\n    \"\\n\",\n    \"import jax\\n\",\n    \"from jax import lax, numpy as jnp, tree_util as jtu\\n\",\n    \"\\n\",\n    \"from jax.sharding import PartitionSpec, Mesh, NamedSharding\\n\",\n    \"from jax.experimental import mesh_utils\\n\",\n    \"import flax\\n\",\n    \"from flax import nnx\\n\",\n    \"import flax.traverse_util\\n\",\n    \"import numpy as np\\n\",\n    \"import orbax.checkpoint as orbax\\n\",\n    \"\\n\",\n    \"key = jax.random.key(0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class TwoLayerMLP(nnx.Module):\\n\",\n    \"  def __init__(self, dim, rngs: nnx.Rngs):\\n\",\n    \"    self.linear1 = nnx.Linear(dim, dim, rngs=rngs)\\n\",\n    \"    self.linear2 = nnx.Linear(dim, dim, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x = self.linear1(x)\\n\",\n    \"    return self.linear2(x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Pythonic `nnx.Module` manipulation\\n\",\n    \"\\n\",\n    \"It is easier to perform model surgery when:\\n\",\n    \"\\n\",\n    \"1) You already have a fully fleshed-out model loaded with correct parameters; and\\n\",\n    \"2) You don't intend to change your model definition code.\\n\",\n    \"\\n\",\n    \"You can perform a variety of Pythonic operations on its sub-`Module`s, such as sub-`Module` swapping, `Module` sharing, variable sharing, and monkey-patching:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"model = TwoLayerMLP(4, rngs=nnx.Rngs(0))\\n\",\n    \"x = jax.random.normal(jax.random.key(42), (3, 4))\\n\",\n    \"np.testing.assert_allclose(model(x), model.linear2(model.linear1(x)))\\n\",\n    \"\\n\",\n    \"# Sub-`Module` swapping.\\n\",\n    \"original1, original2 = model.linear1, model.linear2\\n\",\n    \"model.linear1, model.linear2 = model.linear2, model.linear1\\n\",\n    \"np.testing.assert_allclose(model(x), original1(original2(x)))\\n\",\n    \"\\n\",\n    \"# `Module` sharing (tying all weights together).\\n\",\n    \"model = TwoLayerMLP(4, rngs=nnx.Rngs(0))\\n\",\n    \"model.linear2 = model.linear1\\n\",\n    \"assert not hasattr(nnx.state(model), 'linear2')\\n\",\n    \"np.testing.assert_allclose(model(x), model.linear1(model.linear1(x)))\\n\",\n    \"\\n\",\n    \"# Variable sharing (weight-tying).\\n\",\n    \"model = TwoLayerMLP(4, rngs=nnx.Rngs(0))\\n\",\n    \"model.linear1.kernel = model.linear2.kernel  # the bias parameter is kept separate\\n\",\n    \"assert 'linear2' in nnx.state(model)\\n\",\n    \"assert 'bias' in nnx.state(model)['linear2']\\n\",\n    \"assert not hasattr(nnx.state(model)['linear2'], 'kernel')\\n\",\n    \"\\n\",\n    \"# Monkey-patching.\\n\",\n    \"model = TwoLayerMLP(4, rngs=nnx.Rngs(0))\\n\",\n    \"def awesome_layer(x): return x\\n\",\n    \"model.linear2 = awesome_layer\\n\",\n    \"np.testing.assert_allclose(model(x), model.linear1(x))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Creating an abstract model or state without memory allocation\\n\",\n    \"\\n\",\n    \"To do more complex model surgery, the key technique you can use is creating and manipulating an abstract model or state without allocating any real parameter data. This makes trial iteration faster and removes any concern on memory constraints.\\n\",\n    \"\\n\",\n    \"To create an abstract model:\\n\",\n    \"\\n\",\n    \"* Create a function that returns a valid Flax NNX model; and\\n\",\n    \"* Run `nnx.eval_shape` (not `jax.eval_shape`) upon it.\\n\",\n    \"\\n\",\n    \"Now you can use `nnx.split` as usual to get its abstract state. Note that all fields that should be `jax.Array`s in a real model are now of an abstract `jax.ShapeDtypeStruct` type with only shape/dtype/sharding information.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"State({\\n\",\n      \"  'linear1': {\\n\",\n      \"    'bias': Param( # 4 (16 B)\\n\",\n      \"      value=ShapeDtypeStruct(shape=(4,), dtype=float32)\\n\",\n      \"    ),\\n\",\n      \"    'kernel': Param( # 16 (64 B)\\n\",\n      \"      value=ShapeDtypeStruct(shape=(4, 4), dtype=float32)\\n\",\n      \"    )\\n\",\n      \"  },\\n\",\n      \"  'linear2': {\\n\",\n      \"    'bias': Param( # 4 (16 B)\\n\",\n      \"      value=ShapeDtypeStruct(shape=(4,), dtype=float32)\\n\",\n      \"    ),\\n\",\n      \"    'kernel': Param( # 16 (64 B)\\n\",\n      \"      value=ShapeDtypeStruct(shape=(4, 4), dtype=float32)\\n\",\n      \"    )\\n\",\n      \"  }\\n\",\n      \"})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"abs_model = nnx.eval_shape(lambda: TwoLayerMLP(4, rngs=nnx.Rngs(0)))\\n\",\n    \"gdef, abs_state = nnx.split(abs_model)\\n\",\n    \"pprint(abs_state)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"When you fill every `nnx.Variable` pytree leaf's `value` attributes with real `jax.Array`s, the abstract model becomes equivalent to a real model.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"model = TwoLayerMLP(4, rngs=nnx.Rngs(0))\\n\",\n    \"abs_state['linear1']['kernel'].value = model.linear1.kernel.value\\n\",\n    \"abs_state['linear1']['bias'].value = model.linear1.bias.value\\n\",\n    \"abs_state['linear2']['kernel'].value = model.linear2.kernel.value\\n\",\n    \"abs_state['linear2']['bias'].value = model.linear2.bias.value\\n\",\n    \"nnx.update(abs_model, abs_state)\\n\",\n    \"np.testing.assert_allclose(abs_model(x), model(x))  # They are equivalent now!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Checkpoint surgery\\n\",\n    \"\\n\",\n    \"With the abstract state technique in hand, you can perform arbitrary manipulation on any checkpoint - or runtime parameter pytree - to make them fit with your given model code, and then call `nnx.update` to merge them.\\n\",\n    \"\\n\",\n    \"This can be helpful if you are trying to significantly change the model code - for example, when migrating from Flax Linen to Flax NNX - and old weights are no longer naturally compatible.\\n\",\n    \"\\n\",\n    \"Let's run a simple example here:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Save a version of model into a checkpoint\\n\",\n    \"checkpointer = orbax.PyTreeCheckpointer()\\n\",\n    \"old_model = TwoLayerMLP(4, rngs=nnx.Rngs(0))\\n\",\n    \"checkpointer.save(f'/tmp/nnx-surgery-state', nnx.state(model), force=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In this new model, the sub-`Module`s are renamed from `linear(1|2)` to `layer(1|2)`. Since the pytree structure has changed, it is impossible to directly load the old checkpoint with the new model state structure:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"This will throw error: <class 'ValueError'>: User-provided restore item and on-disk value metadata tree structures do not match: {'layer1': Diff(lhs={'bias': {'value': ShapeDtypeStruct(shape=(4,), dtype=float32)}, 'kernel': {'value': ShapeDtypeStruct(shape=(4, 4), dtype=float32)}}, rhs=None), 'layer2': Diff(lhs={'bias': {'value': ShapeDtypeStruct(shape=(4,), dtype=float32)}, 'kernel': {'value': ShapeDtypeStruct(shape=(4, 4), dtype=float32)}}, rhs=None), 'linear1': Diff(lhs=None, rhs={'bias': {'value': ValueMetadataEntry(value_type='jax.Array', skip_deserialize=False, write_shape=(4,))}, 'kernel': {'value': ValueMetadataEntry(value_type='jax.Array', skip_deserialize=False, write_shape=(4, 4))}}), 'linear2': Diff(lhs=None, rhs={'bias': {'value': ValueMetadataEntry(value_type='jax.Array', skip_deserialize=False, write_shape=(4,))}, 'kernel': {'value': ValueMetadataEntry(value_type='jax.Array', skip_deserialize=False, write_shape=(4, 4))}})}\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class ModifiedTwoLayerMLP(nnx.Module):\\n\",\n    \"  def __init__(self, dim, rngs: nnx.Rngs):\\n\",\n    \"    self.layer1 = nnx.Linear(dim, dim, rngs=rngs)  # no longer linear1!\\n\",\n    \"    self.layer2 = nnx.Linear(dim, dim, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x = self.layer1(x)\\n\",\n    \"    return self.layer2(x)\\n\",\n    \"\\n\",\n    \"abs_model = nnx.eval_shape(lambda: ModifiedTwoLayerMLP(4, rngs=nnx.Rngs(0)))\\n\",\n    \"try:\\n\",\n    \"  with_item = checkpointer.restore('/tmp/nnx-surgery-state', item=nnx.state(abs_model))\\n\",\n    \"  print(with_item)\\n\",\n    \"except Exception as e:\\n\",\n    \"  print(f'This will throw error: {type(e)}: {e}')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"However, you can load the parameter pytree as a raw dictionary, perform the renames, and generate a new state that is guaranteed to be compatible with your new model definition.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"{'linear1': {'bias': {'value': Array([0., 0., 0., 0.], dtype=float32)},\\n\",\n      \"             'kernel': {'value': Array([[ 0.5350889 , -0.48486355, -0.4022262 , -0.61925626],\\n\",\n      \"       [-0.46665004,  0.31773907,  0.38944173, -0.54608804],\\n\",\n      \"       [ 0.84378934, -0.93099   , -0.67658   ,  0.0724705 ],\\n\",\n      \"       [-0.6101737 ,  0.12972134,  0.877074  ,  0.27292168]],      dtype=float32)}},\\n\",\n      \" 'linear2': {'bias': {'value': Array([0., 0., 0., 0.], dtype=float32)},\\n\",\n      \"             'kernel': {'value': Array([[ 0.67979455,  0.7079946 , -0.22166717, -0.4147039 ],\\n\",\n      \"       [ 0.20622818,  0.01024843,  0.31011865, -0.40491563],\\n\",\n      \"       [ 0.12478007, -0.7697264 , -0.48899388,  0.8853114 ],\\n\",\n      \"       [-0.5123713 , -0.23335123,  0.4374407 ,  0.63321066]],      dtype=float32)}}}\\n\"\n     ]\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"/Users/cgarciae/repos/flax/.venv/lib/python3.11/site-packages/orbax/checkpoint/_src/serialization/type_handlers.py:1251: UserWarning: Sharding info not provided when restoring. Populating sharding info from sharding file. Please note restoration time will be slightly increased due to reading from file. Note also that this option is unsafe when restoring on a different topology than the checkpoint was saved with.\\n\",\n      \"  warnings.warn(\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"def process_raw_dict(raw_state_dict):\\n\",\n    \"  flattened = nnx.traversals.flatten_mapping(raw_state_dict)\\n\",\n    \"  # Cut the '.value' postfix on every leaf path.\\n\",\n    \"  flattened = {(path[:-1] if path[-1] == 'value' else path): value\\n\",\n    \"               for path, value in flattened.items()}\\n\",\n    \"  return nnx.traversals.unflatten_mapping(flattened)\\n\",\n    \"\\n\",\n    \"# Make your local change on the checkpoint dictionary.\\n\",\n    \"raw_dict = checkpointer.restore('/tmp/nnx-surgery-state')\\n\",\n    \"pprint(raw_dict)\\n\",\n    \"raw_dict['layer1'] = raw_dict.pop('linear1')\\n\",\n    \"raw_dict['layer2'] = raw_dict.pop('linear2')\\n\",\n    \"\\n\",\n    \"# Fit it into the model state.\\n\",\n    \"abs_model = nnx.eval_shape(lambda: ModifiedTwoLayerMLP(4, rngs=nnx.Rngs(0)))\\n\",\n    \"graph_def, state = nnx.split(abs_model)\\n\",\n    \"nnx.replace_by_pure_dict(state, process_raw_dict(raw_dict))\\n\",\n    \"restored_model = nnx.merge(graph_def, state)\\n\",\n    \"\\n\",\n    \"np.testing.assert_allclose(restored_model(jnp.ones((3, 4))), old_model(jnp.ones((3, 4))))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Partial initialization\\n\",\n    \"\\n\",\n    \"In some cases - such as with LoRA (Low-Rank Adaption) - you may want to randomly-initialize only *part of* your model parameters. This can be achieved through:\\n\",\n    \"\\n\",\n    \"- Naive partial initialization; or\\n\",\n    \"- Memory-efficient partial initialization.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Naive partial initialization\\n\",\n    \"\\n\",\n    \"To do naive partial initialization, you can just initialize the whole model, then swap the pre-trained parameters in. However, this approach may allocate additional memory midway if your modification requires re-creating module parameters that you will later discard. Below is an example of this.\\n\",\n    \"\\n\",\n    \"> **Note:** You can use `jax.live_arrays()` to check all the arrays live in memory at any given time. This call can be “messed up” when you run a single Jupyter notebook cell multiple times (due to garbage-collection of old Python variables). However, restarting the Python kernel in the notebook and running the code from scratch will always yield the same output.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Number of jax arrays in memory at start: 38\\n\",\n      \"Number of jax arrays in memory midway: 42 (4 new created in LoRALinear - kernel, bias, lora_a & lora_b)\\n\",\n      \"Number of jax arrays in memory at end: 40 (2 discarded - only lora_a & lora_b are used in model)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Some pretrained model state\\n\",\n    \"old_state = nnx.state(TwoLayerMLP(4, rngs=nnx.Rngs(0)))\\n\",\n    \"\\n\",\n    \"simple_model = nnx.eval_shape(lambda: TwoLayerMLP(4, rngs=nnx.Rngs(42)))\\n\",\n    \"print(f'Number of jax arrays in memory at start: {len(jax.live_arrays())}')\\n\",\n    \"# In this line, extra kernel and bias is created inside the new LoRALinear!\\n\",\n    \"# They are wasted, because you are going to use the kernel and bias in `old_state` anyway.\\n\",\n    \"simple_model.linear1 = nnx.LoRALinear(4, 4, lora_rank=3, rngs=nnx.Rngs(42))\\n\",\n    \"print(f'Number of jax arrays in memory midway: {len(jax.live_arrays())}'\\n\",\n    \"      ' (4 new created in LoRALinear - kernel, bias, lora_a & lora_b)')\\n\",\n    \"nnx.update(simple_model, old_state)\\n\",\n    \"print(f'Number of jax arrays in memory at end: {len(jax.live_arrays())}'\\n\",\n    \"      ' (2 discarded - only lora_a & lora_b are used in model)')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Memory-efficient partial initialization\\n\",\n    \"\\n\",\n    \"To do memory-efficient partial initialization, use `nnx.jit`'s efficiently compiled code to make sure only the state parameters you need are initialized:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Number of JAX Arrays in memory at start: 44\\n\",\n      \"Number of JAX Arrays in memory at end: 50 (2 new created - lora_a and lora_b)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Some pretrained model state\\n\",\n    \"old_state = nnx.state(TwoLayerMLP(4, rngs=nnx.Rngs(0)))\\n\",\n    \"\\n\",\n    \"# Use `nnx.jit` (which wraps `jax.jit`) to automatically skip unused arrays - memory efficient!\\n\",\n    \"@nnx.jit(donate_argnums=0)\\n\",\n    \"def partial_init(old_state, rngs):\\n\",\n    \"  model = TwoLayerMLP(4, rngs=rngs)\\n\",\n    \"  # Create a new state.\\n\",\n    \"  model.linear1 = nnx.LoRALinear(4, 4, lora_rank=3, rngs=rngs)\\n\",\n    \"  # Add the existing state.\\n\",\n    \"  nnx.update(model, old_state)\\n\",\n    \"  return model\\n\",\n    \"\\n\",\n    \"print(f'Number of JAX Arrays in memory at start: {len(jax.live_arrays())}')\\n\",\n    \"# Note that `old_state` will be deleted after this `partial_init` call.\\n\",\n    \"good_model = partial_init(old_state, nnx.Rngs(42))\\n\",\n    \"print(f'Number of JAX Arrays in memory at end: {len(jax.live_arrays())}'\\n\",\n    \"      ' (2 new created - lora_a and lora_b)')\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.9\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "docs_nnx/guides/surgery.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Model surgery\n\nModel surgery is an act of making modifications on an existing neural network's building blocks and parameters, such as layer replacement, parameter or state manipulation, or even \"monkey patching\". In this guide, you will learn how to perform model surgery in Flax NNX using several real-world scenarios:\n\n* __Pythonic `nnx.Module` manipulation__: Using Pythonic ways to manipulate sub-`Module`s given a model.\n\n* __Manipulation of an abstract model or state__: A key trick for playing with `flax.nnx.Module`s and states without memory allocation.\n\n* __Checkpoint surgery from a raw state to model__: How to manipulate parameter states when they are incompatible with existing model code.\n\n* __Partial initialization__: How to initialize only a part of the model from scratch using a naive method or a memory-efficient method.\n\n```{code-cell} ipython3\nfrom typing import *\nfrom pprint import pprint\nimport functools\n\nimport jax\nfrom jax import lax, numpy as jnp, tree_util as jtu\n\nfrom jax.sharding import PartitionSpec, Mesh, NamedSharding\nfrom jax.experimental import mesh_utils\nimport flax\nfrom flax import nnx\nimport flax.traverse_util\nimport numpy as np\nimport orbax.checkpoint as orbax\n\nkey = jax.random.key(0)\n```\n\n```{code-cell} ipython3\nclass TwoLayerMLP(nnx.Module):\n  def __init__(self, dim, rngs: nnx.Rngs):\n    self.linear1 = nnx.Linear(dim, dim, rngs=rngs)\n    self.linear2 = nnx.Linear(dim, dim, rngs=rngs)\n\n  def __call__(self, x):\n    x = self.linear1(x)\n    return self.linear2(x)\n```\n\n## Pythonic `nnx.Module` manipulation\n\nIt is easier to perform model surgery when:\n\n1) You already have a fully fleshed-out model loaded with correct parameters; and\n2) You don't intend to change your model definition code.\n\nYou can perform a variety of Pythonic operations on its sub-`Module`s, such as sub-`Module` swapping, `Module` sharing, variable sharing, and monkey-patching:\n\n```{code-cell} ipython3\nmodel = TwoLayerMLP(4, rngs=nnx.Rngs(0))\nx = jax.random.normal(jax.random.key(42), (3, 4))\nnp.testing.assert_allclose(model(x), model.linear2(model.linear1(x)))\n\n# Sub-`Module` swapping.\noriginal1, original2 = model.linear1, model.linear2\nmodel.linear1, model.linear2 = model.linear2, model.linear1\nnp.testing.assert_allclose(model(x), original1(original2(x)))\n\n# `Module` sharing (tying all weights together).\nmodel = TwoLayerMLP(4, rngs=nnx.Rngs(0))\nmodel.linear2 = model.linear1\nassert not hasattr(nnx.state(model), 'linear2')\nnp.testing.assert_allclose(model(x), model.linear1(model.linear1(x)))\n\n# Variable sharing (weight-tying).\nmodel = TwoLayerMLP(4, rngs=nnx.Rngs(0))\nmodel.linear1.kernel = model.linear2.kernel  # the bias parameter is kept separate\nassert 'linear2' in nnx.state(model)\nassert 'bias' in nnx.state(model)['linear2']\nassert not hasattr(nnx.state(model)['linear2'], 'kernel')\n\n# Monkey-patching.\nmodel = TwoLayerMLP(4, rngs=nnx.Rngs(0))\ndef awesome_layer(x): return x\nmodel.linear2 = awesome_layer\nnp.testing.assert_allclose(model(x), model.linear1(x))\n```\n\n## Creating an abstract model or state without memory allocation\n\nTo do more complex model surgery, the key technique you can use is creating and manipulating an abstract model or state without allocating any real parameter data. This makes trial iteration faster and removes any concern on memory constraints.\n\nTo create an abstract model:\n\n* Create a function that returns a valid Flax NNX model; and\n* Run `nnx.eval_shape` (not `jax.eval_shape`) upon it.\n\nNow you can use `nnx.split` as usual to get its abstract state. Note that all fields that should be `jax.Array`s in a real model are now of an abstract `jax.ShapeDtypeStruct` type with only shape/dtype/sharding information.\n\n```{code-cell} ipython3\nabs_model = nnx.eval_shape(lambda: TwoLayerMLP(4, rngs=nnx.Rngs(0)))\ngdef, abs_state = nnx.split(abs_model)\npprint(abs_state)\n```\n\nWhen you fill every `nnx.Variable` pytree leaf's `value` attributes with real `jax.Array`s, the abstract model becomes equivalent to a real model.\n\n```{code-cell} ipython3\nmodel = TwoLayerMLP(4, rngs=nnx.Rngs(0))\nabs_state['linear1']['kernel'].value = model.linear1.kernel.value\nabs_state['linear1']['bias'].value = model.linear1.bias.value\nabs_state['linear2']['kernel'].value = model.linear2.kernel.value\nabs_state['linear2']['bias'].value = model.linear2.bias.value\nnnx.update(abs_model, abs_state)\nnp.testing.assert_allclose(abs_model(x), model(x))  # They are equivalent now!\n```\n\n## Checkpoint surgery\n\nWith the abstract state technique in hand, you can perform arbitrary manipulation on any checkpoint - or runtime parameter pytree - to make them fit with your given model code, and then call `nnx.update` to merge them.\n\nThis can be helpful if you are trying to significantly change the model code - for example, when migrating from Flax Linen to Flax NNX - and old weights are no longer naturally compatible.\n\nLet's run a simple example here:\n\n```{code-cell} ipython3\n# Save a version of model into a checkpoint\ncheckpointer = orbax.PyTreeCheckpointer()\nold_model = TwoLayerMLP(4, rngs=nnx.Rngs(0))\ncheckpointer.save(f'/tmp/nnx-surgery-state', nnx.state(model), force=True)\n```\n\nIn this new model, the sub-`Module`s are renamed from `linear(1|2)` to `layer(1|2)`. Since the pytree structure has changed, it is impossible to directly load the old checkpoint with the new model state structure:\n\n```{code-cell} ipython3\nclass ModifiedTwoLayerMLP(nnx.Module):\n  def __init__(self, dim, rngs: nnx.Rngs):\n    self.layer1 = nnx.Linear(dim, dim, rngs=rngs)  # no longer linear1!\n    self.layer2 = nnx.Linear(dim, dim, rngs=rngs)\n\n  def __call__(self, x):\n    x = self.layer1(x)\n    return self.layer2(x)\n\nabs_model = nnx.eval_shape(lambda: ModifiedTwoLayerMLP(4, rngs=nnx.Rngs(0)))\ntry:\n  with_item = checkpointer.restore('/tmp/nnx-surgery-state', item=nnx.state(abs_model))\n  print(with_item)\nexcept Exception as e:\n  print(f'This will throw error: {type(e)}: {e}')\n```\n\nHowever, you can load the parameter pytree as a raw dictionary, perform the renames, and generate a new state that is guaranteed to be compatible with your new model definition.\n\n```{code-cell} ipython3\ndef process_raw_dict(raw_state_dict):\n  flattened = nnx.traversals.flatten_mapping(raw_state_dict)\n  # Cut the '.value' postfix on every leaf path.\n  flattened = {(path[:-1] if path[-1] == 'value' else path): value\n               for path, value in flattened.items()}\n  return nnx.traversals.unflatten_mapping(flattened)\n\n# Make your local change on the checkpoint dictionary.\nraw_dict = checkpointer.restore('/tmp/nnx-surgery-state')\npprint(raw_dict)\nraw_dict['layer1'] = raw_dict.pop('linear1')\nraw_dict['layer2'] = raw_dict.pop('linear2')\n\n# Fit it into the model state.\nabs_model = nnx.eval_shape(lambda: ModifiedTwoLayerMLP(4, rngs=nnx.Rngs(0)))\ngraph_def, state = nnx.split(abs_model)\nnnx.replace_by_pure_dict(state, process_raw_dict(raw_dict))\nrestored_model = nnx.merge(graph_def, state)\n\nnp.testing.assert_allclose(restored_model(jnp.ones((3, 4))), old_model(jnp.ones((3, 4))))\n```\n\n## Partial initialization\n\nIn some cases - such as with LoRA (Low-Rank Adaption) - you may want to randomly-initialize only *part of* your model parameters. This can be achieved through:\n\n- Naive partial initialization; or\n- Memory-efficient partial initialization.\n\n+++\n\n### Naive partial initialization\n\nTo do naive partial initialization, you can just initialize the whole model, then swap the pre-trained parameters in. However, this approach may allocate additional memory midway if your modification requires re-creating module parameters that you will later discard. Below is an example of this.\n\n> **Note:** You can use `jax.live_arrays()` to check all the arrays live in memory at any given time. This call can be “messed up” when you run a single Jupyter notebook cell multiple times (due to garbage-collection of old Python variables). However, restarting the Python kernel in the notebook and running the code from scratch will always yield the same output.\n\n```{code-cell} ipython3\n# Some pretrained model state\nold_state = nnx.state(TwoLayerMLP(4, rngs=nnx.Rngs(0)))\n\nsimple_model = nnx.eval_shape(lambda: TwoLayerMLP(4, rngs=nnx.Rngs(42)))\nprint(f'Number of jax arrays in memory at start: {len(jax.live_arrays())}')\n# In this line, extra kernel and bias is created inside the new LoRALinear!\n# They are wasted, because you are going to use the kernel and bias in `old_state` anyway.\nsimple_model.linear1 = nnx.LoRALinear(4, 4, lora_rank=3, rngs=nnx.Rngs(42))\nprint(f'Number of jax arrays in memory midway: {len(jax.live_arrays())}'\n      ' (4 new created in LoRALinear - kernel, bias, lora_a & lora_b)')\nnnx.update(simple_model, old_state)\nprint(f'Number of jax arrays in memory at end: {len(jax.live_arrays())}'\n      ' (2 discarded - only lora_a & lora_b are used in model)')\n```\n\n### Memory-efficient partial initialization\n\nTo do memory-efficient partial initialization, use `nnx.jit`'s efficiently compiled code to make sure only the state parameters you need are initialized:\n\n```{code-cell} ipython3\n# Some pretrained model state\nold_state = nnx.state(TwoLayerMLP(4, rngs=nnx.Rngs(0)))\n\n# Use `nnx.jit` (which wraps `jax.jit`) to automatically skip unused arrays - memory efficient!\n@nnx.jit(donate_argnums=0)\ndef partial_init(old_state, rngs):\n  model = TwoLayerMLP(4, rngs=rngs)\n  # Create a new state.\n  model.linear1 = nnx.LoRALinear(4, 4, lora_rank=3, rngs=rngs)\n  # Add the existing state.\n  nnx.update(model, old_state)\n  return model\n\nprint(f'Number of JAX Arrays in memory at start: {len(jax.live_arrays())}')\n# Note that `old_state` will be deleted after this `partial_init` call.\ngood_model = partial_init(old_state, nnx.Rngs(42))\nprint(f'Number of JAX Arrays in memory at end: {len(jax.live_arrays())}'\n      ' (2 new created - lora_a and lora_b)')\n```\n"
  },
  {
    "path": "docs_nnx/guides/tiny_nnx.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Tiny NNX\\n\",\n    \"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/cgarciae/nnx/blob/main/docs/tiny_nnx.ipynb)\\n\",\n    \"\\n\",\n    \"A pedagogical implementation of NNX's core APIs.\\n\",\n    \"\\n\",\n    \"## Core APIs\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import dataclasses\\n\",\n    \"import hashlib\\n\",\n    \"import typing as tp\\n\",\n    \"\\n\",\n    \"import jax\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"from jax import random\\n\",\n    \"\\n\",\n    \"A = tp.TypeVar(\\\"A\\\")\\n\",\n    \"M = tp.TypeVar(\\\"M\\\", bound=\\\"Module\\\")\\n\",\n    \"Sharding = tp.Tuple[tp.Optional[str], ...]\\n\",\n    \"Array = jax.Array\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"class Variable(tp.Generic[A]):\\n\",\n    \"\\n\",\n    \"  def __init__(\\n\",\n    \"      self,\\n\",\n    \"      value: A,\\n\",\n    \"      *,\\n\",\n    \"      sharding: tp.Optional[Sharding] = None,\\n\",\n    \"  ):\\n\",\n    \"    self.value = value\\n\",\n    \"    self.sharding = sharding\\n\",\n    \"\\n\",\n    \"  def __repr__(self) -> str:\\n\",\n    \"    return (\\n\",\n    \"        f\\\"{type(self).__name__}(value={self.value}, sharding={self.sharding})\\\"\\n\",\n    \"    )\\n\",\n    \"\\n\",\n    \"  def __init_subclass__(cls):\\n\",\n    \"    super().__init_subclass__()\\n\",\n    \"    jax.tree_util.register_pytree_node(\\n\",\n    \"        cls,\\n\",\n    \"        lambda x: ((x.value,), (x.sharding,)),\\n\",\n    \"        lambda metadata, value: cls(value[0], sharding=metadata[0]),\\n\",\n    \"    )\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"class State(dict[str, Variable[tp.Any]]):\\n\",\n    \"\\n\",\n    \"  def extract(self, variable_type: tp.Type[Variable]) -> \\\"State\\\":\\n\",\n    \"    return State(\\n\",\n    \"        {\\n\",\n    \"            path: variable\\n\",\n    \"            for path, variable in self.items()\\n\",\n    \"            if isinstance(variable, variable_type)\\n\",\n    \"        }\\n\",\n    \"    )\\n\",\n    \"\\n\",\n    \"  def __repr__(self) -> str:\\n\",\n    \"    elems = \\\",\\\\n  \\\".join(\\n\",\n    \"        f\\\"'{path}': {variable}\\\".replace(\\\"\\\\n\\\", \\\"\\\\n    \\\")\\n\",\n    \"        for path, variable in self.items()\\n\",\n    \"    )\\n\",\n    \"    return f\\\"State({{\\\\n  {elems}\\\\n}})\\\"\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"jax.tree_util.register_pytree_node(\\n\",\n    \"    State,\\n\",\n    \"    # in reality, values and paths should be sorted by path\\n\",\n    \"    lambda x: (tuple(x.values()), tuple(x.keys())),\\n\",\n    \"    lambda paths, values: State(dict(zip(paths, values))),\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"@dataclasses.dataclass\\n\",\n    \"class GraphDef(tp.Generic[M]):\\n\",\n    \"  type: tp.Type[M]\\n\",\n    \"  index: int\\n\",\n    \"  submodules: dict[str, tp.Union[\\\"GraphDef[Module]\\\", int]]\\n\",\n    \"  static_fields: dict[str, tp.Any]\\n\",\n    \"\\n\",\n    \"  def merge(self, state: State) -> M:\\n\",\n    \"    module = GraphDef._build_module_recursive(self, {})\\n\",\n    \"    module.update(state)\\n\",\n    \"    return module\\n\",\n    \"\\n\",\n    \"  @staticmethod\\n\",\n    \"  def _build_module_recursive(\\n\",\n    \"      graphdef: tp.Union[\\\"GraphDef[M]\\\", int],\\n\",\n    \"      index_to_module: dict[int, \\\"Module\\\"],\\n\",\n    \"  ) -> M:\\n\",\n    \"    if isinstance(graphdef, int):\\n\",\n    \"      return index_to_module[graphdef] # type: ignore\\n\",\n    \"\\n\",\n    \"    assert graphdef.index not in index_to_module\\n\",\n    \"\\n\",\n    \"    # add a dummy module to the index to avoid infinite recursion\\n\",\n    \"    module = object.__new__(graphdef.type)\\n\",\n    \"    index_to_module[graphdef.index] = module\\n\",\n    \"\\n\",\n    \"    submodules = {\\n\",\n    \"        name: GraphDef._build_module_recursive(submodule, index_to_module)\\n\",\n    \"        for name, submodule in graphdef.submodules.items()\\n\",\n    \"    }\\n\",\n    \"    vars(module).update(graphdef.static_fields)\\n\",\n    \"    vars(module).update(submodules)\\n\",\n    \"    return module\\n\",\n    \"\\n\",\n    \"  def apply(\\n\",\n    \"      self, state: State\\n\",\n    \"  ) -> tp.Callable[..., tuple[tp.Any, tuple[State, \\\"GraphDef[M]\\\"]]]:\\n\",\n    \"    def _apply(*args, **kwargs):\\n\",\n    \"      module = self.merge(state)\\n\",\n    \"      out = module(*args, **kwargs)  # type: ignore\\n\",\n    \"      return out, module.split()\\n\",\n    \"\\n\",\n    \"    return _apply\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"class Module:\\n\",\n    \"\\n\",\n    \"  def split(self: M) -> tp.Tuple[State, GraphDef[M]]:\\n\",\n    \"    state = State()\\n\",\n    \"    graphdef = Module._partition_recursive(\\n\",\n    \"        module=self, module_id_to_index={}, path_parts=(), state=state\\n\",\n    \"    )\\n\",\n    \"    assert isinstance(graphdef, GraphDef)\\n\",\n    \"    return state, graphdef\\n\",\n    \"\\n\",\n    \"  @staticmethod\\n\",\n    \"  def _partition_recursive(\\n\",\n    \"      module: M,\\n\",\n    \"      module_id_to_index: dict[int, int],\\n\",\n    \"      path_parts: tp.Tuple[str, ...],\\n\",\n    \"      state: State,\\n\",\n    \"  ) -> tp.Union[GraphDef[M], int]:\\n\",\n    \"    if id(module) in module_id_to_index:\\n\",\n    \"      return module_id_to_index[id(module)]\\n\",\n    \"\\n\",\n    \"    index = len(module_id_to_index)\\n\",\n    \"    module_id_to_index[id(module)] = index\\n\",\n    \"\\n\",\n    \"    submodules = {}\\n\",\n    \"    static_fields = {}\\n\",\n    \"\\n\",\n    \"    # iterate fields sorted by name to ensure deterministic order\\n\",\n    \"    for name, value in sorted(vars(module).items(), key=lambda x: x[0]):\\n\",\n    \"      value_path = (*path_parts, name)\\n\",\n    \"      # if value is a Module, recurse\\n\",\n    \"      if isinstance(value, Module):\\n\",\n    \"        submoduledef = Module._partition_recursive(\\n\",\n    \"            value, module_id_to_index, value_path, state\\n\",\n    \"        )\\n\",\n    \"        submodules[name] = submoduledef\\n\",\n    \"      # if value is a Variable, add to state\\n\",\n    \"      elif isinstance(value, Variable):\\n\",\n    \"        state[\\\"/\\\".join(value_path)] = value\\n\",\n    \"      else:  # otherwise, add to graphdef fields\\n\",\n    \"        static_fields[name] = value\\n\",\n    \"\\n\",\n    \"    return GraphDef(\\n\",\n    \"        type=type(module),\\n\",\n    \"        index=index,\\n\",\n    \"        submodules=submodules,\\n\",\n    \"        static_fields=static_fields,\\n\",\n    \"    )\\n\",\n    \"\\n\",\n    \"  def update(self, state: State) -> None:\\n\",\n    \"    for path, value in state.items():\\n\",\n    \"      path_parts = path.split(\\\"/\\\")\\n\",\n    \"      Module._set_value_at_path(self, path_parts, value)\\n\",\n    \"\\n\",\n    \"  @staticmethod\\n\",\n    \"  def _set_value_at_path(\\n\",\n    \"      module: \\\"Module\\\", path_parts: tp.Sequence[str], value: Variable[tp.Any]\\n\",\n    \"  ) -> None:\\n\",\n    \"    if len(path_parts) == 1:\\n\",\n    \"      setattr(module, path_parts[0], value)\\n\",\n    \"    else:\\n\",\n    \"      Module._set_value_at_path(\\n\",\n    \"          getattr(module, path_parts[0]), path_parts[1:], value\\n\",\n    \"      )\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"@dataclasses.dataclass\\n\",\n    \"class Rngs:\\n\",\n    \"  key: jax.Array\\n\",\n    \"  count: int = 0\\n\",\n    \"  count_path: tuple[int, ...] = ()\\n\",\n    \"\\n\",\n    \"  def fork(self) -> \\\"Rngs\\\":\\n\",\n    \"    \\\"\\\"\\\"Forks the context, guaranteeing that all the random numbers generated\\n\",\n    \"    will be different from the ones generated in the original context. Fork is\\n\",\n    \"    used to create a new Rngs that can be passed to a JAX transform\\\"\\\"\\\"\\n\",\n    \"    count_path = self.count_path + (self.count,)\\n\",\n    \"    self.count += 1\\n\",\n    \"    return Rngs(self.key, count_path=count_path)\\n\",\n    \"\\n\",\n    \"  def make_rng(self) -> jax.Array:\\n\",\n    \"    fold_data = self._stable_hash(self.count_path + (self.count,))\\n\",\n    \"    self.count += 1\\n\",\n    \"    return random.fold_in(self.key, fold_data)  # type: ignore\\n\",\n    \"\\n\",\n    \"  @staticmethod\\n\",\n    \"  def _stable_hash(data: tuple[int, ...]) -> int:\\n\",\n    \"    hash_str = \\\" \\\".join(str(x) for x in data)\\n\",\n    \"    _hash = hashlib.blake2s(hash_str.encode())\\n\",\n    \"    hash_bytes = _hash.digest()\\n\",\n    \"    # uint32 is represented as 4 bytes in big endian\\n\",\n    \"    return int.from_bytes(hash_bytes[:4], byteorder=\\\"big\\\")\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"# in the real NNX Rngs is not a pytree, instead\\n\",\n    \"# it has a split/merge API similar to Module\\n\",\n    \"# but for simplicity we use a pytree here\\n\",\n    \"jax.tree_util.register_pytree_node(\\n\",\n    \"    Rngs,\\n\",\n    \"    lambda x: ((x.key,), (x.count, x.count_path)),\\n\",\n    \"    lambda metadata, value: Rngs(value[0], *metadata),\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Basic Layers\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class Param(Variable[A]):\\n\",\n    \"  pass\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"class BatchStat(Variable[A]):\\n\",\n    \"  pass\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"class Linear(Module):\\n\",\n    \"\\n\",\n    \"  def __init__(self, din: int, dout: int, *, rngs: Rngs):\\n\",\n    \"    self.din = din\\n\",\n    \"    self.dout = dout\\n\",\n    \"    key = rngs.make_rng()\\n\",\n    \"    self.w = Param(random.uniform(key, (din, dout)))\\n\",\n    \"    self.b = Param(jnp.zeros((dout,)))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x: jax.Array) -> jax.Array:\\n\",\n    \"    return x @ self.w.value + self.b.value\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"class BatchNorm(Module):\\n\",\n    \"\\n\",\n    \"  def __init__(self, din: int, mu: float = 0.95):\\n\",\n    \"    self.mu = mu\\n\",\n    \"    self.scale = Param(jax.numpy.ones((din,)))\\n\",\n    \"    self.bias = Param(jax.numpy.zeros((din,)))\\n\",\n    \"    self.mean = BatchStat(jax.numpy.zeros((din,)))\\n\",\n    \"    self.var = BatchStat(jax.numpy.ones((din,)))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x, train: bool) -> jax.Array:\\n\",\n    \"    if train:\\n\",\n    \"      axis = tuple(range(x.ndim - 1))\\n\",\n    \"      mean = jax.numpy.mean(x, axis=axis)\\n\",\n    \"      var = jax.numpy.var(x, axis=axis)\\n\",\n    \"      # ema update\\n\",\n    \"      self.mean.value = self.mu * self.mean.value + (1 - self.mu) * mean\\n\",\n    \"      self.var.value = self.mu * self.var.value + (1 - self.mu) * var\\n\",\n    \"    else:\\n\",\n    \"      mean, var = self.mean.value, self.var.value\\n\",\n    \"\\n\",\n    \"    scale, bias = self.scale.value, self.bias.value\\n\",\n    \"    x = (x - mean) / jax.numpy.sqrt(var + 1e-5) * scale + bias\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"class Dropout(Module):\\n\",\n    \"\\n\",\n    \"  def __init__(self, rate: float):\\n\",\n    \"    self.rate = rate\\n\",\n    \"\\n\",\n    \"  def __call__(self, x: jax.Array, *, train: bool, rngs: Rngs) -> jax.Array:\\n\",\n    \"    if train:\\n\",\n    \"      mask = random.bernoulli(rngs.make_rng(), (1 - self.rate), x.shape)\\n\",\n    \"      x = x * mask / (1 - self.rate)\\n\",\n    \"    return x\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Scan Over Layers Example\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class Block(Module):\\n\",\n    \"\\n\",\n    \"  def __init__(self, din: int, dout: int, *, rngs: Rngs):\\n\",\n    \"    self.linear = Linear(din, dout, rngs=rngs)\\n\",\n    \"    self.bn = BatchNorm(dout)\\n\",\n    \"    self.dropout = Dropout(0.1)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x: jax.Array, *, train: bool, rngs: Rngs) -> jax.Array:\\n\",\n    \"    x = self.linear(x)\\n\",\n    \"    x = self.bn(x, train=train)\\n\",\n    \"    x = jax.nn.gelu(x)\\n\",\n    \"    x = self.dropout(x, train=train, rngs=rngs)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"class ScanMLP(Module):\\n\",\n    \"\\n\",\n    \"  def __init__(self, hidden_size: int, n_layers: int, *, rngs: Rngs):\\n\",\n    \"    self.n_layers = n_layers\\n\",\n    \"\\n\",\n    \"    # lift init\\n\",\n    \"    key = random.split(rngs.make_rng(), n_layers - 1)\\n\",\n    \"    graphdef: GraphDef[Block] = None  # type: ignore\\n\",\n    \"\\n\",\n    \"    def init_fn(key):\\n\",\n    \"      nonlocal graphdef\\n\",\n    \"      state, graphdef = Block(\\n\",\n    \"          hidden_size, hidden_size, rngs=Rngs(key)\\n\",\n    \"      ).split()\\n\",\n    \"      return state\\n\",\n    \"\\n\",\n    \"    state = jax.vmap(init_fn)(key)\\n\",\n    \"    self.layers = graphdef.merge(state)\\n\",\n    \"    self.linear = Linear(hidden_size, hidden_size, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x: jax.Array, *, train: bool, rngs: Rngs) -> jax.Array:\\n\",\n    \"    # lift call\\n\",\n    \"    key: jax.Array = random.split(rngs.make_rng(), self.n_layers - 1)  # type: ignore\\n\",\n    \"    state, graphdef = self.layers.split()\\n\",\n    \"\\n\",\n    \"    def scan_fn(x, inputs: tuple[jax.Array, State]):\\n\",\n    \"      key, state = inputs\\n\",\n    \"      x, (state, _) = graphdef.apply(state)(x, train=train, rngs=Rngs(key))\\n\",\n    \"      return x, state\\n\",\n    \"\\n\",\n    \"    x, state = jax.lax.scan(scan_fn, x, (key, state))\\n\",\n    \"    self.layers.update(state)\\n\",\n    \"    x = self.linear(x)\\n\",\n    \"    return x\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"state = State({\\n\",\n      \"  'layers/bn/bias': Param(value=(4, 10), sharding=None),\\n\",\n      \"  'layers/bn/mean': BatchStat(value=(4, 10), sharding=None),\\n\",\n      \"  'layers/bn/scale': Param(value=(4, 10), sharding=None),\\n\",\n      \"  'layers/bn/var': BatchStat(value=(4, 10), sharding=None),\\n\",\n      \"  'layers/linear/b': Param(value=(4, 10), sharding=None),\\n\",\n      \"  'layers/linear/w': Param(value=(4, 10, 10), sharding=None),\\n\",\n      \"  'linear/b': Param(value=(10,), sharding=None),\\n\",\n      \"  'linear/w': Param(value=(10, 10), sharding=None)\\n\",\n      \"})\\n\",\n      \"graphdef = GraphDef(type=<class '__main__.ScanMLP'>, index=0, submodules={'layers': GraphDef(type=<class '__main__.Block'>, index=1, submodules={'bn': GraphDef(type=<class '__main__.BatchNorm'>, index=2, submodules={}, static_fields={'mu': 0.95}), 'dropout': GraphDef(type=<class '__main__.Dropout'>, index=3, submodules={}, static_fields={'rate': 0.1}), 'linear': GraphDef(type=<class '__main__.Linear'>, index=4, submodules={}, static_fields={'din': 10, 'dout': 10})}, static_fields={}), 'linear': GraphDef(type=<class '__main__.Linear'>, index=5, submodules={}, static_fields={'din': 10, 'dout': 10})}, static_fields={'n_layers': 5})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"module = ScanMLP(hidden_size=10, n_layers=5, rngs=Rngs(random.key(0)))\\n\",\n    \"x = jax.random.normal(random.key(0), (2, 10))\\n\",\n    \"y = module(x, train=True, rngs=Rngs(random.key(1)))\\n\",\n    \"\\n\",\n    \"state, graphdef = module.split()\\n\",\n    \"print(\\\"state =\\\", jax.tree.map(jnp.shape, state))\\n\",\n    \"print(\\\"graphdef =\\\", graphdef)\"\n   ]\n  },\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Filtering State\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"params = State({\\n\",\n      \"  'layers/bn/bias': Param(value=(4, 10), sharding=None),\\n\",\n      \"  'layers/bn/scale': Param(value=(4, 10), sharding=None),\\n\",\n      \"  'layers/linear/b': Param(value=(4, 10), sharding=None),\\n\",\n      \"  'layers/linear/w': Param(value=(4, 10, 10), sharding=None),\\n\",\n      \"  'linear/b': Param(value=(10,), sharding=None),\\n\",\n      \"  'linear/w': Param(value=(10, 10), sharding=None)\\n\",\n      \"})\\n\",\n      \"batch_stats = State({\\n\",\n      \"  'layers/bn/mean': BatchStat(value=(4, 10), sharding=None),\\n\",\n      \"  'layers/bn/var': BatchStat(value=(4, 10), sharding=None)\\n\",\n      \"})\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# split\\n\",\n    \"params = state.extract(Param)\\n\",\n    \"batch_stats = state.extract(BatchStat)\\n\",\n    \"# merge\\n\",\n    \"state = State({**params, **batch_stats})\\n\",\n    \"\\n\",\n    \"print(\\\"params =\\\", jax.tree.map(jnp.shape, params))\\n\",\n    \"print(\\\"batch_stats =\\\", jax.tree.map(jnp.shape, batch_stats))\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.10.13\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "docs_nnx/guides/transforms.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"962be290\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Transformations\\n\",\n    \"\\n\",\n    \"In general, JAX transformations (transforms) operate on [pytrees](https://jax.readthedocs.io/en/latest/pytrees.html) of `jax.Array`s\\n\",\n    \"and abide by value semantics. This presents a challenge for Flax NNX, which represents `nnx.Module`s as regular Python objects\\n\",\n    \"that follow reference semantics. To address this, Flax NNX introduced its own set of transforms that extend JAX\\n\",\n    \"transforms to allow `nnx.Module`s and other Flax NNX objects to be passed in and out of transforms while preserving\\n\",\n    \"reference semantics.\\n\",\n    \"\\n\",\n    \"Flax NNX transforms should feel quite familiar if you have used JAX transforms before. They use the\\n\",\n    \"same APIs and behave like the JAX transforms when only working with pytrees of `jax.Array`s. However, when working with\\n\",\n    \"Flax NNX objects, they allow Python's reference semantics to be preserved for these objects, this includes:\\n\",\n    \"\\n\",\n    \"* Preserving shared references across multiple objects in the inputs and outputs of the transformation.\\n\",\n    \"* Propagating any state changes made to the objects inside the transformation to the objects outside the transformation.\\n\",\n    \"* Enforcing consistency of how objects are transformed when aliases are present across multiple inputs and outputs.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"8d645146\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import jax\\n\",\n    \"from jax import numpy as jnp, random\\n\",\n    \"from flax import nnx\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"b44fb248\",\n   \"metadata\": {},\n   \"source\": [\n    \"Throughout this guide, `nnx.vmap` is used as a case study to demonstrate how Flax NNX transforms work. However, the principles\\n\",\n    \"outlined in this document extends to all transforms.\\n\",\n    \"\\n\",\n    \"## Basic example\\n\",\n    \"\\n\",\n    \"To begin, let's look at a simple example of using `nnx.vmap` to extend an element wise `vector_dot` function to work on\\n\",\n    \"batched inputs. We will define a `Weights` Module with no methods to hold some parameters, these weights will be passed\\n\",\n    \"as an input to the `vector_dot` function along with some data. Both the weights and data will be batched on axis `0` and we will use\\n\",\n    \"`nnx.vmap` to apply `vector_dot` to each batch element, and the result will be a batched on axis `1`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"id\": \"4eab27a4\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"y.shape = (3, 10)\\n\"\n     ]\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"/Users/ivyzheng/envs/f1/lib/python3.12/site-packages/treescope/renderers.py:251: UserWarning: Ignoring error inside wrapper hook <function use_autovisualizer_if_present at 0x11c70dda0>:\\n\",\n      \"Traceback (most recent call last):\\n\",\n      \"  File \\\"/Users/ivyzheng/envs/f1/lib/python3.12/site-packages/treescope/renderers.py\\\", line 225, in _render_subtree\\n\",\n      \"    postprocessed_result = hook(\\n\",\n      \"                           ^^^^^\\n\",\n      \"  File \\\"/Users/ivyzheng/envs/f1/lib/python3.12/site-packages/treescope/_internal/handlers/autovisualizer_hook.py\\\", line 47, in use_autovisualizer_if_present\\n\",\n      \"    result = autoviz(node, path)\\n\",\n      \"             ^^^^^^^^^^^^^^^^^^^\\n\",\n      \"  File \\\"/Users/ivyzheng/envs/f1/lib/python3.12/site-packages/treescope/_internal/api/array_autovisualizer.py\\\", line 306, in __call__\\n\",\n      \"    jax.sharding.PositionalSharding\\n\",\n      \"  File \\\"/Users/ivyzheng/envs/f1/lib/python3.12/site-packages/jax/_src/deprecations.py\\\", line 54, in getattr\\n\",\n      \"    raise AttributeError(message)\\n\",\n      \"AttributeError: jax.sharding.PositionalSharding was deprecated in JAX v0.6.0 and removed in JAX v0.7.0\\n\",\n      \"\\n\",\n      \"  warnings.warn(\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_5b83677f05674a588929eaebe4d0dfc0\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5b83677f05674a588929eaebe4d0dfc0\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNrtWgtX2zgW/itqenZIFmKc94PCWSfk1RZaCC1td+ZkZFu2RRzb2EpCmMN/3yvZeThxUtjSdmdn4BwC0tXVfeu7Eq8CNrPJicR8QgLN9cjAd12G/kCeG1BGXaeOfGJjRifkCBmuw7IGHlF7Vkcj13EDD2swPrUoI1nxRx15PozYNGBZwTrLZh6MOq4DwyrWhqbvjh09q7m269fDpUco+ku1gQD4UZ1ZdWRQBmQOIw47QiPqZKPxnCz/A3i5d9mA3lPHhHWurxM/C0NHyMO6DoNZmxisjvKaxaVxSNYi1LRgJCeV+H4OwxSUW/CPfslOaEBValMGKuIxcxe0WeownzoB1fi2JJyN9Hp4dRja8dXCjll/7MCePowFmk89hrghjvew59lUw9y0h67GCDeTT/Bo7ySdzhyfgOVhv4AhnRhOgI4Rs2ggmYRdglvOXZ2kM5LlBkwS86AaYWjgEYerrGicK1/079+SZrrY0W0C087Yto/CHSQQs++6Doymp64/zKBVGdxrGOJTsWFGNT7oEd9w/RF2NCI57jSdEYEAG6Q3ZlA2XPQKFfIZ4EMNlF6TWrKJYzILHR8jmZPsFN0nbOw7YHdE7IAsBbPGDpdsnXVgUYNx+QQB/+UBvrfskIbwc3R3KvnkdkwCpjh0JNzV9vGIpEObZDiPo42NvHFghWY8StBxvsVxqMYOLR8vA5cidCRzTdMO03cgUgyi1eO8+Aix2QEiEwjwyJNcOvG3NCQzbvSUn+ICRcSSZuMgeAtZHPFNpxY8ByMIw9R884cM2BPCX8T4yavDpATQ6QQJhsepeJ1JIYZV0JTcHafkFKSuzzZJXAdEBGM4MLUrGZItkOZr5rqnIBnDeqe6jLkjXhjqjsvSkuHaOlZhtQNs6xYO0ic2Vol9Ep8ZhHuINXXNItqQ6JkM+ic33bzwMNerI1nKlchos/TwsfusUBn+Pkossw+SKIcDrKo+mYjoFtXxZbmax7K8JNDc0QjUWqHA4ou7Zo0EhyJb7oT4mQT6kJzoAy7wkmFUkDfr9gT76WxWtV1tGA5ljuZVWOif8+5Q4NpU30UZOuFrxA/cvcTnwnkgDrHAIcRfVbrCv6PjSfi3jijDUGb54pj/thxsYK4kNwO1TgPYdDY/wNYJ0QkSYVKvqwRK3orlXmriK9nD4dmUzfHDKTrUwK2LvagjwkaYYceewpmbO+vYHwYEm5BGzubqZwr3hQx8afKiOX1MQnHI1tHer/mSqu39TPHii7YKWf4BQnI/8o3HfsAd6LkANYifsC8Nnm9bkQpio6wojsG2GH+eXZfqMXLHNneRaDAwqB+wgeuERWgztXalkpQv8WxKdBX6ZvFDj6+LyLUaYd8EcBiKIRL64Rt3g1LszdQxlEYnsQAtp5OCNoVSa1RgSIDmycS/klxRT60h+70zDFFBsY36s5Hq2gF6N2ZcXx01w5Xw6c0gMbJTog4BpYeVdwQHryXwOHYYLKc4IPoC278kMv8+2gzzcLXA1LJU4ydkXMswPxK0SC53y5XSFAcDDc4BMOxiPTZY7PSY1+lde66tiW+5avroANMxw1nsgGMFasusDvNNOBT1sTOPZsEW5QJEwGIADrLumD1NlYUE4BhK9BdxScSW6AUdea7PsLPBW/XdIXEGfGRZjL5u3ZVlK/acu/lBsjRrIJrCAc+OFYQUJosshRm7QudznLRC6Ie4aUHJcROoqg80aDR0nziR8vFWDqSMEz4bxFskY3Q0x5Jfw7aWhn4Tmh0ANAIfSwHDfP1C3u8mSYSoQkl0wFUQJhxWrbjjdoxtB9qHAXToBr0DJrHEq4rEg14Jc6Q1xb4DHhjMj4q5dw0Da7lCAqEHzcaG40Q9jIwUDWUjZy4hZV2AZOxnTR/rFNyWRrlCSSfmAXIhSUyCZBCvrFkHYdJAl8BLkBhCkZk3ZNmo1c9zEKCNkp9dBOe6r7fF5jrdAnqHPoTsjpP88OiVpaJmZR4j648J57A1EFIlNgixxkvoAT6xLfBWOgfqrC6J+AZARTK8gY5ssFyaffza+b7zNkasXLCcj2aj4R9ozWUjJiyY5Xk1DiIFc2QE2m1XLAFDPRnBfC3E/4j1iqF/ebV6RK+4m/hBAo7fpatfOvlbGvupBQcm7+uTkB0H2mC8RJqwJVyhAlm3MQo5gEls7AEC+3oX+3QHb99hKWhIRO4AquhbaZ5DjqQtYqZYXoclmSJ+3yTF77nQDg7rqjqxm7dkwsdstpXPM1428/s/9ELxfTyTDN8dpXVXG/ObIomDgkCaYHtMINAyUuCOSFpABX5zyD+lsFHgt4aPbBVSe3BwZhb3tIFFCOOXuWSKmv1+n2vT52P8alZMSj4Rdz39maOlf/9X1J5oZA5ant6qrF4OOfxy2o7GplEOF/nNWuBrdTT27TTHzXU+fzh1DSN/pAIyLxcPdLnWOTOVhiK+eheK4orfGpdT+NltK0pL2fXVGCmKOXTf6L1Wozn9rChXn5uvlbNeo6m0zbte963FgsYZJWahffop/7ZX/jzpe2P6/qx0lXv9qXf58WxyfXbP3s/a7eb+tTm8oo1T2aKnF+PXLb1zI3fVQ2PS073bN2Xr9prSi/GZ07G6xgemfCg3zv2i0u45w1ZZ+zAeO/uXpVstGE4nRts+vL0zW27VVF9PO9VcVzl0lMvSW99/nbvcN+/lS11WXhs587zSnHZu8qbszsaXlcqolStPu59q70zTI1fDWZH01PuSpvrvOgwr5kXvfHqKg1lwMe71Pl232lPl/YXX+6x/ODzcNytXlU8FJhtv3t8qkxLwfKucV5SzqTIy7y/7++MvfdL6dJc3ytr9efGyOyuNG8qb+8aN1/YKtHvRbMlfxu+L/YpjNN62uu2zkUL3q5NW3nJyVmVf/Tj9dDPt+pPTzoemc2O0Wibbf6d9se1KqdZ8PW1UrVrx7KzTL3S+KOaoV7ppXNTYVYd0a61Go9cpnJrFy8PP2kxVOuDTj28OlYsOVshZ01a696135hdmlhvvzXfveqeNIb0okXbjU7PR1qjsWb7rORAb3pfWae4+N+wbTYNZszdOV8ftoGvI56NO67zc0JXbjx89zIL+l5GuY1rLG/e14gd6c1v2Rn75nfu52ad+ZzR53Sn0r/uFdiuvNS6Mq/2u7XqdYjuYlrB5W67SL6R/bnvXTqPbI/qZT8bXt53mKHfd9of9/l0pX76+DqYKSJRB4mGGpfdEWO/xI/N3+LHIfqy7HvQOy5QUz0mSJO2gOAhz9jfgtfuC3hLvG6JhDHtZ4A3h4WgoHbaU8dcnSMErl6cvkEUtJx8LoDxwFryH5o0nnmLKkIMn1MTM9SXg7Kku9nVp6lNGrsgdSy95cUQR8lo+ccARn06tNNj8cQN2uaIjAp14ev76tbHOJyPoljeWPhygvCzLAklB8QXUmhZXRcn7rnTRqaVw/JJsXsH4e1AKvURtTG0obMxFnPiFqGyANh3o66AaU7AZwTq/BNhftV30UPOVJxp+oYBEeTxOxUBVHblDW7MAUleqJY5+5ALKVSpSvlYplAv5ai5fQocAgEDdJDzJL7ZTEfvoCSh+mb3epwFxCABeUccbRwdZShz5qnuXSmQSoQOYDJEB6CgWx/eNvXBEMBHF7yjWJI0f/amTX2zG7QgUu+lik2ttN8xGHMKPa3H8BOnFaKT9Bv85ykklaDV/5kmdQJi8xz4GoFqTUbpQllEjE99wdfXeHEXuxYf50MCe2PHh1EaHG4f1qR3UG6QqxcHx9rj4ljBIdv/3caZh4zvJce4AMAEMAUlsqkpxgwt/pJMcu7e8TNtDrtPkJeR474k1VDxwZvbQ4qbvOCVx46aW6byYAsAk5vjV7folJcyJegHnkQW/R4qfbA+eJ4ZlAeIxl3/WeNy4Dkztmk+dCFB7/AhvU/04tXh0rOIyHMx6TitX9KKq13BerWK5WMxVy5WCruXWNk16rIyiLVnTG4ggAcRRkkUW3aAgnJMYtotZIZ/OyQeokPnFXIbyV9wV6y0S1cWVsqFW9ZJay1eLWNWrOVzLF7SiQYrVfF5Xd1Xz58napBL4P2XVr+fCwVPzZVfV+a5lIuz0dhWLkOJ5SsZ6WYC68JEGUFTpveCILKpDzgKYQcsOmLfG/1Vc/1+Y7/EfWwJ8lwO2HYKr/4CSOgH4/whnbuZLZvcBvEF/sEm/uGOCYHliTv0JT9ftQb4bTA2J7xD7bzj1nRwemneby8PZnwGpAN2n88U/IaTKk5JcrIEIVcBRtVIRF4tVGcuGhtVisWxoPxtS5Z8dVZWIXsGVmlrQauUirhSr5Rqgq3y5WNTBALr2F0FV+b8GsApLwm5ssErzN7j6iSb8CwGs58ysP+lp+2d08/ZrzOW/saO1/6JKPT04Hg9L5pQ6nZz8B7KrUUQ=</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5b83677f05674a588929eaebe4d0dfc0\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5b83677f05674a588929eaebe4d0dfc0\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"class Weights(nnx.Module):\\n\",\n    \"  def __init__(self, kernel: jax.Array, bias: jax.Array):\\n\",\n    \"    self.kernel, self.bias = nnx.Param(kernel), nnx.Param(bias)\\n\",\n    \"\\n\",\n    \"weights = Weights(\\n\",\n    \"  kernel=random.uniform(random.key(0), (10, 2, 3)),\\n\",\n    \"  bias=jnp.zeros((10, 3)),\\n\",\n    \")\\n\",\n    \"x = jax.random.normal(random.key(1), (10, 2))\\n\",\n    \"\\n\",\n    \"def vector_dot(weights: Weights, x: jax.Array):\\n\",\n    \"  assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\\n\",\n    \"  assert x.ndim == 1, 'Batch dimensions not allowed'\\n\",\n    \"  return x @ weights.kernel + weights.bias\\n\",\n    \"\\n\",\n    \"y = nnx.vmap(vector_dot, in_axes=0, out_axes=1)(weights, x)\\n\",\n    \"\\n\",\n    \"print(f'{y.shape = }')\\n\",\n    \"nnx.display(weights)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"d2b222eb\",\n   \"metadata\": {},\n   \"source\": [\n    \"Notice that `in_axes` interacts naturally with the `Weights` Module, treating it as if it were a pytree of `jax.Array`s. Prefix patterns are also allowed, so `in_axes=(0, 0)` would have also worked in this case.\\n\",\n    \"\\n\",\n    \"Objects are also allowed as outputs of Flax NNX transforms, which can be useful to transform initializers. For example,\\n\",\n    \"you can define a `create_weights` function to create an single `Weights` `nnx.Module`, and use `nnx.vmap` to create a stack of\\n\",\n    \"`Weights` with the same shapes as before:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"id\": \"0b076a0f\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_5aed62c96b39448cb9c45352442f166e\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5aed62c96b39448cb9c45352442f166e\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5aed62c96b39448cb9c45352442f166e\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNrtfet627ay6H8/BeteJFUXk9Tdsr2+NJfVtE3jJukly9ufQ5GQTFsiVZKy5Xj5/1nvcc4DnFfYj7KeZM8AIAmAoCQ76Vp778ZNbZKYGQwGA2BmcDvw/CsjTm5m5HDX8+PFzLnZN4IwILuG7x3uTsLozCMTEkXEO7PtHrEnrkU81+sM3cnQcsbdzqTv2Kbjkkln9+ggXjgB/EZ6Ry0nipybK//9mRsGieMHJDJujetzPyFNgHMJZhTNndnIuDN0wC0/mISAMoEvzYkz92fA2zwMQoo9MtxwFkb7xucO/RkZcyea+kFzHCZJON83zJbdJfORnOMiIuuz84PFMjlJbhYgkMgJpmT3FFi4IlHiu86s6cz8aQBc+J43A0oTf5YQ4GEK1GJIJ1WrZoSQlZ/cVM1Wt3bvzPbPwysqqCLp+9ELlvMxiYBgECbV/UnoLuMakB2HkUeiZuR4/jLeN9qL1YeRZM+UaSSf1kmf/ox4dvuGtVgZcTjzvTxpTa6tGCBJFKv6sq72KAuJvwAcSZFHxiKM/cQPodqcMfCwTODb2HEvp1G4DLwmZ5lmpGN4PANYoOJ4nh9MmV6550jWD6CGmuSKBEmcZnbte8n5PtRe0kTmIGlkIGeTWXi9b1z5sT9GxSkW633TDzyygpxN01xfynG42rKU4aoZnzseZm3S/7BYtEAN/sGGD7zo+gJlfA3XsOXOfPfScxLnPjU2Cx2U6NmcxLEzJYL2pC367mCP9SUHSURI7IYL0oyWQfOcRPAtdiN/kRhUNyvOYgE8OCiBvdBNSNKMAceZV4528AeyjRMj5cI4NKrVmnF4ZNzuGAb8mywDF1ENj8Qk8qGVvyc/gzQGVWw0AGAYEUmWUWDQr4+QTmsShfOqk4RjAGoY1TklOG+5oUeOUZSPkqpZq40A+26nPJtnIIakbecZMVbHNwmJgc8H5ZcSmSBtpBKQa4NnRGlVKfnWeDmBvp2j8AIynE1cPw/+JTzTbO7FMWN5RhLjMSrT3Fm8+us3T0AzR2pppiR5DMroB8twGVPg6pUzW5IGU0PARLS0hEhx7MTkjLaGhhFOJjFJGB/+xGCoxsGhYaYYhgAPxTFH/CvDzL/cGWQWE4HI0aFhlRAROWvNSDBNzo2mYRdIWy2ZeEqMidhN4owiy/Jro6onbdVGOj5eOMl5C+QOMsuI1Qpc5Pl8aVicH6GmI6VAJ3kWpyfmKTJlAQuMXM2oc/JGGZJRNyyOKNYOy2y6LjProZlZ+szG6zKzH5qZrWbG9f8kahjThjE+1TfamwBMJ/dR5MZ+cP6KAPUqz++S3NA+/xeu9jN/8cIB3Y6c6xd+wP7iOyfxV2eRqmVGPU4cGM9eo33i8SyqgJE4y1yDUbM/8+NnfgADQ5Um/f3vTIVgqKquasYeIhgHhkWanRwvK+Aq1SxFmzMASisGywyJfU2JfZ3B4A8FmIXTajHXOsf+PUqgUvjbIryurhhAw7BrtUy37wQtztq+JEfjUOoDMJ3JU0lQxI8aw+RfWlgVnpHVcaZCZqWWEtJelNZyCjV3VtW03jlDtVFJSQ8yiIzLf9Mf1uygtrDNGIc7Ss2T1aKaqYAsA2iGuXqD7mRgadGyqs/yAFGlOe2xhiyLZU8RHBs8QLDQ1wKurslkTQ9bx0hQnRRLUZ2MGLQYy5RVIOtesakvCPayqO0w7q5rrHucZkqLt6yUiHY0+eDaypU+lWT+qlNVlRDK1SNBOPcDsDGiTIf9oCqogK7YStfHRUBZEHq7xgYqaUNBdKnaJKaUupMZ1lYgF/5W1SaQy4fYwiiQhK/RkH6dRGBxs7Fett2yQSQduqQh5l00HVe/uI3ujC9up/hrfFd7px1u0L6PnBjMqunDchQg0B0JwKu5AYiWbdk9aJ8RdNGtvtW14XmKz2bfxudx3knlaEeGZQ9y2fPCVKgDVNGqdApCnZiKJFCloOCfz46dBBymAGwgqA/4d9MAN5F+ygZKqOQqqqlPDT74c5CCcCMLvtXrNcVCi8JrgOeAJ/5pqiAZuQtG7gLIAWxG6kIkxQef8Prk4lT8Cpkkqxby/4q4SRWtiwvgHf74DcNqCDZfrpF3BdVijHr+1E+o8Xwc+XMnwqo6obCVzyf0p9KAR2vS74879HEy6U9MQh9t1zFtlz56PbtvD+jjsNPrjz36OHC7vc640uAESbvfd22aMnbHns0erf6YuJMKwFAxqXy9JvDFkznrT/A/iu0Qt08GnLPxuM95GHiTgcO/DgfDHn10u2PT67LHztAddjLOJv1xz2PseGNvPGDsD4nnkG7G2U7GnUtms9fgRQFL/RFLUJwW8Ewm/rTgs3jQ47wMyGPAT3s4qntQydRtaRjchfFj6Nl8L/dlGMFG1qGnCsGNMwotaCFXENqEgctKeDlzz6vd7pcYNKhVRjsaRYKsoCH2KTfsAf/VRutp9k2FZqFhcbpoBGe02ctJpqcnZsPI/502pASLfrWKCR8F47RW8ONkobcwQgHDABTZzRzOitrkXW72S9ZLgVQKJY02fvyj8yNzHmtiQy9IXOz47lt9GnI4KNjdLpUH/K0JlB9UiSBiSxCxdSqOv6XVklWMVajK9VhleZ3WFBM8d82hDp9DPYAjcyMJmtUgVhXIRRNYAH+8YRTqrihS3TD9sapr7cj84TXHRGzdv3oelqTNa33NNR9Udeafoupyieoqoaz5rG+P65uqtaHRqVXEBy4PHV8+hvEubyTbOyncEUbBFLvnoe30vtV93wp/cJU/uNLXNrBNiZbQ/qx7YZYnnopWZ956s+o8MMx7V6f556tOc53czQdX573JaqpTrLrcbEkruCbXbebj39/20GZYICGl4Y+sAhptSjkVVEqqursH64viZW0yJI3KwgFnIeHR4Uoh/PZBFqXcqtLkE9rXnopmJwcBfpbABgytxPtkfX5861MdCEviJjoZZWGbBo3bNITAzf2rYGvlpL73OCzVS3Rij50oib+5eYKgmWNO5SI6XCi//mnxC1Sb3TDa+hQQbHcjRI9DdPBvGxRoI2SXQ1KMzkMw2vi3+2GY8LeH+pJ7n2KM2Q+u0I8HgU4cqCWxsTIr+EvDMj5T4pG59ZRhJ9GSbNDCgEydxL8i2RTiQT7DmcLMnSmY20tPmoJQbTYhXoOzvhlOC/tj2hXXWvFi5ifVSkUx9RhSOll5UFAsnqIzGhD0bIGwkKeKdyIRPhX7eJTyIqIrJM4isiBOEp+FE5ytXs5m0jiuCfxJZEdGve6rYx5v4XEC3DSM2PcI54FzyVgWAoIFGQLgj3SZDpcOwNZk4ExytOsuFkblqditaIJrTGQKW8Z2oioZr9fly4ON98mVgirZau0D2hcWrIN8BOGVk48ihdoSn0stBMUYSttTbb3VUhxFC6ZS2eAn9OsnSvFO9LZf7uRpEzQG4WlJcTUVLJZf2/GIZpEY7GLAnyyNj2lpaNeKbC2G7cVbMpWCZgSbinsUeM8Dz3dJXFUj2T77jg8x6AlFVRYgpR36Ce8SaCecziUBkgE9gQ471SxIOamgHVM5pZYMjDokcmYVUdloHq3FMj5PESijFW3sqUhStdhT1rNFM7yYJxzVWflx5fRUHvhS4EODQ8WX/uKM9kMVZapHYPfdF7ca8Lt9+TMJPPj4Tu+N84wP7psvw9NR3YRjNHV54coknK9az/c29cEqT500K0yIFNmjiPcoUaU6d+JL4hnhMqlVHsTm2SwML5eLArfp/I3x1VfGZxzXnwZhhA4i7S3X1E45X8XiMFWNl+M4ARuNtt1MBRlvZ3SuunKquIsppzJqmedY4HAZXAbhdSCxV2I1CHhiZmXj0jayxyaoEz1ad/pm20IUfZs92tx0MpoPaQFFvbpXtbEsFda3rbWNdba+hZTU112ZIyJmd/CXo4riVIQz0iJRFEbVys+MF7Hvr/BxRLuyi68CYBlchH6Q+h7SAtNHUMmvF8QtzNKegfHn3PwcJP7sF7bgu+oRjP3R5ckNw6FgqeyE8YsuD/dh1Ll5OY5JdEUX7/B1sCSKCcVLk6pVsHEjn8TZQua0vvj3E/O05QuIrzB7UECzOPrwpdgvnOgSl90fGgK/rd+XJLp5Dfasm4TRo9msWlGXbouiZ4WritMSqStEZthglMxkFQKQVkTm4RWp1nSqXJRQy/NjKESAtodamQ3j9i5bKwzFiJNHATgOyOCzyJkTYRF4CfGQPYj1l5oy+pXd46U/8x7xdebP/OkyUirfpdGStNSbVEVm8Gxb6jKLomrek7/UeJqELIiNgR58E1e70lX13zgx6XVyIOFjAfYJCxVJoPSbCEnHrRcwNKmUlQQRB3cgYNDAo8vQObzwUYRdpc1XAM2/iZA3GsgbLWQ8gzHA04ArCSKOHEXLUVxlJYhg5for6AmPSYTLQHIE6bMiySV5Rm1snH94LhjAklTLgEY7eT+FzZjWV77cSqxSGC0nbJuBMFJSANanqJseBC0prqJQCPvBRrJsV0Ip0RQzOUffBPvVp2x8WAbxcrEIowTMII+O/LXicnWqd2doLMmZsn0iilbWRKFlkgtn6OTypfD4wV1GEfTW8seYLKgTY4pejBJKytbq5irbSoMPN+qnmrLGDDNg4ybPP4/9Z/wgq/X0PVvZT/mXPt6p5ZRLnISJM3sczmKl3OHsV9woRctpneYJrDgg1YIbpwigUG7tOroYAAAnBxZCZzyOiStyIaUF1hPYVPSRmlgpWM4RE1lumohly5+/BpJ1KmLIC1f6B9I2iqzkjFqGJylcJrlX4bUiOdDcb4k/PU8KorvZVnQ39xHdzQeI7ma96Hjh8ucNosuLLsgOEWslqsiHHBfjaq+xB35JrVvM9PauXD5KZ72FkBQMQVLFzE+onAKwPk6FPUA7KteuA55bLE7dVb3QXc6h4bXciDgJeToj+FatMNBKto2KvrboRkRc+52rZt2wcXtEuvpQAj+nks3gaX3o4QWhPqa4OE9KVonEK6fKZ1EhtVqxPcqippPgK67p3pTv+cJsKU6cx+zB/MThDatQNo2EWZB05HyVL4AXQ87rpq606/O0KJxn2Xrl6/whwwL8XNgOICzuL6wO56v71ZXhhflltiaG7gB5NI51OWaJ8sKEDNFZrUGkiQWG9RV1yORbnMsoVANf0E6XHKNAcMk9Lt5WhaOfv+cGJF0A/yIluWZ7U/6j47uBuTZyETZyoTQMs2V1a6NtiyPxhB/ruIFvL9+XtzEYzufLcJOQRnX8QFOF99EztvloLm7U2VQos9XdokbW1HATi0NrGDllbxuCMDua3R1gvD9Xd1AKYV3aAYNPqfTA0n6LjET9UPQQxI746/Ud9UjlkLsQ+XgimCOogaxvMpU1FTc5/M16eN6N5UO/gduqZwR65wg3Ld0K8yfcKmCw2ddScOlRcBAAnxrOJ5m8TotKlwYaDyVrmHcAxl/o/I2xb3z2WZ5cQk+zkl2MIChjyz1WuO8U5u0kHZV0UP/IjCnvyglc8jhcBomoew81qrhFlOoW2Dd1oXIzAwe/5nYOBUsNIhm2VKslg01W37xHEPk4OhQMNZwuLszDKq+ybIrFkIupyE7mvbmRd2iaEncqLZQZUKmWCK22Hr9k4ll5HYO9dVk+dXyndLeK7hxm4tCuR1ByK+QljhvGTnHVhtQPrMrV9iFOFPV/1qgsdlH1w9ynKdXXUm1drddWFOdK1tXVOl2VXlYb9HS1TktLdXSl1dFVuY6hkFBD9VKqrUPWqufW6iKZrytVKVfrlHKnNIeywVr+o+nCWzM/IL9yp8QarQGMkyi8JCWT+WWUHzsLBI5/XzoR2Qj9XUhNrcocJ3grf+gQu7N2YOOF1a05YRPp2VaxJppkDU3vtwkmS/1a1EHr1PjLX9BMxbUJazCEfrUMpWxI1Y2jVuk4an0aRz+No2vG0aOPN47ubDd4WiWDp/Vp8PxTD55HH2HwpL/FYFh+ogpJpDhFNSDX6bO8XklIwAFdF+So8anslIWySFtO6Z5RMlxysjkyJkbuCpO0akQwkwRbmnCM01p43sFN2TYH6icLJxLR4AMNwKQDGZ7yYY00O+xFrJsSLFkguM4Zj69BfP6IX4/EhpzH+PNzlE4Bckfubo+kIUqIbgtYmtNwxFjmeiss3TgXL2eJEPP+aJGVdHKYEcnj/9BAtwqnGJw3OSRO6Y3uGb6RkO7kKApqO6bz86YKZyn4cvwmlz7280fMWWo2iwVfN7mUwVBmjc3dPA/qhd5ytoxleHr6U4ZD30Q8tXz565cpvVFhu2sYecoxXiLiHue7sEw/P4cMCdQ1cz5pQ8F03j4Y0lE+WSR3lxq1FvvfP15FoOWJIoRXrYrcPFxFbrZSkY32qqojIsJaJSmW8GFKIiH+eZQkPWdNjY82DG2Ys8HZyYOWp2snKPMjMTGeL5wTlC/BwtBl4D0Gh9MrnQb0/KtKTT5L0Q8oVWFeTp1RYRnfnz6lLKGxGb90gi8nTQ/wzGYjK8LBqJWRFjSbidwCFo8GfkZPBqbefHo2cAl0EjlBjIvNX0b+lAUAknABfcCkjD4MXMdRuCBRclOt+HNnSpoRQe33gyke8ULX3ICQvEptCwLNZnoAafN9GM6RgLUlIp421qQHIcdgmVDMzmJVScXNqkMW9TvXmbnVKyeqKvmi1fzFrThNfLdYpbsCRUpZTWxHioGX0EqPp6UbCVBmYB5UUmVh+FtXkgguyYmek4zC0bFaKU4+q0LnZ/z+AHllpf7ittDx39EDHFtdMsd+FsqbFriE3ptwIZBbbUWusIphNvvBGZOZuLiDzyrR78fK1gc+tuD8+xzaBYWhITU6dT7DV2n+nH5JNQgPv3qNDQmlvxDiaSIUOyn5G3raNsKZLQtKIJyZrMVCHWZHHwldUYWKBo9ts+i0bNVs9bBzLVZiDTWOJchNopZuR7n749cM0fIou6rX95o6SYgtIj2yWSuymaiO5Y2Qey1C+1Pri9cUI2SZ5pegb1/c+qh/VP30eLw/EUq7iRPVdM0jlBrmMAr6mHXyqRNBE3SsIOwjPIEdZSb0CIZW0bmWZx5tYciS07MWxlYXiYl3f/haqn+xSkm9ZhQmQB67zebQ9Mi0oqWt6Ze5RkXY7WuziaTxY2uN02lq03i45t9bg4uG9VoV1iinLJMttHNHtqBBVX94sErk6BtblwC6hQoVoMd5718OlGrBOhjo+9O5oQrWdzmkpLqZUjZDqpWoxJJWKqostv6c8kO7CA0FYTBmMLcUaF8oRwN3WuEnSvuuVrYCkAZevglXJC634D/IR8gzaLkzJ45/8OOkBRYLWLrBJERR8msYMsvpPuEhDsZOOVy7TJR7QWxV8WvOlKL4ykyCwPo2xcZrSSrCKc5KXpxG9d1/BF/c5m3k7uSdsoAHL8AosFaWKb05Q2iVDJlHUY0Ku0yjoqSmS3GKUuIAbI1YebKzksVtKQBxQhbi3IZeFEycDGWT1CqnRkWRE9ObB8qJIWdyoveiVJTEcjFxgDIxpcmlYuIAqpiyz0UPC+3Vjume4+kqMR8g8hFinXwZ0aLsXAycQspj6G/wohJ6XQW5SiSDvSALAGhBUackYZ/y0IeiW+WAO/JMdfmyZX7EhEpJat5CXKgk/q9WOfQ9T/HgBuyICHSXqW40dEJR29VDkIudLkZxZkDhI3a5nGLe/6RfWnQhXSWzP+ZsQ1ylsHcdj/qGrhY38uOhNq0u3VNv4SE04gyBXIKXCz6MS/xneW/DekhJiB2nTDzTqOzccHG/DJpTxY5gy24gR9Z2BEIya+kVS5NCG3nF1iTx5l1xgptiYloqzYYqkQRJHiVJ5I9hMK9WaA03xKpVAnGTEG/a+ngBPk5RO3IrIOud+hQq7c3/FuLlWU2VCE/OBaBPr9SpP093Jk/CWjGOkV3TVNrAikbYVgJJCTOJ/AgdFZYz/VoZqTwoA/nH4wE7oyIPeB6WAnS/cF3R/VAIbR2tK1LiFlh2idNHjEOnNMtMzAxADs8Jx6yvXtB73WDgUHo6DADhwika68f6gnHoG7zSyw+mj2c+MPNK2hQsTPkE4Ba9SsX1xW1KibsqzYw0jb2AoN5pZ4TSGIBg85esBMlM3DRMJ+DIUzwcpEV9BMWByrjWwIuxVr2LhNIWN2pjKaidy2WokNogTDaFk+Gn2neUi459UQ7f2sSuYufL3LNARZUGKWoGTcLge7Vptrpof1ktm8yVQyI+pISGrCc8fltQllwIXFtw45R7Lp6zp1mL8mDSphw4UVeVSHo+Wc7Swqe012ly7t9u0mNONEPA5Twb5SlwI++amiT7+c0dKRQtciPNjL5JF3Ik4UKHBZ9zJHiRcKhc9/PdsxkWTcjx6KuEmd4qWURlKTkuexeQ7zT3s6ybdhD0gIZ+m4YkErE7WjfhUAh3sQsxaQQuywCnVgT68CrFt0roC12nLEGx64z4bNAmYtkMgkiNB3EEcuxLTu9O2ZWOAxNQQ/OFLhmK83UtTGuXCxhmCKY/i8L5a26Z6rcRSqsD8LQT73jFjs/g8+H0a/XaD7zwuuWRK/AwaK4USDqcvwQGb7uSFveo2VjGnpgVvm7KTr7/Z5EBPPIulnEyZzFAJaNSquU37rhxfLx6Q90+eklJFLP9/FXVhpbkIGIpZZcJWmpxEFtC/1opRM34UrhS47BwwMmHTenqj8C8F01nmYSVkmqic5aoh2IJxUVoX2trsqxRbZhwhjZGMxQOrhItLGkKjfF37STuuaC3aovhi1KI58sHDXO1miP6C0ytvqtGhAaU6S2lX9yWKN6dt8AuSAmLzLhbX8jfYHnzU2OUEIB7Tp3GRoYujEa6/kAcrZSCC3GL9IFlXAw8lOV6V3Aiy4MWciHXdG7ZAXIzj80zC+TZaU7wOQVdX+YdqeZiNwpns28km+yWDrq6HPA6Q8pBwxiTc+fKx4tcK3jEkhMklTs1D9GRpvk8D5LwF59cV2816EBzFrqX8CUgDihRTvBO3vRelOc8XMZUM1CmagwtPfwI15mC5MQFpxjaYoX7rWHkL2+lPi3F1OyAztxR1iz5Zb008BsGRDhBUvHiygDLDoPOdkOeXSnHJGaLj7KSbZMpFXQ5e2wuWrLSN7ibdNEs3RBdModWyCNRrJfNWdANH5uzwOqQJ8T0J09KeZcLeM2RObXRFoqgiloIToBpBBA9NQ0tJuKlE2UZZaaa7HM9I6BH/jaNEijY/LsOnR4pljcHaOkiH0eGaXz1lSQyEbiuADPXS+BYdhAVafGlDxXpvFsFhnvHiue43dSxXKy06IruaJw3LZtaDrbKul6WdVlhJYncldTU26ymvk2d8zVV9TarqgxarKtvNd68whttt+vrKp0v/sDKevshlVXsXu5RV2/vUVf55HildAvIVsNXyCwCdfTacojZaoDZyAiN020aQ1+lWw0eOJKm+MXhtLgrBgEePjie5aMiy3Sknqh6dlU8AXY9F/qTPbIAqDzyVB5jAvH2MViuaOOG85DVwhSOO9H9bDdYGfKNWfotRfKOqk0yKdmIVUBT7wXQnxCyla5enxMy0+lq2j86swTyLQa2PDJLnLfoYjDcWot9yblCbH68/BMycUBx1JBofhd1wWiv4d0LeTqjrdx7s9bSF2VSBMR17Qq73DWAUQPhfgC+Z1u67Nn5dCKiRGikboQucnRkdDaVrnlodFT9k3I9KGEXT7MpHGcjsyu91uUdjLqtfqXlODCaGwtS31SQo7KC+MG9CtLcXBDNZKZIYnsfuPjwsKmXolN5q0ZyacN0Ke5bMe6HIVxjT9Hhr5U+r6pISIKW4rgs2Cxk9ps63fMxc9P5snl9aY6rXThx7F+RfXaBy500J6ZbRbGuArUBjMKBtSxQ9TIgT/i1PR/vuFr5dCHxoFHN4lcWvOBA6RcNXBYuFkHZx5F8PFPxCL3tD9ErHKMnH4JXPDavkM5YSNTVYuUH4xXPRaIXSrB7G/jgj2dNgJbgkoh94aIl0Bw+6io7Y7ONRQ9YRVq6hcdynUWlDCrfvbMWbE4jjqzOK2ar3yXzUljBYvUDPL2jKXvOhXkF/VYTPTAax5MZ3QxcuWIqXQq7freRRmKl+yhUSCxV5plX7Fa3slV8d1P4uqzMywQzpJW0WClbNnbW7Q3LNnlJjWCtfoggZcohwpQsQN5RJp3f4LBQOMYcU57OCr74lhpfVuqUrGgc8U9q2W1x7XIBTLv5xcCgPDYBcDWz6UyLrvOCkSgtbHrSwB2Zvyulv3bxdgE6Da40u+t4zr1Yy1oHJy1/d0kgHqxTzFrSd0sHKLtL6oBwp+oDGwH+PCrByvtv0woe6/m3q0Q68MtaUbZkKavL4hn+fI/vLb8sQLGJ9oufmB2nOU5/X/cRoe9GO3c1aoUl5z41AV6FYfJj6JFqrXUexgl4m5MgbqUBqPRERngcHeyB5ewvkqODvSQiJHZhCGhGy6B5TiJydIAL3A26wupwdxLOPLzN4ywAyrtHB1RORwd0YslAy+Fw1z0n7iWUYVeLc5aE0+kMUfcokkyenvZx5ozH4AXvHn01S0ZiciUIE5pYObpwVi0qBAOYBwiJDProQQ6YgvDD46sWmDztWglh45//+D9myzT+8//j75N//uP/0Xt+//mP/wt/T433JAr32+aGTNPk9A+XklhSCk1W8MUj3q6chvvEQRG9MxcVC5RjbTok4upPnpZW6lmmjgqycrvG7tGrdIRnCtFqtVK+tcpANYVXNaj/zHdpU9gL3YQkzRhwnPnuUXZlFmtOVCfZG6rkKAuJTOg1AWtVdsTAWsDD6zAM0ms1OIlkwYMO2NtKl4lUKwmZL9BcQTokikBgYBjihpPchGW3Inz3+uWPLepAV5Fgi+8LV+mxsldqUl8B1BAlu1fEkNtZS3cnBmM3v1AD+4oNjTAtS1qNUpF219TLRRyC/tzu4prI3X1j9wkLu2UH64DXgMFC8JMNhy4AqLWMb9Fw3WPfcSkXuw2BBhp3G8aucAcCUnz03/wnY5le94Ac854AE5SrFWh5fnr6zTb/I7pw1gOg3u46FvyxMEMTHtrgO+3me33hy8ntLm5zwGwAFFJp10BfAcqwAAe/0j0O8BV6nl1onpTUKTzflNEyi7RMYMTUE7NMSk3ZXoUk8bN85BItViq4/HAmJAyGA5LF/nF37qyyZx7ZhXfmUu5m52BnINlx2NmX9C5o5OJk2GNbENr0klqr3WGvHYu+9vqQ0sP/6esAHvt43S57HQ4A2MR7Adldvia+t4fwy2JX91r4oY+XT3bZBxs+2GYbPgzZJbkd3AyBOdhtBmHauB2C/mKZ9BB9iHcVmib9YOGmib5Jv+KHfhdZxpwGFKONj3Y7e0diw04Gjjn2sNA9eg1vG6h1kKJlnZ6iEknbBUBEfaa6ZVF3ph1prfGbBndTHKoi2CB2MSq0FuxEBqEnmND2BEqTK15+dxhVrvRLpmybcoHnDdlYhWzaci7tjZmcbi7tfwQGjAYyHEu8O70TO2jeGcMjDL2F4b5gGyh2zTpD4O/GE7pkad94fPyzkRoaX02TkT4XxgAaANTShYGBBTT2DZyA2zV8Dw2w6CwbL6y+63V6ZOx4/X6nZ06GlmWDVhO7O3AHFuly9uA30jtiVwWBNTFHD+ZM4nYfzKZqSzLv9s+duHrE7J2WzvCjOPvURiQeHrhQo0P5DMOYnzv0B4ZDKB3N/Y+xPQ2e/TY2qCZJKwtdrQL4zFnEmEhNWY1iKDQ+15qz/9PM3rWCKNfkP9hKpnKrnpyYLbyCAf/Pr6v9n/gRxgl6ndQhr+BaiVg/rLvwvE7fHk/I2HXMjjv2Bh3Ta9tWd9LtWvZwbOXdxf9CL9He2GK6bdpk2mmTwXEcGo2FjQYkSttN75O7+Mld/OQupu7Vm8Xi5vi985RcDNznv4f1n49/uzq+iC6fvqlPyevLuvvdm+nx1L98/GQYvqn/VJ98+9v0ePF9929vBr8+ux7sdabh5fHw9x//djH46WIe13/84dn5cXvqWxfd6x9+HtQ73/u/Hb8Jf3efDF4899/uvXnxXfe44397cdFdvlz9vDedJwD/6LdXb+rh334a1H8Op+fH9e/ePnqyZ7fP3b1fXwTL4+Sm7rwfvPzt4nrv6Q/R4+NJZ+xc1H+f3Jh7v4TP58ftb1aXbzqPnl4Mhi/++tvvxz9F3V/f199efz+tf//d0/i43XtE3gx/erKI6/7T/sXx/Pn45fu6c/746d7xCyjy5aNX0Xv78rg92Hv/+8A+vn5sfvu+f/1s+qLeefQ2OH7t3Hjv934aJm/rnePxT8dvp9d/e9P79eUvL/b+KHf2g9xeO3V7LW6aU/+3t9b/fZDPWuoB20Vq9hpvGk38DZ64rce1P6Lv3DRbw2Hf7HUG9mDQt3sD9FszX1qTtMm17pj9/gB+Dwbdod21hwVXW0Pzk+f9yfO+j+dty7nYHyET+5N7/5HsdXtgu8OBaQ/G7rAz9Drjge31LLPdNcfmhJDOv99e/9/lUf+LXYT/3q41uKfDTr/X6xsGuqjD/qDbHw7pc7tt20PolnNfFp3ZTm/Q6w06PYTo9oYDGJUotNXrds222QYnN7tPA6l32oNe2+5SmG673YcRjj53Ot2hNWjbCvX2oD3stdsIMRj22z17wDC7g2GnZxgq9R6MnkPLpNwMTbNn2qwcdts02x0YECTqgwGMRaZFoTvWwG5bfZoTDFngIMMQqFA3+x2za3f7lHp7YHU6bUodRuNhFyQmUx/2uz3b4vn3B+0+/FDeBzYiF3gfDMxup8ek16eSZJKxOsB7z27L1HttczjgkumaVhvy6tDnYdvudGGcVOXe67X7QJTyDuP6wDIpbr/dHaAMZOrmsGtC+boUGso5GPZYSaEMNpglBbl3zY7ds2n57EHH7A26NCcTsu0MTIV3CygifVqrYOX0BqxW2/awO+y0C3LvgdwHXAuH/U7P6nGNhE6y0+72FZ3ptIfdQddmpYOS9jjvIDEbyqFSt4dgTrVZrZqDLlhWFsXldpZSq30L6sI0++wS0O6wZ5q0pJY9MMEwGhZ0httzlGKPqQTTt+Gga/WV1mSZYEu125SibYOW9IcUc2B3TSh55/RDYkYf7N2zs7yee/Qg4dwhte0esSeuRTzX6wzdydByxt3OpO/YpuOSSYe6G1sHr0XgjaErEXjjuHk64kGI0I3CMNGGIdJIAcqKA3FwJTaw28rEeYbJuwXUx7OQLqJL31sufqDZ0DvBRnlKRMAicMmvfnJeldAzopPImfId+srKpCf89RmHwDKk0NLqlpQwpmsO98gqVzncg902nu6+zyjDK18T9c3NcyCeYuO1OyN2T324jFzyhB5rVSLDz9Hw2TXqhoLOD40TxZJRa038KE7zpiUDhDw1D8vcyTWhFfKmKMx/AUcQnUA=</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5aed62c96b39448cb9c45352442f166e\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"def create_weights(seed: jax.Array):\\n\",\n    \"  return Weights(\\n\",\n    \"    kernel=random.uniform(random.key(seed), (2, 3)),\\n\",\n    \"    bias=jnp.zeros((3,)),\\n\",\n    \"  )\\n\",\n    \"\\n\",\n    \"seeds = jnp.arange(10)\\n\",\n    \"weights = nnx.vmap(create_weights)(seeds)\\n\",\n    \"nnx.display(weights)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"fac3dca9\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Transforming methods\\n\",\n    \"\\n\",\n    \"Methods in Python are just functions that take the instance as the first argument, this means that you can decorate methods from `Module` and other Flax NNX subtypes. For example, we can refactor `Weights` from the previous example and decorate `__init__` with `vmap` to do the work of `create_weights`, and add a `__call__` method and decorate it with `@nnx.vmap` to do the work of `vector_dot`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"id\": \"5d9a55fd\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"y.shape = (3, 10)\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_3afdf582c2b64763a175ed6a40527896\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_3afdf582c2b64763a175ed6a40527896\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNrtWgtX2zgW/itqenZIFmKcNwmFsw7kRQsUQgtlZ05GtmVbxLGNrSSEOfz3vZKdhxMnhS1td3YGziEgXem+r74r8S5gE5scSswnJNBcj/R812XoD+S5AWXUdWrIJzZmdET2keE6LGvgAbUnNTRwHTfwsAbjY4sykhV/1JDnw4hNA5YVW2fZxINRx3VgWMVa3/TdoaNnNdd2/Vq4dB9Ff6k2EMB+VGdWDRmUAZnDiMP20YA62Wg8J8v/gL3ch2xAH6ljwjrX14mfhaF95GFdh8GsTQxWQ3nN4tI4JGsRalowkpNKnJ/DMAXlZvtHv2RHNKAqtSkDFfGQuTPaLHWYT52AapwtCWcjvZ7e7YZ2fDezY9YfOsDTh7FA86nHEDfEwRb2PJtqmJt219UY4WbyCR5sHabTmYNDsDzwCxjSieEE6AAxiwaSSdgluOXM1Uk6I1luwCQxD6oRhnoecbjKisZ35Yv+/VvSTBs7uk1g2hna9n7IQQIxu67rwGh67Pr9DFqUwb2GIT4VG2ZU44Me8Q3XH2BHI5LjjtMZEQjAIL0yg7LhoneokM/APtRA6SWpJZs4JrPQwQGSOclG0X3Chr4DdkfEDshcMGvocMmWtw4sajAunyDgvzzB9xoOaQg/R3fHkk/uhyRgikMHwl1NHw9IOrRJhu+xv8LIGwZWaMb9BB2nLA5CNTZo+XwZuBShI5lrmnaYvj2RYhCtHt+LjxCb7SAyggCPPMmlE39LfTLhRk/5KS5QRCxpNg6CD5DF0b7p1GzP3gDCMDVl/pQBe0L4ixg/fLeblAA6HSGx4UEqXmdSiGEVNCUPByk5Banrs1US1wERwRgOTG1KhmQLpPmaqe4pSMaw3qkuY+6AF4aa47K0ZLi2jlVY7cC2NQsH6UMbq8Q+jM/0Qh5iTU2ziNYneiaD/slNNy08zPVqSJZyJTJYLT187DErVIa/9xPL7JMkymEPq6pPRiK6RXV8W97LY1meE2juYABqLVBg8cVds0SCQ5Etd0T8TAJ9SE70Hhd4vmFUkFfr9gj76WxWtV2tHw5l9qdVWOif8x5Q4NpU30QZOuFrxE/cvcTnwnkgDrHAIcRfVLrCv6PjSfi3hijDUGb54pj/1hxsYK4kNwO1TgNgOpkeYMuE6BCJMKnVVAIlb8FybzXxlezh8GzK5vjhFB1q4NYZL+qIsBFm2MBTOHOVs479fkCwCWnkrK5+pXCfycCXJi+a0sckFIdsDW39mi+p2tbPFC++aK2Q5R8gJPcjZzz0A+5AzwWoQfwEvjR4PbYiFQSjrCiOwboYfx2uc/UYeWCrXCQa9AzqB6znOmERWk2tTakk5Us8mxJdhb5Z/NDjyyJyrQbYNwEchmKIhH76Rm5Qir2JOoTS6CQWoPl0UtCmUGqJCgwJ0DyZ+FeSK+qpJWS/dYohKii2UXcyUF07QOdDxvXV0VG4Ej69CSRGdkzUPqD0sPIO4OC1BB7HDoPlFAdEn2H7t0Tm3/urYR6uFphalqr8hIxrGeZHghbJ5W6+UhrjoKfBOQCGna3HBoudHtM6vYnn0po4y0XTRweYjhnOYgccK1BbZnGYM+FQ1MfONJrFtigXIAIWA3CQdYfsZarMJADHUKK/iUsiWKI3dOC5PsPOyt6q7/aJ0+Mj82L0desuLFuw59TNT5KlWT3RFPZ4diwgpDBZZCnM2AU6n+OkBUI/xE0zSo6bQFW9p0GjofvEiZSPt3IgZZzw1SDeLBmjozmW/Bq2tTT0m9DsAKAR+FgKGObrZ/J+N0kiRBVKogOugjDhsGrBHfdDbDvQPvSgQzfoA2wSS7w9kXjQK2GOtMbYd8ADvelRMfWuYWAtV0gg9KDZWHGcqIeRkaKhbOTMOaSsCZCM/azpY52C29IoVyjpxNxBLiSJSZAM4pU1aydMGugSeAkSQygy84osK7X6dQ4CtFLys7PgXPb1uthcpptB79CHkN1xkh8evbJU1KzMc2T9MeEctgZCqsQGIdZ4CT3AJ7YF3krnQJ3FJdG+AVCRDG+gIxvMl2afv3bKd9rGiJWzLaej2Wj4B1pz3ogJC2Z5Xg2DSMEcGYB26xVLwFAvRjBfC/E/Yr1i6F9erZ7RK24mfpJgx+/S1c+d/C2N/diCA5P39UnIjgNtMF4iTdgSLlCBrOs2CncAk9jYAwT29S725Q5ez2EuaEhEHgCq6GtpXkOOJBYxU8yvw5JMEb9vkuL3XGjDDsuqOrGbt2TC5zBbu88rXjbz+z/0RvF9PJEM3x2kdVcb8psiiYOCQBphe0gg0DJS4A5IWkAFfnPIP6WwUeC3hs9sFVJbcHBmZve0gUUI45e5ZIyOut0u16bLx/jVrJiUfCLueroTR0v//q+oPdHIFLS8vFVZvBxy+OW0HY2Noxwu8pu1wNdqaOjbaY6ba3x+d+waRn5fBWReLu7ocrV1aip1RXx1LhTFFb/VL8fws91UlIay6as+UBSz777XO4360fiLolx9OTpRTjv1I6VpPnTaHywW1E8pMQvN45v8h075y6jrDenH09JV7uSmc/n5dHR9+sg+TprNo+1rs39F68eyRY8vhicNvXUnt9VdY9TRvfv3Zev+mtKL4anTstrGJ6Z8KtfP/KLS7Dj9Rln7NBw625eley3oj0dG0969fzAb7p6pnoxbe7m2susol6UPvn+Su9w2H+VLXVZOjJx5Vjkat+7ypuxOhpeVyqCRK4/bN9Vz0/TIVX9SJB31saSp/nmLYcW86JyNj3EwCS6Gnc7NdaM5Vj5eeJ0v+qfd3W2zclW5KTDZeP/xXhmVYM8PyllFOR0rA/Pxsrs9vO2Sxs1D3ihrj2fFy/akNKwr7x/rd17TK9D2xVFDvh1+LHYrjlH/0Gg3TwcK3d4bNfKWk7Mq2+rn8c3duO2Pjlufjpw7o9Ew2fa5dmvblVL16GRc37OqxdPTVrfQulXMQad0V7+osqsWaVcb9XqnVTg2i5e7X7SJqrTAp5/f7yoXLayQ0yNbaT82zs1bZpbrH83z885xvU8vSqRZvzmqNzUqe5bveg7EhnfbOM495vpd48hg1uS909ZxM2gb8tmg1Tgr13Xl/vNnD7OgezvQdUyreeOxWvxE7+7L3sAvn7tfjrrUbw1GJ61C97pbaDbyWv3CuNpu267XKjaDcQmb9+U9eku6Z7Z37dTbHaKf+mR4fd86GuSum36/230o5cvX18FYAYkySDzMsPSWCOstfmT+Dj9m2Y9114PeYZ6S4jlJkqQNFDthzv4Ge22+oLfE+4ZoGMNeFvaG8HA0lA5byvjrE6TglcvTF8iilpOPBVAe+Ba8h+aNJx5jypCDR9TEzPUl2NlTXezr0tinjFyRB5ae78URRbjX/IkDjvh0aqHB5o8bwOWKDgh04unp69fKOp8MoFteWfq0g/KyLAskBcUXUGtaXBUl813oolNz4fgl2bSC8fegFHqLmpjaUNiYizjxG1HZAG060NdBNaZgM4J1fgmwvWi76KHmK080/EIBifJ4kIqBqhpy+7ZmAaSu7JU4+pELKF+SquVybq9cyheKhUoJ7QICAn2TACW/2U5F+0dvQPHb7OVGDYhDBPCOOt4wOslS4sxX3YdU4iYRPIDJEBqAkmJxnG/siSPCiSh+SbEkafzsTx3+YjNuSKDYTBebXOq7YTbaIfy4FudPlyPm9GwmssAKjynUSSVoNn3rSR1CrHzEPga0WpVRulCWUT0TZ7q4emsKJbfiw3yoZ4/s+HBqpc2NY/vUBuoVUpXi4GB9bHxLKCSHwPdxqGHjB8lxHgA1ARYBSWyqSnGDC3+kkxy7Nb9R20Kuc8TryMHWCwupeOXMbKHZdd9BSuLGTc1zejYFqEnM8fvb5ZtKmBNFAw4lC36PFD9cHzwvDMsCxGMu/6rxuHInmNo0nzoUyPbgGd6m+kFq9vJYxHsFA1eral6vFuWyWs1V1Som5cKeWtGJWlhimvRiGUVbsqZ3EEECjaMki8xaQkE4JTFsF7NCPp2Td1Ah84s5D+WvuCvWYCSqW83vVUtGTjYKeqlYKFVwmZTUPZyraIVcuWjImyr662RtUgn8n7Lq13Nh56X5sqnqfNcyEbZ7m4pFSPE6JWO5LEBd+EwDKKr0UeyILKpDzgKiQfM2mPfH/1Vc/1+Y7/kfawJ8kwPWHYKL/4WSOoQe4BnOXM2XzOYDeIV+Z5V+dtEEwfLCnPoTnq7rg3wzmOoT3yH233DqOzk8NO86l4ezPwNSAbpP54t/QkiFSUEmJJ/P5/YqxaqO90oqfObLahFrGKvyz4ZU+VdHVYZc1EqgaUmuyMWySrAs7+nlak7VqpUCrpb+Iqgq/9cAVmFJ2IwNFmn+Blc/0YR/IYD1mpn1Jz1t/4xuXn+VOf9fdrT0r1SplwfH82HJlFKno8P/ALzZUpI=</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_3afdf582c2b64763a175ed6a40527896\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_3afdf582c2b64763a175ed6a40527896\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"class WeightStack(nnx.Module):\\n\",\n    \"  @nnx.vmap\\n\",\n    \"  def __init__(self, seed: jax.Array):\\n\",\n    \"    self.kernel = nnx.Param(random.uniform(random.key(seed), (2, 3)))\\n\",\n    \"    self.bias = nnx.Param(jnp.zeros((3,)))\\n\",\n    \"\\n\",\n    \"  @nnx.vmap(in_axes=0, out_axes=1)\\n\",\n    \"  def __call__(self, x: jax.Array):\\n\",\n    \"    assert self.kernel.ndim == 2, 'Batch dimensions not allowed'\\n\",\n    \"    assert x.ndim == 1, 'Batch dimensions not allowed'\\n\",\n    \"    return x @ self.kernel + self.bias\\n\",\n    \"\\n\",\n    \"weights = WeightStack(jnp.arange(10))\\n\",\n    \"\\n\",\n    \"x = jax.random.normal(random.key(1), (10, 2))\\n\",\n    \"y = weights(x)\\n\",\n    \"\\n\",\n    \"print(f'{y.shape = }')\\n\",\n    \"nnx.display(weights)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"13b52d61\",\n   \"metadata\": {},\n   \"source\": [\n    \"The rest of the guide will focus on transforming individual functions. But do note that all examples can be written in this method style.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"0251e7db\",\n   \"metadata\": {},\n   \"source\": [\n    \"## State propagation\\n\",\n    \"\\n\",\n    \"So far our functions have been stateless. However, the real power of Flax NNX transforms comes when you have stateful functions, because one of their main features is to propagate state changes to preserve reference semantics. Let's update the previous example by adding\\n\",\n    \"a `count` attribute to `Weights` and incrementing it in the new `stateful_vector_dot` function:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"id\": \"a4fbadb3\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Count( # 10 (40 B)\\n\",\n       \"  value=Array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10], dtype=int32)\\n\",\n       \")\"\n      ]\n     },\n     \"execution_count\": 5,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"class Count(nnx.Variable): pass\\n\",\n    \"\\n\",\n    \"class Weights(nnx.Module):\\n\",\n    \"  def __init__(self, kernel: jax.Array, bias: jax.Array, count: jax.Array):\\n\",\n    \"    self.kernel, self.bias = nnx.Param(kernel), nnx.Param(bias)\\n\",\n    \"    self.count = Count(count)\\n\",\n    \"\\n\",\n    \"weights = Weights(\\n\",\n    \"  kernel=random.uniform(random.key(0), (10, 2, 3)),\\n\",\n    \"  bias=jnp.zeros((10, 3)),\\n\",\n    \"  count=jnp.arange(10),\\n\",\n    \")\\n\",\n    \"x = jax.random.normal(random.key(1), (10, 2))\\n\",\n    \"\\n\",\n    \"def stateful_vector_dot(weights: Weights, x: jax.Array):\\n\",\n    \"  assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\\n\",\n    \"  assert x.ndim == 1, 'Batch dimensions not allowed'\\n\",\n    \"  weights.count += 1\\n\",\n    \"  return x @ weights.kernel + weights.bias\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"y = nnx.vmap(stateful_vector_dot, in_axes=0, out_axes=1)(weights, x)\\n\",\n    \"\\n\",\n    \"weights.count\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"322312ee\",\n   \"metadata\": {},\n   \"source\": [\n    \"After running `stateful_vector_dot` once, you verified that the `count` attribute was correctly updated. Because `Weights` was vectorized, `count` was initialized as an `arange(10)`, and all of its elements were incremented by `1` inside the transformation. The most important part is that updates were propagated to the original `Weights` object outside the transformation. Nice!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"7294661f\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Graph updates propagation\\n\",\n    \"\\n\",\n    \"JAX transforms see inputs as pytrees of `jax.Array`s, and Flax NNX sees inputs as  pytrees of `jax.Array`s and Python references, where references form a graph. Flax NNX's state propagation machinery can track arbitrary updates to the objects as long as they're local to the inputs (updates to globals inside transforms are not supported).\\n\",\n    \"\\n\",\n    \"This means that you can modify graph structure as needed, including updating existing attributes, adding/deleting attributes, swapping attributes, sharing (new) references between objects, sharing `nnx.Variable`s between objects, etc. Sky is the limit!\\n\",\n    \"\\n\",\n    \"The following example demonstrates performing some arbitrary updates to the `Weights` object inside `nnx.vmap`, and verifying that the updates are correctly propagated to the original `Weights` object outside the transformation:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"id\": \"76c58a29\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_68b34aeff02e43029cf4dd2722226fb5\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_68b34aeff02e43029cf4dd2722226fb5\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNrtXI1X2koW/1fmpWdXWCUmIeHL6tlgAW2rrWJr27c97CSZfEhIYjKA+I7/+95JwkcgUO1Tu+0pnlNw5s6d+z2/O8G+jOjEJQc8DQmJdD8gvdD3KfoLBX7kUMf3GigkLqbOiOwh0/doycQDx5000MD3/CjAOoyPbYeSUvxLAwUhjLhOREsx6xKdBDDq+R4Ma1jvW6E/9IyS7rt+2EiW7qH0N80FAuDnGNRuINOhQOZR4tE9NHC8UjouCsI/gJd/U4qcW8ezYJ0fGiQswdAeCrBhwGDJJSZtIEm3mTQeKdnEsWwYEXmF7edR7IByM/7ph9LIiRzNcR0KKuIh9We0JcejoeNFjs62Jclsqtfdy93Eji9ndiyFQw/2DGEs0kMnoIgZYn8LB4Hr6JiZdtfXKWFmCgkebB0UCsX9A7A87BdRZBDTi9A+orYT8Rah5+CWU98ghSJv+xHl43lQjVDUC4jHVFZ1xpUt+vNr3swR9gyXwLQ3dN29ZAcexOz6vgejhbEf9otoUQb/EobYVGaYOjobDEho+uEAezrhPX9cKMaBABsUVmZQKVn0EpWlIvBxTFRYkpp3iWdRG+3vI4GRbBQ9JHQYemB3RNyIzAWzhx6TbJl1ZDsmZfLFBOzDHfys2aEA4ecZ/pgPyfWQRFT1nEHsrnaIB6SQ2KTIeOytbBQMIzsx416OjtMt9hM1Nmh5fxmYFIkjqW9ZbpK+vTjFIFoDxouNEJfuIDKCAE89yaSLf+f7ZMKMzoUcEygl5nUXR9FbyOKUb4Gb8ewNIAy56eZ3RbAnhH8c4wcvd/MSwHBGKGa4z2XrDIco1kBTcrPPCRykbkhXSXwPRARjeDC1KRnyLVBga6a6c5CMSb3TfEr9ASsMDc+nBd70XQNrsNoDtg0bR4UDF2vEPcjO9JI94jUN3SZ6nxjFIvoXM9208FA/aCCBFxUyWC09bOy2FKsMv+/lltk7Pi6HPaxpIRnF0R1XxxeVmoQFYU6g+4MBqLVAgeMXc80SCU5Etv0RCYs59Ck5WBrC11jgKMDLNOcU3nCgkXCRoF4TlcqcIGJV0lqUqSwqojIjIEaPGWVOkBb91bNhhMNCqaS5vt5Phop700of21gMblDku46xiTJx9LeI71gIkZAJF4A4xAanZ7TEVfaTHoFxDDWQQzGUcrY4EyNrDk9wSV4oAbXhRLDpZHpILhOiAxSHYqOhESirC5Z7ocev/ChKzr+SyA7A9OCE0Jnt5XhxaMZm2LBnHDCrOxs47EcEW5Cq3urqR0qpmQxsaf6iKX1Gwvggb6Ct/0iKpm/9SPGyi9YKWXkGIZkf2cbDMGIODHyAMyTM2deJHm/bOBXijUpxAY7Wxfjj7DpXj5IburoL70Q90wkj2vO9pAitptamVOIlhWVTrqvQ3xY/8fiyiEyrAQ4tAKCJGHFC3/3N3aAUBxNtCKXRyy1A8+m8oOUQt0QFhgT4n0/8HyLKBrfUPWydYIgKB7uoOxlovhuhd0PK9DXQYbIS3oMJJEZpTLQ+dAJJ5R3A4W7HmB97FJY7OCLGrH94QQT2s7ca5snqGLcLfJ2dwlktk/zI0SK/3M1X8mMc9XQ4B8Cws/XYpJnTY1qnN+25tCa75aLp0wPMwBSXsAeOjZFhcXGYbcLgboi9aTTHbJEYIQIWAwBS8of0YarMJADHOMT4IytJvCX6wxkEfkixt8JbC/0+8XpsZF6Mvm3dhWUL9py6+Y63dbsXN549lh0LKCxJFoFPMnaBLmRYbIEwTLDZjJJhM1DV6OnQzBgh8VLls+0iSJklfDQYOUvG9GjOJL+OXb0APS00VABoYgzORxSz9TN5n0ySFFElkhiAqyBMGKxacMf1ELsetCi9ICSmcwNMMolXixMP+jHMkNYYhx54oDc9KqbeNU2si+UcwgAamhXHxfUwNVI6VEqdOYeUjRiI47BkhdhwwG0FJJYVg1g7yIcksQgSQLyKbu8kSQOdCCtB8RBKzbwiy0qtfpyDAK2U/NIsOJd9vS42l+lm0DvxIWR3luTZo1fgZd0u3kfW5wnnpDWIpcptEDLNXawH+MS1wVsFEdRZXJLyjYCKFFmTntpgvrR0/7XTfadtTLxyxnI6WkqHn9Ga80YstmCJ5dUwShUUyQC0W69YDoZ6MIL5Voj/lekVE/+yanWPXnEz8R0PHJ/k5mDu5L9zeTC24cBkdwd5yI4BbTBeLk3SEi5QgazrGCUcwCQuDgCBfbuLfbiD1+8wFzQhIjcAVYy1NI8hR94WGVPMr9zyTJG90+Kzd2loA4dlVb3M7V4+4X02W8vnES+02R0j+kMNQzzhzdAfFAxfH7LbKJ6BgogfYXdIINCKfOQPSCGGCux2kr3zSaPAbibv2SpwW3BwFmd3wZFNCGUXxmSMDrvdLtOmy8bY9W88yYckvuvpTjy98N9/p+2JTqag5eGtyuLlkMcuwN10bJzmsMxu76JQb6Bh6BYYbm6w+d2xb5rSngbIvCLvGEK9c2KpTTV+HZ+pqh9/ap6P4d+jtqq21E2v5kBVrb7/xjhuNQ/Hn1X14vPha/XkuHmotq2b46O3No2aJw6xyu1Xn6S3x5XPo24wdN6fKBfi60/H5x9PRpcnt/T9pN0+3L60+hdO85VgO6/Ohq9bRudKONJ2zdGxEVy/qdjXl45zNjzxOvaR+YGqHyrN01BW28dev1XRPwyH3va5cq1H/fHIbLu71zdWy69Z2utxpyYeqbueeq68DcPX4vm2dSucG4L62hSt0+rhuHMlWYI/GZ5Xq4OWWBkffaq/s6yAXPQnMjnWbhVdC991KFats+PT8SscTaKz4fHxp8tWe6y+PwuOPxsfdne3repF9VOZCuab99fqSAGeb9XTqnoyVgfW7Xl3e/ilS1qfbiSzot+eyudHE2XYVN/cNq+CdlB2js4OW8KX4Xu5W/XM5tvWUftkoDrbtVFLsj3Rrm5rH8efrsZH4ehV58Ohd2W2Whbdfqd/cd2qUj98PW7W7Lp8ctLpljtfVGtwrFw1z+r0okOO6q1m87hTfmXJ57uf9YmmdsCnH9/sqmcdrJKTQ1c9um29s75Qq9J8b717d/yq2XfOFNJufjpstnVHCOzQDzyIjeBL65V4K/a75qFJ7ckb78jA7ejIFE4HndZppWmo1x8/BphG3S8Dw8BOXTJv6/IH5+q6EgzCyjv/82HXCTuD0etOuXvZLbdbkt48My+2j1w/6MjtaKxg67pSc76Q7qkbXHrNo2NinIRkeHndORyIl+2w3+3eKFLl8jIaqyBREcUPf2hhKw7rLXZk/hf+mWU/NvwAeod5SsaPrHie30Cxk+TsV+C1+SGAHT9DiRvGpJcF3hAeno4KSUuZfcIFKXjhs/QFsrTlZGMRlAfGgvXQrPHEY+xQ5OGRY2HqhzxwDjQfhwY/Dh1KLsgNLcx5MUSR8Jo/RoEjvsAtNNjsAQrscuEMCHTihekTtpV1IRlAt7yy9G4HSYIgxEgKii+g1kJ8VZS/70IXzc2FY5dk0wrGnjlx6AVqY8eFwkZ9xIj/iCsboE0P+jqoxg7YjGCDXQJsL9oufRj0jcdA7EIBxeVxn8uAqgby+65uA6Su1hSGfoQyEqtVXqpXy5WyVBMlBe0CAAJ18/Aku9jmUvbpY6bsZfZynwbECQB46XjBMD3IuPjI1/wbLpdJig5gMkEGoGO8OLtv5glHChNR9o5iSdLs0c8d/NOlzI5AsZkuM7nUdsNsyiF5u4yPn6gwG021X+E/RTlcjlbTR0ncAYTJIQjCMKmACrKAmsUd9B6HGKBrBUakdOjCp9htoCobqrGhrFSLW2xNoeZWdpgN9dyRmx3mVtrgLPbnNlCvkOpMlf310fN3giU/SJ7D5bGDCnme3prfrm0h3ztkNWV/64FFNX6qWtxCs6u/fY6PDcnNE3w2BxAqmWS3ucv3ljAZlxA4omz4nGp5sD5UHhipsxB9tOBbuSDkNs1zBzHM3b+HZx1jn5s9hixj2RB0XBfNqizXDai6FQ3LuGwIuF4zldrSpnmPL9PIytf0Ct/wMTRHeRaZ9Ycx4ZTE8WhZKojCTvGf1jxkv+GpTKeRq6okKlqlXDVlXMNy3TQ03TD0mqCVdZHoulzdVNsfJzvzCuL/jUW/nQI7D02TTZXlaetD0vRtrBIJyePUiuV6AAXhoxNB6XRuY47IdgzIVgA2aN4Nszb5u6L6FzHg/d/WBPgmF6w77Ba/8sIdQDNwD3eu5kxx80G7Qr+zSj+7cYJweWBe/ZRH6/pA34yb+iT0iJs517ZWn5Fs5XL+KQGW6UKB97wbSK/QYZK4jsZnzReD4OdFXYkb1sVGMvsjcNe8EfjJgJeombqkKJJcrVRko17R6oYpi1LFrCgY66LwI4CX6fo4BQpI2kHlx8VfZbOmVQyzZgqaIUs1GRNZkCTJECQimXJV/IXx1wMM+6vAsKQkbIYRizS/gdgPNOFvKPadUIzL+45JfDCdk4Bg9tWVYEJtCDxfu0KYIuFGVISyRGQi5Jn05zy+vxfbeWTcCxiW+Q3vfjC8m3liXYjMCH6DvAeAPLmi1GtSuUaqVUWu1ir1OtaVWlWuy7W6LGnKrwfyFEUxq7okijWzLlfqZU0TTLGuyEQ3y0ZV/g3yfimQN6sKm0HKEtlvqPfDDfkb8P3igO85DvTvhX3sa1iAh/yAhHTy1I9E/3xWHJVRbZ3pM0SPUgyfGPzkJGjy55eQby9upOoeTt5+rtvujBv+FL7ey11A9xQJcw+LJ38Ryx1IP7OVxXtaWfxRVk7/Mpk7aGM3Ik8A1J7J0NI9DS19fVIQ8WTooYyIS9iC6HtxxNef1rlPfK78jKBw/bfj5v8DA1r64zzuwVDyARcaU0rDGR38DwJA6Mg=</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_68b34aeff02e43029cf4dd2722226fb5\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_68b34aeff02e43029cf4dd2722226fb5\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"class Count(nnx.Variable): pass\\n\",\n    \"\\n\",\n    \"class Weights(nnx.Module):\\n\",\n    \"  def __init__(self, kernel: jax.Array, bias: jax.Array, count: jax.Array):\\n\",\n    \"    self.kernel, self.bias = nnx.Param(kernel), nnx.Param(bias)\\n\",\n    \"    self.count = Count(count)\\n\",\n    \"\\n\",\n    \"weights = Weights(\\n\",\n    \"  kernel=random.uniform(random.key(0), (10, 2, 3)),\\n\",\n    \"  bias=jnp.zeros((10, 3)),\\n\",\n    \"  count=jnp.arange(10),\\n\",\n    \")\\n\",\n    \"x = jax.random.normal(random.key(1), (10, 2))\\n\",\n    \"\\n\",\n    \"def crazy_vector_dot(weights: Weights, x: jax.Array):\\n\",\n    \"  assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\\n\",\n    \"  assert x.ndim == 1, 'Batch dimensions not allowed'\\n\",\n    \"  weights.count += 1\\n\",\n    \"  y = x @ weights.kernel + weights.bias\\n\",\n    \"  weights.some_property = ['a', 2, False] # add attribute\\n\",\n    \"  del weights.bias # delete attribute\\n\",\n    \"  weights.new_param = weights.kernel # share reference\\n\",\n    \"  return y\\n\",\n    \"\\n\",\n    \"y = nnx.vmap(crazy_vector_dot, in_axes=0, out_axes=1)(weights, x)\\n\",\n    \"\\n\",\n    \"nnx.display(weights)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"743bcc34\",\n   \"metadata\": {},\n   \"source\": [\n    \"> With great power comes great responsibility.\\n\",\n    \"> <br> \\\\- Uncle Ben\\n\",\n    \"\\n\",\n    \"While this feature is very powerful, it must be used with care because it can clash with JAX's underlying assumptions for certain transforms. For example, `jit` expects the structure of the inputs to be stable in order to cache the compiled function, so changing the graph structure inside an `nnx.jit`-ed function causes continuous recompilations and performance degradation. On the other hand, `scan` only allows a fixed `carry` structure, so adding/removing sub-states declared as carry will cause an error.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"0d11d191\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Transforming sub-states (lift types)\\n\",\n    \"\\n\",\n    \"Certain JAX transforms allow the use of pytree prefixes to specify how different parts of the inputs/outputs should be transformed. Flax NNX supports pytree prefixes for pytree structures but currently it doesn't have the notion of a prefix for graph objects. Instead, Flax NNX introduces the concept of “lift types” which allow specifying how different sub-states of an object should be transformed. Different transforms support different lift types, here is the list of currently supported FLax NNX lift types for each JAX transformation:\\n\",\n    \"\\n\",\n    \"| Lift type        | JAX transforms                          |\\n\",\n    \"|------------------|-----------------------------------------|\\n\",\n    \"| `StateAxes`      | `vmap`, `pmap`, `scan`                  |\\n\",\n    \"| `StateSharding`  | `jit`, `shard_map`                      |\\n\",\n    \"| `DiffState`      | `grad`, `value_and_grad`, `custom_vjp`  |\\n\",\n    \"\\n\",\n    \"To specify how to vectorize different sub-states of an object in `nnx.vmap`, the Flax team created a `nnx.StateAxes`. `StateAxes` maps a set of sub-states via Flax NNX [Filters](https://flax.readthedocs.io/en/latest/guides/filters_guide.html) to their corresponding axes, and you can pass the `nnx.StateAxes` to `in_axes` and `out_axes` as if it/they were a pytree prefix.\\n\",\n    \"\\n\",\n    \"Let's use the previous `stateful_vector_dot` example and vectorize only the `nnx.Param` variables and broadcast the `count` variable so we only keep a single count for all the batch elements.\\n\",\n    \"To do this we will define a `nnx.StateAxes` with a filter that matches the `nnx.Param` variables and maps them to axis `0`, and all the `Count` variables to `None`, and pass this `nnx.StateAxes` to `in_axes` for the `Weights` object.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"id\": \"d10aee8a\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Count( # 1 (4 B)\\n\",\n       \"  value=Array(1, dtype=int32, weak_type=True)\\n\",\n       \")\"\n      ]\n     },\n     \"execution_count\": 7,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"class Weights(nnx.Module):\\n\",\n    \"  def __init__(self, kernel: jax.Array, bias: jax.Array, count: jax.Array):\\n\",\n    \"    self.kernel, self.bias = nnx.Param(kernel), nnx.Param(bias)\\n\",\n    \"    self.count = Count(count)\\n\",\n    \"\\n\",\n    \"weights = Weights(\\n\",\n    \"  kernel=random.uniform(random.key(0), (10, 2, 3)),\\n\",\n    \"  bias=jnp.zeros((10, 3)),\\n\",\n    \"  count=jnp.array(0),\\n\",\n    \")\\n\",\n    \"x = jax.random.normal(random.key(1), (10, 2))\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"def stateful_vector_dot(weights: Weights, x: jax.Array):\\n\",\n    \"  assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\\n\",\n    \"  assert x.ndim == 1, 'Batch dimensions not allowed'\\n\",\n    \"  weights.count += 1\\n\",\n    \"  return x @ weights.kernel + weights.bias\\n\",\n    \"\\n\",\n    \"state_axes = nnx.StateAxes({nnx.Param: 0, Count: None}) # broadcast Count\\n\",\n    \"y = nnx.vmap(stateful_vector_dot, in_axes=(state_axes, 0), out_axes=1)(weights, x)\\n\",\n    \"\\n\",\n    \"weights.count\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1cfd87e1\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here, `count` is now a scalar since it's not being vectorized. Also, note that `nnx.StateAxes` can only be used directly on Flax NNX objects, and it cannot be used as a prefix for a pytree of objects.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1c8bb104\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Random state\\n\",\n    \"\\n\",\n    \"In Flax NNX, a random state is just a regular state. This means that it is stored inside `nnx.Module`s that need it, and it is treated as any other type of state. This is a simplification over Flax Linen, where a random state was handled by a separate mechanism. In practice `nnx.Module`s simply need to keep a reference to a `Rngs` object that is passed to them during initialization, and use it to generate a unique key for each random operation. For the purposes of this guide, this means that random state can be transformed like any other type of state but we also need to be aware of how the state is laid out so we can transform it correctly.\\n\",\n    \"\\n\",\n    \"Suppose you want to change things up a bit and apply the same weights to all elements in the batch. But you also want to add different random noise to each element.\\n\",\n    \"\\n\",\n    \"To do this, you will add an `Rngs` attribute to `Weights`, created from a `seed` key argument passed during construction. This seed key must be `split` beforehand, so that you can vectorize it successfully. For pedagogical reasons, you will assign the seed key to a `noise` “stream” and sample from it. To vectorize the PRNG state, you must configure `nnx.StateAxes` to map all `RngState`s (a base class for all variables in `Rngs`) to axis `0`, and `nnx.Param` and `Count` to `None`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"id\": \"33c284b6\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"False\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_fb24928ebcf34fd283295e04e5f0c2f5\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_fb24928ebcf34fd283295e04e5f0c2f5\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_fb24928ebcf34fd283295e04e5f0c2f5\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_fb24928ebcf34fd283295e04e5f0c2f5\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"class Weights(nnx.Module):\\n\",\n    \"  def __init__(self, kernel, bias, count, seed):\\n\",\n    \"    self.kernel, self.bias = nnx.Param(kernel), nnx.Param(bias)\\n\",\n    \"    self.count = Count(count)\\n\",\n    \"    self.rngs = nnx.Rngs(noise=seed)\\n\",\n    \"\\n\",\n    \"weights = Weights(\\n\",\n    \"  kernel=random.uniform(random.key(0), (2, 3)),\\n\",\n    \"  bias=jnp.zeros((3,)),\\n\",\n    \"  count=jnp.array(0),\\n\",\n    \"  seed=random.split(random.key(0), num=10),\\n\",\n    \")\\n\",\n    \"x = random.normal(random.key(1), (10, 2))\\n\",\n    \"\\n\",\n    \"def noisy_vector_dot(weights: Weights, x: jax.Array):\\n\",\n    \"  assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\\n\",\n    \"  assert x.ndim == 1, 'Batch dimensions not allowed'\\n\",\n    \"  weights.count += 1\\n\",\n    \"  y = x @ weights.kernel + weights.bias\\n\",\n    \"  return y + random.normal(weights.rngs.noise(), y.shape)\\n\",\n    \"\\n\",\n    \"state_axes = nnx.StateAxes({nnx.RngState: 0, (nnx.Param, Count): None})\\n\",\n    \"y1 = nnx.vmap(noisy_vector_dot, in_axes=(state_axes, 0))(weights, x)\\n\",\n    \"y2 = nnx.vmap(noisy_vector_dot, in_axes=(state_axes, 0))(weights, x)\\n\",\n    \"\\n\",\n    \"print(jnp.allclose(y1, y2))\\n\",\n    \"nnx.display(weights)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"6f26b99f\",\n   \"metadata\": {},\n   \"source\": [\n    \"Because `Rngs`'s state is updated in place and automatically propagated by `nnx.vmap`, we will get a different result every time that `noisy_vector_dot` is called.\\n\",\n    \"\\n\",\n    \"In the example above, you manually split the random state during construction. This is fine, as it makes the intention clear, but it also doesn't let you use `Rngs` outside of `nnx.vmap` because its state is always split. To solve this, you can pass an unsplit seed and use the `nnx.split_rngs` decorator before `nnx.vmap` to split the `RngState` right before each call to the function, and then \\\"lower\\\" it back so that it becomes usable.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"id\": \"8c9e5858\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"False\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_00734f5acfb64509aabdd5487c15c3ae\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_00734f5acfb64509aabdd5487c15c3ae\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_00734f5acfb64509aabdd5487c15c3ae\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_00734f5acfb64509aabdd5487c15c3ae\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"weights = Weights(\\n\",\n    \"  kernel=random.uniform(random.key(0), (2, 3)),\\n\",\n    \"  bias=jnp.zeros((3,)),\\n\",\n    \"  count=jnp.array(0),\\n\",\n    \"  seed=0,\\n\",\n    \")\\n\",\n    \"x = random.normal(random.key(1), (10, 2))\\n\",\n    \"\\n\",\n    \"state_axes = nnx.StateAxes({nnx.RngState: 0, (nnx.Param, Count): None})\\n\",\n    \"\\n\",\n    \"@nnx.split_rngs(splits=10)\\n\",\n    \"@nnx.vmap(in_axes=(state_axes, 0))\\n\",\n    \"def noisy_vector_dot(weights: Weights, x: jax.Array):\\n\",\n    \"  assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\\n\",\n    \"  assert x.ndim == 1, 'Batch dimensions not allowed'\\n\",\n    \"  weights.count += 1\\n\",\n    \"  y = x @ weights.kernel + weights.bias\\n\",\n    \"  return y + random.normal(weights.rngs.noise(), y.shape)\\n\",\n    \"\\n\",\n    \"y1 = noisy_vector_dot(weights, x)\\n\",\n    \"y2 = noisy_vector_dot(weights, x)\\n\",\n    \"\\n\",\n    \"print(jnp.allclose(y1, y2))\\n\",\n    \"nnx.display(weights)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"60eee7f9\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Rules and limitations\\n\",\n    \"In this section we will cover some rules and limitations apply when using Modules inside transformations.\\n\",\n    \"\\n\",\n    \"### Mutable Module cannot be passed by closure\\n\",\n    \"\\n\",\n    \"While Python allows for passing objects as closures to functions, this is generally not supported by Flax NNX transforms. The reason is that because Modules are mutable it is very easy to capture tracer into a Module created outside of the transform, this is silent error in JAX. To avoid this, Flax NNX checks that the Modules and Variables being mutated are passed as arguments to the transformed function.\\n\",\n    \"\\n\",\n    \"For example, if we have a stateful Module such as `Counter` that increments a counter every time it is called, and we try to pass it as a closure to a function decorated with `nnx.jit`, we would be leaking the tracer. However Flax NNX will raise an error instead to prevent this:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"id\": \"f8b95c03\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Cannot mutate Param from a different trace level (https://flax.readthedocs.io/en/latest/api_reference/flax.errors.html#flax.errors.TraceContextError)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Counter(nnx.Module):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.count = nnx.Param(jnp.array(0))\\n\",\n    \"\\n\",\n    \"  def increment(self):\\n\",\n    \"    self.count += jnp.array(1)\\n\",\n    \"\\n\",\n    \"counter = Counter()\\n\",\n    \"\\n\",\n    \"@nnx.jit\\n\",\n    \"def f(x):\\n\",\n    \"  counter.increment()\\n\",\n    \"  return 2 * x\\n\",\n    \"\\n\",\n    \"try:\\n\",\n    \"  y = f(3)\\n\",\n    \"except Exception as e:\\n\",\n    \"  print(e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"6f37e23b\",\n   \"metadata\": {},\n   \"source\": [\n    \"To solve this issue pass all Module as arguments to the functions being transformed. In this case `f` should accept `counter` as an argument.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"75edf7a8\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Consistent aliasing\\n\",\n    \"\\n\",\n    \"The main issue with allowing for reference semantics in transforms is that references can be shared across inputs and outputs. This can be problematic if it is not taken care of because it would lead to ill-defined or inconsistent behavior. In the example below you have a single `Weights` Module `m` whose reference appears in multiple places in `arg1` and `arg2`. The problem here is that you also specify that you want to vectorize `arg1` in axis `0` and `arg2` in axis `1`. This would be fine in JAX because of referential transparency of pytrees. But this would be problematic in Flax NNX because you are trying to vectorize `m` in two different ways. Flax NNX will enforce consistency by raising an error.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"id\": \"46b1cc25\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Inconsistent aliasing detected. The following nodes have different prefixes:\\n\",\n      \"Node: Param\\n\",\n      \"  param: 0\\n\",\n      \"  param: 0\\n\",\n      \"  param: 1\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Weights(nnx.Module):\\n\",\n    \"  def __init__(self, array: jax.Array):\\n\",\n    \"    self.param = nnx.Param(array)\\n\",\n    \"\\n\",\n    \"m = Weights(jnp.arange(10))\\n\",\n    \"arg1 = {'a': {'b': m}, 'c': m}\\n\",\n    \"arg2 = [(m, m), m]\\n\",\n    \"\\n\",\n    \"@nnx.vmap(in_axes=(0, 1))\\n\",\n    \"def f(arg1, arg2):\\n\",\n    \"  ...\\n\",\n    \"\\n\",\n    \"try:\\n\",\n    \"  f(arg1, arg2)\\n\",\n    \"except ValueError as e:\\n\",\n    \"  print(e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"46aa978c\",\n   \"metadata\": {},\n   \"source\": [\n    \"Inconsistent aliasing can also happen between inputs and outputs. In the next example you have a trivial function that accepts and immediately returns `arg1`. However, `arg1` is vectorized on axis `0` on the input, and axis `1` on the output. As expected, this is problematic and Flax NNX will raise an error.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"id\": \"cca9cf31\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Inconsistent aliasing detected. The following nodes have different prefixes:\\n\",\n      \"Node: Param\\n\",\n      \"  param: 0\\n\",\n      \"  param: 0\\n\",\n      \"  param: 1\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@nnx.vmap(in_axes=0, out_axes=1)\\n\",\n    \"def f(arg1):\\n\",\n    \"  return arg1\\n\",\n    \"\\n\",\n    \"try:\\n\",\n    \"  f(arg1)\\n\",\n    \"except ValueError as e:\\n\",\n    \"  print(e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"13f9aeea\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Axis metadata\\n\",\n    \"\\n\",\n    \"Flax NNX `Variable`s can hold arbitrary metadata, which can be added by simply passing it as keyword arguments to its constructor. This is often used to store `sharding` information, as used by the `nnx.spmd` APIs (like `nnx.get_partition_spec` and `nnx.get_named_sharding`).\\n\",\n    \"\\n\",\n    \"However, it is often important to keep this axes-related information in sync to what the actual state of the axes is when transforms are involved. For example, if you vectorize a variable on axis `1`, you should remove the `sharding` information at position `1` when inside a `vmap` or `scan` to reflect the fact that the axes are temporarily removed.\\n\",\n    \"\\n\",\n    \"To achieve this, Flax NNX transforms provide a non-standard `transform_metadata` dictionary argument. And when the `nnx.PARTITION_NAME` key is present, the `sharding` metadata will be updated as specified by `in_axes` and `out_axes`.\\n\",\n    \"\\n\",\n    \"Let's see an example of this in action:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"d85c772c\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Inner m.param.shape = (3, 5)\\n\",\n      \"Inner m.param.out_sharding = ('a', None)\\n\",\n      \"Outter m.param.shape = (3, 4, 5)\\n\",\n      \"Outter m.param.out_sharding = ('a', 'b', None)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"mesh = jax.make_mesh((1, 1), ('a', 'b'))\\n\",\n    \"\\n\",\n    \"class Weights(nnx.Module):\\n\",\n    \"  def __init__(self, array: jax.Array, out_sharding: tuple[str | None, ...]):\\n\",\n    \"    self.param = nnx.Param(array, out_sharding=out_sharding)\\n\",\n    \"\\n\",\n    \"@nnx.vmap(in_axes=1, transform_metadata={nnx.PARTITION_NAME: 'b'})\\n\",\n    \"def f(m: Weights):\\n\",\n    \"  print(f'Inner {m.param.shape = }')\\n\",\n    \"  print(f'Inner {m.param.out_sharding = }')\\n\",\n    \"\\n\",\n    \"with jax.set_mesh(mesh):\\n\",\n    \"  m = Weights(jnp.ones((3, 4, 5)), out_sharding=('a', 'b', None))\\n\",\n    \"  f(m)\\n\",\n    \"\\n\",\n    \"print(f'Outter {m.param.shape = }')\\n\",\n    \"print(f'Outter {m.param.out_sharding = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a23bda09\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here, you added a `sharding` metadata to the `nnx.Param` variables, and used `transform_metadata` to update the `sharding` metadata to reflect the axis changes. Specifically, you can see that the first axis `b` was removed from the `sharding` metadata when inside of `nnx.vmap`, and then added back when outside of `nnx.vmap`.\\n\",\n    \"\\n\",\n    \"You can verify that this also works when `nnx.Module`s are created inside the transformation - the new `sharding` axes will be added to the `nnx.Module` `nnx.Variable`s outside the transformation, matching the axes of the transformed `nnx.Variable`s.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"358e51f7\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Outter m.param.shape = (3, 4, 5)\\n\",\n      \"Outter m.param.out_sharding = ('a', 'b', None)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@nnx.vmap(out_axes=1, axis_size=4, transform_metadata={nnx.PARTITION_NAME: 'b'})\\n\",\n    \"def init_vmap():\\n\",\n    \"  return Weights(jnp.ones((3, 5)), out_sharding=('a', None))\\n\",\n    \"\\n\",\n    \"with jax.set_mesh(mesh):\\n\",\n    \"  m = init_vmap()\\n\",\n    \"print(f'Outter {m.param.shape = }')\\n\",\n    \"print(f'Outter {m.param.out_sharding = }')\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"cell_metadata_filter\": \"-all\",\n   \"formats\": \"ipynb,md:myst\",\n   \"main_language\": \"python\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.7\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs_nnx/guides/transforms.md",
    "content": "---\njupytext:\n  cell_metadata_filter: -all\n  formats: ipynb,md:myst\n  main_language: python\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Transformations\n\nIn general, JAX transformations (transforms) operate on [pytrees](https://jax.readthedocs.io/en/latest/pytrees.html) of `jax.Array`s\nand abide by value semantics. This presents a challenge for Flax NNX, which represents `nnx.Module`s as regular Python objects\nthat follow reference semantics. To address this, Flax NNX introduced its own set of transforms that extend JAX\ntransforms to allow `nnx.Module`s and other Flax NNX objects to be passed in and out of transforms while preserving\nreference semantics.\n\nFlax NNX transforms should feel quite familiar if you have used JAX transforms before. They use the\nsame APIs and behave like the JAX transforms when only working with pytrees of `jax.Array`s. However, when working with\nFlax NNX objects, they allow Python's reference semantics to be preserved for these objects, this includes:\n\n* Preserving shared references across multiple objects in the inputs and outputs of the transformation.\n* Propagating any state changes made to the objects inside the transformation to the objects outside the transformation.\n* Enforcing consistency of how objects are transformed when aliases are present across multiple inputs and outputs.\n\n```{code-cell} ipython3\nimport jax\nfrom jax import numpy as jnp, random\nfrom flax import nnx\n```\n\nThroughout this guide, `nnx.vmap` is used as a case study to demonstrate how Flax NNX transforms work. However, the principles\noutlined in this document extends to all transforms.\n\n## Basic example\n\nTo begin, let's look at a simple example of using `nnx.vmap` to extend an element wise `vector_dot` function to work on\nbatched inputs. We will define a `Weights` Module with no methods to hold some parameters, these weights will be passed\nas an input to the `vector_dot` function along with some data. Both the weights and data will be batched on axis `0` and we will use\n`nnx.vmap` to apply `vector_dot` to each batch element, and the result will be a batched on axis `1`:\n\n```{code-cell} ipython3\nclass Weights(nnx.Module):\n  def __init__(self, kernel: jax.Array, bias: jax.Array):\n    self.kernel, self.bias = nnx.Param(kernel), nnx.Param(bias)\n\nweights = Weights(\n  kernel=random.uniform(random.key(0), (10, 2, 3)),\n  bias=jnp.zeros((10, 3)),\n)\nx = jax.random.normal(random.key(1), (10, 2))\n\ndef vector_dot(weights: Weights, x: jax.Array):\n  assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\n  assert x.ndim == 1, 'Batch dimensions not allowed'\n  return x @ weights.kernel + weights.bias\n\ny = nnx.vmap(vector_dot, in_axes=0, out_axes=1)(weights, x)\n\nprint(f'{y.shape = }')\nnnx.display(weights)\n```\n\nNotice that `in_axes` interacts naturally with the `Weights` Module, treating it as if it were a pytree of `jax.Array`s. Prefix patterns are also allowed, so `in_axes=(0, 0)` would have also worked in this case.\n\nObjects are also allowed as outputs of Flax NNX transforms, which can be useful to transform initializers. For example,\nyou can define a `create_weights` function to create an single `Weights` `nnx.Module`, and use `nnx.vmap` to create a stack of\n`Weights` with the same shapes as before:\n\n```{code-cell} ipython3\ndef create_weights(seed: jax.Array):\n  return Weights(\n    kernel=random.uniform(random.key(seed), (2, 3)),\n    bias=jnp.zeros((3,)),\n  )\n\nseeds = jnp.arange(10)\nweights = nnx.vmap(create_weights)(seeds)\nnnx.display(weights)\n```\n\n## Transforming methods\n\nMethods in Python are just functions that take the instance as the first argument, this means that you can decorate methods from `Module` and other Flax NNX subtypes. For example, we can refactor `Weights` from the previous example and decorate `__init__` with `vmap` to do the work of `create_weights`, and add a `__call__` method and decorate it with `@nnx.vmap` to do the work of `vector_dot`:\n\n```{code-cell} ipython3\nclass WeightStack(nnx.Module):\n  @nnx.vmap\n  def __init__(self, seed: jax.Array):\n    self.kernel = nnx.Param(random.uniform(random.key(seed), (2, 3)))\n    self.bias = nnx.Param(jnp.zeros((3,)))\n\n  @nnx.vmap(in_axes=0, out_axes=1)\n  def __call__(self, x: jax.Array):\n    assert self.kernel.ndim == 2, 'Batch dimensions not allowed'\n    assert x.ndim == 1, 'Batch dimensions not allowed'\n    return x @ self.kernel + self.bias\n\nweights = WeightStack(jnp.arange(10))\n\nx = jax.random.normal(random.key(1), (10, 2))\ny = weights(x)\n\nprint(f'{y.shape = }')\nnnx.display(weights)\n```\n\nThe rest of the guide will focus on transforming individual functions. But do note that all examples can be written in this method style.\n\n+++\n\n## State propagation\n\nSo far our functions have been stateless. However, the real power of Flax NNX transforms comes when you have stateful functions, because one of their main features is to propagate state changes to preserve reference semantics. Let's update the previous example by adding\na `count` attribute to `Weights` and incrementing it in the new `stateful_vector_dot` function:\n\n```{code-cell} ipython3\nclass Count(nnx.Variable): pass\n\nclass Weights(nnx.Module):\n  def __init__(self, kernel: jax.Array, bias: jax.Array, count: jax.Array):\n    self.kernel, self.bias = nnx.Param(kernel), nnx.Param(bias)\n    self.count = Count(count)\n\nweights = Weights(\n  kernel=random.uniform(random.key(0), (10, 2, 3)),\n  bias=jnp.zeros((10, 3)),\n  count=jnp.arange(10),\n)\nx = jax.random.normal(random.key(1), (10, 2))\n\ndef stateful_vector_dot(weights: Weights, x: jax.Array):\n  assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\n  assert x.ndim == 1, 'Batch dimensions not allowed'\n  weights.count += 1\n  return x @ weights.kernel + weights.bias\n\n\ny = nnx.vmap(stateful_vector_dot, in_axes=0, out_axes=1)(weights, x)\n\nweights.count\n```\n\nAfter running `stateful_vector_dot` once, you verified that the `count` attribute was correctly updated. Because `Weights` was vectorized, `count` was initialized as an `arange(10)`, and all of its elements were incremented by `1` inside the transformation. The most important part is that updates were propagated to the original `Weights` object outside the transformation. Nice!\n\n+++\n\n### Graph updates propagation\n\nJAX transforms see inputs as pytrees of `jax.Array`s, and Flax NNX sees inputs as  pytrees of `jax.Array`s and Python references, where references form a graph. Flax NNX's state propagation machinery can track arbitrary updates to the objects as long as they're local to the inputs (updates to globals inside transforms are not supported).\n\nThis means that you can modify graph structure as needed, including updating existing attributes, adding/deleting attributes, swapping attributes, sharing (new) references between objects, sharing `nnx.Variable`s between objects, etc. Sky is the limit!\n\nThe following example demonstrates performing some arbitrary updates to the `Weights` object inside `nnx.vmap`, and verifying that the updates are correctly propagated to the original `Weights` object outside the transformation:\n\n```{code-cell} ipython3\nclass Count(nnx.Variable): pass\n\nclass Weights(nnx.Module):\n  def __init__(self, kernel: jax.Array, bias: jax.Array, count: jax.Array):\n    self.kernel, self.bias = nnx.Param(kernel), nnx.Param(bias)\n    self.count = Count(count)\n\nweights = Weights(\n  kernel=random.uniform(random.key(0), (10, 2, 3)),\n  bias=jnp.zeros((10, 3)),\n  count=jnp.arange(10),\n)\nx = jax.random.normal(random.key(1), (10, 2))\n\ndef crazy_vector_dot(weights: Weights, x: jax.Array):\n  assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\n  assert x.ndim == 1, 'Batch dimensions not allowed'\n  weights.count += 1\n  y = x @ weights.kernel + weights.bias\n  weights.some_property = ['a', 2, False] # add attribute\n  del weights.bias # delete attribute\n  weights.new_param = weights.kernel # share reference\n  return y\n\ny = nnx.vmap(crazy_vector_dot, in_axes=0, out_axes=1)(weights, x)\n\nnnx.display(weights)\n```\n\n> With great power comes great responsibility.\n> <br> \\- Uncle Ben\n\nWhile this feature is very powerful, it must be used with care because it can clash with JAX's underlying assumptions for certain transforms. For example, `jit` expects the structure of the inputs to be stable in order to cache the compiled function, so changing the graph structure inside an `nnx.jit`-ed function causes continuous recompilations and performance degradation. On the other hand, `scan` only allows a fixed `carry` structure, so adding/removing sub-states declared as carry will cause an error.\n\n+++\n\n## Transforming sub-states (lift types)\n\nCertain JAX transforms allow the use of pytree prefixes to specify how different parts of the inputs/outputs should be transformed. Flax NNX supports pytree prefixes for pytree structures but currently it doesn't have the notion of a prefix for graph objects. Instead, Flax NNX introduces the concept of “lift types” which allow specifying how different sub-states of an object should be transformed. Different transforms support different lift types, here is the list of currently supported FLax NNX lift types for each JAX transformation:\n\n| Lift type        | JAX transforms                          |\n|------------------|-----------------------------------------|\n| `StateAxes`      | `vmap`, `pmap`, `scan`                  |\n| `StateSharding`  | `jit`, `shard_map`                      |\n| `DiffState`      | `grad`, `value_and_grad`, `custom_vjp`  |\n\nTo specify how to vectorize different sub-states of an object in `nnx.vmap`, the Flax team created a `nnx.StateAxes`. `StateAxes` maps a set of sub-states via Flax NNX [Filters](https://flax.readthedocs.io/en/latest/guides/filters_guide.html) to their corresponding axes, and you can pass the `nnx.StateAxes` to `in_axes` and `out_axes` as if it/they were a pytree prefix.\n\nLet's use the previous `stateful_vector_dot` example and vectorize only the `nnx.Param` variables and broadcast the `count` variable so we only keep a single count for all the batch elements.\nTo do this we will define a `nnx.StateAxes` with a filter that matches the `nnx.Param` variables and maps them to axis `0`, and all the `Count` variables to `None`, and pass this `nnx.StateAxes` to `in_axes` for the `Weights` object.\n\n```{code-cell} ipython3\nclass Weights(nnx.Module):\n  def __init__(self, kernel: jax.Array, bias: jax.Array, count: jax.Array):\n    self.kernel, self.bias = nnx.Param(kernel), nnx.Param(bias)\n    self.count = Count(count)\n\nweights = Weights(\n  kernel=random.uniform(random.key(0), (10, 2, 3)),\n  bias=jnp.zeros((10, 3)),\n  count=jnp.array(0),\n)\nx = jax.random.normal(random.key(1), (10, 2))\n\n\ndef stateful_vector_dot(weights: Weights, x: jax.Array):\n  assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\n  assert x.ndim == 1, 'Batch dimensions not allowed'\n  weights.count += 1\n  return x @ weights.kernel + weights.bias\n\nstate_axes = nnx.StateAxes({nnx.Param: 0, Count: None}) # broadcast Count\ny = nnx.vmap(stateful_vector_dot, in_axes=(state_axes, 0), out_axes=1)(weights, x)\n\nweights.count\n```\n\nHere, `count` is now a scalar since it's not being vectorized. Also, note that `nnx.StateAxes` can only be used directly on Flax NNX objects, and it cannot be used as a prefix for a pytree of objects.\n\n+++\n\n### Random state\n\nIn Flax NNX, a random state is just a regular state. This means that it is stored inside `nnx.Module`s that need it, and it is treated as any other type of state. This is a simplification over Flax Linen, where a random state was handled by a separate mechanism. In practice `nnx.Module`s simply need to keep a reference to a `Rngs` object that is passed to them during initialization, and use it to generate a unique key for each random operation. For the purposes of this guide, this means that random state can be transformed like any other type of state but we also need to be aware of how the state is laid out so we can transform it correctly.\n\nSuppose you want to change things up a bit and apply the same weights to all elements in the batch. But you also want to add different random noise to each element.\n\nTo do this, you will add an `Rngs` attribute to `Weights`, created from a `seed` key argument passed during construction. This seed key must be `split` beforehand, so that you can vectorize it successfully. For pedagogical reasons, you will assign the seed key to a `noise` “stream” and sample from it. To vectorize the PRNG state, you must configure `nnx.StateAxes` to map all `RngState`s (a base class for all variables in `Rngs`) to axis `0`, and `nnx.Param` and `Count` to `None`.\n\n```{code-cell} ipython3\nclass Weights(nnx.Module):\n  def __init__(self, kernel, bias, count, seed):\n    self.kernel, self.bias = nnx.Param(kernel), nnx.Param(bias)\n    self.count = Count(count)\n    self.rngs = nnx.Rngs(noise=seed)\n\nweights = Weights(\n  kernel=random.uniform(random.key(0), (2, 3)),\n  bias=jnp.zeros((3,)),\n  count=jnp.array(0),\n  seed=random.split(random.key(0), num=10),\n)\nx = random.normal(random.key(1), (10, 2))\n\ndef noisy_vector_dot(weights: Weights, x: jax.Array):\n  assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\n  assert x.ndim == 1, 'Batch dimensions not allowed'\n  weights.count += 1\n  y = x @ weights.kernel + weights.bias\n  return y + random.normal(weights.rngs.noise(), y.shape)\n\nstate_axes = nnx.StateAxes({nnx.RngState: 0, (nnx.Param, Count): None})\ny1 = nnx.vmap(noisy_vector_dot, in_axes=(state_axes, 0))(weights, x)\ny2 = nnx.vmap(noisy_vector_dot, in_axes=(state_axes, 0))(weights, x)\n\nprint(jnp.allclose(y1, y2))\nnnx.display(weights)\n```\n\nBecause `Rngs`'s state is updated in place and automatically propagated by `nnx.vmap`, we will get a different result every time that `noisy_vector_dot` is called.\n\nIn the example above, you manually split the random state during construction. This is fine, as it makes the intention clear, but it also doesn't let you use `Rngs` outside of `nnx.vmap` because its state is always split. To solve this, you can pass an unsplit seed and use the `nnx.split_rngs` decorator before `nnx.vmap` to split the `RngState` right before each call to the function, and then \"lower\" it back so that it becomes usable.\n\n```{code-cell} ipython3\nweights = Weights(\n  kernel=random.uniform(random.key(0), (2, 3)),\n  bias=jnp.zeros((3,)),\n  count=jnp.array(0),\n  seed=0,\n)\nx = random.normal(random.key(1), (10, 2))\n\nstate_axes = nnx.StateAxes({nnx.RngState: 0, (nnx.Param, Count): None})\n\n@nnx.split_rngs(splits=10)\n@nnx.vmap(in_axes=(state_axes, 0))\ndef noisy_vector_dot(weights: Weights, x: jax.Array):\n  assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\n  assert x.ndim == 1, 'Batch dimensions not allowed'\n  weights.count += 1\n  y = x @ weights.kernel + weights.bias\n  return y + random.normal(weights.rngs.noise(), y.shape)\n\ny1 = noisy_vector_dot(weights, x)\ny2 = noisy_vector_dot(weights, x)\n\nprint(jnp.allclose(y1, y2))\nnnx.display(weights)\n```\n\n## Rules and limitations\nIn this section we will cover some rules and limitations apply when using Modules inside transformations.\n\n### Mutable Module cannot be passed by closure\n\nWhile Python allows for passing objects as closures to functions, this is generally not supported by Flax NNX transforms. The reason is that because Modules are mutable it is very easy to capture tracer into a Module created outside of the transform, this is silent error in JAX. To avoid this, Flax NNX checks that the Modules and Variables being mutated are passed as arguments to the transformed function.\n\nFor example, if we have a stateful Module such as `Counter` that increments a counter every time it is called, and we try to pass it as a closure to a function decorated with `nnx.jit`, we would be leaking the tracer. However Flax NNX will raise an error instead to prevent this:\n\n```{code-cell} ipython3\nclass Counter(nnx.Module):\n  def __init__(self):\n    self.count = nnx.Param(jnp.array(0))\n\n  def increment(self):\n    self.count += jnp.array(1)\n\ncounter = Counter()\n\n@nnx.jit\ndef f(x):\n  counter.increment()\n  return 2 * x\n\ntry:\n  y = f(3)\nexcept Exception as e:\n  print(e)\n```\n\nTo solve this issue pass all Module as arguments to the functions being transformed. In this case `f` should accept `counter` as an argument.\n\n+++\n\n### Consistent aliasing\n\nThe main issue with allowing for reference semantics in transforms is that references can be shared across inputs and outputs. This can be problematic if it is not taken care of because it would lead to ill-defined or inconsistent behavior. In the example below you have a single `Weights` Module `m` whose reference appears in multiple places in `arg1` and `arg2`. The problem here is that you also specify that you want to vectorize `arg1` in axis `0` and `arg2` in axis `1`. This would be fine in JAX because of referential transparency of pytrees. But this would be problematic in Flax NNX because you are trying to vectorize `m` in two different ways. Flax NNX will enforce consistency by raising an error.\n\n```{code-cell} ipython3\nclass Weights(nnx.Module):\n  def __init__(self, array: jax.Array):\n    self.param = nnx.Param(array)\n\nm = Weights(jnp.arange(10))\narg1 = {'a': {'b': m}, 'c': m}\narg2 = [(m, m), m]\n\n@nnx.vmap(in_axes=(0, 1))\ndef f(arg1, arg2):\n  ...\n\ntry:\n  f(arg1, arg2)\nexcept ValueError as e:\n  print(e)\n```\n\nInconsistent aliasing can also happen between inputs and outputs. In the next example you have a trivial function that accepts and immediately returns `arg1`. However, `arg1` is vectorized on axis `0` on the input, and axis `1` on the output. As expected, this is problematic and Flax NNX will raise an error.\n\n```{code-cell} ipython3\n@nnx.vmap(in_axes=0, out_axes=1)\ndef f(arg1):\n  return arg1\n\ntry:\n  f(arg1)\nexcept ValueError as e:\n  print(e)\n```\n\n## Axis metadata\n\nFlax NNX `Variable`s can hold arbitrary metadata, which can be added by simply passing it as keyword arguments to its constructor. This is often used to store `sharding` information, as used by the `nnx.spmd` APIs (like `nnx.get_partition_spec` and `nnx.get_named_sharding`).\n\nHowever, it is often important to keep this axes-related information in sync to what the actual state of the axes is when transforms are involved. For example, if you vectorize a variable on axis `1`, you should remove the `sharding` information at position `1` when inside a `vmap` or `scan` to reflect the fact that the axes are temporarily removed.\n\nTo achieve this, Flax NNX transforms provide a non-standard `transform_metadata` dictionary argument. And when the `nnx.PARTITION_NAME` key is present, the `sharding` metadata will be updated as specified by `in_axes` and `out_axes`.\n\nLet's see an example of this in action:\n\n```{code-cell} ipython3\nmesh = jax.make_mesh((1, 1), ('a', 'b'))\n\nclass Weights(nnx.Module):\n  def __init__(self, array: jax.Array, out_sharding: tuple[str | None, ...]):\n    self.param = nnx.Param(array, out_sharding=out_sharding)\n\n@nnx.vmap(in_axes=1, transform_metadata={nnx.PARTITION_NAME: 'b'})\ndef f(m: Weights):\n  print(f'Inner {m.param.shape = }')\n  print(f'Inner {m.param.out_sharding = }')\n\nwith jax.set_mesh(mesh):\n  m = Weights(jnp.ones((3, 4, 5)), out_sharding=('a', 'b', None))\n  f(m)\n\nprint(f'Outter {m.param.shape = }')\nprint(f'Outter {m.param.out_sharding = }')\n```\n\nHere, you added a `sharding` metadata to the `nnx.Param` variables, and used `transform_metadata` to update the `sharding` metadata to reflect the axis changes. Specifically, you can see that the first axis `b` was removed from the `sharding` metadata when inside of `nnx.vmap`, and then added back when outside of `nnx.vmap`.\n\nYou can verify that this also works when `nnx.Module`s are created inside the transformation - the new `sharding` axes will be added to the `nnx.Module` `nnx.Variable`s outside the transformation, matching the axes of the transformed `nnx.Variable`s.\n\n```{code-cell} ipython3\n@nnx.vmap(out_axes=1, axis_size=4, transform_metadata={nnx.PARTITION_NAME: 'b'})\ndef init_vmap():\n  return Weights(jnp.ones((3, 5)), out_sharding=('a', None))\n\nwith jax.set_mesh(mesh):\n  m = init_vmap()\nprint(f'Outter {m.param.shape = }')\nprint(f'Outter {m.param.out_sharding = }')\n```\n"
  },
  {
    "path": "docs_nnx/guides/view.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"75afc9f3\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Model Views\\n\",\n    \"This guide covers how to use the `nnx.view` function. This function is useful for handling state in layers like `Dropout` and `BatchNorm`, which behave differently in training and evaluation. Similar to `.view` for numpy arrays, `nnx.view` allows you to set modes of the model while still sharing the same data. For a quick intro to how this function works, refer to the following example:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"8e333aab\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from flax import nnx\\n\",\n    \"\\n\",\n    \"# example model with different train/eval behavior\\n\",\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"model = nnx.Sequential(\\n\",\n    \"  nnx.Linear(2, 4, rngs=rngs), nnx.BatchNorm(4, rngs=rngs), nnx.Dropout(0.1)\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"# set train and eval modes\\n\",\n    \"train_model = nnx.view(model, deterministic=False, use_running_average=False)\\n\",\n    \"eval_model = nnx.view(model, deterministic=True, use_running_average=True)\\n\",\n    \"\\n\",\n    \"# Can see deterministic is different between train_model and eval_model\\n\",\n    \"assert train_model.layers[2].deterministic == False\\n\",\n    \"assert eval_model.layers[2].deterministic == True\\n\",\n    \"\\n\",\n    \"# Weights are shared between the models\\n\",\n    \"assert train_model.layers[0].kernel is eval_model.layers[0].kernel\\n\",\n    \"\\n\",\n    \"# Print information about kwargs for nnx.view with nnx.view_info\\n\",\n    \"print(nnx.view_info(model))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"f70f9353\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Motivation\\n\",\n    \"\\n\",\n    \"Some layers in ML inherently involve state. Consider for example the `nnx.Dropout` layer, which behaves differently during training and evaluation. In these different scenarios, we need a simple way to ensure that the model behaves as intended to avoid silent bugs. A common pattern in other frameworks is to mutate a single `model` object to switch between training and evaluation modes. This requires the programmer to remember to toggle modes in many places throughout the code, which can hurt readability and lead to subtle bugs when a mode switch is forgotten.\\n\",\n    \"\\n\",\n    \"`nnx.view` offers a cleaner alternative: you declare the different model configurations once at the beginning of your code and then simply use the appropriate view wherever needed. Each view shares the same underlying weights, so parameter updates are automatically reflected across all views. We demonstrate this with a simple example below.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"886c7479\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import jax\\n\",\n    \"import optax\\n\",\n    \"import matplotlib.pyplot as plt\\n\",\n    \"\\n\",\n    \"in_dim, hidden_dim, out_dim = 16, 32, 2\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"class MyModel(nnx.Module):\\n\",\n    \"  def __init__(\\n\",\n    \"    self,\\n\",\n    \"    in_dim: int,\\n\",\n    \"    hidden_dim: int,\\n\",\n    \"    out_dim: int,\\n\",\n    \"    dropout_rate: float,\\n\",\n    \"    *,\\n\",\n    \"    rngs: nnx.Rngs,\\n\",\n    \"  ):\\n\",\n    \"    self.lin1 = nnx.Linear(in_dim, hidden_dim, rngs=rngs)\\n\",\n    \"    self.do = nnx.Dropout(dropout_rate)\\n\",\n    \"    self.bn = nnx.BatchNorm(hidden_dim, rngs=rngs)\\n\",\n    \"    self.lin2 = nnx.Linear(hidden_dim, out_dim, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x, *, rngs=None):\\n\",\n    \"    x = nnx.relu(self.do(self.bn(self.lin1(x)), rngs=rngs))\\n\",\n    \"    return self.lin2(x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"0568e6ce\",\n   \"metadata\": {},\n   \"source\": [\n    \"Lets take a look at the model to see what is going on.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"da2c8f80\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# can display to inspect state\\n\",\n    \"model = MyModel(in_dim, hidden_dim, out_dim, 0.1, rngs=nnx.Rngs(0))\\n\",\n    \"nnx.display(model)\\n\",\n    \"\\n\",\n    \"# can assert to inspect state\\n\",\n    \"assert model.do.deterministic == False\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1a075c12\",\n   \"metadata\": {},\n   \"source\": [\n    \"From the model display, we can see that `Dropout` has `deterministic == False`, suggesting that the model is in training mode. In order to know this, we had to display the model and/or know that `Dropout` is set to training mode by default. It is not clear what state the model is in just by looking at the code without additional inspection. We instead want to be very explicit about what state the model is in. \\n\",\n    \"\\n\",\n    \"This is where `nnx.view` comes in. This function updates the modes for each submodule of a neural network based on the kwargs passed into the function. The underlying model weights are then shared between different views. We set up a training and evaluation version of the model below.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"11e59178\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"train_model = nnx.view(model, deterministic=False)\\n\",\n    \"eval_model = nnx.view(model, deterministic=True)\\n\",\n    \"\\n\",\n    \"# weights are references to the same data\\n\",\n    \"assert train_model.lin1.kernel is eval_model.lin1.kernel\\n\",\n    \"\\n\",\n    \"# Dropout.deterministic is different in each model\\n\",\n    \"assert train_model.do.deterministic is False\\n\",\n    \"assert eval_model.do.deterministic is True\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"5c1ee1db\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Example with `nnx.view`\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"e35d1bfd\",\n   \"metadata\": {},\n   \"source\": [\n    \"We first set up data generators and define train/eval step functions. The `train_step` receives an `nnx.Rngs` object for dropout randomness, while `eval_step` doesn't since dropout is disabled in `eval_model`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"a0f72d8d\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ndata, batch_size, total_epochs, lr = 2048, 32, 100, 1e-3\\n\",\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"x = rngs.normal((ndata, in_dim))\\n\",\n    \"y = rngs.normal((ndata, out_dim))\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"@nnx.jit\\n\",\n    \"def train_step(model, optimizer, x, y, rngs):\\n\",\n    \"  def loss_fn(model, rngs):\\n\",\n    \"    return ((model(x, rngs=rngs) - y) ** 2).mean()\\n\",\n    \"\\n\",\n    \"  grads = nnx.grad(loss_fn)(model, rngs)\\n\",\n    \"  optimizer.update(model, grads)\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"@nnx.jit\\n\",\n    \"def eval_step(model, x, y):\\n\",\n    \"  return ((model(x) - y) ** 2).mean()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"70c05c4d\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now we create `train_model` and `eval_model` views up front. During the training loop we simply use the appropriate view — there is no need to call `.train()` or `.eval()`, and it is always clear from the code which mode the model is in.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"175db8e7\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"model = MyModel(in_dim, hidden_dim, out_dim, 0.1, rngs=rngs)\\n\",\n    \"optimizer = nnx.Optimizer(model, optax.adam(lr), wrt=nnx.Param)\\n\",\n    \"train_model = nnx.view(model, deterministic=False)  # training view\\n\",\n    \"eval_model = nnx.view(model, deterministic=True)  # eval view\\n\",\n    \"\\n\",\n    \"eval_results = []\\n\",\n    \"for epoch in range(total_epochs):\\n\",\n    \"  for i in range(ndata // batch_size):\\n\",\n    \"    idx = slice(i * batch_size, (i + 1) * batch_size)\\n\",\n    \"    train_step(train_model, optimizer, x[idx], y[idx], rngs)  # use train_model\\n\",\n    \"\\n\",\n    \"  eval_results.append(eval_step(eval_model, x, y))  # use eval_model\\n\",\n    \"plt.plot(eval_results)\\n\",\n    \"plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"3d666cdb\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Getting information with `nnx.view_info`\\n\",\n    \"To see more information about the options for `nnx.view`, we can use the `nnx.view_info` function to display information about the arguments. This will display each submodule which contains a `set_view` method. It also provides information about the keyword arguments accepted by each submodule, including type information, default values, and docstring descriptions.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"54a65a31\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"print(nnx.view_info(model))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"47479be6\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Writing modules compatible with `nnx.view`\\n\",\n    \"\\n\",\n    \"You can make any custom module work with `nnx.view` by defining a `set_view` method. When `nnx.view` is called, it traverses the module tree and calls `set_view` on every submodule that defines one. `nnx.view` inspects the signature of each `set_view` method and only passes the keyword arguments that match the method's declared parameters. This means each module only receives the kwargs it cares about.\\n\",\n    \"\\n\",\n    \"Your `set_view` method should follow these conventions:\\n\",\n    \"\\n\",\n    \"1. **Accept keyword arguments with `None` defaults.** Each kwarg represents a configurable mode for this module. A `None` default means \\\"leave unchanged\\\", so views only override the modes you explicitly set.\\n\",\n    \"2. **Only update the attribute when the kwarg is not `None`.** This ensures that unrelated views don't accidentally reset each other's settings.\\n\",\n    \"3. **Include a Google-style docstring.** The `nnx.view_info` function parses these docstrings to display human-readable information about available view options.\\n\",\n    \"\\n\",\n    \"The general pattern looks like this:\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"class MyLayer(nnx.Module):\\n\",\n    \"    ...\\n\",\n    \"\\n\",\n    \"    def set_view(self, kwarg1: type1 = None, ..., kwargN: typeN = None):\\n\",\n    \"        \\\"\\\"\\\"Description of the module's configurable modes.\\n\",\n    \"\\n\",\n    \"        Args:\\n\",\n    \"          kwarg1: description of kwarg1.\\n\",\n    \"          ...\\n\",\n    \"          kwargN: description of kwargN.\\n\",\n    \"        \\\"\\\"\\\"\\n\",\n    \"        if kwarg1 is not None:\\n\",\n    \"            self.kwarg1 = kwarg1\\n\",\n    \"        ...\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"Here is a concrete example — a `PrintLayer` that can be toggled to print a message during its forward pass:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"2dfdfd64\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class PrintLayer(nnx.Module):\\n\",\n    \"  def __init__(self, msg: str | None = None):\\n\",\n    \"    self.msg = msg\\n\",\n    \"\\n\",\n    \"  def __call__(self, *args, **kwargs):\\n\",\n    \"    if self.msg:\\n\",\n    \"      print(self.msg)\\n\",\n    \"\\n\",\n    \"  def set_view(self, msg: bool | None = None):\\n\",\n    \"    \\\"\\\"\\\"Example set_view docstring. This follows Google style docstrings.\\n\",\n    \"\\n\",\n    \"    Args:\\n\",\n    \"      msg: bool indicating if a message should be printed.\\n\",\n    \"        If True, the `__call__` method prints the message.\\n\",\n    \"    \\\"\\\"\\\"\\n\",\n    \"    if msg is not None:\\n\",\n    \"      self.msg = msg\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"model = PrintLayer()\\n\",\n    \"model_print = nnx.view(model, msg='Hello, World!')\\n\",\n    \"\\n\",\n    \"model() # nothing printed\\n\",\n    \"model_print() # prints \\\"Hello, World!\\\"\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"c7b261b8\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can use `nnx.view_info` to inspect what view options `PrintLayer` exposes. This is especially handy when working with unfamiliar models — it lists every submodule that defines `set_view`, along with the accepted kwargs, their types, defaults, and docstring descriptions.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"a5e3bc03\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Display the information for nnx.view\\n\",\n    \"print(nnx.view_info(model))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1acbcc09\",\n   \"metadata\": {},\n   \"source\": [\n    \"The output shows that `PrintLayer` accepts a `msg` kwarg of type `bool` in its `set_view` method. When building larger models composed of many custom submodules, `nnx.view_info` gives you a quick summary of all the configurable modes across the entire module tree.\\n\",\n    \"\\n\",\n    \"## Using `with_attributes`\\n\",\n    \"\\n\",\n    \"If you are working with modules that don't implement the `set_view` API, you can use {func}`nnx.with_attributes <flax.nnx.with_attributes>` to create views by directly replacing their attributes. Like `nnx.view`, it returns a new instance that shares jax arrays with the original, leaving the original unchanged.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"62b5c185\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class NoisyLinear(nnx.Module):\\n\",\n    \"  def __init__(self, din, dout, *, training=None, rngs: nnx.Rngs):\\n\",\n    \"    self.linear = nnx.Linear(din, dout, rngs=rngs)\\n\",\n    \"    self.training = training\\n\",\n    \"\\n\",\n    \"  def __call__(self, x, rngs=None):\\n\",\n    \"    assert self.training is not None\\n\",\n    \"    x = self.linear(x)\\n\",\n    \"    if self.training:\\n\",\n    \"      x = x + rngs.normal(x.shape) * 0.1\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"model = nnx.Sequential(\\n\",\n    \"    NoisyLinear(4, 8, rngs=rngs),\\n\",\n    \"    NoisyLinear(8, 2, rngs=rngs),\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"train_model = nnx.with_attributes(model, training=True)\\n\",\n    \"eval_model = nnx.with_attributes(model, training=False)\\n\",\n    \"\\n\",\n    \"print(f'{train_model.layer1.training=}')\\n\",\n    \"y1 = train_model(jnp.ones((1, 4)), rngs=rngs)\\n\",\n    \"\\n\",\n    \"print(f'{eval_model.layer1.training=}')\\n\",\n    \"y2 = eval_model(jnp.ones((1, 4)))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"0cc37a57\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Using `recursive_map`\\n\",\n    \"\\n\",\n    \"For more advanced transformations — such as replacing submodules — you can use {func}`nnx.recursive_map <flax.nnx.recursive_map>`. This function traverses the entire module tree bottom-up, calling a user-defined function `f(path, node)` on every node and leaf. Whatever `f` returns is used as the replacement for that node in the new tree. The resulting model view shares the Variables with the original (unless instructed otherwise).\\n\",\n    \"\\n\",\n    \"In the example below, we use `recursive_map` to replace every `nnx.Linear` layer with a `NoisyLinear` version (reusing the class defined earlier) that adds random noise during training:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"2e77b49d\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import jax.numpy as jnp\\n\",\n    \"\\n\",\n    \"def add_noise(path, node):\\n\",\n    \"  if isinstance(node, nnx.Linear):\\n\",\n    \"    noisy = nnx.eval_shape(\\n\",\n    \"      lambda: NoisyLinear(node.in_features, node.out_features, rngs=nnx.Rngs(0))\\n\",\n    \"    )\\n\",\n    \"    noisy.linear = node\\n\",\n    \"    return noisy\\n\",\n    \"  return node\\n\",\n    \"\\n\",\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"model = nnx.Sequential(\\n\",\n    \"    nnx.Linear(4, 8, rngs=rngs),\\n\",\n    \"    nnx.Linear(8, 2, rngs=rngs),\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"noisy_model = nnx.recursive_map(add_noise, model)\\n\",\n    \"\\n\",\n    \"y = noisy_model(jnp.ones((1, 4)), rngs=rngs)\\n\",\n    \"print(noisy_model)s\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"bf521e45\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here `recursive_map` visited each node, and when it found an `nnx.Linear` instance it created a `NoisyLinear`, swapped in the original `Linear` as its inner layer, and returned it. The original `model` is unchanged and its weights are shared with `noisy_model`.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"name\": \"python\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs_nnx/guides/view.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Model Views\nThis guide covers how to use the `nnx.view` function. This function is useful for handling state in layers like `Dropout` and `BatchNorm`, which behave differently in training and evaluation. Similar to `.view` for numpy arrays, `nnx.view` allows you to set modes of the model while still sharing the same data. For a quick intro to how this function works, refer to the following example:\n\n```{code-cell}\nfrom flax import nnx\n\n# example model with different train/eval behavior\nrngs = nnx.Rngs(0)\nmodel = nnx.Sequential(\n  nnx.Linear(2, 4, rngs=rngs), nnx.BatchNorm(4, rngs=rngs), nnx.Dropout(0.1)\n)\n\n# set train and eval modes\ntrain_model = nnx.view(model, deterministic=False, use_running_average=False)\neval_model = nnx.view(model, deterministic=True, use_running_average=True)\n\n# Can see deterministic is different between train_model and eval_model\nassert train_model.layers[2].deterministic == False\nassert eval_model.layers[2].deterministic == True\n\n# Weights are shared between the models\nassert train_model.layers[0].kernel is eval_model.layers[0].kernel\n\n# Print information about kwargs for nnx.view with nnx.view_info\nprint(nnx.view_info(model))\n```\n\n## Motivation\n\nSome layers in ML inherently involve state. Consider for example the `nnx.Dropout` layer, which behaves differently during training and evaluation. In these different scenarios, we need a simple way to ensure that the model behaves as intended to avoid silent bugs. A common pattern in other frameworks is to mutate a single `model` object to switch between training and evaluation modes. This requires the programmer to remember to toggle modes in many places throughout the code, which can hurt readability and lead to subtle bugs when a mode switch is forgotten.\n\n`nnx.view` offers a cleaner alternative: you declare the different model configurations once at the beginning of your code and then simply use the appropriate view wherever needed. Each view shares the same underlying weights, so parameter updates are automatically reflected across all views. We demonstrate this with a simple example below.\n\n```{code-cell}\nimport jax\nimport optax\nimport matplotlib.pyplot as plt\n\nin_dim, hidden_dim, out_dim = 16, 32, 2\n\n\nclass MyModel(nnx.Module):\n  def __init__(\n    self,\n    in_dim: int,\n    hidden_dim: int,\n    out_dim: int,\n    dropout_rate: float,\n    *,\n    rngs: nnx.Rngs,\n  ):\n    self.lin1 = nnx.Linear(in_dim, hidden_dim, rngs=rngs)\n    self.do = nnx.Dropout(dropout_rate)\n    self.bn = nnx.BatchNorm(hidden_dim, rngs=rngs)\n    self.lin2 = nnx.Linear(hidden_dim, out_dim, rngs=rngs)\n\n  def __call__(self, x, *, rngs=None):\n    x = nnx.relu(self.do(self.bn(self.lin1(x)), rngs=rngs))\n    return self.lin2(x)\n```\n\nLets take a look at the model to see what is going on.\n\n```{code-cell}\n# can display to inspect state\nmodel = MyModel(in_dim, hidden_dim, out_dim, 0.1, rngs=nnx.Rngs(0))\nnnx.display(model)\n\n# can assert to inspect state\nassert model.do.deterministic == False\n```\n\nFrom the model display, we can see that `Dropout` has `deterministic == False`, suggesting that the model is in training mode. In order to know this, we had to display the model and/or know that `Dropout` is set to training mode by default. It is not clear what state the model is in just by looking at the code without additional inspection. We instead want to be very explicit about what state the model is in. \n\nThis is where `nnx.view` comes in. This function updates the modes for each submodule of a neural network based on the kwargs passed into the function. The underlying model weights are then shared between different views. We set up a training and evaluation version of the model below.\n\n```{code-cell}\ntrain_model = nnx.view(model, deterministic=False)\neval_model = nnx.view(model, deterministic=True)\n\n# weights are references to the same data\nassert train_model.lin1.kernel is eval_model.lin1.kernel\n\n# Dropout.deterministic is different in each model\nassert train_model.do.deterministic is False\nassert eval_model.do.deterministic is True\n```\n\n## Example with `nnx.view`\n\n+++\n\nWe first set up data generators and define train/eval step functions. The `train_step` receives an `nnx.Rngs` object for dropout randomness, while `eval_step` doesn't since dropout is disabled in `eval_model`.\n\n```{code-cell}\nndata, batch_size, total_epochs, lr = 2048, 32, 100, 1e-3\nrngs = nnx.Rngs(0)\nx = rngs.normal((ndata, in_dim))\ny = rngs.normal((ndata, out_dim))\n\n\n@nnx.jit\ndef train_step(model, optimizer, x, y, rngs):\n  def loss_fn(model, rngs):\n    return ((model(x, rngs=rngs) - y) ** 2).mean()\n\n  grads = nnx.grad(loss_fn)(model, rngs)\n  optimizer.update(model, grads)\n\n\n@nnx.jit\ndef eval_step(model, x, y):\n  return ((model(x) - y) ** 2).mean()\n```\n\nNow we create `train_model` and `eval_model` views up front. During the training loop we simply use the appropriate view — there is no need to call `.train()` or `.eval()`, and it is always clear from the code which mode the model is in.\n\n```{code-cell}\nmodel = MyModel(in_dim, hidden_dim, out_dim, 0.1, rngs=rngs)\noptimizer = nnx.Optimizer(model, optax.adam(lr), wrt=nnx.Param)\ntrain_model = nnx.view(model, deterministic=False)  # training view\neval_model = nnx.view(model, deterministic=True)  # eval view\n\neval_results = []\nfor epoch in range(total_epochs):\n  for i in range(ndata // batch_size):\n    idx = slice(i * batch_size, (i + 1) * batch_size)\n    train_step(train_model, optimizer, x[idx], y[idx], rngs)  # use train_model\n\n  eval_results.append(eval_step(eval_model, x, y))  # use eval_model\nplt.plot(eval_results)\nplt.show()\n```\n\n## Getting information with `nnx.view_info`\nTo see more information about the options for `nnx.view`, we can use the `nnx.view_info` function to display information about the arguments. This will display each submodule which contains a `set_view` method. It also provides information about the keyword arguments accepted by each submodule, including type information, default values, and docstring descriptions.\n\n```{code-cell}\nprint(nnx.view_info(model))\n```\n\n## Writing modules compatible with `nnx.view`\n\nYou can make any custom module work with `nnx.view` by defining a `set_view` method. When `nnx.view` is called, it traverses the module tree and calls `set_view` on every submodule that defines one. `nnx.view` inspects the signature of each `set_view` method and only passes the keyword arguments that match the method's declared parameters. This means each module only receives the kwargs it cares about.\n\nYour `set_view` method should follow these conventions:\n\n1. **Accept keyword arguments with `None` defaults.** Each kwarg represents a configurable mode for this module. A `None` default means \"leave unchanged\", so views only override the modes you explicitly set.\n2. **Only update the attribute when the kwarg is not `None`.** This ensures that unrelated views don't accidentally reset each other's settings.\n3. **Include a Google-style docstring.** The `nnx.view_info` function parses these docstrings to display human-readable information about available view options.\n\nThe general pattern looks like this:\n\n```python\nclass MyLayer(nnx.Module):\n    ...\n\n    def set_view(self, kwarg1: type1 = None, ..., kwargN: typeN = None):\n        \"\"\"Description of the module's configurable modes.\n\n        Args:\n          kwarg1: description of kwarg1.\n          ...\n          kwargN: description of kwargN.\n        \"\"\"\n        if kwarg1 is not None:\n            self.kwarg1 = kwarg1\n        ...\n```\n\nHere is a concrete example — a `PrintLayer` that can be toggled to print a message during its forward pass:\n\n```{code-cell}\nclass PrintLayer(nnx.Module):\n  def __init__(self, msg: str | None = None):\n    self.msg = msg\n\n  def __call__(self, *args, **kwargs):\n    if self.msg:\n      print(self.msg)\n\n  def set_view(self, msg: bool | None = None):\n    \"\"\"Example set_view docstring. This follows Google style docstrings.\n\n    Args:\n      msg: bool indicating if a message should be printed.\n        If True, the `__call__` method prints the message.\n    \"\"\"\n    if msg is not None:\n      self.msg = msg\n\n\nmodel = PrintLayer()\nmodel_print = nnx.view(model, msg='Hello, World!')\n\nmodel() # nothing printed\nmodel_print() # prints \"Hello, World!\"\n```\n\nWe can use `nnx.view_info` to inspect what view options `PrintLayer` exposes. This is especially handy when working with unfamiliar models — it lists every submodule that defines `set_view`, along with the accepted kwargs, their types, defaults, and docstring descriptions.\n\n```{code-cell}\n# Display the information for nnx.view\nprint(nnx.view_info(model))\n```\n\nThe output shows that `PrintLayer` accepts a `msg` kwarg of type `bool` in its `set_view` method. When building larger models composed of many custom submodules, `nnx.view_info` gives you a quick summary of all the configurable modes across the entire module tree.\n\n## Using `with_attributes`\n\nIf you are working with modules that don't implement the `set_view` API, you can use {func}`nnx.with_attributes <flax.nnx.with_attributes>` to create views by directly replacing their attributes. Like `nnx.view`, it returns a new instance that shares jax arrays with the original, leaving the original unchanged.\n\n```{code-cell}\nclass NoisyLinear(nnx.Module):\n  def __init__(self, din, dout, *, training=None, rngs: nnx.Rngs):\n    self.linear = nnx.Linear(din, dout, rngs=rngs)\n    self.training = training\n\n  def __call__(self, x, rngs=None):\n    assert self.training is not None\n    x = self.linear(x)\n    if self.training:\n      x = x + rngs.normal(x.shape) * 0.1\n    return x\n\nrngs = nnx.Rngs(0)\nmodel = nnx.Sequential(\n    NoisyLinear(4, 8, rngs=rngs),\n    NoisyLinear(8, 2, rngs=rngs),\n)\n\ntrain_model = nnx.with_attributes(model, training=True)\neval_model = nnx.with_attributes(model, training=False)\n\nprint(f'{train_model.layer1.training=}')\ny1 = train_model(jnp.ones((1, 4)), rngs=rngs)\n\nprint(f'{eval_model.layer1.training=}')\ny2 = eval_model(jnp.ones((1, 4)))\n```\n\n## Using `recursive_map`\n\nFor more advanced transformations — such as replacing submodules — you can use {func}`nnx.recursive_map <flax.nnx.recursive_map>`. This function traverses the entire module tree bottom-up, calling a user-defined function `f(path, node)` on every node and leaf. Whatever `f` returns is used as the replacement for that node in the new tree. The resulting model view shares the Variables with the original (unless instructed otherwise).\n\nIn the example below, we use `recursive_map` to replace every `nnx.Linear` layer with a `NoisyLinear` version (reusing the class defined earlier) that adds random noise during training:\n\n```{code-cell}\nimport jax.numpy as jnp\n\ndef add_noise(path, node):\n  if isinstance(node, nnx.Linear):\n    noisy = nnx.eval_shape(\n      lambda: NoisyLinear(node.in_features, node.out_features, rngs=nnx.Rngs(0))\n    )\n    noisy.linear = node\n    return noisy\n  return node\n\nrngs = nnx.Rngs(0)\nmodel = nnx.Sequential(\n    nnx.Linear(4, 8, rngs=rngs),\n    nnx.Linear(8, 2, rngs=rngs),\n)\n\nnoisy_model = nnx.recursive_map(add_noise, model)\n\ny = noisy_model(jnp.ones((1, 4)), rngs=rngs)\nprint(noisy_model)s\n```\n\nHere `recursive_map` visited each node, and when it found an `nnx.Linear` instance it created a `NoisyLinear`, swapped in the original `Linear` as its inner layer, and returned it. The original `model` is unchanged and its weights are shared with `noisy_model`.\n"
  },
  {
    "path": "docs_nnx/guides_advanced.rst",
    "content": "Advanced Guides\n======\n\n.. toctree::\n   :maxdepth: 1\n   :caption: Advanced\n\n   guides/flax_gspmd\n   guides/performance\n   guides/bridge_guide\n   guides/surgery\n   guides/extracting_intermediates\n"
  },
  {
    "path": "docs_nnx/guides_basic.rst",
    "content": "Basic Guides\n======\n\n.. toctree::\n   :maxdepth: 1\n   :caption: Basic\n\n   guides/pytree\n   guides/transforms\n   guides/view\n   guides/filters_guide\n   guides/randomness\n   guides/checkpointing\n   guides/jax_and_nnx_transforms"
  },
  {
    "path": "docs_nnx/hijax/hijax.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"15c2d208\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Hijax\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"id\": \"99809892\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from flax import nnx\\n\",\n    \"import jax\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"import optax\\n\",\n    \"\\n\",\n    \"current_mode = nnx.var_defaults().hijax # ignore: only needed for testing\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"id\": \"d1aaa0ec\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"0.85250294\\n\",\n      \"0.8165137\\n\",\n      \"0.7814907\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"nnx.var_defaults(hijax=True)\\n\",\n    \"\\n\",\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"model = nnx.Linear(2, 3, rngs=rngs)\\n\",\n    \"optimizer = nnx.Optimizer(model, optax.adamw(1e-2), wrt=nnx.Param)\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def train_step(x, y):\\n\",\n    \"  loss_fn = lambda m: jnp.mean((m(x) - y) ** 2)\\n\",\n    \"  loss, grads = jax.value_and_grad(loss_fn)(nnx.vars_as(model, mutable=False))  # tmp fix for jax.grad\\n\",\n    \"  optimizer.update(model, grads)\\n\",\n    \"  return loss\\n\",\n    \"\\n\",\n    \"x, y = rngs.uniform((4, 2)), rngs.uniform((4, 3))\\n\",\n    \"for _ in range(3):\\n\",\n    \"  print(train_step(x, y))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"04458d66\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Hijax Variable\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"f4220c6f\",\n   \"metadata\": {},\n   \"source\": [\n    \"State propagation:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"id\": \"396a07a3\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"0\\n\",\n      \"1\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"v = nnx.Variable(jnp.array(0), hijax=True)\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def inc(v):\\n\",\n    \"  v[...] += 1\\n\",\n    \"\\n\",\n    \"print(v[...]); inc(v); print(v[...])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"id\": \"2ab7d801\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"{ \\u001b[34;1mlambda \\u001b[39;22m; a\\u001b[35m:Variable()\\u001b[39m. \\u001b[34;1mlet\\n\",\n      \"    \\u001b[39;22mjit[\\n\",\n      \"      name=inc\\n\",\n      \"      jaxpr={ \\u001b[34;1mlambda \\u001b[39;22m; a\\u001b[35m:Variable()\\u001b[39m. \\u001b[34;1mlet\\n\",\n      \"          \\u001b[39;22mb\\u001b[35m:i32[]\\u001b[39m = get_variable[\\n\",\n      \"            avals=(ShapedArray(int32[], weak_type=True),)\\n\",\n      \"            has_qdd=True\\n\",\n      \"            treedef=PyTreeDef(CustomNode(Variable[(('eager_sharding', True), ('hijax', True), ('mutable', True), ('ref', False))], [*]))\\n\",\n      \"            var_type=<class 'flax.nnx.variablelib.Variable'>\\n\",\n      \"          ] a\\n\",\n      \"          c\\u001b[35m:i32[]\\u001b[39m = add b 1:i32[]\\n\",\n      \"          _\\u001b[35m:i32[]\\u001b[39m = get_variable[\\n\",\n      \"            avals=(ShapedArray(int32[], weak_type=True),)\\n\",\n      \"            has_qdd=True\\n\",\n      \"            treedef=PyTreeDef(CustomNode(Variable[(('eager_sharding', True), ('hijax', True), ('mutable', True), ('ref', False))], [*]))\\n\",\n      \"            var_type=<class 'flax.nnx.variablelib.Variable'>\\n\",\n      \"          ] a\\n\",\n      \"          set_variable[\\n\",\n      \"            treedef=PyTreeDef(CustomNode(Variable[(('eager_sharding', True), ('hijax', True), ('mutable', True), ('ref', False))], [*]))\\n\",\n      \"            var_type=<class 'flax.nnx.variablelib.Variable'>\\n\",\n      \"          ] a c\\n\",\n      \"        \\u001b[34;1min \\u001b[39;22m() }\\n\",\n      \"    ] a\\n\",\n      \"  \\u001b[34;1min \\u001b[39;22m() }\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"v = nnx.Variable(jnp.array(0), hijax=True)\\n\",\n    \"print(jax.make_jaxpr(inc)(v))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"39070460\",\n   \"metadata\": {},\n   \"source\": [\n    \"Pytree values:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"id\": \"fcd0de3f\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[38;2;79;201;177mVariable\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 2 (8 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;255;213;3m{\\u001b[0m\\u001b[38;2;207;144;120m'a'\\u001b[0m: Array(0, dtype=int32, weak_type=True), \\u001b[38;2;207;144;120m'b'\\u001b[0m: Array(2, dtype=int32, weak_type=True)\\u001b[38;2;255;213;3m}\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"\\u001b[38;2;79;201;177mVariable\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 2 (8 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;255;213;3m{\\u001b[0m\\u001b[38;2;207;144;120m'a'\\u001b[0m: Array(1, dtype=int32, weak_type=True), \\u001b[38;2;207;144;120m'b'\\u001b[0m: Array(4, dtype=int32, weak_type=True)\\u001b[38;2;255;213;3m}\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"v = nnx.Variable({'a': jnp.array(0), 'b': jnp.array(2)}, hijax=True)\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def inc_and_double(v):\\n\",\n    \"  v['a'] += 1\\n\",\n    \"  v['b'] *= 2\\n\",\n    \"\\n\",\n    \"print(v); inc_and_double(v); print(v)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"f0cfe954\",\n   \"metadata\": {},\n   \"source\": [\n    \"Dynamic state structure:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"id\": \"0d83a130\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Before: \\u001b[38;2;79;201;177mVariable\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;255;213;3m{\\u001b[0m\\u001b[38;2;255;213;3m}\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"After: \\u001b[38;2;79;201;177mVariable\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;255;213;3m{\\u001b[0m\\u001b[38;2;207;144;120m'y_mean'\\u001b[0m: Array(-1.1782329, dtype=float32)\\u001b[38;2;255;213;3m}\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"x = rngs.uniform((4, 5))\\n\",\n    \"w = rngs.normal((5, 3))\\n\",\n    \"metrics = nnx.Variable({}, hijax=True)\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def linear(x, w, metrics: nnx.Variable):\\n\",\n    \"  y = x @ w\\n\",\n    \"  metrics['y_mean'] = jnp.mean(y)\\n\",\n    \"  return y\\n\",\n    \"\\n\",\n    \"print(\\\"Before:\\\", metrics)\\n\",\n    \"y = linear(x, w, metrics)\\n\",\n    \"print(\\\"After:\\\", metrics)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"id\": \"0a55df94\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[38;2;79;201;177mVariable\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 3 (12 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray([1, 2, 3], dtype=int32),\\n\",\n      \"  \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# set default Variable mode for the rest of the guide\\n\",\n    \"nnx.var_defaults(hijax=True)\\n\",\n    \"\\n\",\n    \"variable = nnx.Variable(jnp.array([1, 2, 3]))\\n\",\n    \"\\n\",\n    \"print(variable)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1b2632f1\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Mutability\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"id\": \"b7b1f421\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"nnx.vars_as(model, mutable=False) = \\u001b[38;2;79;201;177mLinear\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # Param: 3 (12 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mkernel\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mParam\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 3 (12 B)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mArray\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;156;220;254mshape\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;182;207;169m1\\u001b[0m, \\u001b[38;2;182;207;169m3\\u001b[0m\\u001b[38;2;255;213;3m)\\u001b[0m, \\u001b[38;2;156;220;254mdtype\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mdtype('float32')\\u001b[38;2;255;213;3m)\\u001b[0m,\\n\",\n      \"    \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m,\\n\",\n      \"    \\u001b[38;2;156;220;254mmutable\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mFalse\\u001b[0m\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"nnx.vars_as(model, mutable=True) = \\u001b[38;2;79;201;177mLinear\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # Param: 3 (12 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mkernel\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mParam\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 3 (12 B)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mArray\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;156;220;254mshape\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;182;207;169m1\\u001b[0m, \\u001b[38;2;182;207;169m3\\u001b[0m\\u001b[38;2;255;213;3m)\\u001b[0m, \\u001b[38;2;156;220;254mdtype\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mdtype('float32')\\u001b[38;2;255;213;3m)\\u001b[0m,\\n\",\n      \"    \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Linear(nnx.Module):\\n\",\n    \"  def __init__(self, in_features, out_features, rngs: nnx.Rngs):\\n\",\n    \"    self.kernel = nnx.Param(rngs.normal((in_features, out_features)))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    return x @ self.kernel\\n\",\n    \"\\n\",\n    \"model = Linear(1, 3, rngs=nnx.Rngs(0))\\n\",\n    \"\\n\",\n    \"print(f\\\"{nnx.vars_as(model, mutable=False) = !s}\\\")\\n\",\n    \"print(f\\\"{nnx.vars_as(model, mutable=True) = !s}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"id\": \"594cb65e\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"ImmutableVariableError: Cannot mutate Variable as it is marked as immutable. (https://flax.readthedocs.io/en/latest/api_reference/flax.errors.html#flax.errors.ImmutableVariableError)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"v = nnx.Variable(jnp.array(0))\\n\",\n    \"v_immut = nnx.vars_as(v, mutable=False)\\n\",\n    \"assert not v_immut.mutable\\n\",\n    \"\\n\",\n    \"try:\\n\",\n    \"  v_immut[...] += 1  # raises an error\\n\",\n    \"except Exception as e:\\n\",\n    \"  print(f\\\"{type(e).__name__}: {e}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"58692a37\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Ref support\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"id\": \"fcd4fb4f\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[38;2;79;201;177mVariable\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray(0, dtype=int32, weak_type=True),\\n\",\n      \"  \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mref\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"Ref(0, dtype=int32, weak_type=True)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"v = nnx.Variable(jnp.array(0))\\n\",\n    \"v_ref = nnx.vars_as(v, ref=True)\\n\",\n    \"assert v_ref.ref\\n\",\n    \"print(v_ref)\\n\",\n    \"print(v_ref.get_raw_value())\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"id\": \"18256668\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"immutable = \\u001b[38;2;79;201;177mVariable\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray(0, dtype=int32, weak_type=True),\\n\",\n      \"  \\u001b[38;2;156;220;254mhad_ref\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mmutable\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mFalse\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"mutable = \\u001b[38;2;79;201;177mVariable\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray(0, dtype=int32, weak_type=True),\\n\",\n      \"  \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mref\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"v_immut = nnx.vars_as(v_ref, mutable=False)\\n\",\n    \"assert not v_immut.ref\\n\",\n    \"print(\\\"immutable =\\\", v_immut)\\n\",\n    \"\\n\",\n    \"v_ref = nnx.vars_as(v_immut, mutable=True)\\n\",\n    \"assert v_ref.ref\\n\",\n    \"print(\\\"mutable =\\\", v_ref)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"f4e35e75\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Examples\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"id\": \"5400fe58\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class Block(nnx.Module):\\n\",\n    \"  def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\\n\",\n    \"    self.linear = Linear(din, dmid, rngs=rngs)\\n\",\n    \"    self.bn = nnx.BatchNorm(dmid, rngs=rngs)\\n\",\n    \"    self.dropout = nnx.Dropout(0.1, rngs=rngs)\\n\",\n    \"    self.linear_out = Linear(dmid, dout, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    x = nnx.gelu(self.dropout(self.bn(self.linear(x))))\\n\",\n    \"    return self.linear_out(x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"ba980b6b\",\n   \"metadata\": {},\n   \"source\": [\n    \"#### Training Loop\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"id\": \"566c4249\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"loss = 1.000178\\n\",\n      \"loss = 0.9700456\\n\",\n      \"loss = 0.93967044\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# hijax Variables by default\\n\",\n    \"model = Block(2, 64, 3, rngs=nnx.Rngs(0))\\n\",\n    \"optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def train_step(model, optimizer, x, y):\\n\",\n    \"  graphdef, params, nondiff = nnx.split(model, nnx.Param, ...)\\n\",\n    \"  def loss_fn(params):\\n\",\n    \"    model =  nnx.merge(graphdef, params, nondiff)\\n\",\n    \"    return ((model(x) - y) ** 2).mean()\\n\",\n    \"\\n\",\n    \"  loss, grads = jax.value_and_grad(loss_fn)(nnx.vars_as(params, mutable=False))  # immutable for jax.grad\\n\",\n    \"  optimizer.update(model, grads)\\n\",\n    \"\\n\",\n    \"  return loss\\n\",\n    \"\\n\",\n    \"for _ in range(3):\\n\",\n    \"  loss = train_step(model, optimizer, x=jnp.ones((10, 2)), y=jnp.ones((10, 3)))\\n\",\n    \"  print(f\\\"{loss = !s}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1dea99c1\",\n   \"metadata\": {},\n   \"source\": [\n    \"#### Scan Over Layers\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"id\": \"d8136be4\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# TODO: does not work with hijax yet\\n\",\n    \"# @jax.vmap\\n\",\n    \"# def create_stack(rngs):\\n\",\n    \"#   return nnx.as_immutable_vars(Block(2, 64, 2, rngs=rngs))\\n\",\n    \"\\n\",\n    \"# block_stack = nnx.as_mutable_vars(create_stack(nnx.Rngs(0).fork(split=8)))\\n\",\n    \"\\n\",\n    \"# def scan_fn(x, block):\\n\",\n    \"#   x = block(x)\\n\",\n    \"#   return x, None\\n\",\n    \"\\n\",\n    \"# x = jax.random.uniform(jax.random.key(0), (3, 2))\\n\",\n    \"# y, _ = jax.lax.scan(scan_fn, x, block_stack)\\n\",\n    \"\\n\",\n    \"# print(\\\"y = \\\", y)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"7ca18a0d\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Limitations\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1dd39c79\",\n   \"metadata\": {},\n   \"source\": [\n    \"#### Mutable Outputs\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"id\": \"c6062d19\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Error: mutable hitypes should use lo_ty_qdd instead\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@jax.jit\\n\",\n    \"def create_model(rngs):\\n\",\n    \"  return Block(2, 64, 3, rngs=rngs)\\n\",\n    \"\\n\",\n    \"try:\\n\",\n    \"  model = create_model(nnx.Rngs(0))\\n\",\n    \"except Exception as e:\\n\",\n    \"  print(f\\\"Error:\\\", e)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"id\": \"8bb1e9e7\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"model.linear = \\u001b[38;2;79;201;177mLinear\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # Param: 128 (512 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mkernel\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mParam\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 128 (512 B)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mArray\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;156;220;254mshape\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;182;207;169m2\\u001b[0m, \\u001b[38;2;182;207;169m64\\u001b[0m\\u001b[38;2;255;213;3m)\\u001b[0m, \\u001b[38;2;156;220;254mdtype\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mdtype('float32')\\u001b[38;2;255;213;3m)\\u001b[0m,\\n\",\n      \"    \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@jax.jit\\n\",\n    \"def create_model(rngs):\\n\",\n    \"  return nnx.vars_as((Block(2, 64, 3, rngs=rngs)), hijax=False)\\n\",\n    \"\\n\",\n    \"model = nnx.vars_as(create_model(nnx.Rngs(0)), hijax=True)\\n\",\n    \"\\n\",\n    \"print(\\\"model.linear =\\\", model.linear)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"609bed7c\",\n   \"metadata\": {},\n   \"source\": [\n    \"#### Reference Sharing (aliasing)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 17,\n   \"id\": \"045d03c1\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"None\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# NOTE: doesn't currently fail on the jax side\\n\",\n    \"def get_error(f, *args):\\n\",\n    \"  try:\\n\",\n    \"    return f(*args)\\n\",\n    \"  except Exception as e:\\n\",\n    \"    return f\\\"{type(e).__name__}: {e}\\\"\\n\",\n    \"\\n\",\n    \"x = nnx.Variable(jnp.array(0))\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def f(a, b):\\n\",\n    \"  ...\\n\",\n    \"\\n\",\n    \"print(get_error(f, x, x))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 18,\n   \"id\": \"bc2e87e5\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"None\\n\",\n      \"\\u001b[38;2;79;201;177mHasShared\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # Variable: 1 (4 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254ma\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mVariable\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray(0, dtype=int32, weak_type=True),\\n\",\n      \"    \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mb\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mVariable\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray(0, dtype=int32, weak_type=True),\\n\",\n      \"    \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# NOTE: doesn't currently fail on the jax side\\n\",\n    \"class HasShared(nnx.Pytree):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.a = nnx.Variable(jnp.array(0))\\n\",\n    \"    self.b = self.a\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def g(has_shared):\\n\",\n    \"  has_shared.a[...] = 5\\n\",\n    \"\\n\",\n    \"has_shared = HasShared()\\n\",\n    \"\\n\",\n    \"print(get_error(g, has_shared))\\n\",\n    \"print(has_shared)  # updates don't propagate\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 19,\n   \"id\": \"6298f3d9\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Duplicates found:\\n\",\n      \"- [('a',), ('b',)]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Duplicates found:\\\")\\n\",\n    \"if (all_duplicates := nnx.find_duplicates(has_shared)):\\n\",\n    \"  for duplicates in all_duplicates:\\n\",\n    \"    print(\\\"-\\\", duplicates)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 20,\n   \"id\": \"00854d38\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[38;2;79;201;177mHasShared\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # Variable: 1 (4 B)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254ma\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mVariable\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray(5, dtype=int32, weak_type=True),\\n\",\n      \"    \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mb\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mVariable\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 1 (4 B)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mArray(5, dtype=int32, weak_type=True),\\n\",\n      \"    \\u001b[38;2;156;220;254mhijax\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@jax.jit\\n\",\n    \"def h(graphdef, state):\\n\",\n    \"  has_shared = nnx.merge(graphdef, state)\\n\",\n    \"  has_shared.a[...] = 5\\n\",\n    \"\\n\",\n    \"graphdef, state = nnx.split(has_shared)\\n\",\n    \"h(graphdef, state)\\n\",\n    \"print(has_shared)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 21,\n   \"id\": \"195296c8\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# clean up for CI tests\\n\",\n    \"_ = nnx.var_defaults(hijax=current_mode)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.9\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs_nnx/hijax/hijax.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Hijax\n\n```{code-cell} ipython3\nfrom flax import nnx\nimport jax\nimport jax.numpy as jnp\nimport optax\n\ncurrent_mode = nnx.var_defaults().hijax # ignore: only needed for testing\n```\n\n```{code-cell} ipython3\nnnx.var_defaults(hijax=True)\n\nrngs = nnx.Rngs(0)\nmodel = nnx.Linear(2, 3, rngs=rngs)\noptimizer = nnx.Optimizer(model, optax.adamw(1e-2), wrt=nnx.Param)\n\n@jax.jit\ndef train_step(x, y):\n  loss_fn = lambda m: jnp.mean((m(x) - y) ** 2)\n  loss, grads = jax.value_and_grad(loss_fn)(nnx.vars_as(model, mutable=False))  # tmp fix for jax.grad\n  optimizer.update(model, grads)\n  return loss\n\nx, y = rngs.uniform((4, 2)), rngs.uniform((4, 3))\nfor _ in range(3):\n  print(train_step(x, y))\n```\n\n## Hijax Variable\n\n+++\n\nState propagation:\n\n```{code-cell} ipython3\nv = nnx.Variable(jnp.array(0), hijax=True)\n\n@jax.jit\ndef inc(v):\n  v[...] += 1\n\nprint(v[...]); inc(v); print(v[...])\n```\n\n```{code-cell} ipython3\nv = nnx.Variable(jnp.array(0), hijax=True)\nprint(jax.make_jaxpr(inc)(v))\n```\n\nPytree values:\n\n```{code-cell} ipython3\nv = nnx.Variable({'a': jnp.array(0), 'b': jnp.array(2)}, hijax=True)\n\n@jax.jit\ndef inc_and_double(v):\n  v['a'] += 1\n  v['b'] *= 2\n\nprint(v); inc_and_double(v); print(v)\n```\n\nDynamic state structure:\n\n```{code-cell} ipython3\nrngs = nnx.Rngs(0)\nx = rngs.uniform((4, 5))\nw = rngs.normal((5, 3))\nmetrics = nnx.Variable({}, hijax=True)\n\n@jax.jit\ndef linear(x, w, metrics: nnx.Variable):\n  y = x @ w\n  metrics['y_mean'] = jnp.mean(y)\n  return y\n\nprint(\"Before:\", metrics)\ny = linear(x, w, metrics)\nprint(\"After:\", metrics)\n```\n\n```{code-cell} ipython3\n# set default Variable mode for the rest of the guide\nnnx.var_defaults(hijax=True)\n\nvariable = nnx.Variable(jnp.array([1, 2, 3]))\n\nprint(variable)\n```\n\n### Mutability\n\n```{code-cell} ipython3\nclass Linear(nnx.Module):\n  def __init__(self, in_features, out_features, rngs: nnx.Rngs):\n    self.kernel = nnx.Param(rngs.normal((in_features, out_features)))\n\n  def __call__(self, x):\n    return x @ self.kernel\n\nmodel = Linear(1, 3, rngs=nnx.Rngs(0))\n\nprint(f\"{nnx.vars_as(model, mutable=False) = !s}\")\nprint(f\"{nnx.vars_as(model, mutable=True) = !s}\")\n```\n\n```{code-cell} ipython3\nv = nnx.Variable(jnp.array(0))\nv_immut = nnx.vars_as(v, mutable=False)\nassert not v_immut.mutable\n\ntry:\n  v_immut[...] += 1  # raises an error\nexcept Exception as e:\n  print(f\"{type(e).__name__}: {e}\")\n```\n\n### Ref support\n\n```{code-cell} ipython3\nv = nnx.Variable(jnp.array(0))\nv_ref = nnx.vars_as(v, ref=True)\nassert v_ref.ref\nprint(v_ref)\nprint(v_ref.get_raw_value())\n```\n\n```{code-cell} ipython3\nv_immut = nnx.vars_as(v_ref, mutable=False)\nassert not v_immut.ref\nprint(\"immutable =\", v_immut)\n\nv_ref = nnx.vars_as(v_immut, mutable=True)\nassert v_ref.ref\nprint(\"mutable =\", v_ref)\n```\n\n### Examples\n\n```{code-cell} ipython3\nclass Block(nnx.Module):\n  def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n    self.linear = Linear(din, dmid, rngs=rngs)\n    self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n    self.dropout = nnx.Dropout(0.1, rngs=rngs)\n    self.linear_out = Linear(dmid, dout, rngs=rngs)\n\n  def __call__(self, x):\n    x = nnx.gelu(self.dropout(self.bn(self.linear(x))))\n    return self.linear_out(x)\n```\n\n#### Training Loop\n\n```{code-cell} ipython3\n# hijax Variables by default\nmodel = Block(2, 64, 3, rngs=nnx.Rngs(0))\noptimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\n\n@jax.jit\ndef train_step(model, optimizer, x, y):\n  graphdef, params, nondiff = nnx.split(model, nnx.Param, ...)\n  def loss_fn(params):\n    model =  nnx.merge(graphdef, params, nondiff)\n    return ((model(x) - y) ** 2).mean()\n\n  loss, grads = jax.value_and_grad(loss_fn)(nnx.vars_as(params, mutable=False))  # immutable for jax.grad\n  optimizer.update(model, grads)\n\n  return loss\n\nfor _ in range(3):\n  loss = train_step(model, optimizer, x=jnp.ones((10, 2)), y=jnp.ones((10, 3)))\n  print(f\"{loss = !s}\")\n```\n\n#### Scan Over Layers\n\n```{code-cell} ipython3\n# TODO: does not work with hijax yet\n# @jax.vmap\n# def create_stack(rngs):\n#   return nnx.as_immutable_vars(Block(2, 64, 2, rngs=rngs))\n\n# block_stack = nnx.as_mutable_vars(create_stack(nnx.Rngs(0).fork(split=8)))\n\n# def scan_fn(x, block):\n#   x = block(x)\n#   return x, None\n\n# x = jax.random.uniform(jax.random.key(0), (3, 2))\n# y, _ = jax.lax.scan(scan_fn, x, block_stack)\n\n# print(\"y = \", y)\n```\n\n### Limitations\n\n+++\n\n#### Mutable Outputs\n\n```{code-cell} ipython3\n@jax.jit\ndef create_model(rngs):\n  return Block(2, 64, 3, rngs=rngs)\n\ntry:\n  model = create_model(nnx.Rngs(0))\nexcept Exception as e:\n  print(f\"Error:\", e)\n```\n\n```{code-cell} ipython3\n@jax.jit\ndef create_model(rngs):\n  return nnx.vars_as((Block(2, 64, 3, rngs=rngs)), hijax=False)\n\nmodel = nnx.vars_as(create_model(nnx.Rngs(0)), hijax=True)\n\nprint(\"model.linear =\", model.linear)\n```\n\n#### Reference Sharing (aliasing)\n\n```{code-cell} ipython3\n# NOTE: doesn't currently fail on the jax side\ndef get_error(f, *args):\n  try:\n    return f(*args)\n  except Exception as e:\n    return f\"{type(e).__name__}: {e}\"\n\nx = nnx.Variable(jnp.array(0))\n\n@jax.jit\ndef f(a, b):\n  ...\n\nprint(get_error(f, x, x))\n```\n\n```{code-cell} ipython3\n# NOTE: doesn't currently fail on the jax side\nclass HasShared(nnx.Pytree):\n  def __init__(self):\n    self.a = nnx.Variable(jnp.array(0))\n    self.b = self.a\n\n@jax.jit\ndef g(has_shared):\n  has_shared.a[...] = 5\n\nhas_shared = HasShared()\n\nprint(get_error(g, has_shared))\nprint(has_shared)  # updates don't propagate\n```\n\n```{code-cell} ipython3\nprint(\"Duplicates found:\")\nif (all_duplicates := nnx.find_duplicates(has_shared)):\n  for duplicates in all_duplicates:\n    print(\"-\", duplicates)\n```\n\n```{code-cell} ipython3\n@jax.jit\ndef h(graphdef, state):\n  has_shared = nnx.merge(graphdef, state)\n  has_shared.a[...] = 5\n\ngraphdef, state = nnx.split(has_shared)\nh(graphdef, state)\nprint(has_shared)\n```\n\n```{code-cell} ipython3\n# clean up for CI tests\n_ = nnx.var_defaults(hijax=current_mode)\n```\n"
  },
  {
    "path": "docs_nnx/hijax/index.rst",
    "content": "Hijax (experimental)\n====================\n\nBasic usage\n^^^^^^^^^^^^\n\n.. testsetup::\n\n  import jax\n  import jax.numpy as jnp\n\n  current_mode = nnx.var_defaults().hijax\n\n.. testcode::\n\n  from flax import nnx\n  import optax\n\n  nnx.var_defaults(hijax=True)\n\n  class Model(nnx.Module):\n    def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n      self.linear = nnx.Linear(din, dmid, rngs=rngs)\n      self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n      self.dropout = nnx.Dropout(0.2)\n      self.linear_out = nnx.Linear(dmid, dout, rngs=rngs)\n\n    def __call__(self, x, rngs):\n      x = nnx.relu(self.dropout(self.bn(self.linear(x)), rngs=rngs))\n      return self.linear_out(x)\n\n  model = Model(2, 64, 3, rngs=nnx.Rngs(0))  # eager initialization\n  optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\n\n  @jax.jit\n  def train_step(model, optimizer, rngs, x, y):\n    graphdef, params, nondiff = nnx.split(model, nnx.Param, ...)\n    def loss_fn(params):\n      model = nnx.merge(graphdef, params, nondiff)\n      return ((model(x, rngs) - y) ** 2).mean()\n    loss, grads = jax.value_and_grad(loss_fn)(nnx.vars_as(params, mutable=False))\n    optimizer.update(model, grads)  # in-place updates\n    return loss\n\n  nnx.var_defaults(hijax=current_mode)  # clean up for CI tests\n\n\n----\n\n.. toctree::\n   :hidden:\n   :maxdepth: 2\n\n   hijax\n"
  },
  {
    "path": "docs_nnx/index.rst",
    "content": "\nFlax\n====\n.. div:: sd-text-left sd-font-italic\n\n   **N**\\ eural **N**\\ etworks for JA\\ **X**\n\n\n----\n\nFlax provides a **flexible end-to-end user experience for researchers and developers who use JAX for neural networks**. Flax enables you to use the full power of `JAX <https://jax.readthedocs.io>`__.\n\nAt the core of Flax is **NNX - a simplified API that makes it easier to create, inspect,\ndebug, and analyze neural networks in JAX.** Flax NNX has first class support\nfor Python reference semantics, enabling users to express their models using regular\nPython objects. Flax NNX is an evolution of the previous `Flax Linen <https://flax-linen.readthedocs.io/>`__\nAPI, and it took years of experience to bring a simpler and more user-friendly API.\n\n.. note::\n   Flax Linen API is not going to be deprecated in the near future as most of Flax users still rely on this API. However, new users are encouraged to use Flax NNX. Check out `Why Flax NNX <why.html>`_ for a comparison between Flax NNX and Linen, and our reasoning to make the new API.\n\n   To move your Flax Linen codebase to Flax NNX, get familiarized with the API in `NNX Basics <https://flax.readthedocs.io/en/latest/nnx_basics.html>`_ and then start your move following the `evolution guide <guides/linen_to_nnx.html>`_.\n\nFeatures\n^^^^^^^^^\n\n.. grid::\n\n   .. grid-item::\n      :columns: 12 12 12 6\n\n      .. card:: Pythonic\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-fs-5\n\n         .. div:: sd-font-normal\n\n            Flax NNX supports the use of regular Python objects, providing an intuitive\n            and predictable development experience.\n\n   .. grid-item::\n      :columns: 12 12 12 6\n\n      .. card:: Simple\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-fs-5\n\n         .. div:: sd-font-normal\n\n            Flax NNX relies on Python's object model, which results in simplicity for\n            the user and increases development speed.\n\n   .. grid-item::\n      :columns: 12 12 12 6\n\n      .. card:: Expressive\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-fs-5\n\n         .. div:: sd-font-normal\n\n            Flax NNX allows fine-grained control of the model's state via\n            its `Filter <https://flax.readthedocs.io/en/latest/guides/filters_guide.html>`__\n            system.\n\n   .. grid-item::\n      :columns: 12 12 12 6\n\n      .. card:: Familiar\n         :class-card: sd-border-0\n         :shadow: none\n         :class-title: sd-fs-5\n\n         .. div:: sd-font-normal\n\n            Flax NNX makes it very easy to integrate objects with regular JAX code\n            via the `Functional API <nnx_basics.html#the-flax-functional-api>`__.\n\nBasic usage\n^^^^^^^^^^^^\n\n.. testsetup::\n\n   import jax\n   import jax.numpy as jnp\n\n.. testcode::\n\n   from flax import nnx\n   import optax\n\n\n   class Model(nnx.Module):\n     def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n       self.linear = nnx.Linear(din, dmid, rngs=rngs)\n       self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n       self.dropout = nnx.Dropout(0.2)\n       self.linear_out = nnx.Linear(dmid, dout, rngs=rngs)\n\n     def __call__(self, x, rngs):\n       x = nnx.relu(self.dropout(self.bn(self.linear(x)), rngs=rngs))\n       return self.linear_out(x)\n\n   model = Model(2, 64, 3, rngs=nnx.Rngs(0))  # eager initialization\n   optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\n\n   @nnx.jit  # automatic state propagation\n   def train_step(model, optimizer, x, y):\n     loss_fn = lambda model: ((model(x) - y) ** 2).mean()\n     loss, grads = nnx.value_and_grad(loss_fn)(model)\n     optimizer.update(model, grads)  # in-place updates\n     return loss\n\n\nInstallation\n^^^^^^^^^^^^\n\nInstall via pip:\n\n.. code-block:: bash\n\n   pip install flax\n\nOr install the latest version from the repository:\n\n.. code-block:: bash\n\n   pip install git+https://github.com/google/flax.git\n\n\n----\n\nLearn more\n^^^^^^^^^^\n\n.. grid::\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`rocket_launch;2em` Flax NNX Basics\n         :class-card: sd-text-black sd-bg-light\n         :link: nnx_basics.html\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`library_books;2em` MNIST Tutorial\n         :class-card: sd-text-black sd-bg-light\n         :link: mnist_tutorial.html\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`library_books;2em` Guides\n         :class-card: sd-text-black sd-bg-light\n         :link: guides/index.html\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`transform;2em` Flax Linen to Flax NNX\n         :class-card: sd-text-black sd-bg-light\n         :link: guides/linen_to_nnx.html\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`menu_book;2em` API reference\n         :class-card: sd-text-black sd-bg-light\n         :link: api_reference/index.html\n\n   .. grid-item::\n      :columns: 6 6 6 4\n\n      .. card:: :material-regular:`import_contacts;2em` Glossary\n         :class-card: sd-text-black sd-bg-light\n         :link: nnx_glossary.html\n\n\n----\n\n.. toctree::\n   :hidden:\n   :maxdepth: 3\n\n   nnx_basics\n   mnist_tutorial\n   why\n   key_concepts\n   guides_basic\n   guides_advanced\n   Models (Bonsai) <https://github.com/jax-ml/bonsai>\n   Post-training (Tunix) <https://tunix.readthedocs.io/en/latest/index.html>\n   hijax/index\n   migrating/index\n   examples/index\n   nnx_glossary\n   philosophy\n   contributing\n   api_reference/index\n"
  },
  {
    "path": "docs_nnx/key_concepts.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"929920d0\",\n   \"metadata\": {},\n   \"source\": [\n    \"# JAX/Flax Key Concepts\\n\",\n    \"\\n\",\n    \"Flax is a **neural network library** built on top of JAX, a language for **accelerated numerical computations**. In effect, Flax is a pretty thin layer, and you likely will use some JAX APIs directly to do anything more than using the built-in Flax modules.\\n\",\n    \"\\n\",\n    \"This means a **basic understanding on JAX helps you to use Flax well**. You would have better a mental model to understand what's happening underneath and how to debug a confusing error. This doc aims to clarify a few key concepts and help you build that uniquely-JAX mental model as a practical model developer (pun intended).\\n\",\n    \"\\n\",\n    \"[JAX documentations](https://docs.jax.dev/en/latest/index.html) are great sources to learn more. We recommend all Flax users to at least read the [JAX Key Concepts](https://docs.jax.dev/en/latest/key-concepts.html) doc.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"id\": \"3515d62b\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import jax\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"import flax\\n\",\n    \"from flax import nnx\\n\",\n    \"from functools import partial\\n\",\n    \"\\n\",\n    \"# For simulating multi-device environment\\n\",\n    \"jax.config.update('jax_num_cpu_devices', 8)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"be2cad4a\",\n   \"metadata\": {},\n   \"source\": [\n    \"## What is JAX?\\n\",\n    \"\\n\",\n    \"JAX is the lower level library that does **all the large-scale data computations**. It provides the singular data container, aka the `jax.Array`, and all the ways we possibly deal with them:\\n\",\n    \"\\n\",\n    \"* **Make arithmetic operations upon the arrays**, including: the `jax.numpy` ops, automatic differentiation (`jax.grad`), batching (`jax.vmap`), and more.\\n\",\n    \"\\n\",\n    \"* **Run computation on accelerators**, including: interface with various accelerator platforms and layouts; allocating buffers for arrays; compile and execute computation programs across accelerators.\\n\",\n    \"\\n\",\n    \"* **Bundle multiple arrays together** using a simple concept called [pytrees](#pytrees).\\n\",\n    \"\\n\",\n    \"This implies that any error related with accelerators and numericals are probably a JAX issue, or an issue with Flax built-in layers.\\n\",\n    \"\\n\",\n    \"It also means you *can* build a neural network model with JAX alone, especially if you are comfortable with functional programming. JAX docsite have some [simple examples](https://docs.jax.dev/en/latest/notebooks/neural_network_with_tfds_data.html). The article [GPT in 60 Lines of NumPy](https://jaykmody.com/blog/gpt-from-scratch/) also shows how to implement all the key elements of a GPT using JAX.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"id\": \"a3769631\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"def jax_linear(x, kernel, bias):\\n\",\n    \"  return jnp.dot(x, kernel) + bias\\n\",\n    \"\\n\",\n    \"params = {'kernel': jax.random.normal(jax.random.key(42), (4, 2)), \\n\",\n    \"          'bias': jnp.zeros((2,))}\\n\",\n    \"x = jax.random.normal(jax.random.key(0), (2, 4))\\n\",\n    \"y = jax_linear(x, params['kernel'], params['bias'])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"ee6f86e7\",\n   \"metadata\": {},\n   \"source\": [\n    \"## What is Flax?\\n\",\n    \"\\n\",\n    \"Flax is a **neural network toolkit**, offering higher level abstractions that are handy for model developers. Such as:\\n\",\n    \"\\n\",\n    \"* **Object-oriented `Module` class** to represent layers/models and bookkeep parameters.\\n\",\n    \"\\n\",\n    \"* **Modeling utilities** like random number handling, model traversal and surgery, optimizers, advanced parameter bookkeeping, sharding annotations, and more.\\n\",\n    \"\\n\",\n    \"* **Some built-in commonly-used** layers, initializers, and model examples.\\n\",\n    \"\\n\",\n    \"Take the example below: A Flax layer `Linear`, during initialization, takes one RNG key and automatically initialize all internal parameters as `jax.Array`s. In forward pass, it carries out the exact same computation via JAX APIs.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"id\": \"14caace1\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"<class 'flax.nnx.variablelib.Param'>\\n\",\n      \"<class 'jaxlib._jax.ArrayImpl'>\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Eligible parameters were created inside `linear`, using one RNG key 42\\n\",\n    \"linear = nnx.Linear(in_features=4, out_features=2, rngs=nnx.Rngs(42))\\n\",\n    \"\\n\",\n    \"# Flax created a `Param` wrapper over the actual `jax.Array` parameter to track metadata\\n\",\n    \"print(type(linear.kernel))        # flax.nnx.Param\\n\",\n    \"print(type(linear.kernel.value))  # jax.Array\\n\",\n    \"\\n\",\n    \"# The computation of the two are the same\\n\",\n    \"x = jax.random.normal(jax.random.key(0), (2, 4))\\n\",\n    \"flax_y = linear(x)\\n\",\n    \"jax_y = jax_linear(x, linear.kernel.value, linear.bias.value)\\n\",\n    \"assert jnp.array_equal(flax_y, jax_y)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"09989bf7\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Pytrees\\n\",\n    \"\\n\",\n    \"Your code likely needs more than one `jax.Array`. A **pytree** is a container structure of multiple pytrees, possibly nested. It is a key and handly concept in the JAX world.\\n\",\n    \"\\n\",\n    \"Many things are pytrees: Python dicts, lists, tuples, dataclasses, and more. The key is that a pytree can be \\\"flattened\\\" into multiple children, which are either pytrees or individual leaves - a `jax.Array` counts as a leaf. Other metadata of a pytree are stored in the `PyTreeDef` object, allowing \\\"unflattening\\\" to restore the old pytree.\\n\",\n    \"\\n\",\n    \"Pytree is the primary data holder in JAX. When JAX transforms see a pytree argument, they automatically trace its internal `jax.Array`s when compiling. Therefore, it's crucial to organize your data as pytrees. You can use [`flax.struct.dataclass`](https://flax.readthedocs.io/en/latest/api_reference/flax.struct.html#flax.struct.dataclass) to quickly construct a pytree node dataclass, or register your own classes via JAX API. [JAX pytree documentation](https://docs.jax.dev/en/latest/working-with-pytrees.html) has a thorough overview on pytrees and JAX APIs to manipulate them. \\n\",\n    \"\\n\",\n    \"In Flax, a `Module` is a pytree, and variables are its flattenable data. This means you can directly run JAX transforms upon a Flax model.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"id\": \"a2059c47\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"linear.bias.value: [0. 0.]\\n\",\n      \"linear.kernel.value: [[ 0.04119061 -0.2629074 ]\\n\",\n      \" [ 0.6772455   0.2807398 ]\\n\",\n      \" [ 0.16276604  0.16813846]\\n\",\n      \" [ 0.310975   -0.43336964]]\\n\",\n      \"treedef = PyTreeDef(CustomNode(Linear[(('_pytree__state', 'bias', 'kernel'), (('_object__nodes', frozenset({'kernel', '_pytree__state', 'bias'})), ('bias_init', <function zeros at 0x117826700>), ('dot_general', <function dot_general at 0x1172aa480>), ('dtype', None), ('in_features', 4), ('kernel_init', <function variance_scaling.<locals>.init at 0x120f45260>), ('out_features', 2), ('param_dtype', <class 'jax.numpy.float32'>), ('precision', None), ('promote_dtype', <function promote_dtype at 0x120f45440>), ('use_bias', True)))], [CustomNode(ObjectState[(False, False)], []), CustomNode(Param[()], [*]), CustomNode(Param[()], [*])]))\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Flatten allows you to see all the content inside a pytree\\n\",\n    \"arrays, treedef = jax.tree.flatten_with_path(linear)\\n\",\n    \"assert len(arrays) > 1\\n\",\n    \"for kp, value in arrays:\\n\",\n    \"  print(f'linear{jax.tree_util.keystr(kp)}: {value}')\\n\",\n    \"print(f'{treedef = }')\\n\",\n    \"\\n\",\n    \"# Unflatten brings the pytree back intact\\n\",\n    \"linear = jax.tree.unflatten(treedef, [value for _, value in arrays])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"id\": \"4ea2f351\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"y = jax.jit(linear)(x)  # JAX transforms works on Flax modules\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"723b3f42\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Traced vs. static data\\n\",\n    \"\\n\",\n    \"A pytree *contains* JAX arrays, but a pytree is *more than* its JAX arrays. For example, a dictionary keeps information like the key of every array, and it might contain entries that are not JAX arrays. From JAX's standpoint, all data are one of the two types:\\n\",\n    \"\\n\",\n    \"* **Traced** (\\\"dynamic\\\") data: JAX will trace them during compilation and optimize the operations upon them. If they stay inside a pytree argument, `jax.tree.flatten` must return them as leaves. They must be data values (`jax.Array`, Numpy array, scalar, etc), and implement basic functionalities like `__eq__` and `__hash__`.\\n\",\n    \"\\n\",\n    \"* **\\\"Static\\\"** data: They stay as simple Python objects that don't get traced by JAX.\\n\",\n    \"\\n\",\n    \"In practice, you would want to control what data goes into dynamic, and what to static. Dynamic data and their computation will be optimized by JAX, but you cannot base your code control flow upon its values. Non-data values like strings must stay static.\\n\",\n    \"\\n\",\n    \"Take a Flax model: you would want JAX to only track and optimize its parameters, and the RNG keys. For trivial things like the model hyperparameters (e.g., the param shape, the initializer function), they can stay static to save compilation bandwidth and allow code path customization.\\n\",\n    \"\\n\",\n    \"Current Flax module automatically classifies this for you. Only the `jax.Array` attributes are treated as dynamic data, unless you explicitly wrap a data value using `nnx.Variable` classes.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"id\": \"7b6cbc3a\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"['rng']['default']['count'].value: 1\\n\",\n      \"['rng']['default']['key'].value: Array((), dtype=key<fry>) overlaying:\\n\",\n      \"[0 0]\\n\",\n      \"['traced_dim'].value: 4\\n\",\n      \"['w'].value: [[ 1.0040143  -0.9063372  -0.7481722  -1.1713669 ]\\n\",\n      \" [-0.8712328   0.5888381   0.72392994 -1.0255982 ]\\n\",\n      \" [ 1.661628   -1.8910251  -1.2889339   0.13360691]\\n\",\n      \" [-1.1530392   0.23929629  1.7448074   0.5050189 ]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Foo(nnx.Module):\\n\",\n    \"  def __init__(self, dim, rngs):\\n\",\n    \"    self.w = nnx.Param(jax.random.normal(rngs.param(), (dim, dim)))\\n\",\n    \"    self.dim = dim\\n\",\n    \"    self.traced_dim = nnx.Param(dim)  # This became traced!\\n\",\n    \"    self.rng = rngs\\n\",\n    \"\\n\",\n    \"foo = Foo(4, nnx.Rngs(0))\\n\",\n    \"for kp, x in jax.tree.flatten_with_path(nnx.state(foo))[0]:\\n\",\n    \"  print(f'{jax.tree_util.keystr(kp)}: {x}')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"0b10383c\",\n   \"metadata\": {},\n   \"source\": [\n    \"When compiling a function using this pytree, you'll notice the difference between traced and static values. You can only use static ones in control flows.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"id\": \"395c9d79\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"model.dim = 4\\n\",\n      \"model.traced_dim.value = JitTracer<~int32[]>\\n\",\n      \"Code path based on static data value works fine.\\n\",\n      \"Code path based on JAX data value throws error: Attempted boolean conversion of traced array with shape bool[].\\n\",\n      \"The error occurred while tracing the function jitted at /var/folders/4c/ylxxyg_n67957jf6616c7z5000gbn1/T/ipykernel_69242/584946237.py:1 for jit. This concrete value was not available in Python because it depends on the value of the argument model.traced_dim.value.\\n\",\n      \"See https://docs.jax.dev/en/latest/errors.html#jax.errors.TracerBoolConversionError\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@jax.jit\\n\",\n    \"def jitted(model):\\n\",\n    \"  print(f'{model.dim = }')\\n\",\n    \"  print(f'{model.traced_dim.value = }')  # This is being traced\\n\",\n    \"  if model.dim == 4:\\n\",\n    \"    print('Code path based on static data value works fine.')\\n\",\n    \"  try:\\n\",\n    \"    if model.traced_dim.value == 4:\\n\",\n    \"      print('This will never run :(')\\n\",\n    \"  except jax.errors.TracerBoolConversionError as e:\\n\",\n    \"    print(f'Code path based on JAX data value throws error: {e}')\\n\",\n    \"\\n\",\n    \"jitted(foo)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"202bf52b\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Abstract arrays\\n\",\n    \"\\n\",\n    \"Abstract array is a JAX class to represent an array not by its value, but simply by its metadata information like shape, dtype and sharding. It is fast and handy because it doesn't allocate any memory for the array data.\\n\",\n    \"\\n\",\n    \"You can construct an abstract array by calling [`jax.ShapeDtypeStruct`](https://docs.jax.dev/en/latest/_autosummary/jax.ShapeDtypeStruct.html) on your own, or use [`jax.eval_shape`](https://docs.jax.dev/en/latest/_autosummary/jax.eval_shape.html), which takes a function and arguments and returns the abstract version of its output.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"id\": \"21ebeebf\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"[[ 1.0040143  -0.9063372  -0.7481722  -1.1713669 ]\\n\",\n      \" [-0.8712328   0.5888381   0.72392994 -1.0255982 ]\\n\",\n      \" [ 1.661628   -1.8910251  -1.2889339   0.13360691]\\n\",\n      \" [-1.1530392   0.23929629  1.7448074   0.5050189 ]]\\n\",\n      \"ShapeDtypeStruct(shape=(4, 4), dtype=float32)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(x)\\n\",\n    \"abs_x = jax.eval_shape(lambda x: x, x)\\n\",\n    \"print(abs_x)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"e8345d12\",\n   \"metadata\": {},\n   \"source\": [\n    \"It is a good way to dry-run your code and debug a model without any actual compute and memory cost. For example, you can have an overview of the parameters inside this very large model.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"f9b1b308\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[38;2;79;201;177mLinear\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # Param: 67,084,290 (268.3 MB)\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mbias\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mParam\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 8,190 (32.8 KB)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mShapeDtypeStruct(shape=(8190,), dtype=float32)\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mkernel\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mParam\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m # 67,076,100 (268.3 MB)\\u001b[0m\\n\",\n      \"    \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mShapeDtypeStruct(shape=(8190, 8190), dtype=float32)\\n\",\n      \"  \\u001b[38;2;255;213;3m)\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mbias_init\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m<function zeros at 0x117826700>,\\n\",\n      \"  \\u001b[38;2;156;220;254mdot_general\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m<function dot_general at 0x1172aa480>,\\n\",\n      \"  \\u001b[38;2;156;220;254mdtype\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mNone\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254min_features\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;182;207;169m8190\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mkernel_init\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m<function variance_scaling.<locals>.init at 0x120f45260>,\\n\",\n      \"  \\u001b[38;2;156;220;254mout_features\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;182;207;169m8190\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mparam_dtype\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;79;201;177mfloat32\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mprecision\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mNone\\u001b[0m,\\n\",\n      \"  \\u001b[38;2;156;220;254mpromote_dtype\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m<function promote_dtype at 0x120f45440>,\\n\",\n      \"  \\u001b[38;2;156;220;254muse_bias\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0m\\u001b[38;2;86;156;214mTrue\\u001b[0m\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class MLP(nnx.Module):\\n\",\n    \"  def __init__(self, dim, nlayers, rngs):\\n\",\n    \"    self.blocks = nnx.List([nnx.Linear(dim, dim, rngs=rngs) for _ in range(nlayers)])\\n\",\n    \"    self.activation = jax.nn.relu\\n\",\n    \"    self.nlayers = nlayers\\n\",\n    \"  def __call__(self, x):\\n\",\n    \"    for block in self.blocks:\\n\",\n    \"      x = self.activation(block(x))\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"dim, nlayers = 8190, 64   # Some very big numbers\\n\",\n    \"@partial(jax.jit, static_argnums=(0, 1))\\n\",\n    \"def init_state(dim, nlayers):\\n\",\n    \"  return MLP(dim, nlayers, nnx.Rngs(0))\\n\",\n    \"abstract_model = jax.eval_shape(partial(init_state, dim, nlayers))\\n\",\n    \"print(abstract_model.blocks[0])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"8894cbc6\",\n   \"metadata\": {},\n   \"source\": [\n    \"Once you have an abstract pytree for your function input or output, it's easier to describe how you want your data to be sharded. You should use such a pytree with sharding information to instruct your checkpoint loading library to load your arrays distributedly. Our checkpointing guide contains [an example of how to do this](https://flax.readthedocs.io/en/latest/guides/flax_gspmd.html#load-a-sharded-model-from-a-checkpoint).\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"b98b5184\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Distributed computing\\n\",\n    \"\\n\",\n    \"Another big use case for abstract pytrees is to tell JAX machinery how you want each array to be sharded during any point of your computation.\\n\",\n    \"\\n\",\n    \"Remember what we mentioned earlier? JAX handles the actual computation and data allocation on accelerators. This means you **must** use some `jax.jit`-compiled function to run any distributed computation task.\\n\",\n    \"\\n\",\n    \"There are a few ways to tell `jax.jit` of your model sharding. The simplest way is to call `jax.lax.with_sharding_constraint` to constraint the so-to-be model with your predetermined model sharding.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"id\": \"9b289c02\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[38;2;79;201;177mParam\\u001b[0m\\u001b[38;2;255;213;3m(\\u001b[0m\\u001b[38;2;105;105;105m\\u001b[0m\\n\",\n      \"  \\u001b[38;2;156;220;254mvalue\\u001b[0m\\u001b[38;2;212;212;212m=\\u001b[0mNamedSharding(mesh=Mesh('model': 8, axis_types=(Auto,)), spec=PartitionSpec(None, 'model'), memory_kind=unpinned_host)\\n\",\n      \"\\u001b[38;2;255;213;3m)\\u001b[0m\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<pre style=\\\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\\\"><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">  CPU 0  </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">  CPU 1  </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">  CPU 2  </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">  CPU 3  </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">  CPU 4  </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">  CPU 5  </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">  CPU 6  </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">  CPU 7  </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"<span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\\\">         </span><span style=\\\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\\\">         </span><span style=\\\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\\\">         </span>\\n\",\n       \"</pre>\\n\"\n      ],\n      \"text/plain\": [\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;57;59;121mCPU 0\\u001b[0m\\u001b[38;2;255;255;255;48;2;57;59;121m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107mCPU 1\\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82mCPU 2\\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214mCPU 3\\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m  \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m  \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148mCPU 4\\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207mCPU 5\\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148mCPU 6\\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m  \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49mCPU 7\\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m  \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\",\n       \"\\u001b[38;2;255;255;255;48;2;57;59;121m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;214;97;107m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;162;82m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;222;158;214m         \\u001b[0m\\u001b[38;2;0;0;0;48;2;231;203;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;107;110;207m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;165;81;148m         \\u001b[0m\\u001b[38;2;255;255;255;48;2;140;109;49m         \\u001b[0m\\n\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Some smaller numbers so that we actually can run it\\n\",\n    \"dim, nlayers = 1024, 2\\n\",\n    \"abstract_model = jax.eval_shape(partial(init_state, dim, nlayers))\\n\",\n    \"mesh = jax.make_mesh((jax.device_count(), ), 'model')\\n\",\n    \"\\n\",\n    \"# Generate sharding for each of your params manually, sharded along the last axis.\\n\",\n    \"def make_sharding(abs_x):\\n\",\n    \"  if len(abs_x.shape) > 1:\\n\",\n    \"    pspec = jax.sharding.PartitionSpec(None, 'model')  # kernel\\n\",\n    \"  else:\\n\",\n    \"    pspec = jax.sharding.PartitionSpec('model',)       # bias\\n\",\n    \"  return jax.sharding.NamedSharding(mesh, pspec)\\n\",\n    \"model_shardings = jax.tree.map(make_sharding, abstract_model)\\n\",\n    \"print(model_shardings.blocks[0].kernel)\\n\",\n    \"\\n\",\n    \"@partial(jax.jit, static_argnums=(0, 1))\\n\",\n    \"def sharded_init(dim, nlayers):\\n\",\n    \"  model = MLP(dim, nlayers, nnx.Rngs(0))\\n\",\n    \"  return jax.lax.with_sharding_constraint(model, model_shardings)\\n\",\n    \"model = sharded_init(dim, nlayers)\\n\",\n    \"jax.debug.visualize_array_sharding(model.blocks[0].kernel.value)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"3b7ac7ea\",\n   \"metadata\": {},\n   \"source\": [\n    \"The example below are just to showcase how to do sharding in pure JAX API. Flax offers a small API to annotate the sharding when you define a parameter, so that you don't have to write an arbitrary `make_sharding()` function at top level. Check out our [GSPMD guide](https://flax.readthedocs.io/en/latest/guides/flax_gspmd.html) to learn more.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"7dbf421a\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Transformations\\n\",\n    \"\\n\",\n    \"For Flax transforms and their relation with JAX transforms, refer to [Flax Transforms](https://flax.readthedocs.io/en/latest/guides/transforms.html) doc. This should be a rarer use case now that Flax NNX modules are JAX pytrees.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.7\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs_nnx/key_concepts.md",
    "content": "# JAX/Flax Key Concepts\n\nFlax is a **neural network library** built on top of JAX, a language for **accelerated numerical computations**. In effect, Flax is a pretty thin layer, and you likely will use some JAX APIs directly to do anything more than using the built-in Flax modules.\n\nThis means a **basic understanding on JAX helps you to use Flax well**. You would have better a mental model to understand what's happening underneath and how to debug a confusing error. This doc aims to clarify a few key concepts and help you build that uniquely-JAX mental model as a practical model developer (pun intended).\n\n[JAX documentations](https://docs.jax.dev/en/latest/index.html) are great sources to learn more. We recommend all Flax users to at least read the [JAX Key Concepts](https://docs.jax.dev/en/latest/key-concepts.html) doc.\n\n\n```python\nimport jax\nimport jax.numpy as jnp\nimport flax\nfrom flax import nnx\nfrom functools import partial\n\n# For simulating multi-device environment\njax.config.update('jax_num_cpu_devices', 8)\n```\n\n## What is JAX?\n\nJAX is the lower level library that does **all the large-scale data computations**. It provides the singular data container, aka the `jax.Array`, and all the ways we possibly deal with them:\n\n* **Make arithmetic operations upon the arrays**, including: the `jax.numpy` ops, automatic differentiation (`jax.grad`), batching (`jax.vmap`), and more.\n\n* **Run computation on accelerators**, including: interface with various accelerator platforms and layouts; allocating buffers for arrays; compile and execute computation programs across accelerators.\n\n* **Bundle multiple arrays together** using a simple concept called [pytrees](#pytrees).\n\nThis implies that any error related with accelerators and numericals are probably a JAX issue, or an issue with Flax built-in layers.\n\nIt also means you *can* build a neural network model with JAX alone, especially if you are comfortable with functional programming. JAX docsite have some [simple examples](https://docs.jax.dev/en/latest/notebooks/neural_network_with_tfds_data.html). The article [GPT in 60 Lines of NumPy](https://jaykmody.com/blog/gpt-from-scratch/) also shows how to implement all the key elements of a GPT using JAX.\n\n\n```python\ndef jax_linear(x, kernel, bias):\n  return jnp.dot(x, kernel) + bias\n\nparams = {'kernel': jax.random.normal(jax.random.key(42), (4, 2)), \n          'bias': jnp.zeros((2,))}\nx = jax.random.normal(jax.random.key(0), (2, 4))\ny = jax_linear(x, params['kernel'], params['bias'])\n```\n\n## What is Flax?\n\nFlax is a **neural network toolkit**, offering higher level abstractions that are handy for model developers. Such as:\n\n* **Object-oriented `Module` class** to represent layers/models and bookkeep parameters.\n\n* **Modeling utilities** like random number handling, model traversal and surgery, optimizers, advanced parameter bookkeeping, sharding annotations, and more.\n\n* **Some built-in commonly-used** layers, initializers, and model examples.\n\nTake the example below: A Flax layer `Linear`, during initialization, takes one RNG key and automatically initialize all internal parameters as `jax.Array`s. In forward pass, it carries out the exact same computation via JAX APIs.\n\n\n```python\n# Eligible parameters were created inside `linear`, using one RNG key 42\nlinear = nnx.Linear(in_features=4, out_features=2, rngs=nnx.Rngs(42))\n\n# Flax created a `Param` wrapper over the actual `jax.Array` parameter to track metadata\nprint(type(linear.kernel))        # flax.nnx.Param\nprint(type(linear.kernel.value))  # jax.Array\n\n# The computation of the two are the same\nx = jax.random.normal(jax.random.key(0), (2, 4))\nflax_y = linear(x)\njax_y = jax_linear(x, linear.kernel.value, linear.bias.value)\nassert jnp.array_equal(flax_y, jax_y)\n```\n\n    <class 'flax.nnx.variablelib.Param'>\n    <class 'jaxlib._jax.ArrayImpl'>\n\n\n## Pytrees\n\nYour code likely needs more than one `jax.Array`. A **pytree** is a container structure of multiple pytrees, possibly nested. It is a key and handly concept in the JAX world.\n\nMany things are pytrees: Python dicts, lists, tuples, dataclasses, and more. The key is that a pytree can be \"flattened\" into multiple children, which are either pytrees or individual leaves - a `jax.Array` counts as a leaf. Other metadata of a pytree are stored in the `PyTreeDef` object, allowing \"unflattening\" to restore the old pytree.\n\nPytree is the primary data holder in JAX. When JAX transforms see a pytree argument, they automatically trace its internal `jax.Array`s when compiling. Therefore, it's crucial to organize your data as pytrees. You can use [`flax.struct.dataclass`](https://flax.readthedocs.io/en/latest/api_reference/flax.struct.html#flax.struct.dataclass) to quickly construct a pytree node dataclass, or register your own classes via JAX API. [JAX pytree documentation](https://docs.jax.dev/en/latest/working-with-pytrees.html) has a thorough overview on pytrees and JAX APIs to manipulate them. \n\nIn Flax, a `Module` is a pytree, and variables are its flattenable data. This means you can directly run JAX transforms upon a Flax model.\n\n\n```python\n# Flatten allows you to see all the content inside a pytree\narrays, treedef = jax.tree.flatten_with_path(linear)\nassert len(arrays) > 1\nfor kp, value in arrays:\n  print(f'linear{jax.tree_util.keystr(kp)}: {value}')\nprint(f'{treedef = }')\n\n# Unflatten brings the pytree back intact\nlinear = jax.tree.unflatten(treedef, [value for _, value in arrays])\n```\n\n    linear.bias.value: [0. 0.]\n    linear.kernel.value: [[ 0.04119061 -0.2629074 ]\n     [ 0.6772455   0.2807398 ]\n     [ 0.16276604  0.16813846]\n     [ 0.310975   -0.43336964]]\n    treedef = PyTreeDef(CustomNode(Linear[(('_pytree__state', 'bias', 'kernel'), (('_object__nodes', frozenset({'kernel', '_pytree__state', 'bias'})), ('bias_init', <function zeros at 0x117826700>), ('dot_general', <function dot_general at 0x1172aa480>), ('dtype', None), ('in_features', 4), ('kernel_init', <function variance_scaling.<locals>.init at 0x120f45260>), ('out_features', 2), ('param_dtype', <class 'jax.numpy.float32'>), ('precision', None), ('promote_dtype', <function promote_dtype at 0x120f45440>), ('use_bias', True)))], [CustomNode(ObjectState[(False, False)], []), CustomNode(Param[()], [*]), CustomNode(Param[()], [*])]))\n\n\n\n```python\ny = jax.jit(linear)(x)  # JAX transforms works on Flax modules\n```\n\n## Traced vs. static data\n\nA pytree *contains* JAX arrays, but a pytree is *more than* its JAX arrays. For example, a dictionary keeps information like the key of every array, and it might contain entries that are not JAX arrays. From JAX's standpoint, all data are one of the two types:\n\n* **Traced** (\"dynamic\") data: JAX will trace them during compilation and optimize the operations upon them. If they stay inside a pytree argument, `jax.tree.flatten` must return them as leaves. They must be data values (`jax.Array`, Numpy array, scalar, etc), and implement basic functionalities like `__eq__` and `__hash__`.\n\n* **\"Static\"** data: They stay as simple Python objects that don't get traced by JAX.\n\nIn practice, you would want to control what data goes into dynamic, and what to static. Dynamic data and their computation will be optimized by JAX, but you cannot base your code control flow upon its values. Non-data values like strings must stay static.\n\nTake a Flax model: you would want JAX to only track and optimize its parameters, and the RNG keys. For trivial things like the model hyperparameters (e.g., the param shape, the initializer function), they can stay static to save compilation bandwidth and allow code path customization.\n\nCurrent Flax module automatically classifies this for you. Only the `jax.Array` attributes are treated as dynamic data, unless you explicitly wrap a data value using `nnx.Variable` classes.\n\n\n```python\nclass Foo(nnx.Module):\n  def __init__(self, dim, rngs):\n    self.w = nnx.Param(jax.random.normal(rngs.param(), (dim, dim)))\n    self.dim = dim\n    self.traced_dim = nnx.Param(dim)  # This became traced!\n    self.rng = rngs\n\nfoo = Foo(4, nnx.Rngs(0))\nfor kp, x in jax.tree.flatten_with_path(nnx.state(foo))[0]:\n  print(f'{jax.tree_util.keystr(kp)}: {x}')\n```\n\n    ['rng']['default']['count'].value: 1\n    ['rng']['default']['key'].value: Array((), dtype=key<fry>) overlaying:\n    [0 0]\n    ['traced_dim'].value: 4\n    ['w'].value: [[ 1.0040143  -0.9063372  -0.7481722  -1.1713669 ]\n     [-0.8712328   0.5888381   0.72392994 -1.0255982 ]\n     [ 1.661628   -1.8910251  -1.2889339   0.13360691]\n     [-1.1530392   0.23929629  1.7448074   0.5050189 ]]\n\n\nWhen compiling a function using this pytree, you'll notice the difference between traced and static values. You can only use static ones in control flows.\n\n\n```python\n@jax.jit\ndef jitted(model):\n  print(f'{model.dim = }')\n  print(f'{model.traced_dim.value = }')  # This is being traced\n  if model.dim == 4:\n    print('Code path based on static data value works fine.')\n  try:\n    if model.traced_dim.value == 4:\n      print('This will never run :(')\n  except jax.errors.TracerBoolConversionError as e:\n    print(f'Code path based on JAX data value throws error: {e}')\n\njitted(foo)\n```\n\n    model.dim = 4\n    model.traced_dim.value = JitTracer<~int32[]>\n    Code path based on static data value works fine.\n    Code path based on JAX data value throws error: Attempted boolean conversion of traced array with shape bool[].\n    The error occurred while tracing the function jitted at /var/folders/4c/ylxxyg_n67957jf6616c7z5000gbn1/T/ipykernel_69242/584946237.py:1 for jit. This concrete value was not available in Python because it depends on the value of the argument model.traced_dim.value.\n    See https://docs.jax.dev/en/latest/errors.html#jax.errors.TracerBoolConversionError\n\n\n## Abstract arrays\n\nAbstract array is a JAX class to represent an array not by its value, but simply by its metadata information like shape, dtype and sharding. It is fast and handy because it doesn't allocate any memory for the array data.\n\nYou can construct an abstract array by calling [`jax.ShapeDtypeStruct`](https://docs.jax.dev/en/latest/_autosummary/jax.ShapeDtypeStruct.html) on your own, or use [`jax.eval_shape`](https://docs.jax.dev/en/latest/_autosummary/jax.eval_shape.html), which takes a function and arguments and returns the abstract version of its output.\n\n\n```python\nprint(x)\nabs_x = jax.eval_shape(lambda x: x, x)\nprint(abs_x)\n```\n\n    [[ 1.0040143  -0.9063372  -0.7481722  -1.1713669 ]\n     [-0.8712328   0.5888381   0.72392994 -1.0255982 ]\n     [ 1.661628   -1.8910251  -1.2889339   0.13360691]\n     [-1.1530392   0.23929629  1.7448074   0.5050189 ]]\n    ShapeDtypeStruct(shape=(4, 4), dtype=float32)\n\n\nIt is a good way to dry-run your code and debug a model without any actual compute and memory cost. For example, you can have an overview of the parameters inside this very large model.\n\n\n```python\nclass MLP(nnx.Module):\n  def __init__(self, dim, nlayers, rngs):\n    self.blocks = [nnx.Linear(dim, dim, rngs=rngs) for _ in range(nlayers)]\n    self.activation = jax.nn.relu\n    self.nlayers = nlayers\n  def __call__(self, x):\n    for block in self.blocks:\n      x = self.activation(block(x))\n    return x\n\ndim, nlayers = 8190, 64   # Some very big numbers\n@partial(jax.jit, static_argnums=(0, 1))\ndef init_state(dim, nlayers):\n  return MLP(dim, nlayers, nnx.Rngs(0))\nabstract_model = jax.eval_shape(partial(init_state, dim, nlayers))\nprint(abstract_model.blocks[0])\n```\n\n    \u001b[38;2;79;201;177mLinear\u001b[0m\u001b[38;2;255;213;3m(\u001b[0m\u001b[38;2;105;105;105m # Param: 67,084,290 (268.3 MB)\u001b[0m\n      \u001b[38;2;156;220;254mbias\u001b[0m\u001b[38;2;212;212;212m=\u001b[0m\u001b[38;2;79;201;177mParam\u001b[0m\u001b[38;2;255;213;3m(\u001b[0m\u001b[38;2;105;105;105m # 8,190 (32.8 KB)\u001b[0m\n        \u001b[38;2;156;220;254mvalue\u001b[0m\u001b[38;2;212;212;212m=\u001b[0mShapeDtypeStruct(shape=(8190,), dtype=float32)\n      \u001b[38;2;255;213;3m)\u001b[0m,\n      \u001b[38;2;156;220;254mkernel\u001b[0m\u001b[38;2;212;212;212m=\u001b[0m\u001b[38;2;79;201;177mParam\u001b[0m\u001b[38;2;255;213;3m(\u001b[0m\u001b[38;2;105;105;105m # 67,076,100 (268.3 MB)\u001b[0m\n        \u001b[38;2;156;220;254mvalue\u001b[0m\u001b[38;2;212;212;212m=\u001b[0mShapeDtypeStruct(shape=(8190, 8190), dtype=float32)\n      \u001b[38;2;255;213;3m)\u001b[0m,\n      \u001b[38;2;156;220;254mbias_init\u001b[0m\u001b[38;2;212;212;212m=\u001b[0m<function zeros at 0x117826700>,\n      \u001b[38;2;156;220;254mdot_general\u001b[0m\u001b[38;2;212;212;212m=\u001b[0m<function dot_general at 0x1172aa480>,\n      \u001b[38;2;156;220;254mdtype\u001b[0m\u001b[38;2;212;212;212m=\u001b[0m\u001b[38;2;86;156;214mNone\u001b[0m,\n      \u001b[38;2;156;220;254min_features\u001b[0m\u001b[38;2;212;212;212m=\u001b[0m\u001b[38;2;182;207;169m8190\u001b[0m,\n      \u001b[38;2;156;220;254mkernel_init\u001b[0m\u001b[38;2;212;212;212m=\u001b[0m<function variance_scaling.<locals>.init at 0x120f45260>,\n      \u001b[38;2;156;220;254mout_features\u001b[0m\u001b[38;2;212;212;212m=\u001b[0m\u001b[38;2;182;207;169m8190\u001b[0m,\n      \u001b[38;2;156;220;254mparam_dtype\u001b[0m\u001b[38;2;212;212;212m=\u001b[0m\u001b[38;2;79;201;177mfloat32\u001b[0m,\n      \u001b[38;2;156;220;254mprecision\u001b[0m\u001b[38;2;212;212;212m=\u001b[0m\u001b[38;2;86;156;214mNone\u001b[0m,\n      \u001b[38;2;156;220;254mpromote_dtype\u001b[0m\u001b[38;2;212;212;212m=\u001b[0m<function promote_dtype at 0x120f45440>,\n      \u001b[38;2;156;220;254muse_bias\u001b[0m\u001b[38;2;212;212;212m=\u001b[0m\u001b[38;2;86;156;214mTrue\u001b[0m\n    \u001b[38;2;255;213;3m)\u001b[0m\n\n\nOnce you have an abstract pytree for your function input or output, it's easier to describe how you want your data to be sharded. You should use such a pytree with sharding information to instruct your checkpoint loading library to load your arrays distributedly. Our checkpointing guide contains [an example of how to do this](https://flax.readthedocs.io/en/latest/guides/flax_gspmd.html#load-a-sharded-model-from-a-checkpoint).\n\n## Distributed computing\n\nAnother big use case for abstract pytrees is to tell JAX machinery how you want each array to be sharded during any point of your computation.\n\nRemember what we mentioned earlier? JAX handles the actual computation and data allocation on accelerators. This means you **must** use some `jax.jit`-compiled function to run any distributed computation task.\n\nThere are a few ways to tell `jax.jit` of your model sharding. The simplest way is to call `jax.lax.with_sharding_constraint` to constraint the so-to-be model with your predetermined model sharding.\n\n\n```python\n# Some smaller numbers so that we actually can run it\ndim, nlayers = 1024, 2\nabstract_model = jax.eval_shape(partial(init_state, dim, nlayers))\nmesh = jax.make_mesh((jax.device_count(), ), 'model')\n\n# Generate sharding for each of your params manually, sharded along the last axis.\ndef make_sharding(abs_x):\n  if len(abs_x.shape) > 1:\n    pspec = jax.sharding.PartitionSpec(None, 'model')  # kernel\n  else:\n    pspec = jax.sharding.PartitionSpec('model',)       # bias\n  return jax.sharding.NamedSharding(mesh, pspec)\nmodel_shardings = jax.tree.map(make_sharding, abstract_model)\nprint(model_shardings.blocks[0].kernel)\n\n@partial(jax.jit, static_argnums=(0, 1))\ndef sharded_init(dim, nlayers):\n  model = MLP(dim, nlayers, nnx.Rngs(0))\n  return jax.lax.with_sharding_constraint(model, model_shardings)\nmodel = sharded_init(dim, nlayers)\njax.debug.visualize_array_sharding(model.blocks[0].kernel.value)\n```\n\n    \u001b[38;2;79;201;177mParam\u001b[0m\u001b[38;2;255;213;3m(\u001b[0m\u001b[38;2;105;105;105m\u001b[0m\n      \u001b[38;2;156;220;254mvalue\u001b[0m\u001b[38;2;212;212;212m=\u001b[0mNamedSharding(mesh=Mesh('model': 8, axis_types=(Auto,)), spec=PartitionSpec(None, 'model'), memory_kind=unpinned_host)\n    \u001b[38;2;255;213;3m)\u001b[0m\n\n\n\n<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\">         </span><span style=\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\">         </span>\n<span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\">         </span><span style=\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\">         </span>\n<span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\">         </span><span style=\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\">         </span>\n<span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\">         </span><span style=\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\">         </span>\n<span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\">         </span><span style=\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\">         </span>\n<span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\">  CPU 0  </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\">  CPU 1  </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\">  CPU 2  </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\">  CPU 3  </span><span style=\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\">  CPU 4  </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\">  CPU 5  </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\">  CPU 6  </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\">  CPU 7  </span>\n<span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\">         </span><span style=\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\">         </span>\n<span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\">         </span><span style=\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\">         </span>\n<span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\">         </span><span style=\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\">         </span>\n<span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\">         </span><span style=\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\">         </span>\n<span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #393b79\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #d6616b\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8ca252\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #de9ed6\">         </span><span style=\"color: #000000; text-decoration-color: #000000; background-color: #e7cb94\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #6b6ecf\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #a55194\">         </span><span style=\"color: #ffffff; text-decoration-color: #ffffff; background-color: #8c6d31\">         </span>\n</pre>\n\n\n\nThe example below are just to showcase how to do sharding in pure JAX API. Flax offers a small API to annotate the sharding when you define a parameter, so that you don't have to write an arbitrary `make_sharding()` function at top level. Check out our [GSPMD guide](https://flax.readthedocs.io/en/latest/guides/flax_gspmd.html) to learn more.\n\n## Transformations\n\nFor Flax transforms and their relation with JAX transforms, refer to [Flax Transforms](https://flax.readthedocs.io/en/latest/guides/transforms.html) doc. This should be a rarer use case now that Flax NNX modules are JAX pytrees.\n"
  },
  {
    "path": "docs_nnx/migrating/convert_pytorch_to_flax.rst",
    "content": "Convert PyTorch models to Flax\n==============================\n\n.. testsetup::\n\n  import numpy as np\n  import jax\n  from jax import random, numpy as jnp\n  from flax import nnx\n\n  import torch\n\nWe will show how to convert PyTorch models to Flax. We will cover convolutions, fc layers, batch norm, and average pooling.\n\n\nFC Layers\n--------------------------------\n\nLet's start with fc layers. The only thing to be aware of here is that the PyTorch kernel has shape [outC, inC]\nand the Flax kernel has shape [inC, outC]. Transposing the kernel will do the trick.\n\n.. testcode::\n\n    t_fc = torch.nn.Linear(in_features=3, out_features=4)\n\n    kernel = t_fc.weight.detach().cpu().numpy()\n    bias = t_fc.bias.detach().cpu().numpy()\n\n    # [outC, inC] -> [inC, outC]\n    kernel = jnp.transpose(kernel, (1, 0))\n\n    key = random.key(0)\n    x = random.normal(key, (1, 3))\n\n    j_fc = nnx.Linear(in_features=3, out_features=4, rngs=nnx.Rngs(0))\n    j_fc.kernel.value = kernel\n    j_fc.bias.value = jnp.array(bias)\n    j_out = j_fc(x)\n\n    t_x = torch.from_numpy(np.array(x))\n    t_out = t_fc(t_x)\n    t_out = t_out.detach().cpu().numpy()\n\n    np.testing.assert_almost_equal(j_out, t_out, decimal=6)\n\n\nConvolutions\n--------------------------------\n\nLet's now look at 2D convolutions. PyTorch uses the NCHW format and Flax uses NHWC.\nConsequently, the kernels will have different shapes. The kernel in PyTorch has shape [outC, inC, kH, kW]\nand the Flax kernel has shape [kH, kW, inC, outC]. Transposing the kernel will do the trick.\n\n.. testcode::\n\n    t_conv = torch.nn.Conv2d(in_channels=3, out_channels=4, kernel_size=2, padding='valid')\n\n    kernel = t_conv.weight.detach().cpu().numpy()\n    bias = t_conv.bias.detach().cpu().numpy()\n\n    # [outC, inC, kH, kW] -> [kH, kW, inC, outC]\n    kernel = jnp.transpose(kernel, (2, 3, 1, 0))\n\n    key = random.key(0)\n    x = random.normal(key, (1, 6, 6, 3))\n\n    j_conv = nnx.Conv(3, 4, kernel_size=(2, 2), padding='valid', rngs=nnx.Rngs(0))\n    j_conv.kernel.value = kernel\n    j_conv.bias.value = bias\n    j_out = j_conv(x)\n\n    # [N, H, W, C] -> [N, C, H, W]\n    t_x = torch.from_numpy(np.transpose(np.array(x), (0, 3, 1, 2)))\n    t_out = t_conv(t_x)\n    # [N, C, H, W] -> [N, H, W, C]\n    t_out = np.transpose(t_out.detach().cpu().numpy(), (0, 2, 3, 1))\n\n    np.testing.assert_almost_equal(j_out, t_out, decimal=6)\n\n\n\nConvolutions and FC Layers\n--------------------------------\n\nWe have to be careful, when we have a model that uses convolutions followed by fc layers (ResNet, VGG, etc).\nIn PyTorch, the activations will have shape [N, C, H, W] after the convolutions and are then\nreshaped to [N, C * H * W] before being fed to the fc layers.\nWhen we port our weights from PyTorch to Flax, the activations after the convolutions will be of shape [N, H, W, C] in Flax.\nBefore we reshape the activations for the fc layers, we have to transpose them to [N, C, H, W].\n\nConsider this PyTorch model:\n\n.. testcode::\n\n  class TModel(torch.nn.Module):\n\n    def __init__(self):\n      super(TModel, self).__init__()\n      self.conv = torch.nn.Conv2d(in_channels=3, out_channels=4, kernel_size=2, padding='valid')\n      self.fc = torch.nn.Linear(in_features=100, out_features=2)\n\n    def forward(self, x):\n      x = self.conv(x)\n      x = x.reshape(x.shape[0], -1)\n      x = self.fc(x)\n      return x\n\n\n  t_model = TModel()\n\n\n\nNow, if you want to use the weights from this model in Flax, the corresponding Flax model has to look like this:\n\n\n.. testcode::\n\n    class JModel(nnx.Module):\n        def __init__(self, rngs):\n            self.conv = nnx.Conv(3, 4, kernel_size=(2, 2), padding='valid', rngs=rngs)\n            self.linear = nnx.Linear(100, 2, rngs=rngs)\n\n        def __call__(self, x):\n            x = self.conv(x)\n            # [N, H, W, C] -> [N, C, H, W]\n            x = jnp.transpose(x, (0, 3, 1, 2))\n            x = jnp.reshape(x, (x.shape[0], -1))\n            x = self.linear(x)\n            return x\n\n    j_model = JModel(nnx.Rngs(0))\n\n\n\nThe model looks very similar to the PyTorch model, except that we included a transpose operation before\nreshaping our activations for the fc layer.\nWe can omit the transpose operation if we apply pooling before reshaping such that the spatial dimensions are 1x1.\n\nOther than the transpose operation before reshaping, we can convert the weights the same way as we did before:\n\n\n.. testcode::\n\n  conv_kernel = t_model.state_dict()['conv.weight'].detach().cpu().numpy()\n  conv_bias = t_model.state_dict()['conv.bias'].detach().cpu().numpy()\n  fc_kernel = t_model.state_dict()['fc.weight'].detach().cpu().numpy()\n  fc_bias = t_model.state_dict()['fc.bias'].detach().cpu().numpy()\n\n  # [outC, inC, kH, kW] -> [kH, kW, inC, outC]\n  conv_kernel = jnp.transpose(conv_kernel, (2, 3, 1, 0))\n\n  # [outC, inC] -> [inC, outC]\n  fc_kernel = jnp.transpose(fc_kernel, (1, 0))\n\n  j_model.conv.kernel.value = conv_kernel\n  j_model.conv.bias.value = conv_bias\n  j_model.linear.kernel.value = fc_kernel\n  j_model.linear.bias.value = fc_bias\n\n  key = random.key(0)\n  x = random.normal(key, (1, 6, 6, 3))\n  j_out = j_model(x)\n\n  t_x = torch.from_numpy(np.transpose(np.array(x), (0, 3, 1, 2)))\n  t_out = t_model(t_x)\n  t_out = t_out.detach().cpu().numpy()\n\n  np.testing.assert_almost_equal(j_out, t_out, decimal=6)\n\n\n\nBatch Norm\n--------------------------------\n\n``torch.nn.BatchNorm2d`` uses ``0.1`` as the default value for the ``momentum`` parameter while\n|nnx.BatchNorm|_ uses ``0.9``. However, this corresponds to the same computation, because PyTorch multiplies\nthe estimated statistic with ``(1 − momentum)`` and the new observed value with ``momentum``,\nwhile Flax multiplies the estimated statistic with ``momentum`` and the new observed value with ``(1 − momentum)``.\n\n.. |nnx.BatchNorm| replace:: ``nnx.BatchNorm``\n.. _nnx.BatchNorm: https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/normalization.html#flax.nnx.BatchNorm\n\n.. testcode::\n\n  t_bn = torch.nn.BatchNorm2d(num_features=3, momentum=0.1)\n  t_bn.eval()\n\n  key = random.key(0)\n  x = random.normal(key, (1, 6, 6, 3))\n\n  j_bn = nnx.BatchNorm(num_features=3, momentum=0.9, use_running_average=True, rngs=nnx.Rngs(0))\n\n  j_out = j_bn(x)\n\n  # [N, H, W, C] -> [N, C, H, W]\n  t_x = torch.from_numpy(np.transpose(np.array(x), (0, 3, 1, 2)))\n  t_out = t_bn(t_x)\n  # [N, C, H, W] -> [N, H, W, C]\n  t_out = np.transpose(t_out.detach().cpu().numpy(), (0, 2, 3, 1))\n\n  np.testing.assert_almost_equal(j_out, t_out, decimal=6)\n\n\nAverage Pooling\n--------------------------------\n\n``torch.nn.AvgPool2d`` and |nnx.avg_pool()|_ are compatible when using default parameters.\nHowever, ``torch.nn.AvgPool2d`` has a parameter ``count_include_pad``. When ``count_include_pad=False``,\nthe zero-padding will not be considered for the average calculation. There does not exist a similar\nparameter for |nnx.avg_pool()|_. However, we can easily implement a wrapper around the pooling\noperation. ``nnx.pool()`` is the core function behind |nnx.avg_pool()|_ and |nnx.max_pool()|_.\n\n.. |nnx.avg_pool()| replace:: ``nnx.avg_pool()``\n.. _nnx.avg_pool(): https://flax.readthedocs.io/en/latest/api_reference/flax.linen/layers.html#flax.linen.avg_pool\n\n.. |nnx.max_pool()| replace:: ``nnx.max_pool()``\n.. _nnx.max_pool(): https://flax.readthedocs.io/en/latest/api_reference/flax.linen/layers.html#flax.linen.max_pool\n\n\n.. testcode::\n\n  def avg_pool(inputs, window_shape, strides=None, padding='VALID'):\n    \"\"\"\n    Pools the input by taking the average over a window.\n    In comparison to nnx.avg_pool(), this pooling operation does not\n    consider the padded zero's for the average computation.\n    \"\"\"\n    assert len(window_shape) == 2\n\n    y = nnx.pool(inputs, 0., jax.lax.add, window_shape, strides, padding)\n    counts = nnx.pool(jnp.ones_like(inputs), 0., jax.lax.add, window_shape, strides, padding)\n    y = y / counts\n    return y\n\n\n  key = random.key(0)\n  x = random.normal(key, (1, 6, 6, 3))\n\n  j_out = avg_pool(x, window_shape=(2, 2), strides=(1, 1), padding=((1, 1), (1, 1)))\n  t_pool = torch.nn.AvgPool2d(kernel_size=2, stride=1, padding=1, count_include_pad=False)\n\n  # [N, H, W, C] -> [N, C, H, W]\n  t_x = torch.from_numpy(np.transpose(np.array(x), (0, 3, 1, 2)))\n  t_out = t_pool(t_x)\n  # [N, C, H, W] -> [N, H, W, C]\n  t_out = np.transpose(t_out.detach().cpu().numpy(), (0, 2, 3, 1))\n\n  np.testing.assert_almost_equal(j_out, t_out, decimal=6)\n\n\n\nTransposed Convolutions\n--------------------------------\n\n``torch.nn.ConvTranspose2d`` and |nnx.ConvTranspose|_ are not compatible.\n|nnx.ConvTranspose|_ is a wrapper around |jax.lax.conv_transpose|_ which computes a fractionally strided convolution,\nwhile ``torch.nn.ConvTranspose2d`` computes a gradient based transposed convolution. Currently, there is no\nimplementation of a gradient based transposed convolution is ``Jax``. However, there is a pending `pull request`_\nthat contains an implementation.\n\nTo load ``torch.nn.ConvTranspose2d`` parameters into Flax, we need to use the ``transpose_kernel`` arg in Flax's\n``nnx.ConvTranspose`` layer.\n\n.. testcode::\n\n  t_conv = torch.nn.ConvTranspose2d(in_channels=3, out_channels=4, kernel_size=2, padding=0)\n\n  kernel = t_conv.weight.detach().cpu().numpy()\n  bias = t_conv.bias.detach().cpu().numpy()\n\n  # [inC, outC, kH, kW] -> [kH, kW, outC, inC]\n  kernel = jnp.transpose(kernel, (2, 3, 1, 0))\n\n  key = random.key(0)\n  x = random.normal(key, (1, 6, 6, 3))\n\n  # ConvTranspose expects the kernel to be [kH, kW, inC, outC],\n  # but with `transpose_kernel=True`, it expects [kH, kW, outC, inC] instead\n  j_conv = nnx.ConvTranspose(3, 4, kernel_size=(2, 2), padding='VALID', transpose_kernel=True, rngs=nnx.Rngs(0))\n  j_conv.kernel.value = kernel\n  j_conv.bias.value = bias\n  j_out = j_conv(x)\n\n  # [N, H, W, C] -> [N, C, H, W]\n  t_x = torch.from_numpy(np.transpose(np.array(x), (0, 3, 1, 2)))\n  t_out = t_conv(t_x)\n  # [N, C, H, W] -> [N, H, W, C]\n  t_out = np.transpose(t_out.detach().cpu().numpy(), (0, 2, 3, 1))\n  np.testing.assert_almost_equal(j_out, t_out, decimal=6)\n\n.. _`pull request`: https://github.com/jax-ml/jax/pull/5772\n\n.. |nnx.ConvTranspose| replace:: ``nnx.ConvTranspose``\n.. _nnx.ConvTranspose: https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/nn/linear.html#flax.nnx.ConvTranspose\n\n.. |jax.lax.conv_transpose| replace:: ``jax.lax.conv_transpose``\n.. _jax.lax.conv_transpose: https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv_transpose.html\n"
  },
  {
    "path": "docs_nnx/migrating/haiku_to_flax.rst",
    "content": "Haiku to Flax NNX\n############################\n\nThis guide demonstrates the differences between Haiku and Flax NNX models, providing side-by-side example code to help you migrate to the Flax NNX API from Haiku.\n\nIf you are new to Flax NNX, make sure you become familiarized with `Flax NNX basics <https://flax.readthedocs.io/en/latest/nnx_basics.html>`__, which covers the :class:`nnx.Module<flax.nnx.Module>` system, `Flax transformations <https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html>`__, and the `Functional API <https://flax.readthedocs.io/en/latest/nnx_basics.html#the-flax-functional-api>`__ with examples.\n\nLet’s start with some imports.\n\n.. testsetup:: Haiku, Flax NNX\n\n  import jax\n  import jax.numpy as jnp\n  import optax\n  from typing import Any\n\n\nBasic Module definition\n=======================\n\nBoth Haiku and Flax use the ``Module`` class as the default unit to express a neural network library layer. For example, to create a one-layer network with dropout and a ReLU activation function, you:\n\n* First, create a ``Block`` (by subclassing ``Module``) composed of one linear layer with dropout and a ReLU activation function.\n* Then, use ``Block`` as a sub-``Module`` when creating a ``Model`` (also by subclassing ``Module``), which is made up of ``Block`` and a linear layer.\n\nThere are two fundamental differences between Haiku and Flax ``Module`` objects:\n\n* **Stateless vs. stateful**:\n\n  * A ``haiku.Module`` instance is stateless. This means, the variables are returned from a purely functional ``Module.init()`` call and managed separately.\n  * A :class:`flax.nnx.Module`, however, owns its variables as attributes of this Python object.\n\n* **Lazy vs. eager**:\n\n  * A ``haiku.Module`` only allocates space to create variables when they actually see the input when the user calls the model (lazy).\n  * A ``flax.nnx.Module`` instance creates variables the moment they are instantiated, before seeing a sample input (eager).\n\n\n.. codediff::\n  :title: Haiku, Flax NNX\n  :sync:\n\n  import haiku as hk\n\n  class Block(hk.Module):\n    def __init__(self, features: int, name=None):\n      super().__init__(name=name)\n      self.features = features\n\n    def __call__(self, x, training: bool):\n      x = hk.Linear(self.features)(x)\n      x = hk.dropout(hk.next_rng_key(), 0.5 if training else 0, x)\n      x = jax.nn.relu(x)\n      return x\n\n  class Model(hk.Module):\n    def __init__(self, dmid: int, dout: int, name=None):\n      super().__init__(name=name)\n      self.dmid = dmid\n      self.dout = dout\n\n    def __call__(self, x, training: bool):\n      x = Block(self.dmid)(x, training)\n      x = hk.Linear(self.dout)(x)\n      return x\n\n  ---\n\n  from flax import nnx\n\n  class Block(nnx.Module):\n    def __init__(self, in_features: int , out_features: int, rngs: nnx.Rngs):\n      self.linear = nnx.Linear(in_features, out_features, rngs=rngs)\n      self.dropout = nnx.Dropout(0.5, rngs=rngs)\n\n    def __call__(self, x):\n      x = self.linear(x)\n      x = self.dropout(x)\n      x = jax.nn.relu(x)\n      return x\n\n  class Model(nnx.Module):\n    def __init__(self, din: int, dmid: int, dout: int, rngs: nnx.Rngs):\n      self.block = Block(din, dmid, rngs=rngs)\n      self.linear = nnx.Linear(dmid, dout, rngs=rngs)\n\n\n    def __call__(self, x):\n      x = self.block(x)\n      x = self.linear(x)\n      return x\n\n\nVariable creation\n=================\n\nThis section is about instantiating a model and initializing its parameters.\n\n* To generate model parameters for a Haiku model, you need to put it inside a forward function and use ``haiku.transform`` to make it purely functional. This results in a nested dictionary of `JAX Arrays <https://jax.readthedocs.io/en/latest/key-concepts.html#jax-arrays-jax-array>`__ (``jax.Array`` data types) to be carried around and maintained separately.\n\n* In Flax NNX, the model parameters are automatically initialized when you instantiate the model, and the variables (:class:`nnx.Variable<flax.nnx.Variable>` objects) are stored inside the :class:`nnx.Module<flax.nnx.Module>` (or its sub-Module) as attributes. You still need to provide it with a `pseudorandom number generator (PRNG) <https://jax.readthedocs.io/en/latest/random-numbers.html>`__ key, but that key will be wrapped inside an :class:`nnx.Rngs<flax.nnx.Rngs>` class and stored inside, generating more PRNG keys when needed.\n\nIf you want to access Flax model parameters in the stateless, dictionary-like fashion for checkpoint saving or model surgery, check out the `Flax NNX split/merge API <https://flax.readthedocs.io/en/latest/nnx_basics.html#state-and-graphdef>`__ (:func:`nnx.split<flax.nnx.split>` / :func:`nnx.merge<flax.nnx.merge>`).\n\n\n.. codediff::\n  :title: Haiku, Flax NNX\n  :sync:\n\n  def forward(x, training: bool):\n    return Model(256, 10)(x, training)\n\n  model = hk.transform(forward)\n  sample_x = jnp.ones((1, 784))\n  params = model.init(jax.random.key(0), sample_x, training=False)\n\n\n  assert params['model/linear']['b'].shape == (10,)\n  assert params['model/block/linear']['w'].shape == (784, 256)\n\n  ---\n\n  ...\n\n\n  model = Model(784, 256, 10, rngs=nnx.Rngs(0))\n\n\n  # Parameters were already initialized during model instantiation.\n\n  assert model.linear.bias.value.shape == (10,)\n  assert model.block.linear.kernel.value.shape == (784, 256)\n\nTraining step and compilation\n=============================\n\nThis section covers writing a training step and compiling it using the `JAX just-in-time compilation <https://jax.readthedocs.io/en/latest/jit-compilation.html>`__.\n\nWhen compiling the training step:\n\n* Haiku uses ``@jax.jit`` - a `JAX transformation <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`__ - to compile a purely functional training step.\n* Flax NNX uses :meth:`@nnx.jit<flax.nnx.jit>` - a `Flax NNX transformation <https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html>`__ (one of several transform APIs that behave similarly to JAX transforms, but also `work well with Flax objects <https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html>`__). While ``jax.jit`` only accepts functions with pure stateless arguments, ``flax.nnx.jit`` allows the arguments to be stateful Modules. This greatly reduces the number of lines needed for a train step.\n\nWhen taking gradients:\n\n* Similarly, Haiku uses ``jax.grad`` (a JAX transformation for `automatic differentiation <https://jax.readthedocs.io/en/latest/automatic-differentiation.html#taking-gradients-with-jax-grad>`__) to return a raw dictionary of gradients.\n* Meanwhile, Flax NNX uses :meth:`flax.nnx.grad<flax.nnx.grad>` (a Flax NNX transformation) to return the gradients of Flax NNX Modules as :class:`flax.nnx.State<flax.nnx.State>` dictionaries. If you want to use regular ``jax.grad`` with Flax NNX, you need to use the `split/merge API <https://flax.readthedocs.io/en/latest/nnx_basics.html#state-and-graphdef>`__.\n\nFor optimizers:\n\n* If you are already using `Optax <https://optax.readthedocs.io/>`__ optimizers like ``optax.adamw`` (instead of the raw ``jax.tree.map`` computation shown here) with Haiku, check out the :class:`flax.nnx.Optimizer<flax.nnx.Optimizer>` example in the `Flax basics <https://flax.readthedocs.io/en/latest/nnx_basics.html#transforms>`__ guide for a much more concise way of training and updating your model.\n\nModel updates during each training step:\n\n* The Haiku training step needs to return a `JAX pytree <https://jax.readthedocs.io/en/latest/working-with-pytrees.html>`__ of parameters as the input of the next step.\n* The Flax NNX training step does not need to return anything, because the ``model`` was already updated in-place within :meth:`nnx.jit<flax.nnx.jit>`.\n* In addition, :class:`nnx.Module<flax.nnx.Module>` objects are stateful, and ``Module`` automatically tracks several things within it, such as PRNG keys and ``flax.nnx.BatchNorm`` stats. That is why you don't need to explicitly pass a PRNG key in at every step. Also note that you can use :meth:`flax.nnx.reseed<flax.nnx.reseed>` to reset its underlying PRNG state.\n\nThe dropout behavior:\n\n* In Haiku, you need to explicitly define and pass in the ``training`` argument to toggle ``haiku.dropout`` and make sure that random dropout only happens if ``training=True``.\n* In Flax NNX, you can call ``model.train()`` (:meth:`flax.nnx.Module.train`) to automatically switch :class:`flax.nnx.Dropout<flax.nnx.Dropout>` to the training mode. Conversely, you can call ``model.eval()`` (:meth:`flax.nnx.Module.eval`) to turn off the training mode. You can learn more about what ``flax.nnx.Module.train`` does in its `API reference <https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html#flax.nnx.Module.train>`__.\n\n.. codediff::\n  :title: Haiku, Flax NNX\n  :sync:\n\n  ...\n\n  @jax.jit\n  def train_step(key, params, inputs, labels):\n    def loss_fn(params):\n      logits = model.apply(\n        params, key,\n        inputs, training=True # <== inputs\n\n      )\n      return optax.softmax_cross_entropy_with_integer_labels(logits, labels).mean()\n\n    grads = jax.grad(loss_fn)(params)\n\n\n    params = jax.tree_util.tree_map(lambda p, g: p - 0.1 * g, params, grads)\n\n    return params\n\n  ---\n\n  model.train() # set deterministic=False\n\n  @nnx.jit\n  def train_step(model, inputs, labels):\n    def loss_fn(model):\n      logits = model(\n\n        inputs, # <== inputs\n\n      )\n      return optax.softmax_cross_entropy_with_integer_labels(logits, labels).mean()\n\n    grads = nnx.grad(loss_fn)(model)\n    _, params, rest = nnx.split(model, nnx.Param, ...)\n    params = jax.tree.map(lambda p, g: p - 0.1 * g, params, grads)\n    nnx.update(model, nnx.merge_state(params, rest))\n\n.. testcode:: Haiku\n  :hide:\n\n  train_step(jax.random.key(0), params, sample_x, jnp.ones((1,), dtype=jnp.int32))\n\n.. testcode:: Flax NNX\n  :hide:\n\n  sample_x = jnp.ones((1, 784))\n  train_step(model, sample_x, jnp.ones((1,), dtype=jnp.int32))\n\n\n\nHandling non-parameter states\n=============================\n\nHaiku makes a distinction between trainable parameters and all other data (\"states\") that the model tracks. For example, the batch stats used in batch norm is considered a state. Models with states needs to be transformed with ``hk.transform_with_state`` so that their ``.init()`` returns both params and states.\n\nIn Flax, there isn't such a strong distinction - they are all subclasses of ``nnx.Variable`` and seen by a module as its attributes. Parameters are instances of a subclass called ``nnx.Param``, and batch stats can be of another subclass called ``nnx.BatchStat``. You can use :func:`nnx.split<flax.nnx.split>` to quickly extract all data of a certain variable type.\n\nLet's see an example of this by taking the ``Block`` definition above but replace dropout with ``BatchNorm``.\n\n.. codediff::\n  :title: Haiku, Flax NNX\n  :sync:\n\n  class Block(hk.Module):\n    def __init__(self, features: int, name=None):\n      super().__init__(name=name)\n      self.features = features\n\n\n\n    def __call__(self, x, training: bool):\n      x = hk.Linear(self.features)(x)\n      x = hk.BatchNorm(\n        create_scale=True, create_offset=True, decay_rate=0.99\n      )(x, is_training=training)\n      x = jax.nn.relu(x)\n      return x\n\n  def forward(x, training: bool):\n    return Model(256, 10)(x, training)\n  model = hk.transform_with_state(forward)\n\n  sample_x = jnp.ones((1, 784))\n  params, batch_stats = model.init(jax.random.key(0), sample_x, training=True)\n\n  ---\n\n  class Block(nnx.Module):\n    def __init__(self, in_features: int , out_features: int, rngs: nnx.Rngs):\n      self.linear = nnx.Linear(in_features, out_features, rngs=rngs)\n      self.batchnorm = nnx.BatchNorm(\n        num_features=out_features, momentum=0.99, rngs=rngs\n      )\n\n    def __call__(self, x):\n      x = self.linear(x)\n      x = self.batchnorm(x)\n\n\n      x = jax.nn.relu(x)\n      return x\n\n\n\n  model = Block(4, 4, rngs=nnx.Rngs(0))\n\n  model.linear.kernel   # Param(value=...)\n  model.batchnorm.mean  # BatchStat(value=...)\n\n\nFlax takes the difference of trainable params and other data into account. ``nnx.grad`` will only take gradients on the ``nnx.Param`` variables, thus skipping the ``batchnorm`` arrays automatically. Therefore, the training step will look the same for Flax NNX with this model.\n\n\nUsing multiple methods\n======================\n\nIn this section you will learn how to use multiple methods in Haiku and Flax. As an example, you will implement an auto-encoder model with three methods: ``encode``, ``decode``, and ``__call__``.\n\nIn Haiku, you need to use ``hk.multi_transform`` to explicitly define how the model shall be initialized and what methods (``encode`` and ``decode`` here) it can call. Note that you still need to define a ``__call__`` that activates both layers for the lazy initialization of all model parameters.\n\nIn Flax, it's simpler as you initialized parameters in ``__init__`` and the :class:`nnx.Module<flax.nnx.Module>` methods ``encode`` and ``decode`` can be used directly.\n\n.. codediff::\n  :title: Haiku, Flax NNX\n  :sync:\n\n  class AutoEncoder(hk.Module):\n\n    def __init__(self, embed_dim: int, output_dim: int, name=None):\n      super().__init__(name=name)\n      self.encoder = hk.Linear(embed_dim, name=\"encoder\")\n      self.decoder = hk.Linear(output_dim, name=\"decoder\")\n\n    def encode(self, x):\n      return self.encoder(x)\n\n    def decode(self, x):\n      return self.decoder(x)\n\n    def __call__(self, x):\n      x = self.encode(x)\n      x = self.decode(x)\n      return x\n\n  def forward():\n    module = AutoEncoder(256, 784)\n    init = lambda x: module(x)\n    return init, (module.encode, module.decode)\n\n  model = hk.multi_transform(forward)\n  params = model.init(jax.random.key(0), x=jnp.ones((1, 784)))\n\n  ---\n\n  class AutoEncoder(nnx.Module):\n\n    def __init__(self, in_dim: int, embed_dim: int, output_dim: int, rngs):\n\n      self.encoder = nnx.Linear(in_dim, embed_dim, rngs=rngs)\n      self.decoder = nnx.Linear(embed_dim, output_dim, rngs=rngs)\n\n    def encode(self, x):\n      return self.encoder(x)\n\n    def decode(self, x):\n      return self.decoder(x)\n\n\n\n\n\n\n\n\n\n\n\n  model = AutoEncoder(784, 256, 784, rngs=nnx.Rngs(0))\n  ...\n\n\nThe parameter structure is as follows:\n\n.. tab-set::\n\n  .. tab-item:: Haiku\n    :sync: Haiku\n\n    .. code-block:: python\n\n      ...\n\n\n      {\n          'auto_encoder/~/decoder': {\n              'b': (784,),\n              'w': (256, 784)\n          },\n          'auto_encoder/~/encoder': {\n              'b': (256,),\n              'w': (784, 256)\n          }\n      }\n\n  .. tab-item:: Flax NNX\n    :sync: Flax NNX\n\n    .. code-block:: python\n\n      _, params, _ = nnx.split(model, nnx.Param, ...)\n\n      params\n      {\n        'decoder': {\n          'bias': Param(value=(784,)),\n          'kernel': Param(value=(256, 784))\n        },\n        'encoder': {\n          'bias': Param(value=(256,)),\n          'kernel': Param(value=(784, 256))\n        }\n      }\n\n\nTo call those custom methods:\n\n* In Haiku, you need to decouple the `.apply` function to extract your method before calling it.\n* In Flax, you can simply call the method directly.\n\n.. codediff::\n  :title: Haiku, Flax NNX\n  :sync:\n\n  encode, decode = model.apply\n  z = encode(params, None, x=jnp.ones((1, 784)))\n\n  ---\n\n  ...\n  z = model.encode(jnp.ones((1, 784)))\n\n\n\nTransformations\n=======================\n\nBoth Haiku and `Flax transformations <https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html>`__ provide their own set of transforms that wrap `JAX transforms <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`__ in a way that they can be used with ``Module`` objects.\n\nFor more information on Flax transforms, check out the `Transforms guide <https://flax.readthedocs.io/en/latest/guides/transforms.html>`__.\n\nLet's start with an example:\n\n* First, define an ``RNNCell`` ``Module`` that will contain the logic for a single step of the RNN.\n* Define a ``initial_state`` method that will be used to initialize the state (a.k.a. ``carry``) of the RNN. Like with ``jax.lax.scan`` (`API doc <https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.scan.html>`__), the ``RNNCell.__call__`` method will be a function that takes the carry and input, and returns the new carry and output. In this case, the carry and the output are the same.\n\n\n.. codediff::\n  :title: Haiku, Flax NNX\n  :sync:\n\n  class RNNCell(hk.Module):\n    def __init__(self, hidden_size: int, name=None):\n      super().__init__(name=name)\n      self.hidden_size = hidden_size\n\n    def __call__(self, carry, x):\n      x = jnp.concatenate([carry, x], axis=-1)\n      x = hk.Linear(self.hidden_size)(x)\n      x = jax.nn.relu(x)\n      return x, x\n\n    def initial_state(self, batch_size: int):\n      return jnp.zeros((batch_size, self.hidden_size))\n\n  ---\n\n  class RNNCell(nnx.Module):\n    def __init__(self, input_size, hidden_size, rngs):\n      self.linear = nnx.Linear(hidden_size + input_size, hidden_size, rngs=rngs)\n      self.hidden_size = hidden_size\n\n    def __call__(self, carry, x):\n      x = jnp.concatenate([carry, x], axis=-1)\n      x = self.linear(x)\n      x = jax.nn.relu(x)\n      return x, x\n\n    def initial_state(self, batch_size: int):\n      return jnp.zeros((batch_size, self.hidden_size))\n\nNext, we will define a ``RNN`` Module that will contain the logic for the entire RNN. In both cases, we use the library's ``scan`` call to run the ``RNNCell`` over the input sequence.\n\nThe only difference is that Flax ``nnx.scan`` allows you to specify which axis to repeat over in arguments ``in_axes`` and ``out_axes``, which will be forwarded to the underlying `jax.lax.scan <https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.scan.html>`__, whereas in Haiku you need to transpose the input and output explicitly.\n\n.. codediff::\n  :title: Haiku, Flax NNX\n  :sync:\n\n  class RNN(hk.Module):\n    def __init__(self, hidden_size: int, name=None):\n      super().__init__(name=name)\n      self.hidden_size = hidden_size\n\n    def __call__(self, x):\n      cell = RNNCell(self.hidden_size)\n      carry = cell.initial_state(x.shape[0])\n      carry, y = hk.scan(\n        cell, carry,\n        jnp.swapaxes(x, 1, 0)\n      )\n      y = jnp.swapaxes(y, 0, 1)\n      return y\n\n  ---\n\n  class RNN(nnx.Module):\n    def __init__(self, input_size: int, hidden_size: int, rngs: nnx.Rngs):\n      self.hidden_size = hidden_size\n      self.cell = RNNCell(input_size, self.hidden_size, rngs=rngs)\n\n    def __call__(self, x):\n      scan_fn = lambda carry, cell, x: cell(carry, x)\n      carry = self.cell.initial_state(x.shape[0])\n      carry, y = nnx.scan(\n        scan_fn, in_axes=(nnx.Carry, None, 1), out_axes=(nnx.Carry, 1)\n      )(carry, self.cell, x)\n\n      return y\n\n\nScan over layers\n=======================\n\nMost Haiku transforms should look similar with Flax, since they all wraps their JAX counterparts, but the scan-over-layers use case is an exception.\n\nScan-over-layers is a technique where you run an input through a sequence of N repeated layers, passing the output of each layer as the input to the next layer. This pattern can significantly reduce compilation time for large models. In the example below, you will repeat the ``Block`` ``Module`` 5 times in the top-level ``MLP`` ``Module``.\n\nIn Haiku, we define the ``Block`` Module as usual, and then inside ``MLP`` we will\nuse ``hk.experimental.layer_stack`` over a ``stack_block`` function to create a stack\nof ``Block`` Modules. The same code will create 5 layers of parameters in initialization time, and run the input through them in call time.\n\nIn Flax, model initialization and calling code are completely decoupled, so we use the :func:`nnx.vmap<flax.nnx.vmap>` transform to initialize the underlying ``Block`` parameters, and the :func:`nnx.scan<flax.nnx.scan>` transform to run the model input through them.\n\n.. codediff::\n  :title: Haiku, Flax NNX\n  :sync:\n\n  class Block(hk.Module):\n    def __init__(self, features: int, name=None):\n      super().__init__(name=name)\n      self.features = features\n\n    def __call__(self, x, training: bool):\n      x = hk.Linear(self.features)(x)\n      x = hk.dropout(hk.next_rng_key(), 0.5 if training else 0, x)\n      x = jax.nn.relu(x)\n      return x\n\n  class MLP(hk.Module):\n    def __init__(self, features: int, num_layers: int, name=None):\n        super().__init__(name=name)\n        self.features = features\n        self.num_layers = num_layers\n\n\n\n\n\n    def __call__(self, x, training: bool):\n\n      @hk.experimental.layer_stack(self.num_layers)\n      def stack_block(x):\n        return Block(self.features)(x, training)\n\n      stack = hk.experimental.layer_stack(self.num_layers)\n      return stack_block(x)\n\n  def forward(x, training: bool):\n    return MLP(64, num_layers=5)(x, training)\n  model = hk.transform(forward)\n\n  sample_x = jnp.ones((1, 64))\n  params = model.init(jax.random.key(0), sample_x, training=False)\n\n  ---\n\n  class Block(nnx.Module):\n    def __init__(self, input_dim, features, rngs):\n      self.linear = nnx.Linear(input_dim, features, rngs=rngs)\n      self.dropout = nnx.Dropout(0.5, rngs=rngs)\n\n    def __call__(self, x: jax.Array):  # No need to require a second input!\n      x = self.linear(x)\n      x = self.dropout(x)\n      x = jax.nn.relu(x)\n      return x   # No need to return a second output!\n\n  class MLP(nnx.Module):\n    def __init__(self, features, num_layers, rngs):\n      @nnx.split_rngs(splits=num_layers)\n      @nnx.vmap(in_axes=(0,), out_axes=0)\n      def create_block(rngs: nnx.Rngs):\n        return Block(features, features, rngs=rngs)\n\n      self.blocks = create_block(rngs)\n      self.num_layers = num_layers\n\n    def __call__(self, x):\n      @nnx.split_rngs(splits=self.num_layers)\n      @nnx.scan(in_axes=(nnx.Carry, 0), out_axes=nnx.Carry)\n      def forward(x, model):\n        x = model(x)\n        return x\n\n      return forward(x, self.blocks)\n\n\n\n  model = MLP(64, num_layers=5, rngs=nnx.Rngs(0))\n\n\nThere are a few other details to explain in the Flax example above:\n\n* **The `@nnx.split_rngs` decorator:** Flax transforms, like their JAX counterparts, are completely agnostic of the PRNG state and rely on input for PRNG keys. The ``nnx.split_rngs`` decorator allows you to split the ``nnx.Rngs`` before passing them to the decorated function and 'lower' them afterwards, so they can be used outside.\n\n  * Here, you split the PRNG keys because ``jax.vmap`` and ``jax.lax.scan`` require a list of PRNG keys if each of its internal operations needs its own key. So for the 5 layers inside the ``MLP``, you split and provide 5 different PRNG keys from its arguments before going down to the JAX transform.\n\n  * Note that actually ``create_block()`` knows it needs to create 5 layers *precisely because* it sees 5 PRNG keys, because ``in_axes=(0,)`` indicates that ``vmap`` will look into the first argument's first dimension to know the size it will map over.\n\n  * Same goes for ``forward()``, which looks at the variables inside the first argument (aka. ``model``) to find out how many times it needs to scan. ``nnx.split_rngs`` here actually splits the PRNG state inside the ``model``. (If the ``Block`` ``Module`` doesn't have dropout, you don't need the :meth:`nnx.split_rngs<flax.nnx.split_rngs>` line as it would not consume any PRNG key anyway.)\n\n* **Why the Block Module in Flax doesn't need to take and return that extra dummy value:** ``jax.lax.scan`` `(API doc <https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.scan.html>`__ requires its function to return two inputs - the carry and the stacked output. In this case, we didn't use the latter. Flax simplifies this, so that you can now choose to ignore the second output if you set ``out_axes=nnx.Carry`` instead of the default ``(nnx.Carry, 0)``.\n\n  * This is one of the rare cases where Flax NNX transforms diverge from the `JAX transforms <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`__ APIs.\n\nThere are more lines of code in the Flax example above, but they express what happens at each time more precisely. Since Flax transforms become way closer to the JAX transform APIs, it is recommended to have a good understanding of the underlying `JAX transforms <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`__ before using their `Flax NNX equivalents <https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html>`__\n\nNow inspect the variable pytree on both sides:\n\n.. tab-set::\n\n  .. tab-item:: Haiku\n    :sync: Haiku\n\n    .. code-block:: python\n\n      ...\n\n\n      {\n          'mlp/__layer_stack_no_per_layer/block/linear': {\n              'b': (5, 64),\n              'w': (5, 64, 64)\n          }\n      }\n\n\n\n      ...\n\n  .. tab-item:: Flax NNX\n    :sync: Flax NNX\n\n    .. code-block:: python\n\n      _, params, _ = nnx.split(model, nnx.Param, ...)\n\n      params\n      {\n        'blocks': {\n          'linear': {\n            'bias': Param(value=(5, 64)),\n            'kernel': Param(value=(5, 64, 64))\n          }\n        }\n      }\n\n\nTop-level Haiku functions vs top-level Flax modules\n=======================\n\nIn Haiku, it is possible to write the entire model as a single function by using\nthe raw ``hk.{get,set}_{parameter,state}`` to define/access model parameters and\nstates. It is very common to write the top-level \"Module\" as a function instead.\n\nThe Flax team recommends a more Module-centric approach that uses ``__call__`` to\ndefine the forward function. In Flax modules, the parameters and variables can\nbe set and accessed as normal using regular Python class semantics.\n\n.. codediff::\n  :title: Haiku, Flax NNX\n  :sync:\n\n  ...\n\n\n  def forward(x):\n\n\n    counter = hk.get_state('counter', shape=[], dtype=jnp.int32, init=jnp.ones)\n    multiplier = hk.get_parameter(\n      'multiplier', shape=[1,], dtype=x.dtype, init=jnp.ones\n    )\n\n    output = x + multiplier * counter\n\n    hk.set_state(\"counter\", counter + 1)\n    return output\n\n  model = hk.transform_with_state(forward)\n\n  params, state = model.init(jax.random.key(0), jnp.ones((1, 64)))\n\n  ---\n\n  class Counter(nnx.Variable):\n    pass\n\n  class FooModule(nnx.Module):\n\n    def __init__(self, rngs):\n      self.counter = Counter(jnp.ones((), jnp.int32))\n      self.multiplier = nnx.Param(\n        nnx.initializers.ones(rngs.params(), [1,], jnp.float32)\n      )\n    def __call__(self, x):\n      output = x + self.multiplier * self.counter.value\n\n      self.counter.value += 1\n      return output\n\n  model = FooModule(rngs=nnx.Rngs(0))\n\n  _, params, counter = nnx.split(model, nnx.Param, Counter)\n\n\n\n\n"
  },
  {
    "path": "docs_nnx/migrating/index.rst",
    "content": "Migrating\n------------------------\n\n.. toctree::\n   :maxdepth: 2\n\n   convert_pytorch_to_flax\n   nnx_010_to_nnx_011\n   linen_to_nnx\n   haiku_to_flax\n"
  },
  {
    "path": "docs_nnx/migrating/linen_to_nnx.rst",
    "content": "Flax Linen to Flax NNX\n################################\n\nThis guide demonstrates the differences between Flax Linen and Flax NNX models, providing side-by-side example code to help you migrate to the Flax NNX API from Flax Linen.\n\nThis document mainly teaches how to convert arbitrary Flax Linen code to Flax NNX. If you want to play it “safe” and convert your codebase iteratively, check out the `Use Flax NNX and Linen together via nnx.bridge <https://flax.readthedocs.io/en/latest/guides/bridge_guide.html>`__ guide.\n\nTo get the most out of this guide, it is highly recommended to get go through `Flax NNX basics <https://flax.readthedocs.io/en/latest/nnx_basics.html>`__ document, which covers the :class:`nnx.Module<flax.nnx.Module>` system, `Flax transformations <https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html>`__, and the `Functional API <https://flax.readthedocs.io/en/latest/nnx_basics.html#the-flax-functional-api>`__ with examples.\n\n.. testsetup:: Linen, NNX\n\n  import jax\n  import jax.numpy as jnp\n  import optax\n  import flax.linen as nn\n  from typing import Any\n\nBasic ``Module`` definition\n===========================\n\nBoth Flax Linen and Flax NNX use the ``Module`` class as the default unit to express a neural network library layer. In the example below, you first create a ``Block`` (by subclassing ``Module``) composed of one linear layer with dropout and a ReLU activation function; then you use it as a sub-``Module`` when creating a ``Model`` (also by subclassing ``Module``), which is made up of ``Block`` and a linear layer.\n\nThere are two fundamental differences between Flax Linen and Flax NNX ``Module`` objects:\n\n* **Stateless vs. stateful**: A ``flax.linen.Module`` (``nn.Module``) instance is stateless - the variables are returned from a purely functional ``Module.init()`` call and managed separately. A :class:`flax.nnx.Module`, however, owns its variables as attributes of this Python object.\n\n* **Lazy vs. eager**: A ``flax.linen.Module`` only allocates space to create variables when they actually see their input (lazy). A :class:`flax.nnx.Module` instance creates variables the moment they are instantiated before seeing a sample input (eager).\n\n* Flax Linen can use the ``@nn.compact`` decorator to define the model in a single method, and use shape inference from the input sample. A Flax NNX ``Module`` generally requests additional shape information to create all parameters during ``__init__`` , and separately defines the computation in the ``__call__`` method.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  import flax.linen as nn\n\n  class Block(nn.Module):\n    features: int\n\n\n    @nn.compact\n    def __call__(self, x, training: bool):\n      x = nn.Dense(self.features)(x)\n      x = nn.Dropout(0.5, deterministic=not training)(x)\n      x = jax.nn.relu(x)\n      return x\n\n  class Model(nn.Module):\n    dmid: int\n    dout: int\n\n    @nn.compact\n    def __call__(self, x, training: bool):\n      x = Block(self.dmid)(x, training)\n      x = nn.Dense(self.dout)(x)\n      return x\n\n  ---\n\n  from flax import nnx\n\n  class Block(nnx.Module):\n    def __init__(self, in_features: int , out_features: int, rngs: nnx.Rngs):\n      self.linear = nnx.Linear(in_features, out_features, rngs=rngs)\n      self.dropout = nnx.Dropout(0.5, rngs=rngs)\n\n    def __call__(self, x):\n      x = self.linear(x)\n      x = self.dropout(x)\n      x = jax.nn.relu(x)\n      return x\n\n  class Model(nnx.Module):\n    def __init__(self, din: int, dmid: int, dout: int, rngs: nnx.Rngs):\n      self.block = Block(din, dmid, rngs=rngs)\n      self.linear = nnx.Linear(dmid, dout, rngs=rngs)\n\n    def __call__(self, x):\n      x = self.block(x)\n      x = self.linear(x)\n      return x\n\n\nVariable creation\n=================\n\nNext, let’s discuss instantiating the model and initializing its parameters:\n\n* To generate model parameters for a Flax Linen model, you call the ``flax.linen.Module.init`` (``nn.Module.init``) method with a ``jax.random.key`` (`doc <https://jax.readthedocs.io/en/latest/random-numbers.html>`__) plus some sample inputs that the model shall take. This results in a nested dictionary of `JAX Arrays <https://jax.readthedocs.io/en/latest/key-concepts.html#jax-arrays-jax-array>`__ (``jax.Array`` data types) to be carried around and maintained separately.\n\n* In Flax NNX, the model parameters are automatically initialized when you instantiate the model, and the variables (:class:`nnx.Variable<flax.nnx.Variable>` objects) are stored inside the :class:`nnx.Module<flax.nnx.Module>` (or its sub-``Module``) as attributes. You still need to provide it with a `pseudorandom number generator (PRNG) <https://jax.readthedocs.io/en/latest/random-numbers.html>`__ key, but that key will be wrapped inside an :class:`nnx.Rngs<flax.nnx.Rngs>` class and stored inside, generating more PRNG keys when needed.\n\nIf you want to access Flax NNX model parameters in the stateless, dictionary-like fashion for checkpoint saving or model surgery, check out the `Flax NNX split/merge API <https://flax.readthedocs.io/en/latest/nnx_basics.html#state-and-graphdef>`__ (:func:`nnx.split<flax.nnx.split>` / :func:`nnx.merge<flax.nnx.merge>`).\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  model = Model(256, 10)\n  sample_x = jnp.ones((1, 784))\n  variables = model.init(jax.random.key(0), sample_x, training=False)\n  params = variables[\"params\"]\n\n  assert params['Dense_0']['bias'].shape == (10,)\n  assert params['Block_0']['Dense_0']['kernel'].shape == (784, 256)\n\n  ---\n\n  model = Model(784, 256, 10, rngs=nnx.Rngs(0))\n\n\n  # Parameters were already initialized during model instantiation.\n\n  assert model.linear.bias.value.shape == (10,)\n  assert model.block.linear.kernel.value.shape == (784, 256)\n\n\nTraining step and compilation\n=============================\n\nNow, let’s proceed to writing a training step and compiling it using `JAX just-in-time compilation <https://jax.readthedocs.io/en/latest/jit-compilation.html>`__. Below are certain differences between Flax Linen and Flax NNX approaches.\n\nCompiling the training step:\n\n* Flax Linen uses ``@jax.jit`` - a `JAX transform <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`__ - to compile the training step.\n* Flax NNX uses :meth:`@nnx.jit<flax.nnx.jit>` - a `Flax NNX transform <https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html>`__ (one of several transform APIs that behave similarly to JAX transforms, but also `work well with Flax NNX objects <https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html>`__). So, while ``jax.jit`` only accepts functions pure stateless arguments, ``nnx.jit`` allows the arguments to be stateful NNX Modules. This greatly reduced the number of lines needed for a train step.\n\nTaking gradients:\n\n* Similarly, Flax Linen uses ``jax.grad`` (a JAX transform for `automatic differentiation <https://jax.readthedocs.io/en/latest/automatic-differentiation.html#taking-gradients-with-jax-grad>`__) to return a raw dictionary of gradients.\n* Flax NNX uses :meth:`nnx.grad<flax.nnx.grad>` (a Flax NNX transform) to return the gradients of NNX Modules as :class:`nnx.State<flax.nnx.State>` dictionaries. If you want to use regular ``jax.grad`` with Flax NNX you need to use the `Flax NNX split/merge API <https://flax.readthedocs.io/en/latest/nnx_basics.html#state-and-graphdef>`__.\n\nOptimizers:\n\n* If you are already using `Optax <https://optax.readthedocs.io/>`__ optimizers like ``optax.adamw`` (instead of the raw ``jax.tree.map`` computation shown here) with Flax Linen, check out the :class:`nnx.Optimizer<flax.nnx.Optimizer>` example in the `Flax NNX basics <https://flax.readthedocs.io/en/latest/nnx_basics.html#transforms>`__ guide for a much more concise way of training and updating your model.\n\nModel updates during each training step:\n\n* The Flax Linen training step needs to return a `pytree <https://jax.readthedocs.io/en/latest/working-with-pytrees.html>`__ of parameters as the input of the next step.\n* The Flax NNX training step doesn't need to return anything, because the ``model`` was already updated in-place within :meth:`nnx.jit<flax.nnx.jit>`.\n* In addition, :class:`nnx.Module<flax.nnx.Module>` objects are stateful, and ``Module`` automatically tracks several things within it, such as PRNG keys and ``BatchNorm`` stats. That is why you don't need to explicitly pass an PRNG key in on every step. Also note that you can use :meth:`nnx.reseed<flax.nnx.reseed>` to reset its underlying PRNG state.\n\nDropout behavior:\n\n* In Flax Linen, you need to explicitly define and pass in the ``training`` argument to control the behavior of ``flax.linen.Dropout`` (``nn.Dropout``), namely, its ``deterministic`` flag, which means random dropout only happens if ``training=True``.\n* In Flax NNX, you can call ``model.train()`` (:meth:`flax.nnx.Module.train`) to automatically switch :class:`nnx.Dropout<flax.nnx.Dropout>` to the training mode. Conversely, you can call ``model.eval()`` (:meth:`flax.nnx.Module.eval`) to turn off the training mode. You can learn more about what ``nnx.Module.train`` does in its `API reference <https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html#flax.nnx.Module.train>`__.\n\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  ...\n\n  @jax.jit\n  def train_step(key, params, inputs, labels):\n    def loss_fn(params):\n      logits = model.apply(\n        {'params': params},\n        inputs, training=True, # <== inputs\n        rngs={'dropout': key}\n      )\n      return optax.softmax_cross_entropy_with_integer_labels(logits, labels).mean()\n\n    grads = jax.grad(loss_fn)(params)\n\n    params = jax.tree.map(lambda p, g: p - 0.1 * g, params, grads)\n    return params\n\n  ---\n\n  model.train() # Sets ``deterministic=False` under the hood for nnx.Dropout\n\n  @nnx.jit\n  def train_step(model, inputs, labels):\n    def loss_fn(model):\n      logits = model(inputs)\n\n\n\n\n      return optax.softmax_cross_entropy_with_integer_labels(logits, labels).mean()\n\n    grads = nnx.grad(loss_fn)(model)\n    _, params, rest = nnx.split(model, nnx.Param, ...)\n    params = jax.tree.map(lambda p, g: p - 0.1 * g, params, grads)\n    nnx.update(model, nnx.merge_state(params, rest))\n\n.. testcode:: Linen\n  :hide:\n\n  train_step(jax.random.key(0), params, sample_x, jnp.ones((1,), dtype=jnp.int32))\n\n.. testcode:: NNX\n  :hide:\n\n  sample_x = jnp.ones((1, 784))\n  train_step(model, sample_x, jnp.ones((1,), dtype=jnp.int32))\n\n\nCollections and variable types\n==============================\n\nOne key difference between Flax Linen and NNX APIs is how they group variables into categories. Flax Linen uses different collections, while Flax NNX, since all variables shall be top-level Python attributes, you use different variable types.\n\nIn Flax NNX, you can freely create your own variable types as subclasses of ``nnx.Variable``.\n\nFor all the built-in Flax Linen layers and collections, Flax NNX already creates the corresponding layers and variable types. For example:\n\n* ``flax.linen.Dense`` (``nn.Dense``) creates ``params`` -> :class:`nnx.Linear<flax.nnx.Linear>` creates :class:nnx.Param<flax.nnx.Param>`.\n* ``flax.linen.BatchNorm`` (``nn.BatchNorm``) creates ``batch_stats`` -> :class:`nnx.BatchNorm<flax.nnx.BatchNorm>` creates :class:`nnx.BatchStats<flax.nnx.BatchStats>`.\n* ``flax.linen.Module.sow()`` creates ``intermediates`` -> :class:`nnx.Module.sow()<flax.nnx.Module.sow>` creates :class:`nnx.Intermediaries<flax.nnx.Intermediates>`.\n* In Flax NNX, you can also simply obtain the intermediates by assigning it to an ``nnx.Module`` attribute - for example, ``self.sowed = nnx.Intermediates(x)``. This will be similar to Flax Linen's ``self.variable('intermediates' 'sowed', lambda: x)``.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  class Block(nn.Module):\n    features: int\n    def setup(self):\n      self.dense = nn.Dense(self.features)\n      self.batchnorm = nn.BatchNorm(momentum=0.99)\n      self.count = self.variable('counter', 'count',\n                                  lambda: jnp.zeros((), jnp.int32))\n\n\n    @nn.compact\n    def __call__(self, x, training: bool):\n      x = self.dense(x)\n      x = self.batchnorm(x, use_running_average=not training)\n      self.count.value += 1\n      x = jax.nn.relu(x)\n      return x\n\n  x = jax.random.normal(jax.random.key(0), (2, 4))\n  model = Block(4)\n  variables = model.init(jax.random.key(0), x, training=True)\n  variables['params']['dense']['kernel'].shape         # (4, 4)\n  variables['batch_stats']['batchnorm']['mean'].shape  # (4, )\n  variables['counter']['count']                        # 1\n\n  ---\n\n  class Counter(nnx.Variable): pass\n\n  class Block(nnx.Module):\n    def __init__(self, in_features: int , out_features: int, rngs: nnx.Rngs):\n      self.linear = nnx.Linear(in_features, out_features, rngs=rngs)\n      self.batchnorm = nnx.BatchNorm(\n        num_features=out_features, momentum=0.99, rngs=rngs\n      )\n      self.count = Counter(jnp.array(0))\n\n    def __call__(self, x):\n      x = self.linear(x)\n      x = self.batchnorm(x)\n      self.count.value += 1\n      x = jax.nn.relu(x)\n      return x\n\n\n\n  model = Block(4, 4, rngs=nnx.Rngs(0))\n\n  model.linear.kernel   # Param(value=...)\n  model.batchnorm.mean  # BatchStat(value=...)\n  model.count           # Counter(value=...)\n\nIf you want to extract certain arrays from the pytree of variables:\n\n* In Flax Linen, you can access the specific dictionary path.\n* In Flax NNX, you can use :func:`nnx.split<flax.nnx.split>` to distinguish the types apart in Flax NNX. The code below is a simple example that splits up the variables by their types - check out the `Flax NNX Filters <https://flax.readthedocs.io/en/latest/guides/filters_guide.html>`__ guide for more sophisticated filtering expressions.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  params, batch_stats, counter = (\n    variables['params'], variables['batch_stats'], variables['counter'])\n  params.keys()       # ['dense', 'batchnorm']\n  batch_stats.keys()  # ['batchnorm']\n  counter.keys()      # ['count']\n\n  # ... make arbitrary modifications ...\n  # Merge back with raw dict to carry on:\n  variables = {'params': params, 'batch_stats': batch_stats, 'counter': counter}\n\n  ---\n\n  graphdef, params, batch_stats, count = nnx.split(\n    model, nnx.Param, nnx.BatchStat, Counter)\n  params.keys()       # ['batchnorm', 'linear']\n  batch_stats.keys()  # ['batchnorm']\n  count.keys()        # ['count']\n\n  # ... make arbitrary modifications ...\n  # Merge back with ``nnx.merge`` to carry on:\n  model = nnx.merge(graphdef, params, batch_stats, count)\n\n\n\nUsing multiple methods\n======================\n\nIn this section you will learn how to use multiple methods in both Flax Linen and Flax NNX. As an example, you will implement an auto-encoder model with three methods: ``encode``, ``decode``, and ``__call__``.\n\nDefining the encoder and decoder layers:\n\n* In Flax Linen, as before, define the layers without having to pass in the input shape, since the ``flax.linen.Module`` parameters will be initialized lazily using shape inference.\n* In Flax NNX, you must pass in the input shape since the :class:`nnx.Module<flax.nnx.Module>` parameters will be initialized eagerly without shape inference.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  class AutoEncoder(nn.Module):\n    embed_dim: int\n    output_dim: int\n\n    def setup(self):\n      self.encoder = nn.Dense(self.embed_dim)\n      self.decoder = nn.Dense(self.output_dim)\n\n    def encode(self, x):\n      return self.encoder(x)\n\n    def decode(self, x):\n      return self.decoder(x)\n\n    def __call__(self, x):\n      x = self.encode(x)\n      x = self.decode(x)\n      return x\n\n  model = AutoEncoder(256, 784)\n  variables = model.init(jax.random.key(0), x=jnp.ones((1, 784)))\n\n  ---\n\n  class AutoEncoder(nnx.Module):\n\n\n\n    def __init__(self, in_dim: int, embed_dim: int, output_dim: int, rngs):\n      self.encoder = nnx.Linear(in_dim, embed_dim, rngs=rngs)\n      self.decoder = nnx.Linear(embed_dim, output_dim, rngs=rngs)\n\n    def encode(self, x):\n      return self.encoder(x)\n\n    def decode(self, x):\n      return self.decoder(x)\n\n    def __call__(self, x):\n      x = self.encode(x)\n      x = self.decode(x)\n      return x\n\n  model = AutoEncoder(784, 256, 784, rngs=nnx.Rngs(0))\n\n\nThe variable structure is as follows:\n\n.. tab-set::\n\n  .. tab-item:: Linen\n    :sync: Linen\n\n    .. code-block:: python\n\n\n      # variables['params']\n      {\n        decoder: {\n            bias: (784,),\n            kernel: (256, 784),\n        },\n        encoder: {\n            bias: (256,),\n            kernel: (784, 256),\n        },\n      }\n\n  .. tab-item:: NNX\n    :sync: NNX\n\n    .. code-block:: python\n\n      # _, params, _ = nnx.split(model, nnx.Param, ...)\n      # params\n      {\n        'decoder': {\n          'bias': Param(value=(784,)),\n          'kernel': Param(value=(256, 784))\n        },\n        'encoder': {\n          'bias': Param(value=(256,)),\n          'kernel': Param(value=(784, 256))\n        }\n      }\n\nTo call methods other than ``__call__``:\n\n* In Flax Linen, you still need to use the ``apply`` API.\n* In Flax NNX, you can simply call the method directly.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  z = model.apply(variables, x=jnp.ones((1, 784)), method=\"encode\")\n\n  ---\n\n  z = model.encode(jnp.ones((1, 784)))\n\n\n\nTransformations\n===============\n\nBoth Flax Linen and `Flax NNX transformations <https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html>`__ provide their own set of transforms that wrap `JAX transforms <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`__ in a way that they can be used with ``Module`` objects.\n\nMost of the transforms in Flax Linen, such as ``grad`` or ``jit``, don't change much in Flax NNX. But, for example, if you try to do ``scan`` over layers, as described in the next section, the code differs by a lot.\n\nLet’s start with an example:\n\n* First, define an ``RNNCell`` ``Module`` that will contain the logic for a single step of the RNN.\n* Define a ``initial_state`` method that will be used to initialize the state (a.k.a. ``carry``) of the RNN. Like with ``jax.lax.scan`` (`API doc <https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.scan.html>`__), the ``RNNCell.__call__`` method will be a function that takes the carry and input, and returns the new carry and output. In this case, the carry and the output are the same.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  class RNNCell(nn.Module):\n    hidden_size: int\n\n\n    @nn.compact\n    def __call__(self, carry, x):\n      x = jnp.concatenate([carry, x], axis=-1)\n      x = nn.Dense(self.hidden_size)(x)\n      x = jax.nn.relu(x)\n      return x, x\n\n    def initial_state(self, batch_size: int):\n      return jnp.zeros((batch_size, self.hidden_size))\n\n  ---\n\n  class RNNCell(nnx.Module):\n    def __init__(self, input_size, hidden_size, rngs):\n      self.linear = nnx.Linear(hidden_size + input_size, hidden_size, rngs=rngs)\n      self.hidden_size = hidden_size\n\n    def __call__(self, carry, x):\n      x = jnp.concatenate([carry, x], axis=-1)\n      x = self.linear(x)\n      x = jax.nn.relu(x)\n      return x, x\n\n    def initial_state(self, batch_size: int):\n      return jnp.zeros((batch_size, self.hidden_size))\n\nNext, define an ``RNN`` ``Module`` that will contain the logic for the entire RNN.\n\nIn Flax Linen:\n\n* You will use ``flax.linen.scan`` (``nn.scan``) to define a new temporary type that wraps ``RNNCell``. During this process you will also: 1) instruct ``nn.scan`` to broadcast the ``params`` collection (all steps share the same parameters) and to not split the ``params`` PRNG stream (so that all steps initialize with the same parameters); and, finally, 2) specify that you want scan to run over the second axis of the input and stack outputs along the second axis as well.\n* You will then use this temporary type immediately to create an instance of the “lifted” ``RNNCell`` and use it to create the ``carry``, and the run the ``__call__`` method, which will ``scan`` over the sequence.\n\nIn Flax NNX:\n\n* You will create a ``scan`` function (``scan_fn``) that will use the ``RNNCell`` defined in ``__init__`` to scan over the sequence, and explicitly set ``in_axes=(nnx.Carry, None, 1)``. ``nnx.Carry`` means that the ``carry`` argument will be the carry, ``None`` means that ``cell`` will be broadcasted to all steps, and ``1`` means ``x`` will be scanned across axis `1`.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  class RNN(nn.Module):\n    hidden_size: int\n\n    @nn.compact\n    def __call__(self, x):\n      rnn = nn.scan(\n        RNNCell, variable_broadcast='params',\n        split_rngs={'params': False}, in_axes=1, out_axes=1\n      )(self.hidden_size)\n      carry = rnn.initial_state(x.shape[0])\n      carry, y = rnn(carry, x)\n\n      return y\n\n  x = jnp.ones((3, 12, 32))\n  model = RNN(64)\n  variables = model.init(jax.random.key(0), x=jnp.ones((3, 12, 32)))\n  y = model.apply(variables, x=jnp.ones((3, 12, 32)))\n\n  ---\n\n  class RNN(nnx.Module):\n    def __init__(self, input_size: int, hidden_size: int, rngs: nnx.Rngs):\n      self.hidden_size = hidden_size\n      self.cell = RNNCell(input_size, self.hidden_size, rngs=rngs)\n\n    def __call__(self, x):\n      scan_fn = lambda carry, cell, x: cell(carry, x)\n      carry = self.cell.initial_state(x.shape[0])\n      carry, y = nnx.scan(\n        scan_fn, in_axes=(nnx.Carry, None, 1), out_axes=(nnx.Carry, 1)\n      )(carry, self.cell, x)\n\n      return y\n\n  x = jnp.ones((3, 12, 32))\n  model = RNN(x.shape[2], 64, rngs=nnx.Rngs(0))\n\n  y = model(x)\n\n\n\nScan over layers\n================\n\nIn general, transforms of Flax Linen and Flax NNX should look the same. However, `Flax NNX transforms <https://flax.readthedocs.io/en/latest/guides/transforms.html>`__ are designed to be closer to their lower-level `JAX counterparts <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`__, and thus we throw away some assumptions in certain Linen lifted transforms. This scan-over-layers use case will be a good example to showcase it.\n\nScan-over-layers is a technique where you run an input through a sequence of N repeated layers, passing the output of each layer as the input to the next layer. This pattern can significantly reduce compilation time for large models. In the example below, you will repeat the ``Block`` ``Module`` 5 times in the top-level ``MLP`` ``Module``.\n\n* In Flax Linen, you apply the ``flax.linen.scan`` (``nn.scan``) transforms upon the ``Block`` ``nn.Module`` to create a larger ``ScanBlock`` ``nn.Module`` that contains 5 ``Block`` ``nn.Module`` objects. It will automatically create a large parameter of shape ``(5, 64, 64)`` at initialization time, and iterate over at call time every ``(64, 64)`` slice for a total of 5 times, like a ``jax.lax.scan`` (`API doc <https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.scan.html>`__) would.\n* Up close, in the logic of this model there actually is no need for the ``jax.lax.scan`` operation at initialization time. What happens there is more like a ``jax.vmap`` operation - you are given a ``Block`` sub-``Module`` that accepts ``(in_dim, out_dim)``, and you \"vmap\" it over ``num_layers`` of times to create a larger array.\n* In Flax NNX, you take advantage of the fact that model initialization and running code are completely decoupled, and instead use the :func:`nnx.vmap<flax.nnx.vmap>` transform to initialize the underlying ``Block`` parameters, and the :func:`nnx.scan<flax.nnx.scan>` transform to run the model input through them.\n\nFor more information on Flax NNX transforms, check out the `Transforms guide <https://flax.readthedocs.io/en/latest/guides/transforms.html>`__.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  class Block(nn.Module):\n    features: int\n    training: bool\n\n    @nn.compact\n    def __call__(self, x, _):\n      x = nn.Dense(self.features)(x)\n      x = nn.Dropout(0.5)(x, deterministic=not self.training)\n      x = jax.nn.relu(x)\n      return x, None\n\n  class MLP(nn.Module):\n    features: int\n    num_layers: int\n\n\n\n\n    @nn.compact\n    def __call__(self, x, training: bool):\n      ScanBlock = nn.scan(\n        Block, variable_axes={'params': 0}, split_rngs={'params': True},\n        length=self.num_layers)\n\n      y, _ = ScanBlock(self.features, training)(x, None)\n      return y\n\n  model = MLP(64, num_layers=5)\n\n  ---\n\n  class Block(nnx.Module):\n    def __init__(self, input_dim, features, rngs):\n      self.linear = nnx.Linear(input_dim, features, rngs=rngs)\n      self.dropout = nnx.Dropout(0.5, rngs=rngs)\n\n    def __call__(self, x: jax.Array):  # No need to require a second input!\n      x = self.linear(x)\n      x = self.dropout(x)\n      x = jax.nn.relu(x)\n      return x   # No need to return a second output!\n\n  class MLP(nnx.Module):\n    def __init__(self, features, num_layers, rngs):\n      @nnx.split_rngs(splits=num_layers)\n      @nnx.vmap(in_axes=(0,), out_axes=0)\n      def create_block(rngs: nnx.Rngs):\n        return Block(features, features, rngs=rngs)\n\n      self.blocks = create_block(rngs)\n      self.num_layers = num_layers\n\n    def __call__(self, x):\n      @nnx.scan(in_axes=(nnx.Carry, 0), out_axes=nnx.Carry)\n      def forward(x, model):\n        x = model(x)\n        return x\n\n      return forward(x, self.blocks)\n\n  model = MLP(64, num_layers=5, rngs=nnx.Rngs(0))\n\n\nThere are a few other details to explain in the Flax NNX example above:\n\n* **The `@nnx.split_rngs` decorator:** Flax NNX transforms are completely agnostic of PRNG state, which makes them behave more like JAX transforms but diverge from the Flax Linen transforms that handle PRNG state. To regain this functionality, the ``nnx.split_rngs`` decorator allows you to split the ``nnx.Rngs`` before passing them to the decorated function and 'lower' them afterwards, so they can be used outside.\n\n  * Here, you split the PRNG keys because ``jax.vmap`` and ``jax.lax.scan`` require a list of PRNG keys if each of its internal operations needs its own key. So for the 5 layers inside the ``MLP``, you split and provide 5 different PRNG keys from its arguments before going down to the JAX transform.\n\n  * Note that actually ``create_block()`` knows it needs to create 5 layers *precisely because* it sees 5 PRNG keys, because ``in_axes=(0,)`` indicates that ``vmap`` will look into the first argument's first dimension to know the size it will map over.\n\n  * Same goes for ``forward()``, which looks at the variables inside the first argument (aka. ``model``) to find out how many times it needs to scan. ``nnx.split_rngs`` here actually splits the PRNG state inside the ``model``. (If the ``Block`` ``Module`` doesn't have dropout, you don't need the :meth:`nnx.split_rngs<flax.nnx.split_rngs>` line as it would not consume any PRNG key anyway.)\n\n* **Why the Block Module in Flax NNX doesn't need to take and return that extra dummy value:** This is a requirement from ``jax.lax.scan`` `(API doc <https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.scan.html>`__. Flax NNX simplifies this, so that you can now choose to ignore the second output if you set ``out_axes=nnx.Carry`` instead of the default ``(nnx.Carry, 0)``.\n\n  * This is one of the rare cases where Flax NNX transforms diverge from the `JAX transforms <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`__ APIs.\n\nThere are more lines of code in the Flax NNX example above, but they express what happens at each time more precisely. Since Flax NNX transforms become way closer to the JAX transform APIs, it is recommended to have a good understanding of the underlying `JAX transforms <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`__ before using their `Flax NNX equivalents <https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html>`__\n\nNow inspect the variable pytree on both sides:\n\n.. tab-set::\n\n  .. tab-item:: Linen\n    :sync: Linen\n\n    .. code-block:: python\n\n      # variables = model.init(key, x=jnp.ones((1, 64)), training=True)\n      # variables['params']\n      {\n        ScanBlock_0: {\n          Dense_0: {\n            bias: (5, 64),\n            kernel: (5, 64, 64),\n          },\n        },\n      }\n\n  .. tab-item:: NNX\n    :sync: NNX\n\n    .. code-block:: python\n\n      # _, params, _ = nnx.split(model, nnx.Param, ...)\n      # params\n      {\n        'blocks': {\n          'linear': {\n            'bias': Param(value=(5, 64)),\n            'kernel': Param(value=(5, 64, 64))\n          }\n        }\n      }\n\n\nUsing ``TrainState`` in Flax NNX\n================================\n\nFlax Linen has a convenient ``TrainState`` data class to bundle the model,\nparameters and optimizer. In Flax NNX, this is not really necessary. In this section,\nyou will learn how to construct your Flax NNX code around ``TrainState`` for any backward\ncompatibility needs.\n\nIn Flax NNX:\n\n* You must first call :meth:`nnx.split<flax.linen.split>` on the model to get the\n  separate :class:`nnx.GraphDef<flax.nnx.GraphDef>` and :class:`nnx.State<flax.nnx.State>`\n  objects.\n* You can pass in :class:`nnx.Param<flax.nnx.Param>` to filter all trainable parameters\n  into a single :class:`nnx.State<flax.nnx.State>`, and pass in ``...`` for the remaining\n  variables.\n* You also need to subclass ``TrainState`` to add a field for the other variables.\n* Then, you can pass in :meth:`nnx.GraphDef.apply<flax.nnx.GraphDef.apply>` as the ``apply`` function,\n  :class:`nnx.State<flax.nnx.State>` as the parameters and other variables, and an optimizer as arguments to the\n  ``TrainState`` constructor.\n\nNote that :class:`nnx.GraphDef.apply<flax.nnx.GraphDef.apply>` will take in :class:`nnx.State<flax.nnx.State>` objects as arguments and\nreturn a callable function. This function can be called on the inputs to output the\nmodel's logits, as well as the updated :class:`nnx.GraphDef<flax.nnx.GraphDef>` and :class:`nnx.State<flax.nnx.State>` objects.\nNotice below the use of ``@jax.jit`` since you aren't passing in Flax NNX Modules into\nthe ``train_step``.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  from flax.training import train_state\n\n  sample_x = jnp.ones((1, 784))\n  model = nn.Dense(features=10)\n  params = model.init(jax.random.key(0), sample_x)['params']\n\n\n\n\n  state = train_state.TrainState.create(\n    apply_fn=model.apply,\n    params=params,\n\n    tx=optax.adam(1e-3)\n  )\n\n  @jax.jit\n  def train_step(key, state, inputs, labels):\n    def loss_fn(params):\n      logits = state.apply_fn(\n        {'params': params},\n        inputs, # <== inputs\n        rngs={'dropout': key}\n      )\n      return optax.softmax_cross_entropy_with_integer_labels(logits, labels).mean()\n\n    grads = jax.grad(loss_fn)(state.params)\n\n\n    state = state.apply_gradients(grads=grads)\n\n    return state\n\n  ---\n\n  from flax.training import train_state\n\n  model = nnx.Linear(784, 10, rngs=nnx.Rngs(0))\n  model.train() # set deterministic=False\n  graphdef, params, other_variables = nnx.split(model, nnx.Param, ...)\n\n  class TrainState(train_state.TrainState):\n    other_variables: nnx.State\n\n  state = TrainState.create(\n    apply_fn=graphdef.apply,\n    params=params,\n    other_variables=other_variables,\n    tx=optax.adam(1e-3)\n  )\n\n  @jax.jit\n  def train_step(state, inputs, labels):\n    def loss_fn(params, other_variables):\n      logits, (graphdef, new_state) = state.apply_fn(\n        params,\n        other_variables\n\n      )(inputs) # <== inputs\n      return optax.softmax_cross_entropy_with_integer_labels(logits, labels).mean()\n\n    grads = jax.grad(loss_fn)(state.params, state.other_variables)\n\n\n    state = state.apply_gradients(grads=grads)\n\n    return state\n\n.. testcode:: Linen\n  :hide:\n\n  train_step(jax.random.key(0), state, sample_x, jnp.ones((1,), dtype=jnp.int32))\n\n.. testcode:: NNX\n  :hide:\n\n  sample_x = jnp.ones((1, 784))\n  train_step(state, sample_x, jnp.ones((1,), dtype=jnp.int32))\n"
  },
  {
    "path": "docs_nnx/migrating/nnx_010_to_nnx_011.rst",
    "content": "NNX 0.10 to NNX 0.11\n#########################################\n\nIn this guide we present the code changes required when we update Flax NNX code from Flax version\n``0.10.x`` to ``0.11.x``.\n\n\nUsing Rngs in NNX Transforms\n====================================\n\nNNX layers that use RNGs like Dropout or MultiHeadAttention now hold a ``fork``-ed copy of the ``Rngs``\nobject given at construction time instead of a shared reference to the original ``Rngs`` object. This has\ntwo consequences:\n* It changes the checkpoint structure, as each layer will have unique RNG state.\n* It changes how ``nnx.split_rngs`` interacts with transforms like ``nnx.vmap`` and ``nnx.scan``,\n  as the resulting RNG state will now not be stored in scalar form.\n\nHere is how a \"scan over layers\" looks like in the new version:\n\n.. tab-set::\n\n  .. tab-item:: v0.11\n    :sync: v0.11\n\n    .. code-block:: python\n\n      import flax.nnx as nnx\n\n      class MLP(nnx.Module):\n        @nnx.split_rngs(splits=5)\n        @nnx.vmap(in_axes=(0, 0))\n        def __init__(self, rngs: nnx.Rngs):\n          self.linear = nnx.Linear(3, 3, rngs=rngs)\n          self.bn = nnx.BatchNorm(3, rngs=rngs)\n          self.dropout = nnx.Dropout(0.5, rngs=rngs)\n          self.node = nnx.Param(jnp.ones((2,)))\n\n\n        @nnx.scan(in_axes=(0, nnx.Carry), out_axes=nnx.Carry)\n        def __call__(self, x: jax.Array):\n          return nnx.gelu(self.dropout(self.bn(self.linear(x))))\n\n  .. tab-item:: v0.10\n    :sync: v0.10\n\n    .. code-block:: python\n      :emphasize-lines: 12\n\n      import flax.nnx as nnx\n\n      class MLP(nnx.Module):\n        @nnx.split_rngs(splits=5)\n        @nnx.vmap(in_axes=(0, 0))\n        def __init__(self, rngs: nnx.Rngs):\n          self.linear = nnx.Linear(3, 3, rngs=rngs)\n          self.bn = nnx.BatchNorm(3, rngs=rngs)\n          self.dropout = nnx.Dropout(0.5, rngs=rngs)\n          self.node = nnx.Param(jnp.ones((2,)))\n\n        @nnx.split_rngs(splits=5)\n        @nnx.scan(in_axes=(0, nnx.Carry), out_axes=nnx.Carry)\n        def __call__(self, x: jax.Array):\n          return nnx.gelu(self.dropout(self.bn(self.linear(x))))\n\n\nThe main thing to note is that the ``nnx.split_rngs`` over ``scan`` is not needed anymore, as the RNGs produced\nby ``__init__`` are no longer in scalar form (they keep the additional dimension) and thus can be used directly\nin ``scan`` without the need to split them again. Alternatively, can even remove the ``nnx.split_rngs`` decorator\nfrom the ``__init__`` method and use ``Rngs.fork`` directly before passing the RNGs to the module.\n\n.. code-block:: python\n\n  class MLP(nnx.Module):\n    @nnx.vmap(in_axes=(0, 0))\n    def __init__(self, rngs: nnx.Rngs):\n      self.linear = nnx.Linear(3, 3, rngs=rngs)\n      self.bn = nnx.BatchNorm(3, rngs=rngs)\n      self.dropout = nnx.Dropout(0.5, rngs=rngs)\n      self.node = nnx.Param(jnp.ones((2,)))\n\n    @nnx.scan(in_axes=(0, nnx.Carry), out_axes=nnx.Carry)\n    def __call__(self, x: jax.Array):\n      return nnx.gelu(self.dropout(self.bn(self.linear(x))))\n\n  rngs = nnx.Rngs(0)\n  mlp = MLP(rngs=rngs.fork(splits=5))\n\nLoading Checkpoints with RNGs\n==================================================\n\nWhen loading checkpoints in the new version, you need to drop the old RNGs structure and\npartially reinitialize the model with new RNGs. To do this, you can use ``nnx.jit`` to\n\n1. Remove the RNGs from the checkpoint.\n2. Perform partial initialization of the model with new RNGs.\n\n.. code-block:: python\n\n  # load checkpoint\n  checkpointer = ocp.StandardCheckpointer()\n  checkpoint = checkpointer.restore(path / \"state\")\n\n  @jax.jit\n  def fix_checkpoint(checkpoint, rngs: nnx.Rngs):\n    # drop rngs keys\n    flat_paths = nnx.traversals.flatten_mapping(checkpoint)\n    flat_paths = {\n        path[:-1] if path[-1] == \"value\" else path: value  # remove \"value\" suffix\n        for path, value in flat_paths.items()\n        if \"rngs\" not in path  # remove rngs paths\n    }\n    checkpoint = nnx.traversals.unflatten_mapping(flat_paths)\n\n    # initialize new model with given rngs\n    model = MyModel(rngs=rngs)\n    # overwrite model parameters with checkpoint\n    nnx.update(model, checkpoint)\n    # get full checkpoint with new rngs\n    new_checkpoint = nnx.state(model)\n\n    return new_checkpoint\n\n  checkpoint = fix_checkpoint(checkpoint, rngs=nnx.Rngs(params=0, dropout=1))\n  checkpointer.save(path.with_name(path.name + \"_new\"), checkpoint)\n\nThe previous code is efficient because ``jit`` performs dead code elimination (DCE) so it will not\nactually initialize the existing model parameters in memory.\n\nOptimizer Updates\n====================================\n\nOptimizer has been updated to not hold a reference to the model anymore. Instead, it now\ntakes the model and gradients as arguments in the ``update`` method. Concretely, these are the\nthe new changes:\n\n1. The ``wrt`` constructor argument is now required.\n2. The ``model`` attribute has been removed.\n3. The ``update`` method now takes ``(model, grads)`` instead of only ``(grads)``.\n\n.. tab-set::\n\n  .. tab-item:: v0.11\n    :sync: v0.11\n\n    .. code-block:: python\n      :emphasize-lines: 17, 26\n\n      from flax import nnx\n      import optax\n\n\n      class Model(nnx.Module):\n        def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n          self.linear = nnx.Linear(din, dmid, rngs=rngs)\n          self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n          self.dropout = nnx.Dropout(0.2, rngs=rngs)\n          self.linear_out = nnx.Linear(dmid, dout, rngs=rngs)\n\n        def __call__(self, x):\n          x = nnx.relu(self.dropout(self.bn(self.linear(x))))\n          return self.linear_out(x)\n\n      model = Model(2, 64, 3, rngs=nnx.Rngs(0))\n      optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\n\n      @nnx.jit\n      def train_step(model, optimizer, x, y):\n        def loss_fn(model):\n          y_pred = model(x)\n          return ((y_pred - y) ** 2).mean()\n\n        loss, grads = nnx.value_and_grad(loss_fn)(model)\n        optimizer.update(model, grads)\n\n        return loss\n\n  .. tab-item:: v0.10\n    :sync: v0.10\n\n    .. code-block:: python\n      :emphasize-lines: 17, 26\n\n      from flax import nnx\n      import optax\n\n\n      class Model(nnx.Module):\n        def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n          self.linear = nnx.Linear(din, dmid, rngs=rngs)\n          self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n          self.dropout = nnx.Dropout(0.2, rngs=rngs)\n          self.linear_out = nnx.Linear(dmid, dout, rngs=rngs)\n\n        def __call__(self, x):\n          x = nnx.relu(self.dropout(self.bn(self.linear(x))))\n          return self.linear_out(x)\n\n      model = Model(2, 64, 3, rngs=nnx.Rngs(0))\n      optimizer = nnx.Optimizer(model, optax.adam(1e-3))\n\n      @nnx.jit\n      def train_step(model, optimizer, x, y):\n        def loss_fn(model):\n          y_pred = model(x)\n          return ((y_pred - y) ** 2).mean()\n\n        loss, grads = nnx.value_and_grad(loss_fn)(model)\n        optimizer.update(grads)\n\n        return loss\n\nPytrees containing NNX Objects\n====================================\n\nIn the new version, NNX modules are now Pytrees. This means that you can use them with JAX transforms\nlike ``jax.vmap`` and ``jax.jit`` directly (more documentation on this will be available soon). However,\nthis also means that code using ``jax.tree.*`` functions on structures that contain NNX modules will\nneed to take this into account to maintain the current behavior. In these cases, the solution is to\nuse the ``is_leaf`` argument to specify that NNX modules and other NNX objects should be treated as leaves.\n\n\n.. code-block:: python\n\n  modules = [nnx.Linear(3, 3, rngs=nnx.Rngs(0)), nnx.BatchNorm(3, rngs=nnx.Rngs(1))]\n\n  type_names = jax.tree.map(\n      lambda x: type(x).__name__,\n      modules,\n      is_leaf=lambda x: isinstance(x, nnx.Pytree)  # <-- specify that NNX objects are leaves\n  )\n"
  },
  {
    "path": "docs_nnx/mnist_tutorial.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"0\",\n   \"metadata\": {},\n   \"source\": [\n    \"[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google/flax/blob/main/docs_nnx/mnist_tutorial.ipynb)\\n\",\n    \"[![Open On GitHub](https://img.shields.io/badge/Open-on%20GitHub-blue?logo=GitHub)](https://github.com/google/flax/blob/main/docs_nnx/mnist_tutorial.ipynb)\\n\",\n    \"\\n\",\n    \"# MNIST tutorial\\n\",\n    \"\\n\",\n    \"Welcome to Flax NNX! In this tutorial you will learn how to build and train a simple convolutional neural network (CNN) to classify handwritten digits on the MNIST dataset using the Flax NNX API.\\n\",\n    \"\\n\",\n    \"Flax NNX is a Python neural network library built upon [JAX](https://github.com/jax-ml/jax). If you have used the Flax Linen API before, check out [Why Flax NNX](https://flax.readthedocs.io/en/latest/why.html). You should have some knowledge of the main concepts of deep learning.\\n\",\n    \"\\n\",\n    \"Let’s get started!\\n\",\n    \"\\n\",\n    \"## 1. Install Flax\\n\",\n    \"\\n\",\n    \"If `flax` is not installed in your Python environment, use `pip` to install the package from PyPI (below, just uncomment the code in the cell if you are working from Google Colab/Jupyter Notebook):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"2\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# !pip install -U \\\"jax[cuda12]\\\"\\n\",\n    \"# !pip install -U flax\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"3\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 2. Load the MNIST dataset\\n\",\n    \"\\n\",\n    \"First, you need to load the MNIST dataset and then prepare the training and testing sets via Tensorflow Datasets (TFDS). You normalize image values, shuffle the data and divide it into batches, and prefetch samples to enhance performance.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"id\": \"4\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import tensorflow_datasets as tfds  # TFDS to download MNIST.\\n\",\n    \"import tensorflow as tf  # TensorFlow / `tf.data` operations.\\n\",\n    \"\\n\",\n    \"tf.random.set_seed(0)  # Set the random seed for reproducibility.\\n\",\n    \"\\n\",\n    \"train_steps = 1200\\n\",\n    \"eval_every = 200\\n\",\n    \"batch_size = 32\\n\",\n    \"\\n\",\n    \"train_ds: tf.data.Dataset = tfds.load('mnist', split='train')\\n\",\n    \"test_ds: tf.data.Dataset = tfds.load('mnist', split='test')\\n\",\n    \"\\n\",\n    \"train_ds = train_ds.map(\\n\",\n    \"  lambda sample: {\\n\",\n    \"    'image': tf.cast(sample['image'], tf.float32) / 255,\\n\",\n    \"    'label': sample['label'],\\n\",\n    \"  }\\n\",\n    \")  # normalize train set\\n\",\n    \"test_ds = test_ds.map(\\n\",\n    \"  lambda sample: {\\n\",\n    \"    'image': tf.cast(sample['image'], tf.float32) / 255,\\n\",\n    \"    'label': sample['label'],\\n\",\n    \"  }\\n\",\n    \")  # Normalize the test set.\\n\",\n    \"\\n\",\n    \"# Create a shuffled dataset by allocating a buffer size of 1024 to randomly draw elements from.\\n\",\n    \"train_ds = train_ds.repeat().shuffle(1024)\\n\",\n    \"# Group into batches of `batch_size` and skip incomplete batches, prefetch the next sample to improve latency.\\n\",\n    \"train_ds = train_ds.batch(batch_size, drop_remainder=True).take(train_steps).prefetch(1)\\n\",\n    \"# Group into batches of `batch_size` and skip incomplete batches, prefetch the next sample to improve latency.\\n\",\n    \"test_ds = test_ds.batch(batch_size, drop_remainder=True).prefetch(1)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"5\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 3. Define the model with Flax NNX\\n\",\n    \"\\n\",\n    \"Create a CNN for classification with Flax NNX by subclassing `nnx.Module`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"6\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_79fdedab11444712830d108f7c0b1793\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_79fdedab11444712830d108f7c0b1793\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_79fdedab11444712830d108f7c0b1793\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_79fdedab11444712830d108f7c0b1793\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"from flax import nnx  # The Flax NNX API.\\n\",\n    \"from functools import partial\\n\",\n    \"from typing import Optional\\n\",\n    \"\\n\",\n    \"class CNN(nnx.Module):\\n\",\n    \"  \\\"\\\"\\\"A simple CNN model.\\\"\\\"\\\"\\n\",\n    \"\\n\",\n    \"  def __init__(self, *, rngs: nnx.Rngs):\\n\",\n    \"    self.conv1 = nnx.Conv(1, 32, kernel_size=(3, 3), rngs=rngs)\\n\",\n    \"    self.batch_norm1 = nnx.BatchNorm(32, rngs=rngs)\\n\",\n    \"    self.dropout1 = nnx.Dropout(rate=0.025)\\n\",\n    \"    self.conv2 = nnx.Conv(32, 64, kernel_size=(3, 3), rngs=rngs)\\n\",\n    \"    self.batch_norm2 = nnx.BatchNorm(64, rngs=rngs)\\n\",\n    \"    self.avg_pool = partial(nnx.avg_pool, window_shape=(2, 2), strides=(2, 2))\\n\",\n    \"    self.linear1 = nnx.Linear(3136, 256, rngs=rngs)\\n\",\n    \"    self.dropout2 = nnx.Dropout(rate=0.025)\\n\",\n    \"    self.linear2 = nnx.Linear(256, 10, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x, rngs: nnx.Rngs | None = None):\\n\",\n    \"    x = self.avg_pool(nnx.relu(self.batch_norm1(self.dropout1(self.conv1(x), rngs=rngs))))\\n\",\n    \"    x = self.avg_pool(nnx.relu(self.batch_norm2(self.conv2(x))))\\n\",\n    \"    x = x.reshape(x.shape[0], -1)  # flatten\\n\",\n    \"    x = nnx.relu(self.dropout2(self.linear1(x), rngs=rngs))\\n\",\n    \"    x = self.linear2(x)\\n\",\n    \"    return x\\n\",\n    \"\\n\",\n    \"# Instantiate the model.\\n\",\n    \"model = CNN(rngs=nnx.Rngs(0))\\n\",\n    \"# Visualize it.\\n\",\n    \"nnx.display(model)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"7\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Run the model\\n\",\n    \"\\n\",\n    \"Let's put the CNN model to the test!  Here, you’ll perform a forward pass with arbitrary data and print the results.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"id\": \"8\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Array([[ 0.11409501,  0.4546129 , -0.6421267 , -0.12122799, -0.22859162,\\n\",\n       \"         0.13616608,  1.0126765 , -0.03625144,  0.6132787 , -0.06018351]],      dtype=float32)\"\n      ]\n     },\n     \"execution_count\": 3,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"import jax.numpy as jnp  # JAX NumPy\\n\",\n    \"\\n\",\n    \"y = model(jnp.ones((1, 28, 28, 1)), nnx.Rngs(0))\\n\",\n    \"y\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"9\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 4. Create the optimizer and define some metrics\\n\",\n    \"\\n\",\n    \"In Flax NNX, you need to create an `nnx.Optimizer` object to manage the model's parameters and apply gradients during training. `nnx.Optimizer` receives the model's reference, so that it can update its parameters, and an [Optax](https://optax.readthedocs.io/) optimizer to define the update rules. Additionally, you will define an `nnx.MultiMetric` object to keep track of the `Accuracy` and the `Average` loss.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"id\": \"12\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_920e157205b44214a73cd99be5592f27\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_920e157205b44214a73cd99be5592f27\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_920e157205b44214a73cd99be5592f27\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_920e157205b44214a73cd99be5592f27\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"import optax\\n\",\n    \"\\n\",\n    \"learning_rate = 0.005\\n\",\n    \"momentum = 0.9\\n\",\n    \"\\n\",\n    \"optimizer = nnx.Optimizer(\\n\",\n    \"  model, optax.adamw(learning_rate, momentum), wrt=nnx.Param\\n\",\n    \")\\n\",\n    \"metrics = nnx.MultiMetric(\\n\",\n    \"  accuracy=nnx.metrics.Accuracy(),\\n\",\n    \"  loss=nnx.metrics.Average('loss'),\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"nnx.display(optimizer)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"13\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 5. Define training step functions\\n\",\n    \"\\n\",\n    \"In this section, you will define a loss function using the cross entropy loss ([`optax.softmax_cross_entropy_with_integer_labels()`](https://optax.readthedocs.io/en/latest/api/losses.html#optax.softmax_cross_entropy_with_integer_labels)) that the CNN model will optimize over.\\n\",\n    \"\\n\",\n    \"In addition to the `loss`, during training and testing you will also get the `logits`, which will be used to calculate the accuracy metric.\\n\",\n    \"\\n\",\n    \"During training — the `train_step` — you will use `nnx.value_and_grad` to compute the gradients and update the model's parameters using the `optimizer` you have already defined. The `train_step` also receives an `nnx.Rngs` object to provide randomness for dropout. The `eval_step` omits `rngs` because the eval view already has `deterministic=True`, so dropout is disabled and no random key is needed. During both steps, the `loss` and `logits` are used to update the metrics.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"14\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"def loss_fn(model: CNN, batch, rngs: nnx.Rngs | None = None):\\n\",\n    \"  logits = model(batch['image'], rngs)\\n\",\n    \"  loss = optax.softmax_cross_entropy_with_integer_labels(\\n\",\n    \"    logits=logits, labels=batch['label']\\n\",\n    \"  ).mean()\\n\",\n    \"  return loss, logits\\n\",\n    \"\\n\",\n    \"@nnx.jit\\n\",\n    \"def train_step(model: CNN, optimizer: nnx.Optimizer, metrics: nnx.MultiMetric, rngs: nnx.Rngs, batch):\\n\",\n    \"  \\\"\\\"\\\"Train for a single step.\\\"\\\"\\\"\\n\",\n    \"  grad_fn = nnx.value_and_grad(loss_fn, has_aux=True)\\n\",\n    \"  (loss, logits), grads = grad_fn(model, batch, rngs)\\n\",\n    \"  metrics.update(loss=loss, logits=logits, labels=batch['label'])  # In-place updates.\\n\",\n    \"  optimizer.update(model, grads)  # In-place updates.\\n\",\n    \"\\n\",\n    \"@nnx.jit\\n\",\n    \"def eval_step(model: CNN, metrics: nnx.MultiMetric, batch):\\n\",\n    \"  loss, logits = loss_fn(model, batch)\\n\",\n    \"  metrics.update(loss=loss, logits=logits, labels=batch['label'])  # In-place updates.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"17\",\n   \"metadata\": {},\n   \"source\": [\n    \"In the code above, the [`nnx.jit`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.jit) transformation decorator traces the `train_step` function for just-in-time compilation with [XLA](https://www.tensorflow.org/xla), optimizing performance on hardware accelerators, such as Google TPUs and GPUs. `nnx.jit` is a stateful version of the `jax.jit` transform that allows its function input and outputs to be Flax NNX objects. Similarly, `nnx.value_and_grad` is a stateful version of `jax.value_and_grad`. Check out [the transforms guide](https://flax.readthedocs.io/en/latest/guides/transforms.html) to learn more.\\n\",\n    \"\\n\",\n    \"> **Note:** The code shows how to perform several in-place updates to the model, the optimizer, the RNG streams, and the metrics, but _state updates_ were not explicitly returned. This is because Flax NNX transformations respect _reference semantics_ for Flax NNX objects, and will propagate the state updates of the objects passed as input arguments. This is a key feature of Flax NNX that allows for a more concise and readable code. You can learn more in [Why Flax NNX](https://flax.readthedocs.io/en/latest/why.html).\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"## 6. Train and evaluate the model\\n\",\n    \"\\n\",\n    \"Now, you can train the CNN model. Before the training loop, we use [`nnx.view`](https://flax.readthedocs.io/en/latest/guides/view.html) to create a `train_model` (with dropout enabled and batch norm in training mode) and an `eval_model` (with dropout disabled and batch norm using running statistics). These views share the same underlying weights, so updates during training are automatically reflected during evaluation.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"22\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAABL4AAAHDCAYAAAAqZtO0AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjUsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvWftoOwAAAAlwSFlzAAAPYQAAD2EBqD+naQAApvxJREFUeJzs3Qd4VFX6BvB3Mukd0hMCoXcSepMmCIrS7QXL2sWy/NUVReyyuitrwYq9NzoqiDRBmhBC7530AOmkzcz/+c7NpEACCSS5U97f81wyc+dm5oRkkjvvfOc7BovFYgEREREREREREZGDcdF7AERERERERERERPWBwRcRERERERERETkkBl9EREREREREROSQGHwREREREREREZFDYvBFREREREREREQOicEXERERERERERE5JAZfRERERERERETkkBh8ERERERERERGRQ2LwRUREREREREREDonBFxEREREREREROSQGX0Skq88//xwGgwGbNm3SeyhEREREVOq9995T52i9e/fWeyhERJeEwRcRERERERFV8s033yAmJgYbN27EgQMH9B4OEdFFY/BFREREREREZQ4fPoy1a9dixowZCAkJUSGYLcrLy9N7CERkBxh8EZHN27JlC6666ir4+/vD19cXQ4cOxfr16ysdU1xcjBdeeAGtW7eGp6cngoKCcNlll2Hp0qVlx6SkpODOO+9EkyZN4OHhgYiICIwZMwZHjhzR4asiIiIisk0SdDVq1AhXX301rr322iqDr8zMTPzzn/9UVWFyXiXnVxMnTkRGRkbZMQUFBXj++efRpk0bdX4m517jx4/HwYMH1e0rV65U0ynlY0Vybib7pSWG1R133KHOA+VzR44cCT8/P9xyyy3qttWrV+O6665D06ZN1Viio6PV2M6cOXPOuPfs2YPrr79eBXpeXl5o27YtnnnmGXXbihUr1OPOnTv3nM/79ttv1W3r1q27pP9bImp4rjo8JhFRje3cuRMDBgxQodeTTz4JNzc3fPjhhxg8eDBWrVpV1ndCTqqmT5+Ou+++G7169UJ2drbqGxYfH48rrrhCHTNhwgR1fw8//LA6SUtLS1PB2LFjx9R1IiIiItKCLwmo3N3dcdNNN+H999/H33//jZ49e6rbc3Nz1fnZ7t27cdddd6Fbt24q8FqwYAFOnDiB4OBgmEwmXHPNNVi2bBlu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\",\n      \"text/plain\": [\n       \"<Figure size 1500x500 with 2 Axes>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"from IPython.display import clear_output\\n\",\n    \"import matplotlib.pyplot as plt\\n\",\n    \"\\n\",\n    \"metrics_history = {\\n\",\n    \"  'train_loss': [],\\n\",\n    \"  'train_accuracy': [],\\n\",\n    \"  'test_loss': [],\\n\",\n    \"  'test_accuracy': [],\\n\",\n    \"}\\n\",\n    \"\\n\",\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"train_model = nnx.view(model, deterministic=False, use_running_average=False)\\n\",\n    \"eval_model = nnx.view(model, deterministic=True, use_running_average=True)\\n\",\n    \"\\n\",\n    \"for step, batch in enumerate(train_ds.as_numpy_iterator()):\\n\",\n    \"  # Run the optimization for one step and make a stateful update to the following:\\n\",\n    \"  # - The train state's model parameters\\n\",\n    \"  # - The optimizer state\\n\",\n    \"  # - The training loss and accuracy batch metrics\\n\",\n    \"  train_step(train_model, optimizer, metrics, rngs, batch)\\n\",\n    \"\\n\",\n    \"  if step > 0 and (step % eval_every == 0 or step == train_steps - 1):  # One training epoch has passed.\\n\",\n    \"    # Log the training metrics.\\n\",\n    \"    for metric, value in metrics.compute().items():  # Compute the metrics.\\n\",\n    \"      metrics_history[f'train_{metric}'].append(value)  # Record the metrics.\\n\",\n    \"    metrics.reset()  # Reset the metrics for the test set.\\n\",\n    \"\\n\",\n    \"    # Compute the metrics on the test set after each training epoch.\\n\",\n    \"    for test_batch in test_ds.as_numpy_iterator():\\n\",\n    \"      eval_step(eval_model, metrics, test_batch)\\n\",\n    \"\\n\",\n    \"    # Log the test metrics.\\n\",\n    \"    for metric, value in metrics.compute().items():\\n\",\n    \"      metrics_history[f'test_{metric}'].append(value)\\n\",\n    \"    metrics.reset()  # Reset the metrics for the next training epoch.\\n\",\n    \"\\n\",\n    \"    clear_output(wait=True)\\n\",\n    \"    # Plot loss and accuracy in subplots\\n\",\n    \"    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\\n\",\n    \"    ax1.set_title('Loss')\\n\",\n    \"    ax2.set_title('Accuracy')\\n\",\n    \"    for dataset in ('train', 'test'):\\n\",\n    \"      ax1.plot(metrics_history[f'{dataset}_loss'], label=f'{dataset}_loss')\\n\",\n    \"      ax2.plot(metrics_history[f'{dataset}_accuracy'], label=f'{dataset}_accuracy')\\n\",\n    \"    ax1.legend()\\n\",\n    \"    ax2.legend()\\n\",\n    \"    plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"25\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 7. Perform inference on the test set\\n\",\n    \"\\n\",\n    \"Create a `jit`-compiled model inference function (with `nnx.jit`) - `pred_step` - to generate predictions on the test set using the learned model parameters. Since we already have `eval_model` (an `nnx.view` with `deterministic=True` and `use_running_average=True`), we can use it directly for inference. This will enable you to visualize test images alongside their predicted labels for a qualitative assessment of model performance.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"26\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"@nnx.jit\\n\",\n    \"def pred_step(model: CNN, batch):\\n\",\n    \"  logits = model(batch['image'], None)\\n\",\n    \"  return logits.argmax(axis=1)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1d6cb81f\",\n   \"metadata\": {},\n   \"source\": [\n    \"We reuse the `eval_model` view created earlier so that `Dropout` is disabled and `BatchNorm` uses stored running stats during inference.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"27\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAAA7QAAAPGCAYAAADTLdZkAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjUsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvWftoOwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAjY5JREFUeJzt/QeYFFX6P27XINnAoqJgxIgBFUXBhCCirjlnFDNiXgOurgqYMfvVNbuIGDDnrIA5KwaUNbvGFSUYQIL0e1X/XvijddrtdmboOd33fV2sy4dD9ZnhnJ5+qqqfrsnlcrkEAAAAItOo3BMAAACAP0NBCwAAQJQUtAAAAERJQQsAAECUFLQAAABESUELAABAlBS0AAAARElBCwAAQJQUtAAAAESpqgra66+/PqmpqUk+/fTTkv5ejx49ko4dO9bpXNq3b5/su+++dXpM+CPWP9XOHqCaWf9UO3ugclVVQVuJXnnlleTwww9PVl111WTeeedNllpqqWTXXXdN3n///XJPDeaKqVOnJieccEKy2GKLJS1atEi6du2aPP744+WeFpTFmWeemX/BVtcvvqCh+uCDD5Ldd989WWKJJZKWLVsmK620UnLaaaclkydPLvfUoN6lRXFNTU3BX19++WVSDRqXewLUzuDBg5Pnnnsu2WWXXZLVV189+eabb5LLLrssWWuttZIXX3zRixqq4sn8jjvuSI4++uhkhRVWyJ+B3XLLLZORI0cmG264YbmnB3PNF198kZx11ln5k5tQDT7//POkS5cuSatWrfIn9xdccMHkhRdeSAYMGJC89tpryb333lvuKUK96tu3b9KrV6/fZLlcLjnkkEPyV4EXX3zxpBooaCN3zDHHJDfffHPStGnT2dluu+2WrLbaask555yT3HjjjWWdH9Snl19+ORk+fHhy3nnnJccdd1w+22efffIncvr37588//zz5Z4izDXpHlh33XWTX3/9Nfnuu+/KPR2od8OGDUsmTpyYPPvss/k71VIHH3xwMnPmzOSGG25IJkyYkLRu3brc04R6s9566+V/zSndD+kdCnvttVdSLar6luP0zN1WW22Vv1WxWbNmyXLLLZecfvrp+RcDIenZvvXXXz9/W+MyyyyTXHnllcHbH9Mzg8svv3z+mEsuuWT+hXWa14d0PnMWs6n0KlX6xP7ee+/Vy2NSGSph/adXZueZZ578C5hZmjdvnhxwwAH5s/Tp2Xuo5D0wy9NPP53fDxdffHG9Pg6VoxLW/w8//JD/76KLLvqbvF27dkmjRo0yr4+g0vZAyM0335y/3XjPPfdMqkVVX6FNb02cb7758lc50/+OGDEiOfXUU/NPkOkVnzmlZ/nS2xjT96fuscceyW233Zb069cv/2S5//7758ekZwS33Xbb/JmR9AX2yiuvnLz99tvJRRddlH9P6z333FNwLunfHT9+fFHzTm+tadKkScE/T281+O9//zv7bCVU6vp/4403khVXXDFZYIEFfjMmvQUtNXr06PwPE6jUPZBKX3wdccQRyYEHHpi/OweqZf2nzXrSt16lJzEHDRqULLTQQvk7c6644orkyCOPdPs9Fb8Hfm/69On5uaWFd3rLcdXIVZEhQ4bk0i/5k08+yf9+8uTJmTF9+/bNtWzZMvfLL7/Mzrp3757/exdccMHsbOrUqblOnTrlFllkkdy0adPy2bBhw3KNGjXKPfPMM7855pVXXpn/+88999zsbOmll8716dNn9u/TOaVjivk1cuTIP/w603mk46677ro/9X2iMlXi+l911VVzPXv2zHwdY8aMyY9NHxsqeQ+kLrvsslyrVq1y33777ez5pnsDqmH9n3766bkWLVr8Zsw//vGPOvmeUVkqdQ/M6f7778+Pufzyy3PVpKqv0Ka3DMzy448/5m8H6NatW3LVVVclY8eOTdZYY43Zf964ceP8G69nSc/IpL9Pz86ktyCk71u6/fbb82dj0g57c75/qWfPnvn/pk1q0jMmIW3bti26M+uc8/q9dN6HHXZY/n76Pn36FHU8qlMlrP8pU6bkb+n5vfS241l/DpW8B77//vv8FYVTTjkladOmTYnfAapZJaz/VHoVaqONNkp22mmn/BXaBx98MN8cLT1m2igKKn0P/P524/TqbXoluZpUdUE7ZsyY5OSTT87fYjDrfRizTJo06Te/T++v//2tK+mtjqn086zShZy2jk/ft1roRcW3335bcC7pC/DfdykrVdrhOH0vQHorwqz3FkIlr//0h1HofSm//PLL7D+HSt4D6fzTzq7pLcdQbes/bQqY3tqZ3s6ZfmxPascdd8zfvpl+nFt6a2ha5EKl7oE5/fTTT/n3BW+++eZVt+6rtqBNu+J17949/9679PPK0jeCp4vp9ddfzz8Jpk+GpUr/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Q50aMWJEJttoo42CY5988slgvtVWW2WyGTNmJHNbkcu03jSU9T9lypRg3rRp03p7zPfffz+Yr7zyyklD1qxZs0y27bbbBscOHz681o8X6oaZWnfddaNf/+XaAxMnTsxkrVq1KukYoc7zp5xyStKQdenSJZi/9NJLtT52t27dgvmzzz6bNGTl3gMN5WdAQ9K+ffuiu99fc801wbxfv3719m8d+lSASy+9NDh26623LrrT+t/+9rfg2F9++SWp1PWfsgfKb8UVVyzp9dhdd92VyRo1Cl/L/PXXX4P5AQcckMmGDh2azG3/aw+4QgsAAECUFLQAAABESUELAABAlBS0AAAARElBCwAAQJQal3sC1F7Xrl2D+SqrrFL0MQp1ICxHR2NK69xb3x0Ql1566WDeu3fvTHbjjTcmDcXUqVOL7ub9/PPPB/P111+/6Mdr3rx5CbNjToMGDQrm8803X9HHKNRd9corr/zT8wIKa9u2bdGdcJ966qmif3Y1bty46I7IqZ49ewbzv/71r5nsww8/DI7deeedg/ndd98dzKEcPvjgg2B+zjnnBPNQR+NCrxePP/74YF6OjsZ/hiu0AAAARElBCwAAQJQUtAAAAERJQQsAAECUNIWKSMeOHYP5gw8+GMz/8pe/ZLKnn346OPaxxx6r5eyotkZUiy++eBKb8ePHB/OJEyfO9bnwvxuPzTPPPEUfo2XLlsF8iSWWyGRffvllCbMDQjp16lT02O+++y6YH3LIIZnssMMOC45dddVVg/mECROC+eDBgzPZpZdeGhz7/fffB3NoSHr06BHMd9hhh6KPceGFFwbziy66KImZK7QAAABESUELAABAlBS0AAAARElBCwAAQJQUtAAAAESp4rsch7ribb/99sGx2267bTBfe+21i368Ro3C5whmzpwZzF955ZWistQee+wRzBdaaKGiO7cOHDgwOPaHH34I5jQszz77bDDfcMMN5/pcampqkkpx+OGHB/NPPvmk6K979dVXD+b9+vUL5ldccUVJc6xk559/ftHPya1btw6ObdeuXTC/5ZZbMtmHH36YNGStWrWqt2Ofdtppwfyvf/1rMJ82bVq9zYW4FXrtEfLAAw8E88aNsy9D33jjjeDY/fbbL5gPHz48mE+dOrXo+UFDcuCBBwbza665ptaf7HDmmWcmlcgVWgAAAKKkoAUAACBKCloAAACipKAFAAAgSgpaAAAAohRdl+Odd945mB966KHBvHv37pksl8uV9JiljC/UzbjQMUIdlEvpqvxHjxn6njz99NMlHZuGJdSxNbXBBhvU+tiFumt//fXXwfy6665LKsWyyy5b9L6tz+ePavXOO+8E8/XXXz+T3XPPPcGxHTp0CObLLLNMUVm12HjjjYP5lVdeGcz333//ep4RDd1mm20WzE844YSij1GoW/Z2222XyR555JESZgdxW2KJJTLZUUcdVSfH7tu3byabMGFCUolcoQUAACBKCloAAACipKAFAAAgSgpaAAAAotSgm0LtsMMOmeyGG24Ijm3atGkwHzduXNFNWoYMGRLMf/nll2A+fPjwot9sfdpppwXzgw46KKkvX331Vb0dm8qzyy67BPPPP/88qXTHHHNMrY9R6Pv0xBNP1PrY1Wrs2LGZbPfddw+O7dWrVzA/77zz6nxeMfvpp59KagpF9TjggAOC+dVXXx3MP/zww0z27bffBsd27tw5mDdp0qSkOUKlufPOOzNZx44dSzrGlQWevws1UaxErtACAAAQJQUtAAAAUVLQAgAAECUFLQAAAFFS0AIAABClBtHleOeddw7moY7GhboZF+pQXJ9dhENOPfXUojs217e99tork73wwgvBsdOmTZsLM4LyWn755YP5csstV+tjT5w4sehOoPx5o0ePDuZvvfVWML/ssssy2QUXXBAc+/777wfzq666Kph369Ytkx133HFJbfXo0SOYF/r5V8gll1ySyU444YTg2KlTp5Z0bOKw6KKLZrJzzz03OHbLLbcsqfvxzTffnMmWWmqpkl6jhfbnK6+8Ehz7zTffBHOIwYYbbhjM11hjjaKP8fzzzwfzfv36JdXOFVoAAACipKAFAAAgSgpaAAAAoqSgBQAAIEoKWgAAAKJUk8vlckUNrKmpt0mMGDEimG+00UZFd8o7/PDD661z4+KLLx7M//GPf2Syvn37BscW+jaHuvmdddZZwbH77bdfMN9uu+2Kfsy//e1vwbGXXnpp0pAVuUzrTX2u/1LMN998wfzll18O5h06dCj62DfeeGMw79OnT1IpHY0feOCB4NgVVlih1o8X6viZ2nvvvaNf/w1pD1SDr7/+Opi3bds2mH/33XdF/2wo1CWzoSv3Hmjo679x4/CHVnz//fdFfy09e/YM5q+++motZ5cku+66azAfPnx40Z8Kce+99ybVqtzrP4Y90FCsvfbawfy5554runv9LbfcEhx76KGHlvQpC9W0B1yhBQAAIEoKWgAAAKKkoAUAACBKCloAAACipKAFAAAgSuG2ePVkww03DObdu3cP5v/+978z2UEHHVTrebRv3z6Y9+jRI5ifdNJJwXy55ZbLZNOmTQuOPf/884vu2leoo+D9999fdBfD1F/+8pdMtuOOOwbHDh06NJj/8MMPwZzy+Omnn4L59OnTa33szTbbLJjfcMMNwfyII47IZJMmTUrqS/PmzYP50ksvHczvvvvueulm/MUXXwTzSy65pNbHhj+j0L6LtaMxhTVp0iSYP/3000V/0kOh5/rRo0cn9WWhhRYqemyhrt3Q0DRq1Kjo10yhbsapl156KZNVczfjP8sVWgAAAKKkoAUAACBKCloAAACipKAFAAAgSnO1KdQ//vGPYJ7L5YL58OHDiz728ssvH8w32WSTTHbWWWcFx7Zq1SopxaOPPprJTj311ODYQo2e6sKWW24ZzO+5555M1q1bt+DYf/7zn8F87733ruXsmBtCzcVSHTt2LPoYiyyySDDfa6+9gvkSSyyRyV588cXg2Pvuuy+Yb7vttpmspqam6MdL7bnnnsnctNpqqwVzDdSAurTwwgtnstNPPz04tmvXrsF8/fXXn6vNn5o1a1bSa4lQI83333+/zucF9WHIkCGZbOWVVy7pNcJxxx2XyTR/Kp0rtAAAAERJQQsAAECUFLQAAABESUELAABAlBS0AAAARGmudjnebLPNSupy3L1790z23HPPldTNdb755stkv/zyS3Dsf/7zn5K6qIY6F8+YMSOZ21566aVg/sILL2SybbbZpuhOiKktttgikz388MMlz5H6ddpppwXzH3/8MZOdc845dfKYof0ZylJHHXVUMG/evHkma9QofJ5t5syZSX256667gvkBBxxQ1PcU/qxQZ/xQd1uqz3fffZfJWrZsGRw7fvz4op9jGzcu7aVfp06dgvmSSy6ZyS688MKixxb62TVu3LiS5gf17bDDDgvm++yzT9HH+L//+79g/uyzz/7pefH/cYUWAACAKCloAQAAiJKCFgAAgCgpaAEAAIiSghYAAIAozdUux0OGDAnm++67bzAPdUx99913g2Ovv/76YP7MM89ksi+++CI49sUXX0wqyY477pjJhg4dGhy71157Fd3dUJfjhqdQd+2LLrqoqM7fqRNOOCGYN2nSpJazC3faLKRQ1/NShTplPv7448GxRx55ZDD/4Ycf6mQuUEi7du1q3YX2nnvuqcMZ0ZCFntP/6JMeRowYUW9zCXWef+qpp4Jjt95662A+ZsyYOp8X/FktWrQI5oW6d4c89thjwfy888770/Pif3OFFgAAgCgpaAEAAIiSghYAAIAoKWgBAACIUk2uyA4sNTU1tX6wZs2aBfPllluu6GMUauikeUtx2rRpU1L+0UcfZbKpU6cmc1tdNQr6s+pi/Td0vXv3DuZLLrlkMD/jjDPqZR6NGoXPs73//vslNUl54403MtlLL72UxKjc679a9kA5XHHFFZnskEMOKekYhRoCVVLDnXLvgYa+/tu2bRvMN9lkk1of+7PPPgvmY8eOzWTfffddrR+Phrf+Y9gDdeHMM88M5ieddFIw//DDDzPZ6quvHhw7ZcqUWs6uuuX+xx5whRYAAIAoKWgBAACIkoIWAACAKCloAQAAiJKCFgAAgCjN1S7HEGuHP+ufal7/KXugfuhyHMcesP6p5vVfaXtgoYUWCuaffvppMJ9vvvmC+eabb57JHnvssVrOjhBdjgEAAKhICloAAACipKAFAAAgSgpaAAAAoqSgBQAAIEqNyz0BAKhWoS7Ha621VnDsmWeeGcz/85//1Pm8ACrVNttsU1I340KeeeaZOpoRteUKLQAAAFFS0AIAABAlBS0AAABRUtACAAAQJQUtAAAAUdLlGADK5K233spkXbt2LctcAKpBqd2MCznuuOMy2emnn14nx6Y0rtACAAAQJQUtAAAAUVLQAgAAECUFLQAAAFGqyeVyuaIG1tTU/2yggCKXab2x/qnm9Z+yB6jmPWD9U83rP2UP0JD3gCu0AAAARElBCwAAQJQUtAAAAERJQQsAAECUFLQAAABUdpdjAAAAaEhcoQUAACBKCloAAACipKAFAAAgSgpaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACBKCloAAACipKAFAAAgSlVV0F5//fVJTU1N8umnn5b093r06JF07NixTufSvn37ZN99963TY8Ifsf6pdvYA1cz6p9rZA5WrqgraSvb6668n2267bbLgggsmLVu2zG+8//u//yv3tKDeTZ06NTnhhBOSxRZbLGnRokXStWvX5PHHHy/3tGCu8jOAajRmzJhkl112SZZddtn8ul944YWTjTbaKLn//vvLPTWYK3766adkwIAByV//+tf8839asKeFe7VpXO4JUHuPPfZYss022yRrrrlmcsoppyTzzTdf8tFHHyVffPFFuacG9S49w3nHHXckRx99dLLCCivkn8i33HLLZOTIkcmGG25Y7ulBvfMzgGr12WefJT/++GPSp0+f/EnNyZMnJ3feeWf+5M5VV12VHHzwweWeItSr7777LjnttNOSpZZaKlljjTWSUaNGJdVIQRu5H374Idlnn32SrbbaKv+ivlEjF92pHi+//HIyfPjw5LzzzkuOO+64fJbuh/TqVP/+/ZPnn3++3FOEeuVnANUsPXmZ/prT4YcfnnTu3Dm58MILFbRUvHbt2iVff/110rZt2+TVV19N1llnnaQaVfVPvnvvvTf/IiA9q9esWbNkueWWS04//fTk119/DY5/7bXXkvXXXz9/W+MyyyyTXHnllcHbH9NL/8svv3z+mEsuuWT+hXWa14ebb745+e9//5uceeaZ+RcyP//8czJz5sx6eSwqSyWs//QF/DzzzPObFy3NmzdPDjjggOSFF15IPv/883p5XCpDJewBPwOo5vUfkv5MSB934sSJc+0xiVMl7IH0Mdq2bZtUu6q+QpvempjemnXMMcfk/ztixIjk1FNPzZ/xTq/4zGnChAn5s4C77rprssceeyS33XZb0q9fv6Rp06bJ/vvvnx+TvohIb3N59tln8y+wV1555eTtt99OLrroouT9999P7rnnnoJzSf/u+PHji5p3q1atkiZNmuT//xNPPJEssMACyZdffplsv/32+ceZd955k7333jv/uOmLe6jU9f/GG28kK664Yn4PzKlLly75/44ePTr/wwQqdQ/4GUA1r/9Z0hM5U6ZMSSZNmpTcd999ycMPP5zstttuf+r7QvWopD1Q9XJVZMiQIbn0S/7kk0/yv588eXJmTN++fXMtW7bM/fLLL7Oz7t275//eBRdcMDubOnVqrlOnTrlFFlkkN23atHw2bNiwXKNGjXLPPPPMb4555ZVX5v/+c889Nztbeumlc3369Jn9+3RO6Zhifo0cOXL231t99dXz801/HXHEEbk777wz/9903O67715n3zviV4nrf9VVV8317Nkz83WMGTMmPzZ9bKjkPeBnANW8/uec96w/T+ew884758aPH1/r7xmVpZL3QOqVV17J/3n6dVabqr5Cm94yMEvaVCC9HaBbt275RgJjx47Nv7l6lsaNGyd9+/ad/fv0jEz6+/TsTHoLwrrrrpvcfvvt+bMxK620Uv5N2rP07Nkz/9+0SU16q0JIertAsZ1Z55xX2t0sbYJwyCGHzO5oueOOOybTpk3Lfx3pG8XTRjlQies/PSOf3m7ze7OuSqV/DpW8B/wMoJrX/yxpU8Cdd945+eqrr/JXztJbRtM9ANWyB6pd42pv937yySfnbzFIby+YU3rbypzS++vT27jmlN7qmEo/zypdyB988EHy3nvvJW3atAk+3rfffltwLukL8F69ev3pzZje/jCnPffcM78h0/cRejFDJa//0PtSfvnll9l/DpW+B1J+BlCN63+WtIBIf6XSJmmbbbZZvvP3Sy+9lP8YE6j0PVDtqragTZsFdO/ePf/eo/QMdvpG8HQxpZ/ll36m5Z9pqpH+ndVWWy3fWS/kj97Ll55NHDduXFGPk37OVHpmaNYGSzfkoosu+psxiyyyyOx7/qFS13/a3S997+DvpR3/Zu0PqOQ94GcA1bz+C0mv1qZXz9L3LXbo0KGo41JdKn0PVJuqLWjTz2n6/vvvk7vuuiv/IdyzfPLJJ8Hx6W0sadOBOc/OpE+Uqfbt2+f/m26GN998M9lkk01KPiOYdmNNO6YVI71loUePHvn/n7amT29RSF/Uz/mknc43VegsEdWtUtZ/p06d8r9Pz6zO2RgqPSs/68+hkveAnwFU8/ovZNbbTX5/lQ2qZQ9Um6otaNO27qlcLn3/9P+Tvt/i8ssvD46fMWNG/vattBParLHp79MXC+kLilTa+eyhhx5Krrnmmsxnn6VPrumZm9/frlDbe+fTxzznnHOS6667bvY9+qlrr702f7+/BU8lr//0LPz555+fXH311bM/hza9BXnIkCFJ165ddTim4veAnwFU8/pPb+GcdTfCLNOnT09uuOGG/O34q6yySlHHpPpUyh6gygva9E3ZrVu3Tvr06ZMceeSR+TMpw4YN+83CnlN6W9fgwYPz98mn98zfeuut+Y8ESV9Iz2qdnX5MQtqMIG3OkZ492WCDDfK3EKRvLE/zRx99NFl77bXr9N75NddcM98u/F//+ld+s6W3T6RnndI3pp944oluuaSi139atO6yyy75tZ6+sEk/923o0KH5eaYv8KHS94CfAVTz+k9vK07v0EmvsC2++OLJN998k9x00035x7zgggvyH8UClbwHUpdddln+FupZd+bcf//9yRdffJH//0cccUT+Y34qXq6K23Wn7bPXXXfdXIsWLXKLLbZYrn///rlHH3000xI7bdedfjzIq6++mltvvfVyzZs3z7fbvuyyyzKPkbbuHjx4cH58s2bNcq1bt8517tw5N2jQoNykSZMKtuuujfQxBw4cmD9mkyZNcssvv3zuoosuqpNjUzkqdf1PmTIld9xxx+Xatm2bf8x11lkn98gjj9TJsakslboH/AygWtf/LbfckuvVq1du0UUXzTVu3Dj/eOnv77333lofm8pTiXtg1rGSAh/xM+trrXQ16f+Uu6gGAACAUjUq+W8AAABAA6CgBQAAIEoKWgAAAKKkoAUAACBKCloAAACipKAFAAAgSgpaAAAAotS42IE1NTX1OxP4A+X+uGTrn2pe/yl7gGreA9Y/1bz+U/YADXkPuEILAABAlBS0AAAARElBCwAAQJQUtAAAAERJQQsAAECUFLQAAABESUELAABAlBS0AAAARElBCwAAQJQUtAAAAERJQQsAAECUFLQAAABESUELAABAlBS0AAAARElBCwAAQJQal3sCFG/TTTcN5ocddlgw33bbbTPZueeeGxz797//vZazAwAAmLtcoQUAACBKCloAAACipKAFAAAgSgpaAAAAoqSgBQAAIEo1uVwuV9TAmpr6n00VateuXSbbfPPNg2MvvPDCYN6qVauiH2/69OkldUq+7rrrkoagyGVab6x/qnn9p+yBuWe++eYL5tdcc00w33333YP5iy++WPTPlx9++CFpyMq9B6z/4jRt2jSYN2vWrOhj9OrVK5gPGDAgmK+22mpFH7vQMc4444ykISv3+k/ZA+X/9x40aFAwHzhwYFLp/tcecIUWAACAKCloAQAAiJKCFgAAgCgpaAEAAIiSghYAAIAoNS73BKqlO2Xv3r2D+f7775/JOnfunNSXeeaZJ5jPP//89faYNCyNG4e3/YEHHhjMV1hhhaKP/dNPPwXza6+9Nph/++23mWzq1KlFPx7EbqWVVspkDz30UHBs+/btS+r+2LVr10y29957B8f+85///B8zpSEo9DO8Q4cOwbxv377J3LT66qsH827duhXdObfUjr6ljA/tCSinUjoUd+/evV7nEjNXaAEAAIiS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weDY//73vyUdm7g/daJUofVI3XGFFgAAgCgpaAEAAIiSghYAAIAoKWgBAACIkoIWAACAKOlyXMcmTpxY0vjbb789k2266aZJfTn22GOD+UcffVRvj8mf88EHH2Sy5ZdfPql0p556ajDv27dv0d2MS3XEEUcE82uvvbbWx6bh+f777+vkOCNGjEjqQ+PG4R/NQ4cOLek4w4YNy2RTpkz50/Oi/Jo1axbMd9xxx2B+yCGHZLL//Oc/wbGXXHJJMH/11VeThmD11VcP5scff3wwL7WjccgXX3xRdFd84tejR4+S8lLocly/XKEFAAAgSgpaAAAAoqSgBQAAIEoKWgAAAKJUMU2hampqkoagTZs2wfyEE04I5o0aZc8pzJw5s6THHDNmTDC/+eabM9njjz9e0rEpnwcffDCTHXXUUUmMtt5662B+8sknZ7I111yzpEY5uVyu6HkceuihwVzzp+ry2GOP1clxFlhggUw2fvz4ko7RpEmTohv8FGpM8uWXX5bU5Id4HXfcccF80KBBRR9jgw02KOl5+uOPPw7mb731ViZ76KGHklKceOKJRT+nL7nkksF8wQUXTOrLnnvumckmTJhQb49HedVF8yfKwxVaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACBKCloAAACiVDFdjn/66adgvtFGGxXd+bFQd9X6VEqH1vfffz+Yb7PNNsH8s88++9PzomGu6VK7eYe6sJaqZcuWwXyhhRbKZKecckpw7AEHHFDreRT62qdNmxbMjzjiiEymmzF/1In4qaeeCubdu3cvuuPsSSedVHQ340IdjW+55ZaSfs5ttdVWwXzq1KnBnHgtssgi9Xbs+eefP5ivscYaRed77713rZ/XS3ldVKovvvgimF922WXB/JVXXqm3udDwFHqurwsDBw4sKac0rtACAAAQJQUtAAAAUVLQAgAAECUFLQAAAFFS0AIAABClmlyR7eRK7a7akC299NLB/P777w/mq666ar3N5Zlnnslkw4cPD4594okngvmHH36YVLr67HrYUNd/qJvlO++8Exy74IILFn3cO++8s6R5LLHEEsG8a9euRX+f6uLfr9D6Hzx4cDAfOXJkUinKvf4r7WdAIaGu+KmHH344mE+ZMqXoPTrvvPMG886dOxfdzXjbbbcN5qNGjUoqXbn3QENZ/4U+peGwww5LYlSfXY7vu+++THbqqacGxxbatw1Fudd/Q9oDsX6fN95446p9/p4b/zau0AIAABAlBS0AAABRUtACAAAQJQUtAAAAUarKplDEp9wNERrK+l9xxRWDeb9+/YL5gQcemMlatmxZb9/jUptCjRgxoujmT+eee25Srcq9/hvSHiiHxRZbLJjfcMMNmaxnz57BsRMnTgzmt99+eya79NJLo2xcU8l7oKGs/2bNmgXzxo0bF32MXXfdNZgvu+yyJc3lkEMOyWStW7cu6RhPP/10JnvuuedK2kNXXnllMJ86dWommzFjRhKjcq//hrQH6lN9vg6idjSFAgAAoCIpaAEAAIiSghYAAIAoKWgBAACIkoIWAACAKOlyTBTK3eEv1vXfrl27oruwdurUqdaP9/PPPwfza6+9Nph/++23mWzatGm1nkelKff6j3kPUBnKvQesf6p5/afsAcpJl2MAAAAqkoIWAACAKCloAQAAiJKCFgAAgCgpaAEAAIiSLsdEodwd/qx/qnn9p+wBqnkPWP9U8/pP2QOUky7HAAAAVCQFLQAAAFFS0AIAABAlBS0AAABRUtACAAAQJQUtAAAAUVLQAgAAECUFLQAAAFFS0AIAABAlBS0AAABRUtACAAAQJQUtAAAAUVLQAgAAECUFLQAAAFFS0AIAABAlBS0AAABRqsnlcrlyTwIAAABK5QotAAAAUVLQAgAAECUFLQAAAFFS0AIAABAlBS0AAABRUtACAAAQJQUtAAAAUVLQAgAAECUFLQAAAFGqqoL2+uuvT2pqapJPP/20pL/Xo0ePpGPHjnU6l/bt2yf77rtvnR4T/oj1T7WzB6hm1j/Vzh6oXFVV0FaqDz74INl9992TJZZYImnZsmWy0korJaeddloyefLkck8N6t3UqVOTE044IVlsscWSFi1aJF27dk0ef/zxck8L5or0BVH6Aq3Qry+//LLcU4R69dprryV//etfkwUWWCCZf/75k8022ywZPXp0uacFc80H6oCkcbknQO18/vnnSZcuXZJWrVolhx9+eLLgggsmL7zwQjJgwID8k/y9995b7ilCvb+gv+OOO5Kjjz46WWGFFfJnYLfccstk5MiRyYYbblju6UG96tu3b9KrV6/fZLlcLjnkkEPyVwAWX3zxss0N6tvrr7+ef55fcskl8697Zs6cmVx++eVJ9+7dk5dffjnp0KFDuacI9Uod8P8o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7IcffpjUF02hAAAAqEgKWgAAAKKkoAUAACBKCloAAACipKAFAAAgSlXZ5bhQJ9ZzzjknmB9++OHBvHHjxpns2GOPDY697LLLGmznuhiU+/tUSeu/VIU6SK644opJpfj555+L7nD+1ltvJdW2/su1B0KPucIKKwTH7rjjjsF8scUWK/rxdtppp2Derl27oudX6r/ho48+msk++OCD4NgzzjgjmH/77bdJpSv3HqjmnwEbbbRRMD/xxBMz2WabbVbSv1/o+1rK2FLH77PPPsGxN910U9KQlXv9V/seKOSss84qupvxMsssE8ybNWuW1JdHHnmk6G7hhTqUh7z77rvBfLXVVkvqiy7HAAAAVCQFLQAAAFFS0AIAABAlBS0AAABRUtACAAAQpWyb3grTtm3bTDZo0KDg2IMOOiiYf/7558F8wIABmez6668veY7Vqnnz5pnsl19+KctcKOyJJ56o+C7H8847bzDv0aNHg+hyXK2aNGlSdNft+lSou2JddB4NdYUt1Cl2vvnmC+b/+Mc/gvnXX39dy9lRiQo9391www3BfIcddih6/ZfaCbeU8XVx7EJf42OPPRbMx40bV9JjEreVVlopmF955ZVFfxJCqet08uTJmeztt98Ojr3vvvuC+bPPPhvMX3jhhUx25JFH1rrL8ZQpU5KGxhVaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACBKFdMUatFFFw3mjzzySCZbffXVg2O//PLLYL755psH87Fjx5Y0x2q18847F93IZM0115wLMyJk2WWXDeY77rhjUulCTRlSL7744lyfC//7ubq+FGry9PPPPwfzTz/9NJN16NCh1o2vCunTp08w/89//hPMBw4cWNJcqA5///vfg/l2221Xb03R7rrrrmB+zz33FN2EqpTmVIUUGlvo2FdffXXRxyYe/fv3D+YHH3xwMF9mmWWKPvZPP/0UzE866aRg/u6772aykSNHJnWhVatWmezoo48u6RjTp0/PZIMHD04aGldoAQAAiJKCFgAAgCgpaAEAAIiSghYAAIAoKWgBAACIUsV0OT7rrLOK7pL59ttvB8eus846wXzatGlJtQp14OzcuXNw7GWXXRbMV1lllWDet2/fWs6Ouuw6fckllwTztm3bJg3ZO++8E8w7duxY9DFatmwZzNddd91M9vLLL5cwO2rjjTfeyGS33nprSf/eoQ6NV111VXDsxx9/HMyfeOKJpLZatGgRzO++++5Mtummm5Z07EKd+M8555xM9ssvv5R0bOIW6t578sknB8fOnDkzmNfU1BS9dgv9fClFoS6sheZRSKnjqUzt27evl27GqYceeiiTXXjhhcGxddW5uBS33XZbJltiiSVKOkaoo/Gdd96ZNDSu0AIAABAlBS0AAABRUtACAAAQJQUtAAAAUVLQAgAAEKWaXC6Xq4RucTvttFMwv/TSSzPZIossEhz71FNPBfMzzjijwXQsC2nVqlUwb926dSbba6+9gmN32223ojtzFnq8a6+9NpgX6kr65ptvJsUqcpnWm4a+/kvp7vfggw8Gx6600krJ3HbzzTcX1VHvjxTqzPnwww/Xurvfhx9+mMk6dOiQzG3lXv8x74GG7i9/+UvRnbSXW265ko49YMCAon+eNXTl3gOxrv9XXnklk6211lolfY/vueeeYL7PPvtkssmTJyf1Mec/M+/Qv1mhsYW6+X/33XdJQ1Du9R/zHgh9WsFzzz1X0jGefPLJYL7FFltksl9//TWZ2zbeeOOiuzA3bdo0OPbTTz8tuot+6LVRufeAK7QAAABESUELAABAlBS0AAAARElBCwAAQJQUtAAAAESpcVIh7rzzzmD+0UcfZbLLL7+8pC5ha6+9djAfOnRoJjv33HODY7/44otgPu+882aynXfeueiOgqllllmm6A63n3/+eXDsiBEjgvl7772Xyf71r3816G6A/H/uvffeBtHNePz48cH8vPPOy2TvvPNOnTzm008/ncn23HPPko6x7LLLZrK99947OHbYsGElHRtSEydOLLrjfqldjrfaaqtMdvbZZwfHlqMzJ3F0qy00fuWVVy76GNtvv33Rn1BRqJN8Xcx7nXXWCY71+oU/Uug5eW4/b15wwQXB/Igjjgjm88wzT9EdikMdm1Mff/xxEgNXaAEAAIiSghYAAIAoKWgBAACIkoIWAACAKNXkcrlcUQNLfDN+Q9a4cbgX1gknnBDMDz744GC+5JJLFv2YN910UzDfdtttM9n8889fdPOQ1HXXXVd0o6wXX3wxiVGRy7TeNPT1v+uuuxbdqKjQ+q8Lr776ajAfOHBgMH/44Ydr/ZhLL710MH/55Zcz2cILL1zSsSdPnlx0U60vv/wyqdT1H8MeqCT77bdfML/22mtrfezmzZsH8+nTpycNWbn3QKzr/5VXXslka621Vknf40Jfe2h8KWMLja+LeaTuueeeoptrhp7rG5Jyr/+Y90DTpk2LbiS75ZZbBvMff/wxmG+22WZFvfb4I7179y66eV+rVq2KbjBbyKmnnhrMzzzzzCTmPeAKLQAAAFFS0AIAABAlBS0AAABRUtACAAAQJQUtAAAAUaq/dqcN2HzzzRfMb7/99mC+3HLLBfN999236Mfca6+9ih5baB4XXXRRMI+1czGl23333YP5oEGDgnl9djS++eabM9mhhx5aUofAutChQ4dgXmpH45Bff/11rnYzhrp07733FrWmqVxjx47NZJ07d6637raldsKti2N/9913wXznnXcuaS5UpmnTpmWyt99+u6Qux4U+fST06ShXXnllcOzRRx8dzDfccMOi65RCPv7446JrjzfffDOpRK7QAgAAECUFLQAAAFFS0AIAABAlBS0AAABRUtACAAAQpYrvcty1a9dMdskllwTHdunSJZjPnDkzmE+YMCGT3XrrrcGxbdq0CeY77LBDJuvRo0dw7GmnnRbMqR5LLbVUMF9++eXr7TEPPPDAYH7HHXfM1W7GoU6Aqeuvv77eHvOGG26ot2NDIc2bN6+T43zzzTdF/zyjMu29995Ff2LCyiuvHMzfe++9oh+v0DGGDh1a9DFyuVxSirPOOquk8XDGGWcE844dOwbzrbbaKphvv/32RWWlmj59ejA/++yzg/mNN94YzD/66KOkWrhCCwAAQJQUtAAAAERJQQsAAECUFLQAAABEqeKbQp1//vlFN3/673//G8zPOeecYF6ouVQpTj311Ew2cODA4NgRI0YE88022yyYv/nmm7WcHdVk4sSJwXzkyJHBvL4aQBVq/nTbbbcF80UXXbTWj1noa7n44otrfWwo1UEHHVQnx/n222/r5DhUltdff72kvBShRpepmpqakvKQxx57rN5ei1G5mjZtmslWWGGF4NiVVlopmdtCTc1effXV4Nh77713LswoTq7QAgAAECUFLQAAAFFS0AIAABAlBS0AAABRUtACAAAQpYrpcnzssccG8/XWWy+TzZw5s6TOkg888EBSX84888xMtskmmwTHduvWLZj37t07mOtyTCkefPDBYP7pp5/W+thLL710MO/QoUMmu/766+utm3Eh06ZNC+Yff/xxvT0mpBZZZJFM1rp165KOUahD/zXXXPOn5wV/xkknnRTMc7lc0ccoNHbvvff+0/Oi8nXs2DGYn3zyyZlsl112qZPHnDFjRiZr3Li00uqpp57KZE888USt5lWNXKEFAAAgSgpaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACBKFdPlePvttw/mjRpla/Zbb711rnczLuTXX3/NZNOnTy/pGHvttVcwP/fcczPZuHHjSjo21WPeeecN5k2aNAnmTZs2zWQbbLBBcOywYcOC+cILL5w0BBMmTCj3FKhS++yzTyZbaqmlSjrGa6+9Fsy//PLLPz0v+F9Cz+s1NTUlHSM0/uqrrw6O/e6770o6NtXlgAMOCOaldDSeOnVqMD/vvPOC+cSJEzPZ+eefn5SiZ8+emUyX49K5QgsAAECUFLQAAABESUELAABAlBS0AAAARElBCwAAQJQqpsvxRx99FMxDXVdj7WhaqHvgW2+9Fcx1NKYuOoXffPPNwbxVq1aZbJNNNkkasiFDhgTzUrsSQqlC+yV1+OGHF32MadOmldSBE+rCSiutVPTPjFwuFxxbKA91Lr7mmmtKniPV49JLLw3mhxxySNHHKPS6pl+/fsH8p59+CuZHHXVUUlubb755JjvppJNqfdxq4wotAAAAUVLQAgAAECUFLQAAAFFS0AIAABClimkK9dJLLwXzffbZJ5O1adMmmdu6du0azHv37p3JunfvHhw7adKkYH7GGWfUcnZQ2I477pg0ZJ988knRjZ5GjRoVHDt27Ng6nxfMqU+fPsF8ySWXLPoYTz/9dEk51IUtttgimLds2bLo5pWF3HTTTZns9ddfL+kYVJfddtstmDdqFL5GN3r06KIbSP3888/J3Pb222/P9cesRK7QAgAAECUFLQAAAFFS0AIAABAlBS0AAABRUtACAAAQpYrpcjx06NBgvummm2ayHXbYITj2ySefDOYjRowI5k2bNs1ku+++e3DscsstV3RXtm+//bakzm7PPvtsMKfyFFqj33//fTBfcMEFa92FsqF3M/7rX/8azD/88MN6nhFkrbXWWvXWjf6+++6r9TGgVNtvv30wz+VyRR+j0NizzjrrT88LijF16tR662bcvn37Wh/jxhtvrJO5VDtXaAEAAIiSghYAAIAoKWgBAACIkoIWAACAKCloAQAAiFLFdDmePHlyMO/du3cm22effYJj+/fvH8xPP/30euti9sYbb2Sy66+/Pjh2woQJtZ4HcXvttdeC+SKLLBLMDz/88Ew2cODA4NjWrVvXcnZJMnPmzGBeqLPyjBkzil7/559/fjDXzZiGZPPNNw/m8847b9HH+Oqrr4L5dddd96fnBf9L3759g/lGG21U9PN96JMbCr0WS3333XclzREuuuiiYH7qqacG8w4dOmSyXXfdNTj2nXfeKel5/YgjjkiK9eijjwbz0aNHF30MCnOFFgAAgCgpaAEAAIiSghYAAIAoKWgBAACIUk0ul8sVNbBAUxeYG4pcpvWmktZ/p06dgvlWW20VzI866qhgPm7cuEx2xhlnBMc2a9YsmI8aNSqTffrpp8Gx1azc67/S9kBd2WOPPTLZtddeGxzbvHnzoo/bq1evYD5y5MikWpV7D1TS+m/Tpk0wf+ihh4L5WmutVfS/SaHv0zrrrBPMX3/99T+YKQ1l/cewB0488cRgPmDAgEzWpEmTepvH1KlTg3mXLl1KakRFaXvAFVoAAACipKAFAAAgSgpaAAAAoqSgBQAAIEoKWgAAAKKkyzFRKHeHP+ufal7/1b4H5plnnmB+++23Z7LtttuupGM///zzmWyjjTZqsOugXMr9tVfS+l977bWD+UsvvRTMGzUKX/uYOXNm0V2Lt9hii2D+3Xff/cFMaSjrP+Y9EOpG//e//z04tmPHjiUd+9lnn81k5557bnDsgw8+WNKx+S1djgEAAKhICloAAACipKAFAAAgSgpaAAAAoqSgBQAAIEq6HBOFcnf4s/6p5vVf7XugdevW9dah9bnnniu6y3E1K/ceqKT137Jly5K6HK+yyirB/K677spk/fr1C47VzTju9V9pe4D46HIMAABARVLQAgAAECUFLQAAAFFS0AIAABAlBS0AAABR0uWYKJS7w5/1TzWv/2rfA/PMM08wP/XUUzPZySefHBw7cuTIYL7ffvtlss8//7zkOVa6cu+Bal7/lF+513/KHqCcdDkGAACgIiloAQAAiJKCFgAAgCgpaAEAAIiSplBEodwNEax/qnn9p+wBqnkPWP9U8/pP2QOUk6ZQAAAAVCQFLQAAAFFS0AIAABAlBS0AAABRUtACAABQ2V2OAQAAoCFxhRYAAIAoKWgBAACIkoIWAACAKCloAQAAiJKCFgAAgCgpaAEAAIiSghYAAIAoKWgBAACIkoIWAACAKFVVQXv99dcnNTU1yaefflrS3+vRo0fSsWPHOp1L+/btk3333bdOjwl/xPqn2tkDVDPrn2pnD1Suqipoq8GZZ56Z36x1vfGgIRo1alR+vYd+vfjii+WeHtS7MWPGJLvsskuy7LLLJi1btkwWXnjhZKONNkruv//+ck8N5jqvgahGU6dOTU444YRkscUWS1q0aJF07do1efzxx5Nq0rjcE6DufPHFF8lZZ52VzDvvvOWeCsxVRx55ZLLOOuv8Jlt++eXLNh+YWz777LPkxx9/TPr06ZN/MTN58uTkzjvvTLbddtvkqquuSg4++OByTxHmCq+BqFb77rtvcscddyRHH310ssIKK+SvRG+55ZbJyJEjkw033DCpBgraCnLccccl6667bvLrr78m3333XbmnA3NNt27dkp133rnc04C5Ln3Rkv6a0+GHH5507tw5ufDCCxW0VA2vgahGL7/8cjJ8+PDkvPPOy++B1D777JO/S6F///7J888/n1SDqr7l+N5770222mqr/FntZs2aJcstt1xy+umn558MQ1577bVk/fXXz1/OX2aZZZIrr7wyeNl/wIAB+atD6TGXXHLJ/IJK8/r09NNP58/OXHzxxfX6OFSOSlr/qfQq1YwZM+r9cagclbYHZplnnnnyjztx4sS59pjEp5LWv9dAVOseSNf9PPPM85uTl82bN08OOOCA5IUXXkg+//zzpBpU9RXa9JL8fPPNlxxzzDH5/44YMSI59dRTkx9++CF/pmNOEyZMyJ8F33XXXZM99tgjue2225J+/folTZs2Tfbff//8mJkzZ+Zv83r22WfzC2vllVdO3n777eSiiy5K3n///eSee+4pOJf0744fP76oebdq1Spp0qTJ7N+nG++II45IDjzwwGS11Vb7098PqkulrP/Ufvvtl/z000/5J/X0am06/7XXXvtPfV+oHpW0B37++edkypQpyaRJk5L77rsvefjhh5PddtvtT31fqA6Vsv69BqKa98Abb7yRrLjiiskCCyzwmzFdunTJ/3f06NH5orri5arIkCFDcumX/Mknn+R/P3ny5MyYvn375lq2bJn75ZdfZmfdu3fP/70LLrhgdjZ16tRcp06dcossskhu2rRp+WzYsGG5Ro0a5Z555pnfHPPKK6/M//3nnntudrb00kvn+vTpM/v36ZzSMcX8Gjly5G+Of9lll+VatWqV+/bbb2fPd9VVV62D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0NdSKC/U5KqU5lflbojQ0P9NqGzlXv/Vvge++eabYN6mTZt6+b4++eSTwbGbbrppUq3KvQcqaf2vuuqqwfzvf/97Sc3I7rnnnlrP5dhjj81k5557bknNnzbYYIOk0pV7/VfaHmhIQj9HrrnmmuDYlVdeOZgvX6BJU12sm9C/+0knnRQcO3jw4KS+aAoFAABARVLQAgAAECUFLQAAAFFS0AIAABAlBS0AAABRalzuCcQs1NW3lG7GMQh9PSNHjizpGIU6P+uYR12Yf/75g/m8886byaZMmRIc26JFi1rPY8KECcF86tSptT425XXBBRcE83POOadeHq9p06bBvHHj8I/sGTNm1Ms8qEwdO3YM5r179w7m22+/fdEdV7/44ouS5hLqUNyoUfhay7rrrlsnn7AA5XDAAQcE84MPPjiTde7cOWnI7rjjjqShcYUWAACAKCloAQAAiJKCFgAAgCgpaAEAAIiSghYAAIAo6XJcRKe8Qnmh7r2lGDRoUDI3VcPXSPmccMIJwXzjjTeu9bELdcVeYYUVgvnSSy+dyb766qvg2MUXXzyY53K5ouf3+uuvB/N11lmn6GPQMF100UXB/K9//Wu9dLrfcMMNg/lKK60UzN95551aPyYUMt988wXziy++OJPtvPPO9TaPQt2P+/fvH8x1Oaa+tWnTJpP94x//CI494ogjav06g8JcoQUAACBKCloAAACipKAFAAAgSgpaAAAAolSVTaFKbYBUF00+CjVGGjhwYDI31efXWKgBw9z+Gimfs88+O2nICjV/KtRwqhRt27at9TFomDp27BjMV1111Xp5vFdffTWYf/zxx/XyeFSX1VdfvU6OU8rzZqGGTs2bN6/1PH755ZdaHwP+yD777BPMjz/++Ey28sor1/rx7rvvvqIbsaWefvrpoo99wAEHBPOrrroqiZkrtAAAAERJQQsAAECUFLQAAABESUELAABAlBS0AAAARKniuxyHOux27969oroZl9K1uT67GW+88ca1PjYNz/zzzx/Mn3rqqaKPkcvlgvn777+fyaZMmRIc+9xzzwXzTz/9NJi3b9++6LGlePfdd4P5888/X+tjU16FuhY/+uijwXzhhReul3msscYawfy0004L5qeeemownzx5cp3Oi8rQuXPnBtNZeYsttqj1sYcPH17rY1BdCnXoPvPMM4P53/72t2DepEmT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AICKpKAFAAAgSgpaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACBKCloAAACipKAFAAAgSgpaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACBKCloAAACipKAFAAAgSgpaAAAAoqSgBQAAIEo1uVwuV+5JAAAAQKlcoQUAACBKCloAAACipKAFAAAgSgpaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACBKCloAAACipKAFAAAgSlVV0F5//fVJTU1N8umnn5b093r06JF07NixTufSvn37ZN99963TY8Ifsf6pdvYA1cz6p9rZA5WrqgraSvb6668n2267bbLgggsmLVu2zG+8//u//yv3tGCusP6pVmPGjEl22WWXZNlll82v/YUXXjjZaKONkvvvv7/cU4O5YurUqckJJ5yQLLbYYkmLFi2Srl27Jo8//ni5pwVzxSuvvJIcfvjhyaqrrprMO++8yVJLLZXsuuuuyfvvv59Uk8blngC199hjjyXbbLNNsuaaayannHJKMt988yUfffRR8sUXX5R7alDvrH+q2WeffZb8+OOPSZ8+ffIv6CdPnpzceeed+RM8V111VXLwwQeXe4pQr9KrXHfccUdy9NFHJyussEL+KtyWW26ZjBw5Mtlwww3LPT2oV4MHD06ee+65/InN1VdfPfnmm2+Syy67LFlrrbWSF198sc6vLDdUCtrI/fDDD8k+++yTbLXVVvkn9EaNXHSnelj/VLv0hXv6a07p2frOnTsnF154oYKWivbyyy8nw4cPT84777zkuOOOy2fpz4T0RXz//v2T559/vtxThHp1zDHHJDfffHPStGnT2dluu+2WrLbaask555yT3HjjjUk1qOpXf/fee2/+hXB6VrtZs2bJcsstl5x++unJr7/+Ghz/2muvJeuvv37+lpZlllkmufLKK4O3vgwYMCBZfvnl88dccskl80+qaV4f0kX83//+NznzzDPzL+Z//vnnZObMmfXyWFQW659qVwl7IGSeeebJP+7EiRPn2mMSn0pY/+mJzHS9z3nipnnz5skBBxyQvPDCC8nnn39eL49LZaiEPZDOp+kcxWwqvVMhvQX5vffeS6pFVV+hTW9LSW9PTM9upP8dMWJEcuqpp+av+qRn++Y0YcKE/Fnw9L70PfbYI7ntttuSfv365RfR/vvvnx+TvpBOb/N69tln80+uK6+8cvL2228nF110Uf5e9nvuuafgXNK/O378+KLm3apVq6RJkyb5///EE08kCyywQPLll18m22+/ff5x0nvo99577/zjpk/sEGL9U+0qYQ/Mkp7MmTJlSjJp0qTkvvvuSx5++OH8WXqo5PX/xhtvJCuuuGL+58CcunTpkv/v6NGj8wUFVOoeCMnlcvmT/WlRWzVyVWTIkCG59Ev+5JNP8r+fPHlyZkzfvn1zLVu2zP3yyy+zs+7du+f/3gUXXDA7mzp1aq5Tp065RRZZJDdt2rR8NmzYsFyjRo1yzzzzzG+OeeWVV+b//nPPPTc7W3rppXN9+vSZ/ft0TumYYn6NHDly9t9bffXV8/NNfx1xxBG5O++8M//fdNzuu+9eZ9874mf9U+0qcQ/MOe9Zf57OYeedd86NHz++1t8zKkclrv9VV10117Nnz8zXMWbMmPzY9LGhkvdAyLBhw/Ljrrvuuly1qOortOktA7OkTTXS2wG6deuWb6QxduzYZI011pj9540bN0769u07+/fpGZn09+nZmfQWhHXXXTe5/fbb82djVlpppeS7776bPbZnz575/6YNCtJbA0Latm1bdFe+Oef1008/5ZuAHHLIIbO7uu64447JtGnT8l/Haaedlr/1AH7P+qfaVcIemCVtiLPzzjsnX331Vf7KQXrLXLoPoJLXf3pXQnpb5+/Nujsn/XOo5D3we2PHjk0OO+ywZL311ss3C6wWjav94w5OPvnk/C0G6e0Fc0pv25pTen99eivjnNLbXFLp51mlC/mDDz7I36/epk2b4ON9++23BeeSPvn26tXrT2/G9PaHOe255575DZm+h8QLekKsf6pdJeyBWdIXUOmvWU1xNttss3z375deein/uYtQqT8DQu9N/OWXX2b/OVTyHpjTN998k39PcHpL8qz3l1eLqi1o02YZ3bt3z7/vIr2Kk74RPF1M6edZpp9n9mcay6R/J+0qlnaWDPmj93GkZ9PHjRtX1OOkn7U56w3g6QZLN+Siiy76mzGLLLLI7Hv+4fesf6pdpeyBQtKrtenVg/R9Wx06dCjquFSPSln/7dq1y/dQ+L2vv/569s8IqOQ9MGcBvsUWW+S/rmeeeabq1n7VFrSjRo1Kvv/+++Suu+7Kfwj9LJ988klwfHobV9p0Y86zM7M+tLh9+/b5/6ab4c0330w22WSTks+Ip5340o5pxUhvWejRo0f+/6cfzZDeopA+oc/5oiWdb6rQWSKqm/VPtauUPVDIrFstf3+VASpp/Xfq1Cn/+/Tq2pyNodI7E2b9OVTyHph1R8I222yTn0/aLHOVVVZJqk3VFrSzLsOnncBmSd9vdPnllwfHz5gxI38LY9oJbdbY9PfpC+b0RXUq7Xz20EMPJddcc03ms//SFxfpmZvf365Q23vn08dMP2fquuuum32Pfuraa6/N3+//v170UJ2sf6pdpeyB9Ba2WXckzDJ9+vTkhhtuyN9uWY0vbKie9Z/eiXD++ecnV1999ezPoU1vQR4yZEjStWtXHY6p+D2QXtndbbfd8m+xSj+GKH3vbDWq2oI2fVN269at82+YPvLII/NnUoYNG/abhT2n9NL94MGD8/fJp/fM33rrrfl28OmT6KzW2elHhaTNONIGNenZkw022CC/0NI3aKf5o48+mqy99tp1eu/8mmuumW8X/q9//Su/2dLbJ9KzTukb00888cSqu+WA4lj/VLtK2QPpbcXp1an0CsPiiy+efw/VTTfdlH/MCy64IP9RFFCp6z8tWnfZZZf88316cif97M+hQ4fm55me6IRK3wPHHnts/qPa0iu06cf+3Hjjjb/58969eydVIVfF7brT9tnrrrturkWLFrnFFlss179//9yjjz6aaYmdtutOW8O/+uqrufXWWy/XvHnzfLvtyy67LPMYaevuwYMH58c3a9Ys17p161znzp1zgwYNyk2aNKlgu+7aSB9z4MCB+WM2adIkt/zyy+cuuuiiOjk2lcP6p9pV4h645ZZbcr169cotuuiiucaNG+cfL/39vffeW+tjU1kqcf2npkyZkjvuuONybdu2zT/mOuusk3vkkUfq5NhUlkrcA7M+Uigp8Kta1KT/U+6iGgAAAErVqOS/AQAAAA2AghYAAIAoKWgBAACIkoIWAACAKCloAQAAiJKCFgAAgCgpaAEAAIhS42IH1tTU1O9M4A+U++OSrX+qef2n7AGqeQ9Y/1Tz+k/ZAzTkPeAKLQAAAFFS0AIAABAlBS0AAABRUtACAAAQJQUtAAAAUVLQAgAAECUFLQAAAFFS0AIAABAlBS0AAABRalzuCZC12WabBfPDDz88mG+66abBfIMNNshkr7/+ei1nB6Vr3759ML/lllsy2VlnnRUce//999f5vAAAiJsrtAAAAERJQQsAAECUFLQAAABESUELAABAlBS0AAAAREmX4zLr1atXJrv77ruDYz/66KNgvtpqqwXzDz/8sJazg9I0b948mA8bNiyYv/fee5nswQcfrPN5AQCVa/755w/mxx9/fCbbeuutg2PXXHPNYP7tt98G86uuuiqTffXVV8Gx1113XTCfPn16MKc0rtACAAAQJQUtAAAAUVLQAgAAECUFLQAAAFFS0AIAABClmlwulytqYE1N/c+mgi2xxBLB/J133slkzzzzTHDs/vvvH8zHjRuXVLoil2m9sf6Lc9BBBwXzY445Jpivs846meynn36q83nFrtzrP4Y9sPzyywfzZs2aZbLlllsuOHbbbbcN5vvtt1/R85g4cWIwP+OMM4L5TTfdVHRHzWpW7j3Q0Nd/Ka89br311uDY9dZbr6Rjv/LKK5nsgQceKHqdpz7//PNMputrw1v/DWkPhJ7T/+i1c+fOnZOG4Isvvgjmt9xySya79tprg2Or+dNLcv9jD7hCCwAAQJQUtAAAAERJQQsAAECUFLQAAABESUELAABAlHQ5rmOFuqlddtllwXz06NGZrF+/fnU+r9iVu8Of9V9cV9lXX301OPbss88O5oMHD67zeVWicq//cu2B008/PZNtsMEGwbFrr712MJ933nmL/n5OnTo1mN9///3BfPPNN89kCyywQHBsocd88803G2xXzoak3Hsg1p8Bb731ViZbccUVg2M//fTTYL7ooosG80JrvRQjRozIZAcccEBw7H/+85+kWpV7/TekPdC0adNgfueddwbzVVddNZNdcsklJT3mwgsvHMwPOeSQTDb//PMHxzZp0qToxyu0F0M/c6ql+3FOl2MAAAAqkYIWAACAKCloAQAAiJKCFgAAgChpClXHhg0bFsxXWWWVYK75RxwNEaz/rJtvvjmTLb300sGxG220UTD/9ddf63xelajc67++90CowVjq2WefLbo5RyGff/55JhsyZEhw7OTJk4P5Aw88EMxHjRqVydq0aVPSv+H7779f9M+LalbuPRDrz4Dtt9++6KY1Dz30UDBv1apVML/qqqsyWc+ePZPa+u9//xvM99xzz6L3YaUp9/qPeQ/MbVtttVUw//vf/150Q8NCja8K7dEdd9wxmE+fPj2pFJpCAQAAUJEUtAAAAERJQQsAAECUFLQAAABESUELAABAlHQ5LkLr1q2D+f/93/9lsu222y44dsCAAcH8oosuquXsqkO5O/xV8/rfcMMNg/mTTz5ZdHfWjz76qM7nVU3Kvf7LtQcOOuigTHbFFVcExx533HHB/P777y96PRbq5vrWW28F88UXX7zo79Pzzz8fzHv16pXJpk6dGhxbzcq9B6rhZ8BSSy0VzC+44IKiO6v++OOPwbFjxowJ5qGu5SuuuGJw7JQpU4L5kUceGcyvvfbapFKUe/1Xyx4oh1CX7m7dupV0jNVXX72kfRcjXY4BAACoSApaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACBKjcs9gYZknnnmCebXXXddMN9iiy0y2b777hsce+utt9ZydlAehx56aDC/5ZZbMpluxtSlIUOGZLLnnnsuOLbQ2iulY3CzZs2K7mZcqkcffTSY62jM3NakSZNgfvzxxxfdzTj11VdfZbLDDjssOPa+++4ren59+/YN5meddVYwP+GEE4L522+/ncleeumloucBc8P48ePLPYWK4AotAAAAUVLQAgAAECUFLQAAAFFS0AIAABAlTaHmcPXVVwfz7bbbLpifeOKJmUzzJ2K1ww47BPNdd901mPfq1aueZ0S1mzFjRiZ799136+3xvv3222B+0EEHBfPLL7+86MZSG2ywQTBfZJFFip4H1IV+/fqV1ACwkFBjzHfeeSeprauuuiqYL7fccsH82GOPDeaPPPJIJuvUqVNw7GeffVbSHKGufPPNN+WeQkVwhRYAAIAoKWgBAACIkoIWAACAKCloAQAAiJKCFgAAgChVZZfjYcOGBfO99tormJ977rkl5RCj5s2bB/NCXWWfeeaZep4RNAxDhgwJ5quuumom+9vf/hYcu+mmmwbzJ598MpMdccQRwbGjRo36HzOF3zr44IMz2aBBg4JjZ86cGcxPOumkYF6fHcdDTj755GC+4IILBvP99tuvqM7Hf9S1/8svvyxpjlBI6OdFaueddy76GBMnTgzmU6ZMSaqdK7QAAABESUELAABAlBS0AAAARElBCwAAQJQUtAAAAESpJpfL5YoaWFOTxGjFFVfMZK+99lpw7D333BPMDzjggGA+bdq0Ws6OYhW5TOtNrOu/FFdccUVJXR7POOOMep4RDWX9V8seqAsHHnhgSV3xF1hggUw2ffr04NhC3Y+vvfbapNKVew/Euv7feuutorutvvTSS8F8/fXXT2IUek23zTbbBMeeeuqpwfzMM89MGoJyr/+Y98DcNv/88wfzK6+8MpjvvvvuRR/7sssuC+ZHHXVUUun+1x5whRYAAIAoKWgBAACIkoIWAACAKCloAQAAiJKCFgAAgChVfJfjjz76KJMtvvjiwbFrrbVWMH/33XeThtqxObXtttsWfYyRI0cG80KdnxuKcnf4i3X9l2L06NHB/I477qi3LsddunTJZKecckpwbI8ePYL5r7/+Gsw333zzort4NnTlXv/Vsgfq0xJLLBHM//Wvf2Wynj17lnTsgw46KJgPGTIkqRTl3gMNff23adMmmL/88suZbKmllgqO/fvf/x7MzzvvvCRGnTt3Lur7kfr222+D+VZbbZXJXn/99aTa1n8Me6ChuPTSS4P5oYceWvQxbr/99pKe63/88cek0ulyDAAAQEVS0AIAABAlBS0AAABRUtACAAAQpcZJhVtmmWUy2WGHHdagmz/tsssuwfyGG24I5k2bNi362GPGjAnmXbt2DeZTpkwp+tjE7bPPPqu3Y6+99tpFNz6Yd955g2MLNfNYf/31g3mfPn0qpikU8fviiy+C+ZZbbpnJzjrrrODYY445JphffvnlRc+jkhpF8f9ZffXVg3moAdTkyZODY5988smk0hsdnn766cGxhZoR7rDDDg2iKRTlNd988wXzCy64IJPttNNOJR17/PjxmWzgwIFV2/zpz3KFFgAAgCgpaAEAAIiSghYAAIAoKWgBAACIkoIWAACAKEXX5bhZs2bBfNiwYUV3Dxs+fHjSUOa933771apjZeqFF14ouhva5ptvHhy7wAILBHNdjqvHIossEsxXXHHFWq/z8847L5j/+9//zmR77rlncOx3330XzM8888xg3rp16z+YKTQMM2bMyGT9+/cPjl133XWDebdu3YL5tddem8nmmWeeoscSv5qamkz2888/B8dWWvfeX3/9NZNdddVVwbH77rtvMO/Ro0edz4uGq9CnLFx99dXBfLfddiv62KF6JLXXXntlsrFjxxZ9XP4fV2gBAACIkoIWAACAKCloAQAAiJKCFgAAgCgpaAEAAIhSdF2OC3Uu3WmnnYL54MGDM9mECROSuW3nnXcO5v/85z8z2X/+85+iv5bUNddcE8znn3/+TPb999//j5lSre65555g/ve//z2YN2nSJJN16tQpOLZQvs466xTdzbiQp59+Ophvt912JR0HGro99tgjmBf6mZHL5TLZ6aefHhyry3FlCq2BUFYtvv7665JeRw0YMCCTbbrppsGxjz/+eC1nx9zSsmXLkp4Hd91113rpZpx67LHHij42hblCCwAAQJQUtAAAAERJQQsAAECUFLQAAABEKbqmUKU677zz6u3YzZo1y2SXX355cOwuu+wSzEMNOi655JLg2ELNrDbbbLNgfvXVV2eyESNGBMdqFsWHH34YzFu1ahXMt95660zWvHnz4NiXX365pMcsxQ477BDMZ86cWetjQ0Py5Zdf1voYLVq0CObLLbdcMP/oo49q/Zg0LAsttFAw32KLLYL5ww8/nFS6jz/+OJjPM888mezEE08MjtUUqmEKPef961//Kum1eiGh1+V77rlncKz1Ub9coQUAACBKCloAAACipKAFAAAgSgpaAAAAoqSgBQAAIErRdTku1On39ddfD+adO3fOZE888USdzKV79+6ZbN999w2OnTRpUjC/6qqriv4aW7duXXQ349T06dMz2YABA4JjZ8yYEcypHm+//XYwHz9+fDA//vjjM9l1112X1JemTZsG83322afo+UHMFl988Vofo0mTJsG8TZs2wVyX4ziMGjUqmL/77ruZbJVVVgmObdu2bZ3Pq5q6RNMwbbTRRrXuZjxx4sRgvscee2Qy3YzLwxVaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACBKCloAAACiFF2X46lTp5bUibU+uxyHuqh+/vnnwbFrrrlmMA91NO7Tp09w7HHHHRfMF1100WC+9957Z7LnnnsuOBbef//9YP7www8H87322iuTNW/ePDh23LhxtZxduJvgHx372muvrfVjQkPqZvzII4/U+tiTJ08uuhsu8fj111+DeS6XK/oYBx10UDAfMmRIUul0eI5foU8Cue2222p97L///e/BXEfjhsMVWgAAAKKkoAUAACBKCloAAACipKAFAAAgSgpaAAAAohRdl+NCrrnmmmB+4403ZrJvv/02OHb48OHBfJVVVgnmm2yySSbbaqutgmPnn3/+YP7kk09msk6dOgXHPvvss8F8tdVWC+YffvhhMIdS/POf/wzm22yzTdFrd+bMmcF80KBBmaxZs2bBsf369Qvmp556akkd0aku7dq1y2SNG4d/9BXqUl+fQnumUFfO5ZZbLpg3atSo6H132mmnBcf+8MMP/2OmxOjss8/OZEOHDg2O7dixYzDfYYcdgvndd9+dVIpdd9216LF10TWXulfoeXC++ear9bG7dOkSzCdNmtSg10eHDh0yWc+ePYNj//a3vwXzm2++OZMNHDgwaWhcoQUAACBKCloAAACipKAFAAAgSgpaAAAAolSTy+VyRQ2sqUliFGo6U+iNz19++WUwb9GiRTBfaqmlMtmYMWOCY//yl78E8+eee67o5lQPPPBAMJ8xY0ZS6YpcpvUm1vVfn/bbb79MduGFFwbHtmrVqujva6F/60J7q1BTtEpS7vUfwx5Yfvnlg/nIkSMz2aeffhoc26tXr1o3GCvUXKZQQ6dQU7MmTZokdfFvE1o3oSZZqXHjxiUNWbn3QENf/6U455xzgvkxxxxT0muMCy64oOjXKa+++mow//XXX5P6EnrdVagpWt++fYP5zz//nMkWX3zx4NgpU6Yklbr+Y9gDhZpCHXjggZnsiiuuqJPHDK3fhtRgr2nTppls3nnnLekYF198cSY79thjk7ntf+0BV2gBAACIkoIWAACAKCloAQAAiJKCFgAAgCgpaAEAAIhSxXc5DunatWsw79evX0nHCXWt/Oijj4JjH3zwwWA+YsSITPb999+XNI9qUO4Of5W0/svRafaAAw4I5gcddFAmmzhxYkn7thr2S7nXfwx74Omnnw7mG2ywQdHfz/PPPz+Yt27dOphvt912maxNmzZz/d8w1Ik1dffdd2ey/fffPzh25syZSUNW7j3Q0Nd/Xdhjjz2C+dVXXx3MW7ZsWfSx//nPfxZ97OnTpwfH/vvf/w7mO+ywQzA/5ZRTMtkaa6wRHDtp0qRgPmDAgEx26aWXJtW2/mPeA6Hux6HOx3XZ/biSXKzLMQAAANQfBS0AAABRUtACAAAQJQUtAAAAUVLQAgAAEKWq7HJMfMrd4c/6p5rXfwx7INR1PjVq1KhM1q5du7n+faqLf8MhQ4YE83POOSeYF+q6H6Ny74GGvv7r09prrx3M+/fvn8l22mmnWj/e1KlTg/nzzz8fzLt06RLM55133qIfc9dddw3md955Z9IQlHv9V9oeKPS1LLroorX+FJTFF188mO+33361fq7/8ssvk9oaPXp0MH/ggQeK7oD/66+/JnObLscAAABUJAUtAAAAUVLQAgAAECUFLQAAAFHSFIoolLshgvVPNa//mPfAKqusksnOP//84NjNNtus1o83ceLEYH7GGWcE88cee6zoYxdq8lSoiU4lKfceiHX916dGjbLXRJZaaqmSmrZtvfXWRT/eNttsE8wnT54czJ988slM9sgjjxTdPK4h7a1yr/+UPUA5aQoFAABARVLQAgAAECUFLQAAAFFS0AIAABAlBS0AAABR0uWYKJS7w5/1TzWv/5Q9QDXvAeufal7/KXuActLlGAAAgIqkoAUAACBKCloAAACipKAFAAAgSgpaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACBKCloAAACipKAFAAAgSgpaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACBKCloAAACipKAFAAAgSgpaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACBKCloAAACipKAFAAAgSjW5XC5X7kkAAABAqVyhBQAAIEoKWgAAAKKkoAUAACBKCloAAACipKAFAAAgSgpaAAAAoqSgBQAAIEoKWgAAAKKkoAUAACCJ0f8PVuZNhiYH2eMAAAAASUVORK5CYII=\",\n      \"text/plain\": [\n       \"<Figure size 1200x1200 with 25 Axes>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"test_batch = test_ds.as_numpy_iterator().next()\\n\",\n    \"pred = pred_step(eval_model, test_batch)\\n\",\n    \"\\n\",\n    \"fig, axs = plt.subplots(5, 5, figsize=(12, 12))\\n\",\n    \"for i, ax in enumerate(axs.flatten()):\\n\",\n    \"  ax.imshow(test_batch['image'][i, ..., 0], cmap='gray')\\n\",\n    \"  ax.set_title(f'label={pred[i]}')\\n\",\n    \"  ax.axis('off')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"65342ab4\",\n   \"metadata\": {},\n   \"source\": [\n    \"# 8. Export the model\\n\",\n    \"\\n\",\n    \"Flax models are great for research, but aren't meant to be deployed directly. Instead, high performance inference runtimes like LiteRT or TensorFlow Serving operate on a special [SavedModel](https://www.tensorflow.org/guide/saved_model) format. The [Orbax](https://orbax.readthedocs.io/en/latest/guides/export/orbax_export_101.html) library makes it easy to export Flax models to this format. First, we must create a `JaxModule` object wrapping a model and its prediction method.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"49cace09\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from orbax.export import JaxModule, ExportManager, ServingConfig\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"421309d4\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"def exported_predict(model, y):\\n\",\n    \"    return model(y, None)\\n\",\n    \"\\n\",\n    \"jax_module = JaxModule(eval_model, exported_predict)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"787136af\",\n   \"metadata\": {},\n   \"source\": [\n    \"We also need to tell Tensorflow Serving what input type `exported_predict` expects in its second argument. The export machinery expects type signature arguments to be PyTrees of `tf.TensorSpec`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"9f2ad72e\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sig = [tf.TensorSpec(shape=(1, 28, 28, 1), dtype=tf.float32)]\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"31e9668a\",\n   \"metadata\": {},\n   \"source\": [\n    \"Finally, we can bundle up the input signature and the `JaxModule` together using the `ExportManager` class.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"18cdf9ad\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"export_mgr = ExportManager(jax_module, [\\n\",\n    \"    ServingConfig('mnist_server', input_signature=sig)\\n\",\n    \"])\\n\",\n    \"\\n\",\n    \"output_dir='/tmp/mnist_export'\\n\",\n    \"export_mgr.save(output_dir)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"28\",\n   \"metadata\": {},\n   \"source\": [\n    \"Congratulations! You have learned how to use Flax NNX to build and train a simple classification model end-to-end on the MNIST dataset.\\n\",\n    \"\\n\",\n    \"Next, check out [Why Flax NNX?](https://flax.readthedocs.io/en/latest/why.html) and get started with a series of [Flax NNX Guides](https://flax.readthedocs.io/en/latest/guides/index.html).\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"accelerator\": \"GPU\",\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\",\n   \"main_language\": \"python\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.9\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs_nnx/mnist_tutorial.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  main_language: python\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google/flax/blob/main/docs_nnx/mnist_tutorial.ipynb)\n[![Open On GitHub](https://img.shields.io/badge/Open-on%20GitHub-blue?logo=GitHub)](https://github.com/google/flax/blob/main/docs_nnx/mnist_tutorial.ipynb)\n\n# MNIST tutorial\n\nWelcome to Flax NNX! In this tutorial you will learn how to build and train a simple convolutional neural network (CNN) to classify handwritten digits on the MNIST dataset using the Flax NNX API.\n\nFlax NNX is a Python neural network library built upon [JAX](https://github.com/jax-ml/jax). If you have used the Flax Linen API before, check out [Why Flax NNX](https://flax.readthedocs.io/en/latest/why.html). You should have some knowledge of the main concepts of deep learning.\n\nLet’s get started!\n\n## 1. Install Flax\n\nIf `flax` is not installed in your Python environment, use `pip` to install the package from PyPI (below, just uncomment the code in the cell if you are working from Google Colab/Jupyter Notebook):\n\n```{code-cell} ipython3\n# !pip install -U \"jax[cuda12]\"\n# !pip install -U flax\n```\n\n## 2. Load the MNIST dataset\n\nFirst, you need to load the MNIST dataset and then prepare the training and testing sets via Tensorflow Datasets (TFDS). You normalize image values, shuffle the data and divide it into batches, and prefetch samples to enhance performance.\n\n```{code-cell} ipython3\nimport tensorflow_datasets as tfds  # TFDS to download MNIST.\nimport tensorflow as tf  # TensorFlow / `tf.data` operations.\n\ntf.random.set_seed(0)  # Set the random seed for reproducibility.\n\ntrain_steps = 1200\neval_every = 200\nbatch_size = 32\n\ntrain_ds: tf.data.Dataset = tfds.load('mnist', split='train')\ntest_ds: tf.data.Dataset = tfds.load('mnist', split='test')\n\ntrain_ds = train_ds.map(\n  lambda sample: {\n    'image': tf.cast(sample['image'], tf.float32) / 255,\n    'label': sample['label'],\n  }\n)  # normalize train set\ntest_ds = test_ds.map(\n  lambda sample: {\n    'image': tf.cast(sample['image'], tf.float32) / 255,\n    'label': sample['label'],\n  }\n)  # Normalize the test set.\n\n# Create a shuffled dataset by allocating a buffer size of 1024 to randomly draw elements from.\ntrain_ds = train_ds.repeat().shuffle(1024)\n# Group into batches of `batch_size` and skip incomplete batches, prefetch the next sample to improve latency.\ntrain_ds = train_ds.batch(batch_size, drop_remainder=True).take(train_steps).prefetch(1)\n# Group into batches of `batch_size` and skip incomplete batches, prefetch the next sample to improve latency.\ntest_ds = test_ds.batch(batch_size, drop_remainder=True).prefetch(1)\n```\n\n## 3. Define the model with Flax NNX\n\nCreate a CNN for classification with Flax NNX by subclassing `nnx.Module`:\n\n```{code-cell} ipython3\nfrom flax import nnx  # The Flax NNX API.\nfrom functools import partial\nfrom typing import Optional\n\nclass CNN(nnx.Module):\n  \"\"\"A simple CNN model.\"\"\"\n\n  def __init__(self, *, rngs: nnx.Rngs):\n    self.conv1 = nnx.Conv(1, 32, kernel_size=(3, 3), rngs=rngs)\n    self.batch_norm1 = nnx.BatchNorm(32, rngs=rngs)\n    self.dropout1 = nnx.Dropout(rate=0.025)\n    self.conv2 = nnx.Conv(32, 64, kernel_size=(3, 3), rngs=rngs)\n    self.batch_norm2 = nnx.BatchNorm(64, rngs=rngs)\n    self.avg_pool = partial(nnx.avg_pool, window_shape=(2, 2), strides=(2, 2))\n    self.linear1 = nnx.Linear(3136, 256, rngs=rngs)\n    self.dropout2 = nnx.Dropout(rate=0.025)\n    self.linear2 = nnx.Linear(256, 10, rngs=rngs)\n\n  def __call__(self, x, rngs: nnx.Rngs | None = None):\n    x = self.avg_pool(nnx.relu(self.batch_norm1(self.dropout1(self.conv1(x), rngs=rngs))))\n    x = self.avg_pool(nnx.relu(self.batch_norm2(self.conv2(x))))\n    x = x.reshape(x.shape[0], -1)  # flatten\n    x = nnx.relu(self.dropout2(self.linear1(x), rngs=rngs))\n    x = self.linear2(x)\n    return x\n\n# Instantiate the model.\nmodel = CNN(rngs=nnx.Rngs(0))\n# Visualize it.\nnnx.display(model)\n```\n\n### Run the model\n\nLet's put the CNN model to the test!  Here, you’ll perform a forward pass with arbitrary data and print the results.\n\n```{code-cell} ipython3\nimport jax.numpy as jnp  # JAX NumPy\n\ny = model(jnp.ones((1, 28, 28, 1)), nnx.Rngs(0))\ny\n```\n\n## 4. Create the optimizer and define some metrics\n\nIn Flax NNX, you need to create an `nnx.Optimizer` object to manage the model's parameters and apply gradients during training. `nnx.Optimizer` receives the model's reference, so that it can update its parameters, and an [Optax](https://optax.readthedocs.io/) optimizer to define the update rules. Additionally, you will define an `nnx.MultiMetric` object to keep track of the `Accuracy` and the `Average` loss.\n\n```{code-cell} ipython3\nimport optax\n\nlearning_rate = 0.005\nmomentum = 0.9\n\noptimizer = nnx.Optimizer(\n  model, optax.adamw(learning_rate, momentum), wrt=nnx.Param\n)\nmetrics = nnx.MultiMetric(\n  accuracy=nnx.metrics.Accuracy(),\n  loss=nnx.metrics.Average('loss'),\n)\n\nnnx.display(optimizer)\n```\n\n## 5. Define training step functions\n\nIn this section, you will define a loss function using the cross entropy loss ([`optax.softmax_cross_entropy_with_integer_labels()`](https://optax.readthedocs.io/en/latest/api/losses.html#optax.softmax_cross_entropy_with_integer_labels)) that the CNN model will optimize over.\n\nIn addition to the `loss`, during training and testing you will also get the `logits`, which will be used to calculate the accuracy metric.\n\nDuring training — the `train_step` — you will use `nnx.value_and_grad` to compute the gradients and update the model's parameters using the `optimizer` you have already defined. The `train_step` also receives an `nnx.Rngs` object to provide randomness for dropout. The `eval_step` omits `rngs` because the eval view already has `deterministic=True`, so dropout is disabled and no random key is needed. During both steps, the `loss` and `logits` are used to update the metrics.\n\n```{code-cell} ipython3\ndef loss_fn(model: CNN, batch, rngs: nnx.Rngs | None = None):\n  logits = model(batch['image'], rngs)\n  loss = optax.softmax_cross_entropy_with_integer_labels(\n    logits=logits, labels=batch['label']\n  ).mean()\n  return loss, logits\n\n@nnx.jit\ndef train_step(model: CNN, optimizer: nnx.Optimizer, metrics: nnx.MultiMetric, rngs: nnx.Rngs, batch):\n  \"\"\"Train for a single step.\"\"\"\n  grad_fn = nnx.value_and_grad(loss_fn, has_aux=True)\n  (loss, logits), grads = grad_fn(model, batch, rngs)\n  metrics.update(loss=loss, logits=logits, labels=batch['label'])  # In-place updates.\n  optimizer.update(model, grads)  # In-place updates.\n\n@nnx.jit\ndef eval_step(model: CNN, metrics: nnx.MultiMetric, batch):\n  loss, logits = loss_fn(model, batch)\n  metrics.update(loss=loss, logits=logits, labels=batch['label'])  # In-place updates.\n```\n\nIn the code above, the [`nnx.jit`](https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/transforms.html#flax.nnx.jit) transformation decorator traces the `train_step` function for just-in-time compilation with [XLA](https://www.tensorflow.org/xla), optimizing performance on hardware accelerators, such as Google TPUs and GPUs. `nnx.jit` is a stateful version of the `jax.jit` transform that allows its function input and outputs to be Flax NNX objects. Similarly, `nnx.value_and_grad` is a stateful version of `jax.value_and_grad`. Check out [the transforms guide](https://flax.readthedocs.io/en/latest/guides/transforms.html) to learn more.\n\n> **Note:** The code shows how to perform several in-place updates to the model, the optimizer, the RNG streams, and the metrics, but _state updates_ were not explicitly returned. This is because Flax NNX transformations respect _reference semantics_ for Flax NNX objects, and will propagate the state updates of the objects passed as input arguments. This is a key feature of Flax NNX that allows for a more concise and readable code. You can learn more in [Why Flax NNX](https://flax.readthedocs.io/en/latest/why.html).\n\n\n## 6. Train and evaluate the model\n\nNow, you can train the CNN model. Before the training loop, we use [`nnx.view`](https://flax.readthedocs.io/en/latest/guides/view.html) to create a `train_model` (with dropout enabled and batch norm in training mode) and an `eval_model` (with dropout disabled and batch norm using running statistics). These views share the same underlying weights, so updates during training are automatically reflected during evaluation.\n\n```{code-cell} ipython3\nfrom IPython.display import clear_output\nimport matplotlib.pyplot as plt\n\nmetrics_history = {\n  'train_loss': [],\n  'train_accuracy': [],\n  'test_loss': [],\n  'test_accuracy': [],\n}\n\nrngs = nnx.Rngs(0)\ntrain_model = nnx.view(model, deterministic=False, use_running_average=False)\neval_model = nnx.view(model, deterministic=True, use_running_average=True)\n\nfor step, batch in enumerate(train_ds.as_numpy_iterator()):\n  # Run the optimization for one step and make a stateful update to the following:\n  # - The train state's model parameters\n  # - The optimizer state\n  # - The training loss and accuracy batch metrics\n  train_step(train_model, optimizer, metrics, rngs, batch)\n\n  if step > 0 and (step % eval_every == 0 or step == train_steps - 1):  # One training epoch has passed.\n    # Log the training metrics.\n    for metric, value in metrics.compute().items():  # Compute the metrics.\n      metrics_history[f'train_{metric}'].append(value)  # Record the metrics.\n    metrics.reset()  # Reset the metrics for the test set.\n\n    # Compute the metrics on the test set after each training epoch.\n    for test_batch in test_ds.as_numpy_iterator():\n      eval_step(eval_model, metrics, test_batch)\n\n    # Log the test metrics.\n    for metric, value in metrics.compute().items():\n      metrics_history[f'test_{metric}'].append(value)\n    metrics.reset()  # Reset the metrics for the next training epoch.\n\n    clear_output(wait=True)\n    # Plot loss and accuracy in subplots\n    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\n    ax1.set_title('Loss')\n    ax2.set_title('Accuracy')\n    for dataset in ('train', 'test'):\n      ax1.plot(metrics_history[f'{dataset}_loss'], label=f'{dataset}_loss')\n      ax2.plot(metrics_history[f'{dataset}_accuracy'], label=f'{dataset}_accuracy')\n    ax1.legend()\n    ax2.legend()\n    plt.show()\n```\n\n## 7. Perform inference on the test set\n\nCreate a `jit`-compiled model inference function (with `nnx.jit`) - `pred_step` - to generate predictions on the test set using the learned model parameters. Since we already have `eval_model` (an `nnx.view` with `deterministic=True` and `use_running_average=True`), we can use it directly for inference. This will enable you to visualize test images alongside their predicted labels for a qualitative assessment of model performance.\n\n```{code-cell} ipython3\n@nnx.jit\ndef pred_step(model: CNN, batch):\n  logits = model(batch['image'], None)\n  return logits.argmax(axis=1)\n```\n\nWe reuse the `eval_model` view created earlier so that `Dropout` is disabled and `BatchNorm` uses stored running stats during inference.\n\n```{code-cell} ipython3\ntest_batch = test_ds.as_numpy_iterator().next()\npred = pred_step(eval_model, test_batch)\n\nfig, axs = plt.subplots(5, 5, figsize=(12, 12))\nfor i, ax in enumerate(axs.flatten()):\n  ax.imshow(test_batch['image'][i, ..., 0], cmap='gray')\n  ax.set_title(f'label={pred[i]}')\n  ax.axis('off')\n```\n\n# 8. Export the model\n\nFlax models are great for research, but aren't meant to be deployed directly. Instead, high performance inference runtimes like LiteRT or TensorFlow Serving operate on a special [SavedModel](https://www.tensorflow.org/guide/saved_model) format. The [Orbax](https://orbax.readthedocs.io/en/latest/guides/export/orbax_export_101.html) library makes it easy to export Flax models to this format. First, we must create a `JaxModule` object wrapping a model and its prediction method.\n\n```{code-cell} ipython3\nfrom orbax.export import JaxModule, ExportManager, ServingConfig\n```\n\n```{code-cell} ipython3\ndef exported_predict(model, y):\n    return model(y, None)\n\njax_module = JaxModule(eval_model, exported_predict)\n```\n\nWe also need to tell Tensorflow Serving what input type `exported_predict` expects in its second argument. The export machinery expects type signature arguments to be PyTrees of `tf.TensorSpec`.\n\n```{code-cell} ipython3\nsig = [tf.TensorSpec(shape=(1, 28, 28, 1), dtype=tf.float32)]\n```\n\nFinally, we can bundle up the input signature and the `JaxModule` together using the `ExportManager` class.\n\n```{code-cell} ipython3\nexport_mgr = ExportManager(jax_module, [\n    ServingConfig('mnist_server', input_signature=sig)\n])\n\noutput_dir='/tmp/mnist_export'\nexport_mgr.save(output_dir)\n```\n\nCongratulations! You have learned how to use Flax NNX to build and train a simple classification model end-to-end on the MNIST dataset.\n\nNext, check out [Why Flax NNX?](https://flax.readthedocs.io/en/latest/why.html) and get started with a series of [Flax NNX Guides](https://flax.readthedocs.io/en/latest/guides/index.html).\n"
  },
  {
    "path": "docs_nnx/nnx_basics.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Flax basics\\n\",\n    \"\\n\",\n    \"Flax NNX is a new simplified API that is designed to make it easier to create, inspect, debug, and analyze neural networks in [JAX](https://jax.readthedocs.io/). It achieves this by adding first class support for Python reference semantics. This allows users to express their models using regular Python objects, which are modeled as PyGraphs (instead of pytrees), enabling reference sharing and mutability. Such API design should make PyTorch or Keras users feel at home.\\n\",\n    \"\\n\",\n    \"To begin, install Flax with `pip` and import necessary dependencies:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# ! pip install -U flax\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from flax import nnx\\n\",\n    \"import jax\\n\",\n    \"import jax.numpy as jnp\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## The Flax NNX Module system\\n\",\n    \"\\n\",\n    \"The main difference between the Flax `Module` and other Module systems in [Flax Linen](https://flax-linen.readthedocs.io/en/latest/api_reference/flax.linen/module.html) or [Haiku](https://dm-haiku.readthedocs.io/en/latest/notebooks/basics.html#Built-in-Haiku-nets-and-nested-modules) is that in NNX everything is **explicit**. This  means, among other things, that the NNX Module itself holds the state (such as parameters) directly, the [PRNG](https://jax.readthedocs.io/en/latest/random-numbers.html) state is threaded by the user, and all shape information must be provided on initialization (no shape inference).\\n\",\n    \"\\n\",\n    \"Let's begin by creating a Linear `Module`. As shown next, dynamic state is usually stored in `Param`s, and static state (all types not handled by NNX) such as integers or strings are stored directly. Attributes of type `jax.Array` and `numpy.ndarray` are also treated as dynamic states, although storing them inside Variables, such as Param, is preferred. Also the `Rngs` object can be used to get new unique keys based on a root PRNG key passed to the constructor.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"class Linear(nnx.Module):\\n\",\n    \"  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\\n\",\n    \"    self.w = nnx.Param(rngs.params.uniform((din, dout)))\\n\",\n    \"    self.b = nnx.Param(jnp.zeros((dout,)))\\n\",\n    \"    self.din, self.dout = din, dout\\n\",\n    \"\\n\",\n    \"  def __call__(self, x: jax.Array):\\n\",\n    \"    return x @ self.w + self.b[None]\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Also note that the inner values of `Variable`s can be accessed using the `value` property, but for convenience they implement all numeric operators and can be used directly in arithmetic expressions (as shown in the code above).\\n\",\n    \"\\n\",\n    \"To initialize a Flax `Module`, you just call the constructor, and all the parameters of a Module are usually created eagerly. Since Modules hold their own state methods, you can call them directly without the need for a separate apply method.\\n\",\n    \"This can be very convenient for debugging, allowing you to directly inspect the entire structure of the model.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"[[1.5643291  0.94782424 0.37971854 1.0724319  0.22112393]]\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_d5660a98b46e49b8a1073c1b8504eadb\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_d5660a98b46e49b8a1073c1b8504eadb\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNrtWwtX2zgW/isaz9khWYjJOyQUzjo0r7bQQmhpu7MnK9uyrcaRja0khD38972SnYcTJ4UdOt3OTDingHR1dd/6rkRfhHzmklOVB4SEhueTQeB5HP0H+V5IOfVYAwXExZxOyDGyPMZzFh5Rd9ZAI495oY8NGJ86lJOc/KWB/ABGXBrynGSd4zMfRpnHYFjHxtAOvDEzc4bnekEjWnqM4t90FwiAHzW500AW5UDGOGH8GI0oy8XjhXz+b8DLu8uF9J4yG9Z5gUmCHAwdIx+bJgzmXGLxBioajpCGkZxDqO3ASEGtiP0YxxSUW/CPf8hNaEh16lIOKuIx9xa0Ocp4QFlIDbEtiWZjvR5eHEZ2fLGwYy4YM9gzgLHQCKjPkTDEyR72fZcaWJj20DM4EWYKCB7tnWYy2ZNTsDzsF3JkEouF6ARxh4aqTfgVuOXCM0kmqzpeyFU5D6oRjgY+YUJlzRBcxaJ//ittpouZ6RKYZmPXPY52UEHMvucxGM1MvWCYRasyeDcwJKYSw5waYtAngeUFI8wMojJvmsnKQIANMhszKBcteoFKxSzwoRbKrEmtuoTZ3EEnJygvSHaKHhA+DhjYHRE3JEvBnDETkq2zDh1qcSGfJBA/PMDXlh0yEH7M9KZqQG7HJOQaoyPprnaARyQT2SQreBxvbOSPQycy43GKjvMtTiI1dmj5eBmEFJEjuWfbbpS+A5liEK2+4CVGiMsPEJlAgMeeFNLJ39UhmQmjK4EiBIqJVcPFYfgGsjjmm1EWPAcjCENlvvlDFuwJ4S9j/PTFYVoCmHSCJMMTJVlnFMSxDpqSuxMlr0DqBnyTxGMgIhiDwdSuZEi3QEasmeuuQDJG9U73OPdGojA0mMczquW5JtZhNQO2DQeHmVMX68Q9Tc4Moj3kmobhEGNIzGwW/V2Ybl54uOc3UF4tVMhos/SIsfucVBl+P04tsw+qLIcDrOsBmcjoltXx5+pREefzSwLDG41ArRUKLD/CNWskOBLZ8SYkyKbQx+RgaQhfc4VjHj6WtaRg45FOglWC+lGhUl0QEHMgdF4SxDV9s/RPcJDJ5XTXM4bRUPZ4XsilCQv+HQo9l5q7KCM/fo34QUQICYRwPohDHPBpQglcE1/xCSdDpIEox1CpxeJECGw5G8HiaZEC1CYNYdPZ/AxcJ0SnSEZao6ETqJorlvvZkJ/0IImOt1xBnG/xuQiRsdiLMhl50gw79pTxsLmziYNhSLANmcg2Vz9TxixkEEvTF83pExLKc7qB9n4tVnRj73uKl1y0Vcjq7yCk8KPYeByEwoG+B2iFBCn70vD5tpWpIDfKyfoabovx59l1qR4nd3xzF5WGA4sGIR94LCpCm6m1K5XUYkVkU6qr0G8WP/L4uohCqxEObMCXkRgyoR9+425Qiv2ZPobSyFIL0HI6LWgVpKxRgSEB3acT/0oKZVNZaw72zjFEBcUu6s9GuueG6O2YC31NdBathO/+DBIjNyX6EIB+VHlHcHY7EtJjxmE5xSExF+3BzyQvvo43wzxaLWF5Xq2LQzapZZQfKVqkl7vlSnWKw4EB5wAYdrEeWzxxeszr9K4919Ykt1w1fXyAmZjjHGbgWAn8sqvDYhOBZgPM5tEs2aJCiAhYDPBFzhvzp6mykAAcQ4n5U1ISuSX6iY58L+CYbfDWA29I2ECMLIvR1627smzFnnM3P6iO4QxkXzkQ2bECsqJkyatRxq7QBQJqrRAGEfRaUAroBaqaAwN6FTMgLFY+2Q2ClEnCZ0OJi2SMj+ZE8hvYNTLQskK/BIBGQmw15FisX8j7zSSJEVUkiQm4CsJEwKoVd9yOscugAxlAk2/RO2CSSLwjmXjQbmGBtKY4YOCBwfyomHvXsrBRKKUQ+tCvbDhO1sPYSPFQLnbmElI2JM7GQc4OsEnBbRlUKFVMYh8gD5LEJigP4lUN5yBKGmg0RAmSQyg284YsG7X6eQ4CtFHyc4vgXPf1tthcp1tA78iHkN1Jkt89evNq2XCyj5H19wnnqDWQUqU2CIneTeoBPnEd8FamAOqsLon5hkBFsqIHj22wXJp7/Nr5vvM2Rq5csJyP5uLh39Gay0ZMWjAn8mocxgoWyAi0265YCoZ6MoL5Woj/J9ErRv4V1eoRveJu4gcVOH6Ti4Glk3/L3cDUgQNTXA2kITsBtMF4qTRRS7hCBbJuYxRxAJO42AcE9vUu9ukO3r7DUtCIiNwBVDG30jyHHGlbJEyxvFFLM0XyykpNXpWhHRzWVWWJy7t0wsdstpXPM95XiytE9JMWBHimWoE3ypieMRaXTaoABaE6we6YQKBl1dAbkYyECuLyUXxXo0ZBXDw+slVQ9uDgzC6uekOHEC7ug8kUnfX7faFNX4yJ2105qQZE3vX0Z8zI/PsfcXtikDloeXqrsno5xMT9thuPTeMcLovLuTAwGmgcuBmBmxti/nDqWVbxWAdkXi0fmPl659zWmpr89C41zZM/Na+m8G+3rWktbdenOdI0e+i9Nnut5tn0k6Zdfzp7pZ33mmda277rdd84PGyeU2KX2i8/Ft/0qp8mfX9M351XrguvPvauPpxPbs7v+btZu322f2MPr2nzZd6hLy/Hr1pm50u+qx9ak57p376uOrc3lF6Oz1nH6Vrvufa+2rwIylq7x4atqvF+PGb7V5VbIxxOJ1bbPby9s1veka2/mnaOCl3tkGlXlTdB8KpwtW/f56/MvPbKKtgXtbNp50vRznuz8VWtNmoVqtPux/pb2/bJ9XBWJj39vmLowdsOx5p92buYvsThLLwc93ofb1rtqfbu0u99Mt8fHu7btevaxxLPW6/f3WqTCvB8o13UtPOpNrLvr/r748990vp4V7Sqxv1F+ao7q4yb2uv75he/7Zdo9/Kslf88flfu15jVfNPqts9HGt0/mrSKDis4tX39w/Tjl2k3mLzsvD9jX6xWy+b7b43Prlur1M9eTZtHTr18ft7plzqfNXvUq3xpXtb5dYd0661ms9cpvbTLV4efjJmudcCnH14fapcdrJHzM1fr3rfe2p+5XW2+s9++7b1sDullhbSbH8+abYPmfSfwfAax4X9uvSzcF4Z968zizuw165q4HXat/MWo07qoNk3t9sMHH/Ow/3lkmpjWi9Z9vfyefrmt+qOg+tb7dNanQWc0edUp9W/6pXaraDQvrev9ruv5nXI7nFawfVs9op9J/8L1b1iz2yPmeUDGN7eds1Hhph0M+/27SrF6cxNONZAoi+TbDs/sybDeE0fmv+GfRfZj0/Ohd1impHyRUlV1B8VBlLP/Al677/gd+UQiG8aolwXeEB7MQJmopUw+YEEKXnsifYEsbjnFWAjlQbAQPbRoPPEUU44YnlAbcy9QgbOvezgw1WlAObkmdzyz5CUQRcRr+UoCR3xGWWmwxfsI7HJNRwQ68cz8AW1jXUBG0C1vLH04QMV8Pi+RFBRfQK0ZeVWUvu9KF60shROXZPMKJp6UFPQzamPqQmHjHhLEP8nKBmiTQV8H1ZiCzQg2xSXA/qrt4reer7zyiAsFJMvjiZIAVQ3kDV3DAUhdO6oI9JMvoWIBfquXq7VarVQu1SroEBAQ6JsGKMXNthLzj5+RkrfZ640aEEcI4AVl/jg+yRR55uvenZLKJIYHMBlBA1BSLk7um3jiiHEiSl5SrEmaPPuV019cLgwJFLvpEpNrfTfMxhyib29k85tZDMbKb7CfoxwlRan5S5FyCmHyDgdYPKpUUKaaR81scrvVxXtzELmXHBZDA3fiJoeVjQY3ieqVHdQbpNOT7SHxWyIg3fPfxo+Wi+9Uxu4ALAEEAUlcqqtJa0tfZNKcure8SNtDHjsT5eNk74n1U76PZvfQ4pbvRFGnyjKPF+OAlNSpuLBdv5qECVkl4BRy4OdY5dPtMfPEYCzkUab8rFG4cQeo7JpXTiWSPXmEm6l5oixeGvWqdWTUccEyDaNc04t1q1IuHNUB/5VxuVApr22a9kIZh1m6pl8gdCT6RmkWWbSAknBOYrke5qVipniAKtlf7GUIf8VZiX4iVduiXj3CJia1sknKRg3rdbOAC1XdLFTreVLHuwr482RrWtn7fzJq9O1g07iLJhjC/anpsqvcfLv6EPV2W6tENP08tWK9HkBB+EBDqKP0XnJEDjUhWwG7oGXDKzrh/ymkf3DDfTWZ1ysbICeHQiifpNg8/lsQ5bSN3ZA8IjAPnhi839DMc722W3pO8czG3n5ErdSOXQG+DVes/kmQcgrd1COSZbMWZXdjmg36561WPxRg2Z5Eu2Gp/hcs/UZe1rd5Wf8esBR6o+IPiErNegn671rFLFfN8lGZ6GapWChZtWKxaOlHdet7otLKwfNiUqNyVC5XisVCpWqV67V8XceGoRNSqhJA4/ninwCT7jTpHweR6ruBlf4XIv1OhvsLkcZm/hoi1f9CpH8kRPotsMr/ikhNytKyKfpLfuW0+GNZFrTZZluY+g7W9cZ8l3krP1KdEspstS7M/blq0/ann+V/H0Jrf3qqPLmiPaEjmVOadHL6X5ZWPUA=</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_d5660a98b46e49b8a1073c1b8504eadb\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_d5660a98b46e49b8a1073c1b8504eadb\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"model = Linear(2, 5, rngs=nnx.Rngs(params=0))\\n\",\n    \"y = model(x=jnp.ones((1, 2)))\\n\",\n    \"\\n\",\n    \"print(y)\\n\",\n    \"nnx.display(model)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The above visualization by `nnx.display` is generated using the awesome\\n\",\n    \"[Treescope](https://treescope.readthedocs.io/en/stable/index.html#) library.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Stateful computation\\n\",\n    \"\\n\",\n    \"Implementing layers, such as `BatchNorm`, requires performing state updates during a forward pass. In Flax NNX, you just need to create a `Variable` and update its `.value` during the forward pass.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"counter.count[...] = Array(0, dtype=int32, weak_type=True)\\n\",\n      \"counter.count[...] = Array(1, dtype=int32, weak_type=True)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"class Count(nnx.Variable): pass\\n\",\n    \"\\n\",\n    \"class Counter(nnx.Module):\\n\",\n    \"  def __init__(self):\\n\",\n    \"    self.count = Count(jnp.array(0))\\n\",\n    \"\\n\",\n    \"  def __call__(self):\\n\",\n    \"    self.count[...] += 1\\n\",\n    \"\\n\",\n    \"counter = Counter()\\n\",\n    \"print(f'{counter.count[...] = }')\\n\",\n    \"counter()\\n\",\n    \"print(f'{counter.count[...] = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Mutable references are usually avoided in JAX. But Flax NNX provides sound mechanisms\\n\",\n    \"to handle them, as demonstrated in later sections of this guide.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Nested Modules\\n\",\n    \"\\n\",\n    \"Flax `Module`s can be used to compose other Modules in a nested structure. These can be assigned directly as attributes, or inside an attribute of any (nested) pytree type, such as a `list`, `dict`, `tuple`, and so on.\\n\",\n    \"\\n\",\n    \"The example below shows how to define a simple `MLP` by subclassing `Module`. The model consists of two `Linear` layers, a `Dropout` layer, and a `BatchNorm` layer. Note that we need to pass the `__call__` method the RNG state that we want the `Dropout` layer to use.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_5111ac75504b44448a5dd31dbaea5a09\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5111ac75504b44448a5dd31dbaea5a09\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5111ac75504b44448a5dd31dbaea5a09\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_5111ac75504b44448a5dd31dbaea5a09\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"class MLP(nnx.Module):\\n\",\n    \"  def __init__(self, din: int, dmid: int, dout: int, *, rngs: nnx.Rngs):\\n\",\n    \"    self.linear1 = Linear(din, dmid, rngs=rngs)\\n\",\n    \"    self.dropout = nnx.Dropout(rate=0.1)\\n\",\n    \"    self.bn = nnx.BatchNorm(dmid, rngs=rngs)\\n\",\n    \"    self.linear2 = Linear(dmid, dout, rngs=rngs)\\n\",\n    \"\\n\",\n    \"  def __call__(self, x: jax.Array, rngs: nnx.Rngs):\\n\",\n    \"    x = nnx.gelu(self.dropout(self.bn(self.linear1(x)), rngs=rngs))\\n\",\n    \"    return self.linear2(x)\\n\",\n    \"\\n\",\n    \"model = MLP(2, 16, 5, rngs=nnx.Rngs(0))\\n\",\n    \"\\n\",\n    \"y = model(x=jnp.ones((3, 2)), rngs=nnx.Rngs(1))\\n\",\n    \"\\n\",\n    \"nnx.display(model)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Model surgery\\n\",\n    \"\\n\",\n    \"Flax `Module`s are mutable by default. This means that their structure can be changed at any time, which makes [model surgery](https://flax.readthedocs.io/en/latest/guides/surgery.html) quite easy, as any sub-Module attribute can be replaced with anything else, such as new Modules, existing shared Modules, Modules of different types, and so on. Moreover, `Variable`s can also be modified or replaced/shared.\\n\",\n    \"\\n\",\n    \"The following example shows how to replace the `Linear` layers in the `MLP` model from the previous example with `LoraLinear` layers:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_bd219e4e14e14d99998d03b2d380114d\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_bd219e4e14e14d99998d03b2d380114d\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_bd219e4e14e14d99998d03b2d380114d\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_bd219e4e14e14d99998d03b2d380114d\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"class LoraParam(nnx.Param): pass\\n\",\n    \"\\n\",\n    \"class LoraLinear(nnx.Module):\\n\",\n    \"  def __init__(self, linear: Linear, rank: int, rngs: nnx.Rngs):\\n\",\n    \"    self.linear = linear\\n\",\n    \"    self.A = LoraParam(rngs.normal((linear.din, rank)))\\n\",\n    \"    self.B = LoraParam(rngs.normal((rank, linear.dout)))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x: jax.Array):\\n\",\n    \"    return self.linear(x) + x @ self.A @ self.B\\n\",\n    \"\\n\",\n    \"rngs = nnx.Rngs(0)\\n\",\n    \"model = MLP(2, 32, 5, rngs=rngs)\\n\",\n    \"\\n\",\n    \"# Model surgery.\\n\",\n    \"model.linear1 = LoraLinear(model.linear1, 4, rngs=rngs)\\n\",\n    \"model.linear2 = LoraLinear(model.linear2, 4, rngs=rngs)\\n\",\n    \"\\n\",\n    \"y = model(x=jnp.ones((3, 2)), rngs=rngs)\\n\",\n    \"\\n\",\n    \"nnx.display(model)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Flax transformations\\n\",\n    \"\\n\",\n    \"[Flax NNX transformations (transforms)](https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html) extend [JAX transforms](https://jax.readthedocs.io/en/latest/key-concepts.html#transformations) to support `Module`s and other objects. They serve as supersets of their equivalent JAX counterparts with the addition of being aware of the object's state and providing additional APIs to transform it.\\n\",\n    \"\\n\",\n    \"One of the main features of Flax Transforms is the preservation of reference semantics, meaning that any mutation of the object graph that occurs inside the transform is propagated outside as long as it is legal within the transform rules. In practice this means that Flax programs can be expressed using imperative code, highly simplifying the user experience.\\n\",\n    \"\\n\",\n    \"In the following example, you define a `train_step` function that takes a `MLP` model, an `Optimizer`, and a batch of data, and returns the loss for that step. The loss and the gradients are computed using the `nnx.value_and_grad` transform over the `loss_fn`. The gradients are passed to the optimizer's `update` method to update the model's parameters.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"loss = Array(1.0000616, dtype=float32)\\n\",\n      \"optimizer.step.value = Array(1, dtype=uint32)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"import optax\\n\",\n    \"\\n\",\n    \"# An MLP containing 2 custom `Linear` layers, 1 `nnx.Dropout` layer, 1 `nnx.BatchNorm` layer.\\n\",\n    \"model = MLP(2, 16, 10, rngs=nnx.Rngs(0))\\n\",\n    \"optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\\n\",\n    \"\\n\",\n    \"@nnx.jit  # Automatic state management\\n\",\n    \"def train_step(model, optimizer, x, y, rngs):\\n\",\n    \"  def loss_fn(model: MLP, rngs: nnx.Rngs):\\n\",\n    \"    y_pred = model(x, rngs)\\n\",\n    \"    return jnp.mean((y_pred - y) ** 2)\\n\",\n    \"\\n\",\n    \"  loss, grads = nnx.value_and_grad(loss_fn)(model, rngs)\\n\",\n    \"  optimizer.update(model, grads)  # In place updates.\\n\",\n    \"\\n\",\n    \"  return loss\\n\",\n    \"\\n\",\n    \"x, y = jnp.ones((5, 2)), jnp.ones((5, 10))\\n\",\n    \"loss = train_step(model, optimizer, x, y, rngs)\\n\",\n    \"\\n\",\n    \"print(f'{loss = }')\\n\",\n    \"print(f'{optimizer.step.value = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"There are two things happening in this example that are worth mentioning:\\n\",\n    \"\\n\",\n    \"1. The updates to each of the `BatchNorm` and `Dropout` layer's state is automatically propagated from within `loss_fn` to `train_step` all the way to the `model` reference outside.\\n\",\n    \"2. The `optimizer` holds a mutable reference to the model - this relationship is preserved inside the train_step function making it possible to update the model's parameters using the optimizer alone.\\n\",\n    \"\\n\",\n    \"> **Note**<br> `nnx.jit` has performance overhead for small models, check the [Performance Considerations](https://flax.readthedocs.io/en/latest/guides/performance.html) guide for more information.\\n\",\n    \"\\n\",\n    \"### Scan over layers\\n\",\n    \"\\n\",\n    \"The next example uses Flax `nnx.vmap` to create a stack of multiple MLP layers and `nnx.scan` to iteratively apply each layer of the stack to the input.\\n\",\n    \"\\n\",\n    \"In the code below notice the following:\\n\",\n    \"\\n\",\n    \"1. The custom `create_model` function takes in a key and returns an `MLP` object, since you create five keys and use `nnx.vmap` over `create_model` a stack of 5 `MLP` objects is created.\\n\",\n    \"2. The `nnx.scan` is used to iteratively apply each `MLP` in the stack to the input `x`.\\n\",\n    \"3. The nnx.scan (consciously) deviates from `jax.lax.scan` and instead mimics nnx.vmap, which is more expressive. nnx.scan allows specifying multiple inputs, the scan axes of each input/output, and the position of the carry.\\n\",\n    \"4. `State` updates for `BatchNorm` layers are automatically propagated by nnx.scan.\\n\",\n    \"5. The `rngs` object is split into separate streams for each layer using the `fork` method.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"@nnx.vmap(in_axes=0, out_axes=0)\\n\",\n    \"def create_model(rngs):\\n\",\n    \"  return MLP(10, 32, 10, rngs=rngs)\\n\",\n    \"\\n\",\n    \"@nnx.scan(in_axes=(0, 0, nnx.Carry), out_axes=nnx.Carry)\\n\",\n    \"def forward(model: MLP, rngs: nnx.Rngs, x):\\n\",\n    \"  x = model(x, rngs)\\n\",\n    \"  return x\\n\",\n    \"    \\n\",\n    \"param_rngs = nnx.Rngs(0).fork(split=5)\\n\",\n    \"model = create_model(param_rngs)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"y.shape = (3, 10)\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_21eca24c3bc14e6c8b5b9ad2200499bd\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_21eca24c3bc14e6c8b5b9ad2200499bd\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNrtXQlX20qy/iu6znkXM8FC+wKBMzZhSwIJS0KSmTmeltSyFWRJSDIG5uS/v+qWvMgWtkmwjR1xz01Cd/VSX1VX1yLLb6L43sW7bBxiHJl+gOuh78fM/5jAj5zY8b0tJsQuip1bvM3YvhdXbNRy3PstpuV7fhQgE9o7TSfGFfrLFhOE0OI6UVyhU1fi+wBaPd+DZgOZ143Qb3tWxfRdP9xKhm4z6W+GCwQwn2PFzS3GdmIg82LsxdtMy/EqaTvPcf8Hc/l3lch5cLwGjPNDC4cVaNpmAmRZ0FhxsR1vMYLZJLvxcKWJnUYTWnhWJut5MXKAud786T8qt07kGI7rxMAiasd+j7bieHHoeJFjkmVx0pvy9fPNZoLjmx6OlbDtwZohtEVm6AQxQ4DYWUNB4DomItBu+maMCUwhRq213XJ5fWcXkIf1opixsO1FzA4TN52IbeD4HMRy6lu4vM42/ShmaT+whmOmHmCPsFw1yaxk0L/+k9dzhDzLxdDttV13O1mBhW1e+L4HreWOH16vM4N78K+giXRlmmPHJI0BDm0/bCHPxKznd8rrVBFggfJID1NJBr1hRGEd5nFspjy0a9bFXiNuMjs7DEdIxm49xHE79AB3BrsR7m+s2fbIzoanjpqOHZP9UQLyj5/w3yMrlEH9PMvvsCG+aeMornpOi4rrIEQtXE4wWSdzbI8sFLSjZgLjdg6P3SV2EjbGcDn9HsguEkHGfqPhJse3To8YaGtA5iIt2I03GHwLCp5KkuyO/s5e43sCeikskQ2lxKzpoij6AKc4nbdc6s1Zb4EalrqL/1wHPEH9qY7vvtnMOwCWc8vQCXdKWTtTYmJkAKf4bqfEleDohvEoie/BFgEMD7rGHYZ8BMpkTJf3EhzGxN4Zfhz7LWIYtjw/LrO271rIgNEeTLvVRFF510UGdnezPfVkDTpmy2xi8xpb6+vMPwh0XcMT+8EWw7G8jFujpoe0PVQoy/D7dq6Z/clSc1hHhhHiW6rd1Dq+UjQBcVyfwPRbLWBrgALRHyKaIRKUbLnp3+JwPYc+S17vNLFXx3cBKCS2ng2hx1YF+cKhsQYoOPix7T6F124ZOBwk0DVeVvoEEbHNjcE1RF7m5R4BtupEFH2C9KoZvZFuUViuVAzXN6+TpvXt7v1CJcsHd0zku441jjJRr0nEP4ni4pBsLoDt4CYAmeESqeS/9OKlmrvFODGCC4QMzuD+yJUNipAnHqC2nAgWve9ezcOEzC5Dxbu1ZWAw5gPIvTLpT77uJrduhSfXbnpdg8L21nI8eiAoDGPWpGo6urKFwusIowYYCG909DOpaW8PZGj+oC59ZofUfdhi1v4tyIa5tsjtZQc9ukllDpskciQLt8OICDDwwYnCYc66TvR8y9KjQBeqULMfPabjz7Nqn70Y38Wjq7BOVLedMIrrvpcYodGjNe4osYJMTlOuqJjf3n4i8eEtEq5aKGyA25tsgx7on7+5Gpji4N5og2n0cg1QvztPaUtMaYgKgISgI5/435iXrNJQzLJ2gkArHOQyF/ctw3cj5mM7JvxazF4yEv4O7uFgVDrYuIb4I7G8LXApmjTSQF4Mwx0UYasXtbzCHPlve1TNk9E0WuBYndz9WS6T85HDRb65649kOyiqm3APALC98ciOM7dH106PW3NoTHbJQejTC8xCMaogDwRL/dH1wWayCHGyQ+R1tZlOy/ARgwExcHsqfjt+Giu9HYBgHGz9ld0JXZL5y2kFfhgjb2RuI/SvwZMhLX1jNBndgWEDeHbF/JNtms06DXfr5HQM+H7JYeHY5MQO0IXEAxwgDBOPsEdJPEJg1aqbEEJZIfZS5rNBKuwyS/hsrlnvMKZXc+bwm8g1yxBJQxgHDg31/NkoRmR8b78z20nqUSU7scCvAjUhbtWAOG7ayPUgMKoHIbadO5gkc/A0evAgCkTE0+qg0AMJ1LtXRVe6to1MXswhDCCMGhEctYcpSGlTJRVm36Xcou4/CiuNEFkOiK3M8KJs4cYG48MhaWCGg+0pZnMjOTQQ/xATRJuYFOaRvYzY6ue5CJgRk1/pKeewrB/TzWG6nuudyBBOd5Zk7trLsZLZXJ9mr/NR5yQ0oLvKDRAyISXlA2TiNkFaZR7YGRySzhsBFV4nqYEUg/7QyvRju+t2wxg6sjdlt7WSNs8RzX4gRhGskHPVjlIGedwC7h5nLMeHerIHM0nF/5eJFRP5Ems1Raw4nvgnCzPOJF/RF/LvpCxoxoBkLPI8O+JoA3i5NElIOEAFe31somQGgMRFAXhgk6PYpwv48RX6G2Uz6ZFHaJ5jH3lLZKDoJ/ryoMhm0thsBo8ZM8Mwq14mp5hPOM1ij87zjGl0ktlk/qqGIbpn7dBvlS3fbJOkFkucgoi9RW4bg6Kts5HfwmXqKpCcKPmbTQIFkg+dMlQorcHFud7LQEdNjGOSpsYdZu/i4oJwc0HaSNKZdrIhprmei3vPLP/3n2l4YuKu0/L0UGUwOeSRtLubtnXSMyyRnGEUmltMO3TLxG/eIv2bHd+2hW0DPHNF2rA4/fCkUa1V6c/xWbXq03/Vzjvw59FBtbpfHfdTa1WrjWv/vXW8X9vrfKtWL7/tvaueHNf2qgeNu+OjD804qp04uCEevP0qfDhWvt1eBG3n04l8yb/7enz+5eT26uQh/nR/cLD3+qpxfenU3nJN5+1Z+92+dfiDOzI27dtjK7h5rzRvrhznrH3iHTaP7M9x9bNSOw2l6sGxd72vmJ/bbe/1uXxjRtedW/vA3by5a+z7WsN41znU+KPqplc9lz+E4Tv+/HXjgTu3uOo7m2+cqnudwx9Cg/Pv2+eq2trnlc7RV/1joxHgy+t7CR8bD7JphB8PY1RtnB2fdt6i6D46ax8ff73aP+hUP50Fx9+sz5ubrxvqpfpVjDn7/aeb6q0Mc36onqrVk0611Xg4v3jd/n6B97/eCbZiPpxK50f3crtWff9Q+xEcBKJzdLa3z31vf5IuVM+ufdg/OjhpVZ3X2u2+0PT4pvra+NL5+qNzFN6+Pfy85/2w9/cb8euP5nfXVWV9712npjV16eTk8EI8/F5ttI7lH7UzPb48xEf6fq12fCi+bUjnm9/Me6N6CDL98n6zenaIqvhkz60ePex/bHyPG0rtU+Pjx+O3tWvnTMYHta97tQPT4YJm6Ace6Ebwff8t/8BfX9h7dty8f+8dWeggOrK509bh/qlSs6o3X74EKI4uvrcsCzm6YD/o0mfnx40StELlo/9t78IJD1u37w7Fi6sL8WBfMGtn9uXrI9cPDqWDqCOjxo2iOd/xxakbXHm1o2NsnYS4fXVzuNfirw7C64uLO1lQrq6iThV2tM7QklNcXqNqvUauzP/CH73Tjyw/gNihfyRpoYxl2TEUG8mZ/Q/MNb700KSVGxowJrEszA3q4ZlMOQkps3U1OIKXPjm+QJaGnKQtAvNApiAxNAk8UQc5MeOhW6eBYj9kYebA8FFosZ3QifElvovL/bmIR5HM1S/ewBVfLg0E2KRsA6tcOi0MkXi5W9cbGRfiFkTLI0N/bjACx3HUkwLjC15rmaaK8tcdiKJL/c2RJFnXgpFKV4l5xRwgxwXDFvsMIf6LWjbwNj2I68AaO4AZRhZJArwexC4tQU0oPpGEAkPN404p41RtMf61azbBpVY1mXg/nMiIgsqqksRLnCLIzCa4P8BsnjdJ0tqldPK0tJVNZQ9HaUCcXP9vHC9op9dYiV74hn9Xyp0k9Q2gM/ELgEM6OLtupr6ROolMNkMxtNPsxV/a/duNCYpAMZ4u0zkUdENvOkPy18mHT+VeS8r5yNxd/6aUw1G3FlXaBQX5hEIELqq4oYocxC8SqzPva+sbTI0o4UWM4JYTBdLDiknHudcgzZg8LcCUFY4hjZd+jFy4Dzc4hdAqCXF234MbWeu6omvZZtJUd2/dbHNpJEzOxgalMdQjpIa38zTN5UWBFXlRkwRVFSVRnaC9/Fjt/R1lfdlKarvojvU88j+bOEvOA/Ui2awaUMU6BYJxSrzWTxmuMb63RwzlztoTbwpaoF5fY3r5zJ0Sa3ilvuB7HeAUQg9JTg+nYaGHWkS4cZvw7xSE3cc1+xePX+aIPXr20mOmSNAosMqLPWPozol2cjhPis2l3Qqf7ndjFLpehAj4PBHfGWoNS1h6XHVo96/rz+PaNBnnOs0f1EleNMgFPX0GoLR7CqHgUuKeYXGCEDK0C5IIsYsrKgnC2iQJEJr5I284aEj9Z3zxzvgavUWhQ3biOsbQDUrvjPKcr02W4DtG8qR7ERcoTxxOciE+41U4UjIsjesv7dLE184UQnesnVLvwSROU1XLsCzL1G3iViJVU7HJS5pk24olmEOL5j3QlCpdPqc/QJFosi7PiKz1MsaUsEtiuz6KRaEsb5DnSf9u9DV6grwy+cdcdpGhq7JgIluzgEdVR1jTNcPkbcFCsoTk2XvNecHSi0J1FjfCWPMza3uRpIQnWI2E6Hlsx7B9AAPxxYnavViEaToWnF3G8Zh+tpyk0X9Jv1cIxImnfNjmwT3fdEDHx/o5B8iN8BQKu/GC3ByKZpe7Sah36Z4Z+McvsgEDM07xH/NFBh9/Lu2yLDvFIRq1V+vj/aAR+uXxcWfq5PyqZ2vR627l4gnK1hiwaf/80cZB5Li+Ny59weMKJy8d3iljYxBPKeaPeQsj78+I3XqpvbnHbwTjMbIn3UX89pT4TRexIKimJKi6KEkCr/MyFkReM01FEbFmrVr8JpqaqhiWwHOKpPC8ZtlYlVUEMZyhIN0q4rfVit+IQZgYevSJivhtoSAW8VsG8initwxdEb+tRvw2Uyfnl71bnwii3RoXUnCsri8f2ilj4xBPSeaPOiBbtzGK2yEeW4sWhaXDfZC1MdgPks0f/4BUqeo5uYt5PA+T/3H70ljpvcoV8UiQRxxIQDa4Z7Pe4a8+kvXUYGXMfUBuDdrB/P3qTlC3+1tN95g0Dxr4F6DNA4oyRpkHqGZ5WS+PEVgYbNObAPpEXFGTn50OUIDHSJ/2F1mdp2R1TIuTJI5TNFnUJIMXdFmwNV2Wdd5Udc5atao88MnxsiZYmmVLhog0zhJ5CWmyZGOZN7Uiq7NaWR1qESZmJAaoirzOgmEsMjtZ0KdI7WQJi9zOauR2Zuzs/KqT245wffTZ06Hjdhm2l69A3+VsDOZdksXAbqMorlOf3DPxauKfYXGCIDK0i5FI2Pbo21TQLQ5RA099BS2XUIa4nCCWIerFCCYnFF+VIzLpXujRzB95OIzFYy2z1QDY0xjZQ2+R/nhK+kOURU0VsSGbmiLpsmEIoiZKvKKYPOZ4XVix9IesiAova4jTBUsyNR3xqippNhY0hG0e20X6Y7XSH2APJkbtPZoi9bFACIu0xyDgUyQ9BsmKlMe4lMcSCX5Wrs0yivnxt170v3KAGXovbOnpyrGc2bFZvNXilz+vEvqB346f+JoViZVVTuSFhb1eZTneBTT4mpUo9s0mimLHHArH3iYSWMgbVlLpP6aQafciorKcNxe9zDcUhT6yTJLMs5xWtFNeXxIrlIqWze5/giIMUS/AWuEYhy3Hc8g5WrWEZRfkDJOTJJIhnr9AQjii4x+V5ZdTCISxSdgTmgVA7jUocph+WVYe+Mm3AMElRp8gTHeb/LKkwsiwPFEsGeqFCGilXm4D/Iwmken9TF41XF6Ew0L2FE2hB9GquS5PTSybIO34T1DGPcLo4nSRpUBPo5EJ5SL0UmbKwhJWOWRLRJwlkT8lyRIUXZawzkmmqquiqfDcIqocbcdL0vHPW+Gwed5QFcGQTUmQRIHXZazaBjZkbEqqppgrXOGYBtHVqW6MGoTxafrH6IuqxwuEdvbVkOVz37vQTqqNjBkyC0d+WBIxakwRUGEbtd1sQLUUZYoccIHhJ4gCqItq1Qo8oLsYz3X6YwhG70+IWd7j+wVGLADyVFIHugVFKy/nmaysl24hjRcQQpwqSIKGkCBATGLqlqGSL+xWFhGQgJDIjHZ4T4xSvhu9jMYJ+HqC+9ajnoeR+iO8NoLok3y2wQGFx/bsspjWX0tpC29t1by1udzaxfHLQX7ao1ccu1V4pG9+habi8b6Verxv1o9U/WpVOvkOdv6JD/oJPPymS4q64K9TW8bv/vtAEV/Ic32psB9TwrR7gd+hxm+o5GNXKsu93O8eLF6GNFvtZI0J+skaxecBn1IpF3TeEAQBaTyyJUHUkKWpAlZVDkuqYVnyin0eUEKmbYicougmJ8mWigxFVbBscrwk6pomFZ8HXKGKec8kjE8HDpEV9fGFA1l8NnAI9knp1FHKIp2wAlm8eTg9v/yxCmf8V+hwywk1sDUJbCBZANz9D9ytxFuue2COyXsM0swf8U4Ry81Y9p1Jgu8sJJbbUDgSzbHSs6YZ5hPOWbohc4KicYJsSjq2DYhvTAsCHlNRZFtZdDjHc88f0hmSzHOygTTTMiRVxZpqyZIl6xZWVMs0/pDvnZ2I7OqFdZ3popFOEda9ECCLsG4I9mnDuk4R1q1OlXge3k9RH16p+vCsS3O/Vx8WlrQ+XNSFn6B8wnjlExZaF1ZkGrApRV34D80lCJNSpMJi6sJELwVuCcvCtqqpimbxWNVlydA43RA02+Z4QdZ4kRv52pj5R7vPm0PAkqxZhiCK2OAkReeQxlskpWCIgm2bpv6n5BD+oPyBMF01UyjKwi8GyCJ/MAT7dPkDoSgLr2JZeLY+z4zKwstZphQml4WFl1kWXs4yvDBFWVgoysIrGsp1Jgm+KAs/NZyTZENVsI0Enpcl3uB1S7NEpGqIt1We44yFP+X7/CGdIAqChGUkipIoIc5AEOJpuqArOuZsrBt/ypO+f1xY15kuGinKwi8FyCKsG4J92rCuKAuvXFl4tt5PURaeriy8TAozs7pcoSy/ryw9Ssu53f1/bPfFTw==</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_21eca24c3bc14e6c8b5b9ad2200499bd\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_21eca24c3bc14e6c8b5b9ad2200499bd\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"x = jnp.ones((3, 10))\\n\",\n    \"dropout_rngs = nnx.Rngs(1).fork(split=5)\\n\",\n    \"y = forward(model, dropout_rngs, x)\\n\",\n    \"\\n\",\n    \"print(f'{y.shape = }')\\n\",\n    \"nnx.display(model)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"How do Flax NNX transforms achieve this? To understand how Flax NNX objects interact with JAX transforms, the next section explains the Flax NNX Functional API.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## The Flax Functional API\\n\",\n    \"\\n\",\n    \"The Flax NNX Functional API establishes a clear boundary between reference/object semantics and value/pytree semantics. It also allows the same amount of fine-grained control over the state that Flax Linen and Haiku users are used to. The Flax NNX Functional API consists of three basic methods:  `nnx.split`, `nnx.merge`, and `nnx.update`.\\n\",\n    \"\\n\",\n    \"Below is an example of `StatefulLinear` `Module` that uses the Functional API. It contains:\\n\",\n    \"\\n\",\n    \"- Some `Param` Variables; and\\n\",\n    \"- A custom `Count` Variable type, which is used to track the integer scalar state that increases on every forward pass.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_28c54a9a4cf8476087ce1c9723f4630a\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_28c54a9a4cf8476087ce1c9723f4630a\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_28c54a9a4cf8476087ce1c9723f4630a\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_28c54a9a4cf8476087ce1c9723f4630a\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"class Count(nnx.Variable): pass\\n\",\n    \"\\n\",\n    \"class StatefulLinear(nnx.Module):\\n\",\n    \"  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\\n\",\n    \"    self.w = nnx.Param(rngs.uniform((din, dout)))\\n\",\n    \"    self.b = nnx.Param(jnp.zeros((dout,)))\\n\",\n    \"    self.count = Count(jnp.array(0, dtype=jnp.uint32))\\n\",\n    \"\\n\",\n    \"  def __call__(self, x: jax.Array):\\n\",\n    \"    self.count.value += 1\\n\",\n    \"    return x @ self.w + self.b\\n\",\n    \"\\n\",\n    \"model = StatefulLinear(din=3, dout=5, rngs=nnx.Rngs(0))\\n\",\n    \"y = model(jnp.ones((1, 3)))\\n\",\n    \"\\n\",\n    \"nnx.display(model)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### State and GraphDef\\n\",\n    \"\\n\",\n    \"A Flax `Module` can be decomposed into `State` and `GraphDef` using the `nnx.split` function:\\n\",\n    \"\\n\",\n    \"- `State` is a `Mapping` from strings to `Variable`s or nested  `State`s.\\n\",\n    \"- `GraphDef` contains all the static information needed to reconstruct a `Module` graph, it is analogous to [JAX's `PyTreeDef`](https://jax.readthedocs.io/en/latest/pytrees.html#internal-pytree-handling).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_32bf0ff96e2e4ea099a433e87574fd1e\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_32bf0ff96e2e4ea099a433e87574fd1e\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_32bf0ff96e2e4ea099a433e87574fd1e\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_6f65c96f779b472d80b1a08ca4e9bf96\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_6f65c96f779b472d80b1a08ca4e9bf96\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_6f65c96f779b472d80b1a08ca4e9bf96\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_6f65c96f779b472d80b1a08ca4e9bf96\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"graphdef, state = nnx.split(model)\\n\",\n    \"\\n\",\n    \"nnx.display(graphdef, state)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Split, merge, and update\\n\",\n    \"\\n\",\n    \"Flax's `nnx.merge` is the reverse of `nnx.split`. It takes the `GraphDef` + `State` and reconstructs the `Module`. The example below demonstrates this as follows:\\n\",\n    \"\\n\",\n    \"- By using `nnx.split` and `nnx.merge` in sequence any `Module` can be lifted to be used in any JAX transform.\\n\",\n    \"- `nnx.update` can update an object in place with the content of a given `State`.\\n\",\n    \"- This pattern is used to propagate the state from a transform back to the source object outside.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"model.count.value = Array(1, dtype=uint32)\\n\",\n      \"model.count.value = Array(2, dtype=uint32)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(f'{model.count.value = }')\\n\",\n    \"\\n\",\n    \"# 1. Use `nnx.split` to create a pytree representation of the `nnx.Module`.\\n\",\n    \"graphdef, state = nnx.split(model)\\n\",\n    \"\\n\",\n    \"@jax.jit\\n\",\n    \"def forward(graphdef: nnx.GraphDef, state: nnx.State, x: jax.Array) -> tuple[jax.Array, nnx.State]:\\n\",\n    \"  # 2. Use `nnx.merge` to create a new model inside the JAX transformation.\\n\",\n    \"  model = nnx.merge(graphdef, state)\\n\",\n    \"  # 3. Call the `nnx.Module`\\n\",\n    \"  y = model(x)\\n\",\n    \"  # 4. Use `nnx.split` to propagate `nnx.State` updates.\\n\",\n    \"  _, state = nnx.split(model)\\n\",\n    \"  return y, state\\n\",\n    \"\\n\",\n    \"y, state = forward(graphdef, state, x=jnp.ones((1, 3)))\\n\",\n    \"# 5. Update the state of the original `nnx.Module`.\\n\",\n    \"nnx.update(model, state)\\n\",\n    \"\\n\",\n    \"print(f'{model.count.value = }')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The key insight of this pattern is that using mutable references is fine within a transform context (including the base eager interpreter) but it is necessary to use the Functional API when crossing boundaries.\\n\",\n    \"\\n\",\n    \"**Why aren't Modules just pytrees?** The main reason is that it is very easy to lose track of shared references by accident this way, for example if you pass two `Module`s that have a shared Module through a JAX boundary, you will silently lose that sharing. Flax's Functional API makes this behavior explicit, and thus it is much easier to reason about.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Fine-grained State control\\n\",\n    \"\\n\",\n    \"Experienced [Flax Linen](https://flax-linen.readthedocs.io/) or [Haiku](https://dm-haiku.readthedocs.io/) API users may recognize that having all the states in a single structure is not always the best choice as there are cases in which you may want to handle different subsets of the state differently. This is a common occurrence when interacting with [JAX transforms](https://jax.readthedocs.io/en/latest/key-concepts.html#transformations).\\n\",\n    \"\\n\",\n    \"For example:\\n\",\n    \"\\n\",\n    \"- Not every model state can or should be differentiated when interacting with `jax.grad`.\\n\",\n    \"- Or, sometimes, there is a need to specify what part of the model's state is a carry and what part is not when using `jax.lax.scan`.\\n\",\n    \"\\n\",\n    \"To address this, the Flax NNX API has `nnx.split`, which allows you to pass one or more `Filter`s to partition the `Variable`s into mutually exclusive `State`s. Flax NNx uses `Filter` create `State` groups in APIs (such as `nnx.split`, `nnx.state`, and many of NNX transforms).\\n\",\n    \"\\n\",\n    \"The example below shows the most common `Filter`s:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_736a46b5462b429b9b1259685d90555c\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_736a46b5462b429b9b1259685d90555c\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_736a46b5462b429b9b1259685d90555c\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_736a46b5462b429b9b1259685d90555c\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<script> (()=>{ if (customElements.get('treescope-container') === undefined) { class TreescopeContainer extends HTMLElement { constructor() { super(); this.attachShadow({mode: \\\"open\\\"}); this.defns = {}; this.state = {}; } } customElements.define(\\\"treescope-container\\\", TreescopeContainer); } if (customElements.get('treescope-run-here') === undefined) { class RunHere extends HTMLElement { constructor() { super() } connectedCallback() { const run = child => { const fn = new Function(child.textContent); child.textContent = \\\"\\\"; fn.call(this); this.remove(); }; const child = this.querySelector(\\\"script\\\"); if (child) { run(child); } else { new MutationObserver(()=>{ run(this.querySelector(\\\"script\\\")); }).observe(this, {childList: true}); } } } customElements.define(\\\"treescope-run-here\\\", RunHere); } })(); </script> <treescope-container class=\\\"treescope_out_d9ecfb6392a34945a6aadf520b5db673\\\" style=\\\"display:block\\\"></treescope-container> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_d9ecfb6392a34945a6aadf520b5db673\\\")) .filter((elt) => !elt.dataset.setup) )[0]; root.dataset.setup = 1; const msg = document.createElement(\\\"span\\\"); msg.style = \\\"color: #cccccc; font-family: monospace;\\\"; msg.textContent = \\\"(Loading...)\\\"; root.state.loadingMsg = msg; root.shadowRoot.appendChild(msg); root.state.chain = new Promise((resolve, reject) => { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { resolve(); observer.disconnect(); return; } } }, {rootMargin: \\\"1000px\\\"}); window.setTimeout(() => { observer.observe(root); }, 0); }); root.state.deferring = false; const _insertNode = (node) => { for (let oldScript of node.querySelectorAll(\\\"script\\\")) { let newScript = document.createElement(\\\"script\\\"); newScript.type = oldScript.type; newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); } if (root.state.loadingMsg) { root.state.loadingMsg.remove(); root.state.loadingMsg = null; } root.shadowRoot.appendChild(node); }; root.defns.insertContent = ((contentNode, compressed) => { if (compressed) { root.state.deferring = true; } if (root.state.deferring) { root.state.chain = (async () => { await root.state.chain; if (compressed) { const encoded = contentNode.textContent; const blob = new Blob([ Uint8Array.from(atob(encoded), (m) => m.codePointAt(0)) ]); const reader = blob.stream().pipeThrough( new DecompressionStream(\\\"deflate\\\") ).pipeThrough( new TextDecoderStream(\\\"utf-8\\\") ).getReader(); const parts = []; while (true) { const step = await reader.read(); if (step.done) { break; } parts.push(step.value); } const tpl = document.createElement('template'); tpl.innerHTML = parts.join(\\\"\\\"); _insertNode(tpl.content); } else { _insertNode(contentNode.content); } })(); } else { _insertNode(contentNode.content); } }); </script></treescope-run-here><div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >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</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_d9ecfb6392a34945a6aadf520b5db673\\\")) .filter((elt) => !elt.dataset['step0']) )[0]; root.dataset['step0'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<div style=\\\"display:none\\\"> <script type=\\\"application/octet-stream\\\" >eNqNUk1v2zAM/SuCBhQOsrhuEuRjtgNs7Qb0ssN22KEoAlWiYw2K5VFyMGPofx9lOy6ydcAukii+9yg+MVP6xJxvDeRcaVcb0b5jla2AM61yXljcKygAEdQ+uVktN+vNdr5MYLmAZLteL25Wm3lSwGqptiu+y1wtqn5l0gjnci6tIQ3x9IRw4rsr49Pv4mf8HlG00fwtU76tIW905RfzydXBp9l1r3He6H27zCOAk7aGGTbVrAQEKiJR1551fC7q2mgpvLbVtZUe/MwRRxz5TtrKEUrgAfy9cixnD/z/O3pMe76yEq31xPaldjFpfaHws1UQTVI21KBHDqABHv9oANuvYEB6ixGPxz72Ic3/ot4acj4UGeJYhouujMcGCD9mEOivJHzTvowu6KNogeJwhGp4TxOOsSRTPNwN4acBEXo4o2OyEip1W2qjRuGQt8giA55p0ktS2rIXV2MD1cGXdDudTtgvFnB9ksCjMoUfDYTjh/aexM/sB/1IBQLH2QYl3NFI/tPDN2EkOZuyP+h9eGHLqBYXGt25dtcZEV6yCEd76j7y+fInXjV5Eka0mz2azlcG8zfsOCrd</script> <treescope-run-here><script type=\\\"application/octet-stream\\\"> const root = ( Array.from(document.getElementsByClassName( \\\"treescope_out_d9ecfb6392a34945a6aadf520b5db673\\\")) .filter((elt) => !elt.dataset['step1']) )[0]; root.dataset['step1'] = 1; root.defns.insertContent( this.parentNode.querySelector('script[type=\\\"application/octet-stream\\\"]'), true ); this.parentNode.remove(); </script></treescope-run-here> </div>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Use `nnx.Variable` type `Filter`s to split into multiple `nnx.State`s.\\n\",\n    \"graphdef, params, counts = nnx.split(model, nnx.Param, Count)\\n\",\n    \"\\n\",\n    \"nnx.display(params, counts)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"**Note:** `Filter`s must be exhaustive, if a value is not matched an error will be raised.\\n\",\n    \"\\n\",\n    \"As expected, the `nnx.merge` and `nnx.update` methods naturally consume multiple `State`s:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Merge multiple `State`s\\n\",\n    \"model = nnx.merge(graphdef, params, counts)\\n\",\n    \"# Update with multiple `State`s\\n\",\n    \"nnx.update(model, params, counts)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"jupytext\": {\n   \"formats\": \"ipynb,md:myst\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.11.6\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "docs_nnx/nnx_basics.md",
    "content": "---\njupytext:\n  formats: ipynb,md:myst\n  text_representation:\n    extension: .md\n    format_name: myst\n    format_version: 0.13\n    jupytext_version: 1.13.8\n---\n\n# Flax basics\n\nFlax NNX is a new simplified API that is designed to make it easier to create, inspect, debug, and analyze neural networks in [JAX](https://jax.readthedocs.io/). It achieves this by adding first class support for Python reference semantics. This allows users to express their models using regular Python objects, which are modeled as PyGraphs (instead of pytrees), enabling reference sharing and mutability. Such API design should make PyTorch or Keras users feel at home.\n\nTo begin, install Flax with `pip` and import necessary dependencies:\n\n```{code-cell} ipython3\n:tags: [skip-execution]\n\n# ! pip install -U flax\n```\n\n```{code-cell} ipython3\nfrom flax import nnx\nimport jax\nimport jax.numpy as jnp\n```\n\n## The Flax NNX Module system\n\nThe main difference between the Flax `Module` and other Module systems in [Flax Linen](https://flax-linen.readthedocs.io/en/latest/api_reference/flax.linen/module.html) or [Haiku](https://dm-haiku.readthedocs.io/en/latest/notebooks/basics.html#Built-in-Haiku-nets-and-nested-modules) is that in NNX everything is **explicit**. This  means, among other things, that the NNX Module itself holds the state (such as parameters) directly, the [PRNG](https://jax.readthedocs.io/en/latest/random-numbers.html) state is threaded by the user, and all shape information must be provided on initialization (no shape inference).\n\nLet's begin by creating a Linear `Module`. As shown next, dynamic state is usually stored in `Param`s, and static state (all types not handled by NNX) such as integers or strings are stored directly. Attributes of type `jax.Array` and `numpy.ndarray` are also treated as dynamic states, although storing them inside Variables, such as Param, is preferred. Also the `Rngs` object can be used to get new unique keys based on a root PRNG key passed to the constructor.\n\n```{code-cell} ipython3\nclass Linear(nnx.Module):\n  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n    self.w = nnx.Param(rngs.params.uniform((din, dout)))\n    self.b = nnx.Param(jnp.zeros((dout,)))\n    self.din, self.dout = din, dout\n\n  def __call__(self, x: jax.Array):\n    return x @ self.w + self.b[None]\n```\n\nAlso note that the inner values of `Variable`s can be accessed using the `value` property, but for convenience they implement all numeric operators and can be used directly in arithmetic expressions (as shown in the code above).\n\nTo initialize a Flax `Module`, you just call the constructor, and all the parameters of a Module are usually created eagerly. Since Modules hold their own state methods, you can call them directly without the need for a separate apply method.\nThis can be very convenient for debugging, allowing you to directly inspect the entire structure of the model.\n\n```{code-cell} ipython3\nmodel = Linear(2, 5, rngs=nnx.Rngs(params=0))\ny = model(x=jnp.ones((1, 2)))\n\nprint(y)\nnnx.display(model)\n```\n\nThe above visualization by `nnx.display` is generated using the awesome\n[Treescope](https://treescope.readthedocs.io/en/stable/index.html#) library.\n\n+++\n\n### Stateful computation\n\nImplementing layers, such as `BatchNorm`, requires performing state updates during a forward pass. In Flax NNX, you just need to create a `Variable` and update its `.value` during the forward pass.\n\n```{code-cell} ipython3\nclass Count(nnx.Variable): pass\n\nclass Counter(nnx.Module):\n  def __init__(self):\n    self.count = Count(jnp.array(0))\n\n  def __call__(self):\n    self.count[...] += 1\n\ncounter = Counter()\nprint(f'{counter.count[...] = }')\ncounter()\nprint(f'{counter.count[...] = }')\n```\n\nMutable references are usually avoided in JAX. But Flax NNX provides sound mechanisms\nto handle them, as demonstrated in later sections of this guide.\n\n+++\n\n### Nested Modules\n\nFlax `Module`s can be used to compose other Modules in a nested structure. These can be assigned directly as attributes, or inside an attribute of any (nested) pytree type, such as a `list`, `dict`, `tuple`, and so on.\n\nThe example below shows how to define a simple `MLP` by subclassing `Module`. The model consists of two `Linear` layers, a `Dropout` layer, and a `BatchNorm` layer. Note that we need to pass the `__call__` method the RNG state that we want the `Dropout` layer to use.\n\n```{code-cell} ipython3\nclass MLP(nnx.Module):\n  def __init__(self, din: int, dmid: int, dout: int, *, rngs: nnx.Rngs):\n    self.linear1 = Linear(din, dmid, rngs=rngs)\n    self.dropout = nnx.Dropout(rate=0.1)\n    self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n    self.linear2 = Linear(dmid, dout, rngs=rngs)\n\n  def __call__(self, x: jax.Array, rngs: nnx.Rngs):\n    x = nnx.gelu(self.dropout(self.bn(self.linear1(x)), rngs=rngs))\n    return self.linear2(x)\n\nmodel = MLP(2, 16, 5, rngs=nnx.Rngs(0))\n\ny = model(x=jnp.ones((3, 2)), rngs=nnx.Rngs(1))\n\nnnx.display(model)\n```\n\n### Model surgery\n\nFlax `Module`s are mutable by default. This means that their structure can be changed at any time, which makes [model surgery](https://flax.readthedocs.io/en/latest/guides/surgery.html) quite easy, as any sub-Module attribute can be replaced with anything else, such as new Modules, existing shared Modules, Modules of different types, and so on. Moreover, `Variable`s can also be modified or replaced/shared.\n\nThe following example shows how to replace the `Linear` layers in the `MLP` model from the previous example with `LoraLinear` layers:\n\n```{code-cell} ipython3\nclass LoraParam(nnx.Param): pass\n\nclass LoraLinear(nnx.Module):\n  def __init__(self, linear: Linear, rank: int, rngs: nnx.Rngs):\n    self.linear = linear\n    self.A = LoraParam(rngs.normal((linear.din, rank)))\n    self.B = LoraParam(rngs.normal((rank, linear.dout)))\n\n  def __call__(self, x: jax.Array):\n    return self.linear(x) + x @ self.A @ self.B\n\nrngs = nnx.Rngs(0)\nmodel = MLP(2, 32, 5, rngs=rngs)\n\n# Model surgery.\nmodel.linear1 = LoraLinear(model.linear1, 4, rngs=rngs)\nmodel.linear2 = LoraLinear(model.linear2, 4, rngs=rngs)\n\ny = model(x=jnp.ones((3, 2)), rngs=rngs)\n\nnnx.display(model)\n```\n\n## Flax transformations\n\n[Flax NNX transformations (transforms)](https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html) extend [JAX transforms](https://jax.readthedocs.io/en/latest/key-concepts.html#transformations) to support `Module`s and other objects. They serve as supersets of their equivalent JAX counterparts with the addition of being aware of the object's state and providing additional APIs to transform it.\n\nOne of the main features of Flax Transforms is the preservation of reference semantics, meaning that any mutation of the object graph that occurs inside the transform is propagated outside as long as it is legal within the transform rules. In practice this means that Flax programs can be expressed using imperative code, highly simplifying the user experience.\n\nIn the following example, you define a `train_step` function that takes a `MLP` model, an `Optimizer`, and a batch of data, and returns the loss for that step. The loss and the gradients are computed using the `nnx.value_and_grad` transform over the `loss_fn`. The gradients are passed to the optimizer's `update` method to update the model's parameters.\n\n```{code-cell} ipython3\nimport optax\n\n# An MLP containing 2 custom `Linear` layers, 1 `nnx.Dropout` layer, 1 `nnx.BatchNorm` layer.\nmodel = MLP(2, 16, 10, rngs=nnx.Rngs(0))\noptimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\n\n@nnx.jit  # Automatic state management\ndef train_step(model, optimizer, x, y, rngs):\n  def loss_fn(model: MLP, rngs: nnx.Rngs):\n    y_pred = model(x, rngs)\n    return jnp.mean((y_pred - y) ** 2)\n\n  loss, grads = nnx.value_and_grad(loss_fn)(model, rngs)\n  optimizer.update(model, grads)  # In place updates.\n\n  return loss\n\nx, y = jnp.ones((5, 2)), jnp.ones((5, 10))\nloss = train_step(model, optimizer, x, y, rngs)\n\nprint(f'{loss = }')\nprint(f'{optimizer.step.value = }')\n```\n\nThere are two things happening in this example that are worth mentioning:\n\n1. The updates to each of the `BatchNorm` and `Dropout` layer's state is automatically propagated from within `loss_fn` to `train_step` all the way to the `model` reference outside.\n2. The `optimizer` holds a mutable reference to the model - this relationship is preserved inside the train_step function making it possible to update the model's parameters using the optimizer alone.\n\n> **Note**<br> `nnx.jit` has performance overhead for small models, check the [Performance Considerations](https://flax.readthedocs.io/en/latest/guides/performance.html) guide for more information.\n\n### Scan over layers\n\nThe next example uses Flax `nnx.vmap` to create a stack of multiple MLP layers and `nnx.scan` to iteratively apply each layer of the stack to the input.\n\nIn the code below notice the following:\n\n1. The custom `create_model` function takes in a key and returns an `MLP` object, since you create five keys and use `nnx.vmap` over `create_model` a stack of 5 `MLP` objects is created.\n2. The `nnx.scan` is used to iteratively apply each `MLP` in the stack to the input `x`.\n3. The nnx.scan (consciously) deviates from `jax.lax.scan` and instead mimics nnx.vmap, which is more expressive. nnx.scan allows specifying multiple inputs, the scan axes of each input/output, and the position of the carry.\n4. `State` updates for `BatchNorm` layers are automatically propagated by nnx.scan.\n5. The `rngs` object is split into separate streams for each layer using the `fork` method.\n\n```{code-cell} ipython3\n@nnx.vmap(in_axes=0, out_axes=0)\ndef create_model(rngs):\n  return MLP(10, 32, 10, rngs=rngs)\n\n@nnx.scan(in_axes=(0, 0, nnx.Carry), out_axes=nnx.Carry)\ndef forward(model: MLP, rngs: nnx.Rngs, x):\n  x = model(x, rngs)\n  return x\n    \nparam_rngs = nnx.Rngs(0).fork(split=5)\nmodel = create_model(param_rngs)\n```\n\n```{code-cell} ipython3\nx = jnp.ones((3, 10))\ndropout_rngs = nnx.Rngs(1).fork(split=5)\ny = forward(model, dropout_rngs, x)\n\nprint(f'{y.shape = }')\nnnx.display(model)\n```\n\nHow do Flax NNX transforms achieve this? To understand how Flax NNX objects interact with JAX transforms, the next section explains the Flax NNX Functional API.\n\n+++\n\n## The Flax Functional API\n\nThe Flax NNX Functional API establishes a clear boundary between reference/object semantics and value/pytree semantics. It also allows the same amount of fine-grained control over the state that Flax Linen and Haiku users are used to. The Flax NNX Functional API consists of three basic methods:  `nnx.split`, `nnx.merge`, and `nnx.update`.\n\nBelow is an example of `StatefulLinear` `Module` that uses the Functional API. It contains:\n\n- Some `Param` Variables; and\n- A custom `Count` Variable type, which is used to track the integer scalar state that increases on every forward pass.\n\n```{code-cell} ipython3\nclass Count(nnx.Variable): pass\n\nclass StatefulLinear(nnx.Module):\n  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n    self.w = nnx.Param(rngs.uniform((din, dout)))\n    self.b = nnx.Param(jnp.zeros((dout,)))\n    self.count = Count(jnp.array(0, dtype=jnp.uint32))\n\n  def __call__(self, x: jax.Array):\n    self.count.value += 1\n    return x @ self.w + self.b\n\nmodel = StatefulLinear(din=3, dout=5, rngs=nnx.Rngs(0))\ny = model(jnp.ones((1, 3)))\n\nnnx.display(model)\n```\n\n### State and GraphDef\n\nA Flax `Module` can be decomposed into `State` and `GraphDef` using the `nnx.split` function:\n\n- `State` is a `Mapping` from strings to `Variable`s or nested  `State`s.\n- `GraphDef` contains all the static information needed to reconstruct a `Module` graph, it is analogous to [JAX's `PyTreeDef`](https://jax.readthedocs.io/en/latest/pytrees.html#internal-pytree-handling).\n\n```{code-cell} ipython3\ngraphdef, state = nnx.split(model)\n\nnnx.display(graphdef, state)\n```\n\n### Split, merge, and update\n\nFlax's `nnx.merge` is the reverse of `nnx.split`. It takes the `GraphDef` + `State` and reconstructs the `Module`. The example below demonstrates this as follows:\n\n- By using `nnx.split` and `nnx.merge` in sequence any `Module` can be lifted to be used in any JAX transform.\n- `nnx.update` can update an object in place with the content of a given `State`.\n- This pattern is used to propagate the state from a transform back to the source object outside.\n\n```{code-cell} ipython3\nprint(f'{model.count.value = }')\n\n# 1. Use `nnx.split` to create a pytree representation of the `nnx.Module`.\ngraphdef, state = nnx.split(model)\n\n@jax.jit\ndef forward(graphdef: nnx.GraphDef, state: nnx.State, x: jax.Array) -> tuple[jax.Array, nnx.State]:\n  # 2. Use `nnx.merge` to create a new model inside the JAX transformation.\n  model = nnx.merge(graphdef, state)\n  # 3. Call the `nnx.Module`\n  y = model(x)\n  # 4. Use `nnx.split` to propagate `nnx.State` updates.\n  _, state = nnx.split(model)\n  return y, state\n\ny, state = forward(graphdef, state, x=jnp.ones((1, 3)))\n# 5. Update the state of the original `nnx.Module`.\nnnx.update(model, state)\n\nprint(f'{model.count.value = }')\n```\n\nThe key insight of this pattern is that using mutable references is fine within a transform context (including the base eager interpreter) but it is necessary to use the Functional API when crossing boundaries.\n\n**Why aren't Modules just pytrees?** The main reason is that it is very easy to lose track of shared references by accident this way, for example if you pass two `Module`s that have a shared Module through a JAX boundary, you will silently lose that sharing. Flax's Functional API makes this behavior explicit, and thus it is much easier to reason about.\n\n+++\n\n### Fine-grained State control\n\nExperienced [Flax Linen](https://flax-linen.readthedocs.io/) or [Haiku](https://dm-haiku.readthedocs.io/) API users may recognize that having all the states in a single structure is not always the best choice as there are cases in which you may want to handle different subsets of the state differently. This is a common occurrence when interacting with [JAX transforms](https://jax.readthedocs.io/en/latest/key-concepts.html#transformations).\n\nFor example:\n\n- Not every model state can or should be differentiated when interacting with `jax.grad`.\n- Or, sometimes, there is a need to specify what part of the model's state is a carry and what part is not when using `jax.lax.scan`.\n\nTo address this, the Flax NNX API has `nnx.split`, which allows you to pass one or more `Filter`s to partition the `Variable`s into mutually exclusive `State`s. Flax NNx uses `Filter` create `State` groups in APIs (such as `nnx.split`, `nnx.state`, and many of NNX transforms).\n\nThe example below shows the most common `Filter`s:\n\n```{code-cell} ipython3\n# Use `nnx.Variable` type `Filter`s to split into multiple `nnx.State`s.\ngraphdef, params, counts = nnx.split(model, nnx.Param, Count)\n\nnnx.display(params, counts)\n```\n\n**Note:** `Filter`s must be exhaustive, if a value is not matched an error will be raised.\n\nAs expected, the `nnx.merge` and `nnx.update` methods naturally consume multiple `State`s:\n\n```{code-cell} ipython3\n# Merge multiple `State`s\nmodel = nnx.merge(graphdef, params, counts)\n# Update with multiple `State`s\nnnx.update(model, params, counts)\n```\n"
  },
  {
    "path": "docs_nnx/nnx_glossary.rst",
    "content": "*****************\nFlax NNX glossary\n*****************\n\nFor additional terms, refer to the `JAX glossary <https://jax.readthedocs.io/en/latest/glossary.html>`__.\n\n.. glossary::\n\n    Filter\n      A way to extract only certain :term:`nnx.Variable<Variable>` objects out of a Flax NNX :term:`Module<Module>` (``nnx.Module``). This is usually done by calling :meth:`nnx.split <flax.nnx.split>` upon the :class:`nnx.Module<flax.nnx.Module>`. Refer to the `Filter guide <https://flax.readthedocs.io/en/latest/guides/filters_guide.html>`__ to learn more.\n\n    Folding in\n      In Flax, `folding in <https://jax.readthedocs.io/en/latest/_autosummary/jax.random.fold_in.html>`__ means generating a new `JAX pseudorandom number generator (PRNG) <https://jax.readthedocs.io/en/latest/random-numbers.html>`__ key, given an input PRNG key and integer. This is typically used when you want to generate a new key but still be able to use the original PRNG key afterwards. You can also do this in JAX with `jax.random.split <https://jax.readthedocs.io/en/latest/_autosummary/jax.random.split.html>`__, but this method will effectively create two PRNG keys, which is slower. Learn how Flax generates new PRNG keys automatically in the `Randomness/PRNG guide <https://flax.readthedocs.io/en/latest/guides/randomness.html>`__.\n\n    GraphDef\n      :class:`nnx.GraphDef<flax.nnx.GraphDef>` is a class that represents all the static, stateless, and Pythonic parts of a Flax :term:`Module<Module>` (:class:`nnx.Module<flax.nnx.Module>`).\n\n    Merge\n      Refer to :term:`Split and merge<Split and merge>`.\n\n    Module\n      :class:`nnx.Module <flax.nnx.Module>` is a dataclass that enables defining and initializing parameters in a referentially-transparent form. It is responsible for storing and updating :term:`Variable<Variable> objects and parameters within itself.\n\n    Params / parameters\n       :class:`nnx.Param <flax.nnx.Param>` is a particular subclass of :class:`nnx.Variable <flax.nnx.Variable>` that generally contains the trainable weights.\n\n    PRNG states\n      A Flax :class:`nnx.Module <flax.nnx.Module>` can keep a reference of a `pseudorandom number generator (PRNG) <https://jax.readthedocs.io/en/latest/random-numbers.html>`__ state object :class:`nnx.Rngs <flax.nnx.Rngs>` that can generate new `JAX PRNG <https://jax.readthedocs.io/en/latest/random-numbers.html>`__ keys. These keys are used to generate random JAX arrays through `JAX's functional PRNGs <https://jax.readthedocs.io/en/latest/random-numbers.html>`__.\n      You can use a PRNG state with different seeds to add more fine-grained control to your model (for example, to have independent random numbers for parameters and dropout masks).\n      Refer to the Flax `Randomness/PRNG guide <https://flax.readthedocs.io/en/latest/guides/randomness.html>`__\n      for more details.\n\n    Split and merge\n      :meth:`nnx.split <flax.nnx.split>` is a way to represent an :class:`nnx.Module <flax.nnx.Module>` by two parts: 1) a static Flax NNX :term:`GraphDef <GraphDef>` that captures its Pythonic static information; and 2) one or more :term:`Variable state(s)<Variable state>` that capture its `JAX arrays <https://jax.readthedocs.io/en/latest/key-concepts.html#jax-arrays-jax-array>`__ (``jax.Array``) in the form of `JAX pytrees <https://jax.readthedocs.io/en/latest/working-with-pytrees.html>`__. They can be merged back to the original ``nnx.Module`` using :meth:`nnx.merge <flax.nnx.merge>`.\n\n    Transformation\n      A Flax NNX transformation (transform) is a wrapped version of a `JAX transformation <https://flax.readthedocs.io/en/latest/guides/transforms.html>`__ that allows the function that is being transformed to take the Flax NNX :term:`Module<Module>` (``nnx.Module``) as input or output. For example, a \"lifted\" version of `jax.jit <https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html#jax.jit>`__ is :meth:`nnx.jit <flax.nnx.jit>`. Check out the `Flax NNX transforms guide <https://flax.readthedocs.io/en/latest/guides/transforms.html>`__ to learn more.\n\n    Variable\n      The weights / parameters / data / array :class:`nnx.Variable <flax.nnx.Variable>` residing in a Flax :term:`Module<Module>`. Variables are defined inside modules as :class:`nnx.Variable <flax.nnx.Variable>` or its subclasses.\n"
  },
  {
    "path": "docs_nnx/philosophy.md",
    "content": "# The Flax philosophy\n\nIn no particular order:\n\n* Library code should be easy to read and understand.\n* Prefer duplicating code over a bad abstraction.\n* Generally, prefer duplicating code over adding options to functions.\n* Comment-driven design: If it's hard to document your code, consider\n  changing the design.\n* Unit test-driven design: If it's hard to test your code, consider\n  changing the design.\n* People start projects by copying an existing implementation — make\n  base implementations excellent.\n* If we expose an abstraction to our developers, we own the mental\n  overhead.\n* Developer-facing functional programming abstractions confuse some users,\n  expose them where the benefit is high.\n* \"Read the manual\" is not an appropriate response to developer confusion.\n  The framework should guide developers\n  towards good solutions, such as through assertions and error messages.\n* An unhelpful error message is a bug.\n* \"Debugging is twice as hard as writing the code in the first\n  place. Therefore, if you write the code as cleverly as possible, you\n  are, by definition, not smart enough to debug it.\" — Brian Kernighan\n\n## Design principles\n\nFlax is a neural network library built on [JAX](https://jax.readthedocs.io) that has been adopted by a\ngrowing set of users, most notably in the JAX submissions for the MLPerf\n0.7 benchmark. Our experience over the last year (and many conversations\nwith users and JAX core devs) has guided a redesign of the API called\n[Linen](https://github.com/google/flax/blob/main/flax/linen/README.md) ([`flax.linen`](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/index.html)) in response to the following basic design questions.\n\n### How does a neural network library benefit from being built on JAX and leverage JAX’s unique strengths?\n\nThe world already has TensorFlow and PyTorch, and there’s little need to\nbuild a clone of either. We believe that the composable\nfunction-transformation approach that JAX takes opens up new frontiers\nfor making neural net code more maintainable, more scalable and more\nperformant than existing libraries. While we strive to offer an API\nfamiliar to those experienced with Keras/Sonnet/PyTorch, Linen is\nfundamentally a functional system for defining neural nets in JAX. Just\na few examples of what we believe a JAX-targeted library can enable:\n\n- Write models as “single-example” code and introduce batching\n  automatically with [`jax.vmap`](https://jax.readthedocs.io/en/latest/_autosummary/jax.vmap.html).\n- Automatically handle ragged batches in NLP and other masking issues.\n- Create efficient compile-time and runtime models by utilizing\n  rematerialized `scan` for massive convolutional networks.\n- Remove memory headaches by enabling easy rematerialization,\n  reversibility, and model-parallel data sharding.\n\n### How does one interoperate with JAX transformations?\n\nArguably, the entire point of a neural net library is to offer an\nimplicit variable management API to save the user from having to\nmanually thread thousands of variables through a complex tree of\nfunctions. However, JAX operates on pure functions. To handle both\ncurrent and future JAX transforms (configured and composed in any way),\nLinen Modules are directly “functionalized”, that is, automatically cast\nin-place as explicit functions of the form:\n\n$$f \\left( v_{in}, x \\right) \\rightarrow v_{out}, y$$\n\nWhere $v_{in}$ is the variable collections and [PRNG](https://jax.readthedocs.io/en/latest/jep/263-prng.html) state used by\nthe model, $v_{out}$ the mutated output variable collections,\n$x$ the input data and $y$ the output data. Applying JAX\ntransformations then simply reduces to specifying any argument-specific\ntransform options to the various variable collections and PRNG state.\nThis unleashes the flexibility and strength of [JAX transformations](https://jax.readthedocs.io/en/latest/notebooks/quickstart.html) – for\nexample, one can achieve either device-parallel training or per-device\nensembling by using [`jax.pmap`](https://jax.readthedocs.io/en/latest/_autosummary/jax.pmap.html) in different ways, without any explicit\nlibrary support. Moreover, **within [Modules](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module)**, we expose lightweight\nwrappers around the complex JAX transforms such as [`jax.vmap`](https://jax.readthedocs.io/en/latest/_autosummary/jax.vmap.html) and [`jax.lax.scan`](https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.scan.html)\nthat annotate how each variable collection is to be transformed by JAX.\nImportantly, we handle the nontrivial cases of creating new variables\nand transformed variables under mapping and loop transforms correctly\nfor initialization and application.\n\n### How are parameters represented, and how do we handle general “differentiable algorithms” that update stateful variables?\n\nWe follow the JAX functional conventions of storing data in “pytrees”:\nJAX arrays contained in nested tuples, lists, dictionaries. Because\nresearchers inevitably manually interact with this data, we use nested\ndictionaries with meaningful default keys and offer several utilities\n(traversals, etc.) for handling them directly. Linen uses an accelerated\nversion of a Python frozen dictionary that caches its JAX-flattened form\nto speed up [`jit`](https://jax.readthedocs.io/en/latest/_autosummary/jax.jit.html)ted function call overheads.\n\nFlax generalizes the operation of a neural net by allowing models to\naccept collections of several different “kinds”: parameters, batch-norm\nstats, autoregressive caches, debug information, fine-grained\nhyperparameters, etc. Each collection is stored in a nested dictionary\nof the same structure as the model. Importantly, we do *not* conflate\nthese various kinds under the single vague rubric of “state”, but keep\ndifferent logical types of variables separate that can be treated\ndifferently under JAX transformations and under mutations (e.g. training\nvs prediction). Similarly, we allow for multiple separate named PRNG\nchains inside [Modules](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module) for separate treatment of randomness for different\napplications such as initialization, dropout, sampling, etc.\n\nAt every stage the data associated with a neural net is not kept in a\ncustom object hierarchy, but left in an explicit, Python and JAX native\nform that is easy to introspect and modify. Users have utilized this to\nmap TF and PyTorch checkpoints to Flax, to implement submodel-specific\nloss terms, and to perform fast model surgery, etc. For saving this\ndata, most Flax examples store these nested dictionaries via the\nefficient “msgpack” binary format – but as variables are simply Python\ndicts, you can use any (non-JAX-aware) serialization library directly.\n\n### How does one interoperate with purely functional JAX code?\n\nTo be broadly useful to the JAX ecosystem, users shouldn’t need to\nheavily refactor their code in order to add “trainability” for a given\nnumerical task. _“The library should not get in the way.”_ Utilizing\npurely functional code from within Linen is trivial: [Module](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.Module)\nimplementations are just JAX code with named variables. Using Linen\nModules inside otherwise purely functional code can be as simple as\nusing a single top-level Module transformation to allow initialization\nand pure application of any JAX program that might contain various\ntrainable sections.\n"
  },
  {
    "path": "docs_nnx/robots.txt",
    "content": "User-agent: *\n\nDisallow: /api_reference/flax.linen/_autosummary/ # for SEO, since Google still indexes this deprecated link\n\nSitemap: https://flax.readthedocs.io/sitemap.xml\n"
  },
  {
    "path": "docs_nnx/why.rst",
    "content": "Why Flax NNX?\n=============\n\nIn 2020, the Flax team released the Flax Linen API to support modeling research on JAX, with a focus on scaling\nand performance. We have learned a lot from users since then. The team introduced certain ideas that have proven to be beneficial to users, such as:\n\n* Organizing variables into `collections <https://flax.readthedocs.io/en/latest/glossary.html#term-Variable-collections>`_.\n* Automatic and efficient `pseudorandom number generator (PRNG) management <https://flax.readthedocs.io/en/latest/glossary.html#term-RNG-sequences>`_.\n* `Variable metadata <https://flax.readthedocs.io/en/latest/api_reference/flax.linen/spmd.html#flax.linen.with_partitioning>`_\n  for `Single Program Multi Data (SPMD) <https://jax.readthedocs.io/en/latest/glossary.html#term-SPMD>`_ annotations, optimizer metadata, and other use cases.\n\nOne of the choices the Flax team made was to use functional (``compact``) semantics for neural network programming via lazy initialization of parameters.\nThis made for concise implementation code and aligned the Flax Linen API with Haiku.\n\nHowever, this also meant that the semantics of Modules and variables in Flax were non-Pythonic and often surprising. It also led to implementation\ncomplexity and obscured the core ideas of `transformations (transforms) <https://jax.readthedocs.io/en/latest/glossary.html#term-transformation>`_ on neural networks.\n\n.. testsetup:: Linen, NNX\n\n    import jax\n    from jax import random, numpy as jnp\n    from flax import nnx\n    import flax.linen as nn\n\nIntroducing Flax NNX\n--------------------\n\nFast forward to 2024, the Flax team developed Flax NNX - an attempt to retain the features that made Flax Linen useful for users, while introducing some new principles.\nThe central idea behind Flax NNX is to introduce reference semantics into JAX. The following are its main features:\n\n- **NNX is Pythonic**: Regular Python semantics for Modules, including support for mutability and shared references.\n- **NNX is simple**: Many of the complex APIs in Flax Linen are either simplified using Python idioms or completely removed.\n- **Better JAX integration**: Custom NNX transforms adopt the same APIs as the JAX transforms. And with NNX\n  it is easier to use `JAX transforms (higher-order functions) <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`_ directly.\n\nHere is an example of a simple Flax NNX program that illustrates many of the points from above:\n\n.. testcode:: NNX\n\n  from flax import nnx\n  import optax\n\n\n  class Model(nnx.Module):\n    def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n      self.linear = nnx.Linear(din, dmid, rngs=rngs)\n      self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n      self.dropout = nnx.Dropout(0.2, rngs=rngs)\n      self.linear_out = nnx.Linear(dmid, dout, rngs=rngs)\n\n    def __call__(self, x):\n      x = nnx.relu(self.dropout(self.bn(self.linear(x))))\n      return self.linear_out(x)\n\n  model = Model(2, 64, 3, rngs=nnx.Rngs(0))  # Eager initialization\n  optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\n\n  @nnx.jit  # Automatic state management for JAX transforms.\n  def train_step(model, optimizer, x, y):\n    def loss_fn(model):\n      y_pred = model(x)  # call methods directly\n      return ((y_pred - y) ** 2).mean()\n\n    loss, grads = nnx.value_and_grad(loss_fn)(model)\n    optimizer.update(model, grads)  # in-place updates\n\n    return loss\n\nFlax NNX's improvements on Linen\n--------------------------------\n\nThe rest of this document uses various examples that demonstrate how Flax NNX improves on Flax Linen.\n\nInspection\n^^^^^^^^^^\n\nThe first improvement is that Flax NNX Modules are regular Python objects. This means that you can easily\nconstruct and inspect ``Module`` objects.\n\nOn the other hand, Flax Linen Modules are not easy to inspect and debug because they are lazy, which means some attributes are not available upon construction and are only accessible at runtime.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  class Block(nn.Module):\n    def setup(self):\n      self.linear = nn.Dense(10)\n\n  block = Block()\n\n  try:\n    block.linear  # AttributeError: \"Block\" object has no attribute \"linear\".\n  except AttributeError as e:\n    pass\n\n\n\n\n\n  ...\n\n  ---\n\n  class Block(nnx.Module):\n    def __init__(self, rngs):\n      self.linear = nnx.Linear(5, 10, rngs=rngs)\n\n  block = Block(nnx.Rngs(0))\n\n\n  block.linear\n  # Linear(\n  #   kernel=Param(\n  #     value=Array(shape=(5, 10), dtype=float32)\n  #   ),\n  #   bias=Param(\n  #     value=Array(shape=(10,), dtype=float32)\n  #   ),\n  #   ...\n\nNotice that in the Flax NNX example above, there is no shape inference - both the input and output shapes must be provided\nto the ``Linear`` ``nnx.Module``. This is a tradeoff that allows for more explicit and predictable behavior.\n\nRunning computation\n^^^^^^^^^^^^^^^^^^^\n\nIn Flax Linen, all top-level computation must be done through the ``flax.linen.Module.init`` or ``flax.linen.Module.apply`` methods, and the\nparameters or any other type of state are handled as a separate structure. This creates an asymmetry between: 1) code that runs inside\n``apply`` that can run methods and other ``Module`` objects directly; and 2) code that runs outside of ``apply`` that must use the ``apply`` method.\n\nIn Flax NNX, there's no special context because parameters are held as attributes and methods can be called directly. That means your NNX Module's ``__init__`` and ``__call__`` methods are not treated differently from other class methods, whereas Flax Linen Module's ``setup()`` and ``__call__`` methods are special.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  Encoder = lambda: nn.Dense(10)\n  Decoder = lambda: nn.Dense(2)\n\n  class AutoEncoder(nn.Module):\n    def setup(self):\n      self.encoder = Encoder()\n      self.decoder = Decoder()\n\n    def __call__(self, x) -> jax.Array:\n      return self.decoder(self.encoder(x))\n\n    def encode(self, x) -> jax.Array:\n      return self.encoder(x)\n\n  x = jnp.ones((1, 2))\n  model = AutoEncoder()\n  params = model.init(random.key(0), x)['params']\n\n  y = model.apply({'params': params}, x)\n  z = model.apply({'params': params}, x, method='encode')\n  y = Decoder().apply({'params': params['decoder']}, z)\n\n  ---\n\n  Encoder = lambda rngs: nnx.Linear(2, 10, rngs=rngs)\n  Decoder = lambda rngs: nnx.Linear(10, 2, rngs=rngs)\n\n  class AutoEncoder(nnx.Module):\n    def __init__(self, rngs):\n      self.encoder = Encoder(rngs)\n      self.decoder = Decoder(rngs)\n\n    def __call__(self, x) -> jax.Array:\n      return self.decoder(self.encoder(x))\n\n    def encode(self, x) -> jax.Array:\n      return self.encoder(x)\n\n  x = jnp.ones((1, 2))\n  model = AutoEncoder(nnx.Rngs(0))\n\n\n  y = model(x)\n  z = model.encode(x)\n  y = model.decoder(z)\n\nIn Flax Linen, calling sub-Modules directly is not possible because they are not initialized.\nTherefore, what you must do is construct a new instance and then provide a proper parameter structure.\n\nBut in Flax NNX you can call sub-Modules directly without any issues.\n\nState handling\n^^^^^^^^^^^^^^\n\nOne of the areas where Flax Linen is notoriously complex is in state handling. When you use either a\n`Dropout` layer, a `BatchNorm` layer, or both, you suddenly have to handle the new state and use it to\nconfigure the ``flax.linen.Module.apply`` method.\n\nIn Flax NNX, state is kept inside an ``nnx.Module`` and is mutable, which means it can just be called directly.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  class Block(nn.Module):\n    train: bool\n\n    def setup(self):\n      self.linear = nn.Dense(10)\n      self.bn = nn.BatchNorm(use_running_average=not self.train)\n      self.dropout = nn.Dropout(0.1, deterministic=not self.train)\n\n    def __call__(self, x):\n      return nn.relu(self.dropout(self.bn(self.linear(x))))\n\n  x = jnp.ones((1, 5))\n  model = Block(train=True)\n  vs = model.init(random.key(0), x)\n  params, batch_stats = vs['params'], vs['batch_stats']\n\n  y, updates = model.apply(\n    {'params': params, 'batch_stats': batch_stats},\n    x,\n    rngs={'dropout': random.key(1)},\n    mutable=['batch_stats'],\n  )\n  batch_stats = updates['batch_stats']\n\n  ---\n\n  class Block(nnx.Module):\n\n\n    def __init__(self, rngs):\n      self.linear = nnx.Linear(5, 10, rngs=rngs)\n      self.bn = nnx.BatchNorm(10, rngs=rngs)\n      self.dropout = nnx.Dropout(0.1, rngs=rngs)\n\n    def __call__(self, x):\n      return nnx.relu(self.dropout(self.bn(self.linear(x))))\n\n  x = jnp.ones((1, 5))\n  model = Block(nnx.Rngs(0))\n\n\n\n  y = model(x)\n\n\n\n\n\n  ...\n\nThe main benefit of Flax NNX's state handling is that you don't have to change the training code when you add a new stateful layer.\n\nIn addition, in Flax NNX, layers that handle state are also very easy to implement. Below\nis a simplified version of a ``BatchNorm`` layer that updates the mean and variance every time it is called.\n\n.. testcode:: NNX\n\n  class BatchNorm(nnx.Module):\n    def __init__(self, features: int, mu: float = 0.95):\n      # Variables\n      self.scale = nnx.Param(jax.numpy.ones((features,)))\n      self.bias = nnx.Param(jax.numpy.zeros((features,)))\n      self.mean = nnx.BatchStat(jax.numpy.zeros((features,)))\n      self.var = nnx.BatchStat(jax.numpy.ones((features,)))\n      self.mu = mu  # Static\n\n  def __call__(self, x):\n    mean = jax.numpy.mean(x, axis=-1)\n    var = jax.numpy.var(x, axis=-1)\n    # ema updates\n    self.mean.value = self.mu * self.mean + (1 - self.mu) * mean\n    self.var.value = self.mu * self.var + (1 - self.mu) * var\n    # normalize and scale\n    x = (x - mean) / jax.numpy.sqrt(var + 1e-5)\n    return x * self.scale + self.bias\n\n\nModel surgery\n^^^^^^^^^^^^^\n\nIn Flax Linen, `model surgery <https://flax.readthedocs.io/en/latest/guides/surgery.html>`_ has historically been challenging because of two reasons:\n\n1. Due to lazy initialization, it is not guaranteed that you can replace a sub-``Module`` with a new one.\n2. The parameter structure is separated from the ``flax.linen.Module`` structure, which means you have to manually keep them in sync.\n\nIn Flax NNX, you can replace sub-Modules directly as per the Python semantics. Since parameters are\npart of the ``nnx.Module`` structure, they are never out of sync. Below is an example of how you can\nimplement a LoRA layer, and then use it to replace a ``Linear`` layer in an existing model.\n\n.. codediff::\n  :title: Linen, NNX\n  :sync:\n\n  class LoraLinear(nn.Module):\n    linear: nn.Dense\n    rank: int\n\n    @nn.compact\n    def __call__(self, x: jax.Array):\n      A = self.param(random.normal, (x.shape[-1], self.rank))\n      B = self.param(random.normal, (self.rank, self.linear.features))\n\n      return self.linear(x) + x @ A @ B\n\n  try:\n    model = Block(train=True)\n    model.linear = LoraLinear(model.linear, rank=5) # <-- ERROR\n\n    lora_params = model.linear.init(random.key(1), x)\n    lora_params['linear'] = params['linear']\n    params['linear'] = lora_params\n\n  except AttributeError as e:\n    pass\n\n  ---\n\n  class LoraParam(nnx.Param): pass\n\n  class LoraLinear(nnx.Module):\n    def __init__(self, linear, rank, rngs):\n      self.linear = linear\n      self.A = LoraParam(random.normal(rngs(), (linear.in_features, rank)))\n      self.B = LoraParam(random.normal(rngs(), (rank, linear.out_features)))\n\n    def __call__(self, x: jax.Array):\n      return self.linear(x) + x @ self.A @ self.B\n\n  rngs = nnx.Rngs(0)\n  model = Block(rngs)\n  model.linear = LoraLinear(model.linear, rank=5, rngs=rngs)\n\n\n\n\n\n\n  ...\n\nAs shown above, in Flax Linen this doesn't really work in this case because the ``linear`` sub-``Module``\nis not available. However, the rest of the code provides an idea of how the ``params`` structure must be manually updated.\n\nPerforming arbitrary model surgery is not easy in Flax Linen, and currently the\n`intercept_methods <https://flax-linen.readthedocs.io/en/latest/api_reference/flax.linen/module.html#flax.linen.intercept_methods>`_\nAPI is the only way to do generic patching of methods. But this API is not very ergonomic.\n\nIn Flax NNX, to do generic model surgery you can just use ``nnx.iter_graph``, which is much simpler and easier than in Linen. Below is an example of replacing all ``nnx.Linear`` layers in a model with custom-made ``LoraLinear`` NNX layers.\n\n.. testcode:: NNX\n\n  rngs = nnx.Rngs(0)\n  model = Block(rngs)\n\n  for path, module in nnx.iter_graph(model):\n    if isinstance(module, nnx.Module):\n      for name, value in vars(module).items():\n        if isinstance(value, nnx.Linear):\n          setattr(module, name, LoraLinear(value, rank=5, rngs=rngs))\n\nTransforms\n^^^^^^^^^^\n\nFlax Linen transforms are very powerful in that they enable fine-grained control over the model's state.\nHowever, Flax Linen transforms have drawbacks, such as:\n\n1. They expose additional APIs that are not part of JAX, making their behavior confusing and sometimes divergent from their JAX counterparts. This also constrains your ways to interact with `JAX transforms <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`_ and keep up with JAX API changes.\n2. They work on functions with very specific signatures, namely:\n  - A ``flax.linen.Module`` must be the first argument.\n  - They accept other ``Module`` objects as arguments but not as return values.\n3. They can only be used inside ``flax.linen.Module.apply``.\n\nOn the other hand, `Flax NNX transforms <https://flax.readthedocs.io/en/latest/guides/transforms.html>`_\nare intented to be equivalent to their corresponding `JAX transforms <https://jax.readthedocs.io/en/latest/key-concepts.html#transformations>`_\nwith an exception - they can be used on Flax NNX Modules. This means that Flax transforms:\n\n1) Have the same API as JAX transforms.\n2) Can accept Flax NNX Modules on any argument, and ``nnx.Module`` objects can be returned from it/them.\n3) Can be used anywhere including the training loop.\n\nBelow is an example of using ``vmap`` with Flax NNX to both create a stack of weights by transforming the\n``create_weights`` function, which returns some ``Weights``, and to apply that stack of weights to a batch\nof inputs individually by transforming the ``vector_dot`` function, which takes ``Weights`` as the first\nargument and a batch of inputs as the second argument.\n\n.. testcode:: NNX\n\n  class Weights(nnx.Module):\n    def __init__(self, kernel: jax.Array, bias: jax.Array):\n      self.kernel, self.bias = nnx.Param(kernel), nnx.Param(bias)\n\n  def create_weights(seed: jax.Array):\n    return Weights(\n      kernel=random.uniform(random.key(seed), (2, 3)),\n      bias=jnp.zeros((3,)),\n    )\n\n  def vector_dot(weights: Weights, x: jax.Array):\n    assert weights.kernel.ndim == 2, 'Batch dimensions not allowed'\n    assert x.ndim == 1, 'Batch dimensions not allowed'\n    return x @ weights.kernel + weights.bias\n\n  seeds = jnp.arange(10)\n  weights = nnx.vmap(create_weights, in_axes=0, out_axes=0)(seeds)\n\n  x = jax.random.normal(random.key(1), (10, 2))\n  y = nnx.vmap(vector_dot, in_axes=(0, 0), out_axes=1)(weights, x)\n\nContrary to Flax Linen transforms, the ``in_axes`` argument and other APIs do affect how the ``nnx.Module`` state is transformed.\n\nIn addition, Flax NNX transforms can be used as method decorators, because ``nnx.Module`` methods are simply\nfunctions that take a ``Module`` as the first argument. This means that the previous example can be\nrewritten as follows:\n\n.. testcode:: NNX\n\n  class WeightStack(nnx.Module):\n    @nnx.vmap(in_axes=(0, 0), out_axes=0)\n    def __init__(self, seed: jax.Array):\n      self.kernel = nnx.Param(random.uniform(random.key(seed), (2, 3)))\n      self.bias = nnx.Param(jnp.zeros((3,)))\n\n    @nnx.vmap(in_axes=(0, 0), out_axes=1)\n    def __call__(self, x: jax.Array):\n      assert self.kernel.ndim == 2, 'Batch dimensions not allowed'\n      assert x.ndim == 1, 'Batch dimensions not allowed'\n      return x @ self.kernel + self.bias\n\n  weights = WeightStack(jnp.arange(10))\n\n  x = jax.random.normal(random.key(1), (10, 2))\n  y = weights(x)\n\n\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Flax Examples\n\nEach example is designed to be **self-contained and easily forkable**, while\nreproducing relevant results in different areas of machine learning.\n\nAs discussed in [#231](https://github.com/google/flax/issues/231), we decided\nto go for a standard pattern for all examples including the simplest ones\n(like MNIST). This makes every example a bit more verbose, but once you know\none example, you know the structure of all of them. Having unit tests and\nintegration tests is also very useful when you fork these examples.\n\nFor more examples including contributions from the community and other projects currently using Flax see the **[Examples](https://flax.readthedocs.io/en/latest/examples.html)** section in the documentation.\n"
  },
  {
    "path": "examples/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n"
  },
  {
    "path": "examples/cloud/README.md",
    "content": "# Launching jobs on Google Cloud\n\nThis directory provides a simple script that can be used to create a new VM\non Google Cloud, train an example on that VM and then shutting it down.\n\nThe training is implemented in a shell that is run on the VM after startup by\nsetting the `startup_script_file` in the metadata. The script opens a TMUX\nsession, installs Flax repository from Github with all dependencies, and then\nruns the training in parallel with `gcloud storage rsync` that copies the training\nartifacts in a storage bucket.\n\nThe advantage of this approach is that every training is run in a single VM\nthat contains all code and configuration, so it is easy to run multiple\nexperiments in parallel without interference. The individual trainings can also\nbe inspected by logging into the VM via SSH and attaching to the tmux session.\n\nThe script `launch_gce.py` launches the VM and prints out the relevant commands\nto track the progress update and to log into the machine.\n\nNote that the VM also shuts down if an error is encountered, after waiting for\nfive minutes.\n\n## Preparation\n\nPrerequisites:\n\n1. Create a Google Cloud account.\n2. Set up billing: https://console.cloud.google.com/billing\n3. Create a storage bucket (GCS).\n4. Optional: Get quota for accelerators. This is usually granted with a short\n   delay: https://console.cloud.google.com/iam-admin/quotas\n\n## Setting up your environment\n\nThe commands below use the same set of pre-defined environment variables.\n\nMandatory environment variables:\n\n- `$PROJECT`: The name of your Google Cloud project.\n- `$GCS_BUCKET`: The name of the Google Cloud Storage bucket where the model\n  output (artifacts, final checkpoint) are to be stored.\n- `$ZONE`: Compute zone (e.g. `central1-a`).\n\nOptional environment variables:\n\n- `$REPO`: Alternative repo to use instead of the default\n  https://github.com/google/flax - this is useful for development.\n- `$BRANCH`: Alternative branch to use instead of the default `main`.\n\n## Training the MNIST example\n\nUse the following command to launch the MNIST example on cloud (make sure to set\n`$PROJECT` and `$GCS_BUCKET` accordingly):\n\n```shell\npython examples/cloud/launch_gce.py \\\n  --project=$PROJECT \\\n  --zone=us-west1-a \\\n  --machine_type=n2-standard-2 \\\n  --gcs_workdir_base=gs://$GCS_BUCKET/workdir_base \\\n  --repo=${REPO:-https://github.com/google/flax} \\\n  --branch=${BRANCH:-main} \\\n  --example=mnist \\\n  --args='--config=configs/default.py' \\\n  --name=default\n```\n\n## Training the imagenet example\n\nNote that you need to first prepare the `imagenet2012` dataset. For this,\ndownload the data from http://image-net.org/ as described in the\n[tensorflow_datasets catalog](https://www.tensorflow.org/datasets/catalog/imagenet2012).\nThen point the environment variable `$IMAGENET_DOWNLOAD_PATH` to the directory\nwhere the downloads are stored and prepare the dataset by running\n\n```shell\npython -c \"\nimport tensorflow_datasets as tfds\ntfds.builder('imagenet2012').download_and_prepare(\n    download_config=tfds.download.DownloadConfig(\n        manual_dir='$IMAGENET_DOWNLOAD_PATH'))\n\"\n```\n\nThen copy the contents of the directory `~/tensorflow_datasets` into the\ndirectory `gs://$GCS_TFDS_BUCKET/datasets` (note that `$GCS_TFDS_BUCKET` and\n`$GCS_BUCKET` can be identical).\n\nAfter this preparation you can run the imagenet example with the following\ncommand (make sure to set `$PROJECT`, `$GCS_BUCKET` and `$GCS_TFDS_BUCKET`\naccordingly):\n\n```shell\npython examples/cloud/launch_gce.py \\\n  --project=$PROJECT \\\n  --zone=us-west1-a \\\n  --machine_type=n1-standard-96 \\\n  --accelerator_type=nvidia-tesla-v100 --accelerator_count=8 \\\n  --gcs_workdir_base=gs://$GCS_BUCKET/workdir_base \\\n  --tfds_data_dir=gs://$GCS_TFDS_BUCKET/datasets \\\n  --repo=${REPO:-https://github.com/google/flax} \\\n  --branch=${BRANCH:-main} \\\n  --example=imagenet \\\n  --args='--config=configs/v100_x8_mixed_precision.py' \\\n  --name=v100_x8_mixed_precision\n```\n\n## Tips\n\nYou can add `--connect` to above commands to directly land in the training\nsession once the VM is ready. This is very helpful for debugging when changing\nthings. Note that the VM automatically shuts down after 5 minutes of inactivity,\nboth in case of success as in case of failure. On OS X this could be combined\nwith `VM_READY_CMD=\"osascript -e 'display notification \\\"VM ready\\\"'\"` so get\nundistracted when the VM is up and running.\n\nWhen tweaking the startup script or individual arguments, it is often helpful to\nconnect to the VM, stop the scripts and end the tmux session, and then copy and\npaste the contents of the generated `flax-...-startup_script.sh`, after\nmodifying these contents accordingly."
  },
  {
    "path": "examples/cloud/launch_gce.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Script for creating VM on Google cloud and run a Flax example inside.\n# See ./README.md for instructions.\n\nimport datetime\nimport os\nimport re\nimport subprocess\nimport time\nfrom collections.abc import Sequence\n\nfrom absl import app\nfrom absl import flags\n\n# General options.\nflags.DEFINE_bool(\n    'dry_run',\n    False,\n    help=(\n        'If set, then the command to launch the GCE instance will only be '\n        'printed to stdout.'\n    ),\n)\nflags.DEFINE_bool(\n    'connect',\n    False,\n    help='Same as --wait, but directly connect to VM once it is ready.',\n)\nflags.DEFINE_bool(\n    'wait',\n    False,\n    help=(\n        'If set, then the script will wait until VM is ready. If VM_READY_CMD '\n        'is set in environment, then that command will be executed once the VM '\n        'is ready. Useful for sending a notification, e.g. \"osascript\" (mac).'\n    ),\n)\n\n# Machine configuration.\nflags.DEFINE_string('project', None, help='Name of the Google Cloud project.')\nflags.DEFINE_string('zone', None, help='Zone in which the VM will be created.')\nflags.DEFINE_string(\n    'machine_type',\n    None,\n    help='Machine type to use for VM. See \"gcloud compute machine-types list\".',\n)\nflags.DEFINE_string(\n    'accelerator_type',\n    '',\n    help=(\n        'Type of accelerator to use, or empty. '\n        'See \"gcloud compute accelerator-types list\".'\n    ),\n)\nflags.DEFINE_integer(\n    'shutdown_secs',\n    300,\n    help=(\n        'How long to wait (after successful/failed training) before shutting '\n        'down the VM. Set to 0 to disable.'\n    ),\n)\nflags.DEFINE_integer(\n    'accelerator_count', 8, help='Number of accelerators to use.'\n)\n\n# GCS configuration.\nflags.DEFINE_string(\n    'gcs_workdir_base',\n    None,\n    help=(\n        'GCS base directory for model output. The --workdir argument will be '\n        'constructed from {gcs_workdir_base}/{example}/{name}/{timestamp} .'\n    ),\n)\nflags.DEFINE_string(\n    'tfds_data_dir',\n    '',\n    help=(\n        'Optional tfds data directory. This can be useful to prepare datasets'\n        ' on GCS and then point the jobs to this preloaded directory. Dataset'\n        ' will be downloaded from the web if not specified.'\n    ),\n)\n\n# Repo configuration.\nflags.DEFINE_string(\n    'repo', 'https://github.com/google/flax', help='Git repository'\n)\nflags.DEFINE_string('branch', 'main', help='Git repository')\n\n# Example configuration.\nflags.DEFINE_string(\n    'example', None, help='Name of Flax example (e.g. \"imagenet\").'\n)\nflags.DEFINE_string(\n    'args',\n    '',\n    help=(\n        'Any additional command line arguments for {example}_main.py, like '\n        'for example --config. Note that --workdir will be provided by the '\n        'script.'\n    ),\n)\n\n# Run configuration.\nflags.DEFINE_string(\n    'name',\n    None,\n    help=(\n        'Name of the experiment. Note that the provided name will be '\n        'extended to {example}/{name}/{timestamp}'\n    ),\n)\n\nFLAGS = flags.FLAGS\nflags.mark_flags_as_required(\n    ['project', 'zone', 'machine_type', 'gcs_workdir_base', 'example', 'name']\n)\n\ntimestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')\n\n\ndef generate_startup_file(vm_name: str) -> str:\n  directory = os.path.dirname(os.path.abspath(__file__))\n  startup_script_src = os.path.join(directory, 'startup_script.sh')\n  startup_script_dst = os.path.join(directory, f'{vm_name}-startup_script.sh')\n  assert not os.path.exists(startup_script_dst)\n  with open(startup_script_src, encoding='utf8') as f:\n    startup_script_content = f.read()\n  for from_str, to_str in (\n      ('__REPO__', FLAGS.repo),\n      ('__BRANCH__', FLAGS.branch),\n      ('__EXAMPLE__', FLAGS.example),\n      ('__TIMESTAMP__', timestamp),\n      ('__NAME__', FLAGS.name),\n      ('__ARGS__', FLAGS.args),\n      ('__GCS_WORKDIR_BASE__', FLAGS.gcs_workdir_base),\n      ('__TFDS_DATA_DIR__', FLAGS.tfds_data_dir),\n      ('__ACCELERATOR_TYPE__', FLAGS.accelerator_type),\n      ('__SHUTDOWN_SECS__', str(FLAGS.shutdown_secs)),\n  ):\n    startup_script_content = startup_script_content.replace(from_str, to_str)\n  with open(startup_script_dst, 'w', encoding='utf8') as f:\n    f.write(startup_script_content)\n  return startup_script_dst\n\n\ndef launch_gce(*, vm_name: str, startup_script: str):\n  # Note : Use `gcloud compute images list --project ml-images` to get a list\n  # of available VM images.\n  args = [\n      'gcloud',\n      'compute',\n      'instances',\n      'create',\n      vm_name,\n      f'--project={FLAGS.project}',\n      f'--zone={FLAGS.zone}',\n      '--image=c1-deeplearning-tf-2-10-cu113-v20221107-debian-10',\n      '--image-project=ml-images',\n      f'--machine-type={FLAGS.machine_type}',\n      '--scopes=cloud-platform,storage-full',\n      '--boot-disk-size=256GB',\n      '--boot-disk-type=pd-ssd',\n      '--metadata=install-nvidia-driver=True',\n      f'--metadata-from-file=startup-script={startup_script}',\n  ]\n  if FLAGS.accelerator_type and FLAGS.accelerator_count:\n    args.extend([\n        '--maintenance-policy=TERMINATE',\n        f'--accelerator=type={FLAGS.accelerator_type},count={FLAGS.accelerator_count}',\n    ])\n\n  if FLAGS.dry_run:\n    print()\n    print('Would run the following command without --dry-run:')\n    print()\n    print(' \\\\\\n    '.join(args))\n    print()\n    return\n\n  print()\n  print('Creating instance on GCE... This will take some minutes...')\n  print()\n  result = subprocess.run(args)\n  if result.returncode:\n    raise RuntimeError('Could not create VM!')\n\n\ndef print_howto(login_args: Sequence[str]):\n  print(f\"\"\"\n###############################################################################\n###############################################################################\n\nYou can start/stop the instace via the web UI:\nhttps://console.cloud.google.com/compute/instances?project={FLAGS.project}\n\nOnce the VM has started, you can login and connect to the training session:\n\n{' '.join(login_args)}\n\nNote that you can disconnect from the tmux session without stopping the training\nwith the keystrokes 'CTRL-B A'. See \"man tmux\" for help about tmux.\n\nTo observe the training via Tensorboard, simply run in your local computer:\n\n$ tensorboard --logdir={FLAGS.gcs_workdir_base}\n\nYou can also browse the files at\n\nhttps://console.cloud.google.com/storage/browser/{FLAGS.gcs_workdir_base.replace('gs://', '')}\n\n###############################################################################\n###############################################################################\n\"\"\")\n\n\ndef main(_):\n  for name in ('repo', 'branch', 'example', 'name', 'gcs_workdir_base'):\n    value = getattr(FLAGS, name)\n    if re.match(r'[^\\w:/_-]', value):\n      raise ValueError(f'Invalid flag value: --{name}=\"{value}\"')\n  example_base_directory = os.path.join(\n      os.path.dirname(os.path.abspath(__file__)),\n      os.path.pardir,\n  )\n  if not os.path.isdir(os.path.join(example_base_directory, FLAGS.example)):\n    raise ValueError(f'Could not find --example={FLAGS.example}')\n  if FLAGS.connect and FLAGS.dry_run:\n    raise ValueError('Cannot --connect to VM with --dry_run')\n\n  vm_name = '-'.join([\n      'flax',\n      FLAGS.example,\n      timestamp,\n  ])\n  vm_name = re.sub(r'[^a-z0-9-]', '-', vm_name)\n\n  startup_script = generate_startup_file(vm_name)\n  launch_gce(vm_name=vm_name, startup_script=startup_script)\n\n  login_args = [\n      'gcloud',\n      'compute',\n      'ssh',\n      '--project',\n      FLAGS.project,\n      '--zone',\n      FLAGS.zone,\n      vm_name,\n      '--',\n      '/sudo_tmux_a.sh',\n  ]\n\n  print('Your instance is being started...')\n\n  print_howto(login_args)\n\n  if FLAGS.connect or FLAGS.wait:\n    login_true_args = login_args[:-1] + ['true']\n    while True:\n      try:\n        result = subprocess.run(\n            login_true_args, timeout=10, stderr=subprocess.PIPE\n        )\n        if result.returncode == 0:\n          break\n        stderr = result.stderr.decode('utf8')\n        if 'connection refused' in stderr.lower():\n          print('(Connection refused - waiting a little longer...)')\n          time.sleep(20)\n        else:\n          raise ValueError(f'Unknown error: {stderr}')\n      except ValueError as e:\n        if 'HTTP 502' not in str(e):\n          raise e\n        print('(Bad Gateway - waiting a little longer...)')\n        time.sleep(20)\n      except subprocess.TimeoutExpired:\n        print('(Timeout - waiting a little longer...)')\n        time.sleep(20)\n    if 'VM_READY_CMD' in os.environ:\n      os.system(os.environ['VM_READY_CMD'])\n    if FLAGS.connect:\n      result = subprocess.run(login_args)\n      # SSH session has cleared previous message, print it again.\n      print_howto(login_args)\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": "examples/cloud/startup_script.sh",
    "content": "#!/bin/bash\n\n# Note that all __XYZ__ strings are replaced by launch_gce.py\n\nWORKDIR=\"/train/workdir_base/__EXAMPLE__/__NAME__/__TIMESTAMP__\"\n\nmkdir -p /train\ncd /train\n\n# Login directly with:\n# gcloud compute ssh $VM -- /sudo_tmux_a.sh\necho -e '#!/bin/bash\\nsudo /tmux_a.sh' > /sudo_tmux_a.sh\nchmod a+x /sudo_tmux_a.sh\necho -e '#!/bin/bash\\ntmux a' > /tmux_a.sh\nchmod a+x /tmux_a.sh\n\n# Main script running in bottom left tmux pane.\ncat >/install_train_stop.sh <<EOF\nset -x\n(\n  conda activate flax &&\n\n  [ -d flax ] || (\n    git clone --depth 1 -b __BRANCH__ __REPO__ &&\n    cd flax &&\n\n    conda create -yn flax python==3.9 &&\n    conda activate flax &&\n\n    pip install -U pip &&\n    pip install -e . &&\n\n    cd examples/__EXAMPLE__ &&\n    pip install -r requirements.txt &&\n    cd /train\n  ) &&\n\n  conda activate flax &&\n  cd flax &&\n  cd examples/__EXAMPLE__ &&\n\n  TFDS_DATA_DIR='__TFDS_DATA_DIR__' python main.py --workdir=$WORKDIR __ARGS__\n\n) 2>&1 | tee -a $WORKDIR/setup_train_log_${TIMESTAMP}.txt\n\nif [ __SHUTDOWN_SECS__ -gt 0 ]; then\n  echo\n  echo WILL SHUT DOWN IN $((__SHUTDOWN_SECS__/60)) MIN ...\n  sleep __SHUTDOWN_SECS__ && shutdown now\nfi\n\nEOF\n\n\n# Set up TMUX panes:\ntmux new-session -s flax -d\n# - top left: htop\ntmux send 'htop\n'\ntmux split-window\ntmux selectp -U\ntmux split-window -h\n# - top right: htop\ntmux send 'watch nvidia-smi\n'\ntmux selectp -D\n# - bottom left: main script\ntmux send '. /install_train_stop.sh\n'\ntmux split-window -h\n# - bottom right: rsync files to GCS bucket.\ntmux send \"\nwhile true; do\n  gcloud storage rsync --recursive workdir_base __GCS_WORKDIR_BASE__\n  sleep 60\ndone 2>&1 | tee -a $WORKDIR/gcs_rsync_'__TIMESTAMP__'.txt\n\"\n"
  },
  {
    "path": "examples/gemma/README.md",
    "content": "\n## Language modeling\nTrains Gemma model on the One Billion Word Benchmark (lm1b; Chelba *et al.*, 2013).\n\nThis example is based on lm1b and similarly uses linear learning rate warmup and inverse square root learning rate schedule.\n\n\n### Requirements\n\n*   TensorFlow datasets `lm1b` need to be downloaded and prepared (see below).\n    A sentencepiece tokenizer vocabulary will be automatically generated\n    and saved on each training run.\n*   This example additionally depends on the `sentencepiece` and `tensorflow-text` packages.\n\n### Downloading the LM1B Datasets\n\nWe recommend downloading and preparing the TFDS datasets beforehand. You can download and prepare LM1B datasets using TFDS directly: `python -m tensorflow_datasets.scripts.download_and_prepare --datasets=lm1b`.\n\n#### Using Cloud Storage FUSE for TPUs\n\nFor Cloud TPUs, we recommend using a cheap standard instance and saving the prepared TFDS\ndata on a storage bucket, from where it can be mounted to the TPU VM using [Cloud Storage FUSE](https://cloud.google.com/storage/docs/cloud-storage-fuse/quickstart-mount-bucket).\n\n##### Copy the preprocessed dataset to the Cloud Storage\n\nWe assume that the dataset was downloaded and prepared. We also assume we have configured `gcloud` CLI. The following commands helps to setup the storage and copy the dataset:\n\n```bash\n# Install gcsfuse CLI\nexport GCSFUSE_REPO=gcsfuse-`lsb_release -c -s`\n# For example, GCSFUSE_REPO=gcsfuse-noble for Ubuntu 24.04\n\necho \"deb https://packages.cloud.google.com/apt $GCSFUSE_REPO main\" | sudo tee /etc/apt/sources.list.d/gcsfuse.list\ncurl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -\nsudo apt-get update\nsudo apt-get install -y fuse gcsfuse --no-install-recommends\n\ngcsfuse -v\n# gcsfuse version 2.12.2 (Go version go1.24.0)\n```\n\nLet's get where LM1B dataset was locally stored:\n```bash\npython -c \"import tensorflow_datasets as tfds; b=tfds.builder('lm1b'); print(b.info.data_dir)\"\n# For example: /home/user/tensorflow_datasets/lm1b/1.1.0\n```\n\nLet's create a GCS bucket for the dataset and link the bucket to a local folder. We choose the bucket name \"flax-lm1b-tfdataset\" but this can be changed.\n```bash\ngcloud storage buckets create gs://flax-lm1b-tfdataset\n\nmkdir -p $HOME/data\ngcsfuse flax-lm1b-tfdataset $HOME/data\n```\n\nNow let's copy the data to the bucket:\n```bash\n# Let's assume that prepared dataset is at $HOME/tensorflow_datasets/lm1b/\ncp -R $HOME/tensorflow_datasets/lm1b $HOME/data\n```\n\n##### Setup the dataset on TPU VM\n\nWe previously have choosen the bucket name \"flax-lm1b-tfdataset\" where stored the dataset, adapt this name to your situation.\n\n```bash\n# On the TPU VM\ngcsfuse flax-lm1b-tfdataset $HOME/tensorflow_datasets\n\nls $HOME/tensorflow_datasets/lm1b/1.1.0/\n```\n\n### How to run on GPU(s)\n\nInstall Jax with CUDA support, Flax and the example dependencies with the following command:\n```bash\npip install jax[cuda12]\n# Check whether GPUs are available:\n# python3 -c \"import jax; print(jax.devices())\"\n\ngit clone --depth=1 --branch=main https://github.com/google/flax\ncd flax\npip install -e .\ncd examples/gemma\npip install -r requirements.txt\n```\n\nStart the training:\n\n- train a small transformer model:\n```bash\npython3 main.py --workdir=$HOME/logs/small_gemma_lm1b --config=configs/small.py\n```\n\n- train Gemma3-4B model:\n```bash\npython3 main.py --workdir=$HOME/logs/gemma3-4b_lm1b --config=configs/gemma3_4b.py\n```\n\nTo monitor the trainings with the TensorBoard:\n```bash\ntensorboard --logdir=$HOME/logs\n```\n\n\n### How to run on Cloud TPUs\n\nSetup the TPU VM and install the Flax dependencies on it as described\n[here](https://cloud.google.com/tpu/docs/jax-pods) for creating pod slices, or\n[here](https://cloud.google.com/tpu/docs/jax-quickstart-tpu-vm) for a single\nv4-8 TPU.\n\n\nFirst create a single TPUv4-8 VM and connect to it (you can find more detailed\ninstructions [here](https://cloud.google.com/tpu/docs/jax-quickstart-tpu-vm)):\n\n```bash\nZONE=us-central1-a\nTPU_TYPE=v4-8\nTPU_NAME=$USER-flax-gemma-lm1b\ngcloud compute tpus tpu-vm create $TPU_NAME \\\n    --zone $ZONE \\\n    --accelerator-type $TPU_TYPE \\\n    --version tpu-ubuntu2204-base\n\ngcloud compute tpus tpu-vm ssh $TPU_NAME --zone $ZONE -- \\\n    -L 6006:localhost:6006\n```\n\nWhen connected install JAX:\n\n```bash\npip install \"jax[tpu]>=0.2.16\" \\\n    -f https://storage.googleapis.com/jax-releases/libtpu_releases.html\n```\n\nThen install Flax + the example dependencies:\n\n```bash\ngit clone --depth=1 --branch=main https://github.com/google/flax\ncd flax\npip install -e .\ncd examples/gemma\npip install -r requirements.txt\n```\n\nIn case of errors when installing example dependencies, try to upgrade existing `pip` package and downgrade `setuptools` and repeat the installation command\n```bash\n# Optionally\n# pip install -U pip\n# pip install -U \"setuptools<70\"\n# pip install -r requirements.txt\n```\n\nAnd finally start the training:\n\n```bash\npython3 main.py --workdir=$HOME/logs/gemma_lm1b_256 --config.per_device_batch_size=32\n```\n\nNote that you might want to set `TFDS_DATA_DIR` as explained below. You probably\nalso want to start the long-running command above in a `tmux` session and start\nsome monitoring in a separate pane (note that we forwarded port 6006 locally\nabove):\n\n```bash\ntensorboard --logdir=$HOME/logs\n```\n"
  },
  {
    "path": "examples/gemma/configs/default.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Default Hyperparameter configuration.\"\"\"\n\nimport dataclasses\n\nfrom train import MeshRules, TrainConfig\n\n\n@dataclasses.dataclass(unsafe_hash=True)\nclass Config:\n  # Path to load or store sentencepiece vocab file.\n  vocab_path: str | None = None\n  # Vocabulary size if `vocab_path` is not given.\n  vocab_size: int = 35_000  # lm1b dataset vocab size: 35913  (Gemma expected vocab size: 262_144)\n  # Maximum number of characters to use for training.\n  max_corpus_chars: int = 10**7\n  # Name of TFDS translation dataset to use.\n  dataset_name: str = 'lm1b'\n  # Optional name of TFDS translation dataset to use for evaluation.\n  eval_dataset_name: str = 'lm1b'\n  # Optional name of TFDS split to use for evaluation.\n  eval_split: str = 'test'\n  # Per device batch size for training.\n  per_device_batch_size: int = 32\n  # Per device batch size for training.\n  eval_per_device_batch_size: int = 32\n\n  # Prompt for language model sampling\n  prompts: tuple[str, ...] = (\n    'Paris is a the capital',\n    'Flax is a',\n    # From train set:\n    'The shutdown was aimed at creating efficiencies as',\n    # -> the plant was already operating at its maximum capacity of 3,000 tonnes of cellulose paste per day\n    'A big theme of this hire is that there are parts of',\n    # -> our operations that to use a pretty trite phrase , need to be taken to the next level ...\n\n    # From test set:\n    'Because of Bear Stearns , many analysts are',\n    # -> raising the odds that a 2008 recession could be worse than expected\n    'Next month , the Brazilian bourse',\n    # -> opens a London office',\n  )\n  # Temperature for top_p sampling.\n  sampling_temperature: float = 0.0\n  # Top-p sampling threshold.\n  sampling_top_p: float = 0.95\n\n  # Number of steps to take during training.\n  num_train_steps: int = 500_000\n  # Number of steps to take during evaluation.\n  # Large enough to evaluate all samples: 306_688 / (32 * 8) = 1198\n  num_eval_steps: int = 2_000\n  # Number of steps to generate predictions.\n  # -1 will use the whole eval dataset.\n  num_predict_steps: int = 50\n  # Base learning rate.\n  learning_rate: float = 0.0016\n  # Linear learning rate warmup.\n  warmup_steps: int = 1000\n  # Cross entropy loss label smoothing.\n  label_smoothing: float = 0.0\n  # Decay factor for AdamW style weight decay.\n  weight_decay: float = 0.1\n  # Maximum length cutoff for training examples.\n  max_target_length: int = 128\n  # Maximum length cutoff for eval examples.\n  max_eval_target_length: int = 512\n\n  # Gemma transformer name.\n  # Possible values defined in transformer.TransformerConfig:\n  # (gemma_2b, gemma_7b, gemma2_2b, gemma2_9b, gemma2_27b, gemma3_1b, gemma3_4b, ...)\n  transformer_name: str | None = \"gemma3_1b\"\n  # or alternatively define the model using the dict of parameters\n  transformer_params: dict | None = None\n\n  # Whether to save model checkpoints.\n  save_checkpoints: bool = True\n  # Whether to restore from existing model checkpoints.\n  restore_checkpoints: bool = True\n  # Save a checkpoint every these number of steps.\n  checkpoint_every_steps: int = 10_000\n  # Frequency of eval during training, e.g. every 1_000 steps.\n  eval_every_steps: int = 5_000\n  # Use bfloat16 mixed precision training instead of float32.\n  use_bfloat16: bool = True\n  # Integer for PRNG random seed.\n  seed: int = 0\n\n  # Parallelism\n  mesh_axes: tuple[str, ...] = ('data', 'fsdp', 'tensor')\n  axis_rules: MeshRules = MeshRules(\n    embed='fsdp',\n    mlp='tensor',\n    kv='tensor',\n    vocab='tensor',\n  )\n  data_sharding: tuple[str, ...] = ('data', 'fsdp')\n\n  # One axis for each parallelism type may hold a placeholder (-1)\n  # value to auto-shard based on available slices and devices.\n  # By default, product of the DCN axes should equal number of slices\n  # and product of the ICI axes should equal number of devices per slice.\n  # ICI (Inter-Chip Interconnection): A high-speed connection between\n  # sets of TPU chips, which form the TPU network.\n  # DCN (Data Center Network): A connection between the TPU networks;\n  # not as fast as ICI.\n  # ICI has around 100x the bandwidth of DCN, but it is not a general\n  # purpose connection, which is why DCN is necessary for scaling to\n  # extremely large ML models.\n  dcn_data_parallelism: int = -1\n  dcn_fsdp_parallelism: int = 1\n  dcn_tensor_parallelism: int = 1\n  ici_data_parallelism: int = 1\n  ici_fsdp_parallelism: int = -1\n  ici_tensor_parallelism: int = 1\n\n\ndef get_config() -> TrainConfig:\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = Config()\n  return TrainConfig(**dataclasses.asdict(config))\n"
  },
  {
    "path": "examples/gemma/configs/gemma3_4b.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Default Hyperparameter configuration.\"\"\"\n\nimport dataclasses\n\nfrom train import MeshRules, TrainConfig\n\n\n@dataclasses.dataclass(unsafe_hash=True)\nclass Config:\n  # Path to load or store sentencepiece vocab file.\n  vocab_path: str | None = None\n  # Vocabulary size if `vocab_path` is not given.\n  vocab_size: int = 35_000  # lm1b dataset vocab size: 35913  (Gemma expected vocab size: 262_144)\n  # Maximum number of characters to use for training.\n  max_corpus_chars: int = 10**7\n  # Name of TFDS translation dataset to use.\n  dataset_name: str = 'lm1b'\n  # Optional name of TFDS translation dataset to use for evaluation.\n  eval_dataset_name: str = 'lm1b'\n  # Optional name of TFDS split to use for evaluation.\n  eval_split: str = 'test'\n  # Per device batch size for training.\n  per_device_batch_size: int = 32\n  # Per device batch size for training.\n  eval_per_device_batch_size: int = 32\n\n  # Prompt for language model sampling\n  prompts: tuple[str, ...] = (\n    'Paris is a the capital',\n    'Flax is a',\n    # From train set:\n    'The shutdown was aimed at creating efficiencies as',\n    # -> the plant was already operating at its maximum capacity of 3,000 tonnes of cellulose paste per day\n    'A big theme of this hire is that there are parts of',\n    # -> our operations that to use a pretty trite phrase , need to be taken to the next level ...\n\n    # From test set:\n    'Because of Bear Stearns , many analysts are',\n    # -> raising the odds that a 2008 recession could be worse than expected\n    'Next month , the Brazilian bourse',\n    # -> opens a London office',\n  )\n  # Temperature for top_p sampling.\n  sampling_temperature: float = 0.0\n  # Top-p sampling threshold.\n  sampling_top_p: float = 0.95\n\n  # Number of steps to take during training.\n  num_train_steps: int = 500_000\n  # Number of steps to take during evaluation.\n  # Large enough to evaluate all samples: 306_688 / (32 * 8) = 1198\n  num_eval_steps: int = 2_000\n  # Number of steps to generate predictions.\n  # -1 will use the whole eval dataset.\n  num_predict_steps: int = 50\n  # Base learning rate.\n  learning_rate: float = 0.0016\n  # Linear learning rate warmup.\n  warmup_steps: int = 1000\n  # Cross entropy loss label smoothing.\n  label_smoothing: float = 0.0\n  # Decay factor for AdamW style weight decay.\n  weight_decay: float = 0.1\n  # Maximum length cutoff for training examples.\n  max_target_length: int = 128\n  # Maximum length cutoff for eval examples.\n  max_eval_target_length: int = 512\n\n  # Gemma transformer name.\n  # Possible values defined in transformer.TransformerConfig:\n  # (gemma_2b, gemma_7b, gemma2_2b, gemma2_9b, gemma2_27b, gemma3_1b, gemma3_4b, ...)\n  transformer_name: str | None = \"gemma3_4b\"\n  # or alternatively define the model using the dict of parameters\n  transformer_params: dict | None = None\n\n  # Whether to save model checkpoints.\n  save_checkpoints: bool = True\n  # Whether to restore from existing model checkpoints.\n  restore_checkpoints: bool = True\n  # Save a checkpoint every these number of steps.\n  checkpoint_every_steps: int = 10_000\n  # Frequency of eval during training, e.g. every 1_000 steps.\n  eval_every_steps: int = 5_000\n  # Use bfloat16 mixed precision training instead of float32.\n  use_bfloat16: bool = True\n  # Integer for PRNG random seed.\n  seed: int = 0\n\n  # Parallelism\n  mesh_axes: tuple[str, ...] = ('data', 'fsdp', 'tensor')\n  axis_rules: MeshRules = MeshRules(\n    embed='fsdp',\n    mlp='tensor',\n    kv='tensor',\n    vocab='tensor',\n  )\n  data_sharding: tuple[str, ...] = ('data', 'fsdp')\n\n  # One axis for each parallelism type may hold a placeholder (-1)\n  # value to auto-shard based on available slices and devices.\n  # By default, product of the DCN axes should equal number of slices\n  # and product of the ICI axes should equal number of devices per slice.\n  # ICI (Inter-Chip Interconnection): A high-speed connection between\n  # sets of TPU chips, which form the TPU network.\n  # DCN (Data Center Network): A connection between the TPU networks;\n  # not as fast as ICI.\n  # ICI has around 100x the bandwidth of DCN, but it is not a general\n  # purpose connection, which is why DCN is necessary for scaling to\n  # extremely large ML models.\n  dcn_data_parallelism: int = -1\n  dcn_fsdp_parallelism: int = 1\n  dcn_tensor_parallelism: int = 1\n  ici_data_parallelism: int = 1\n  ici_fsdp_parallelism: int = -1\n  ici_tensor_parallelism: int = 1\n\n  def replace(self, **kwargs):\n    return dataclasses.replace(self, **kwargs)\n\n\ndef get_config() -> TrainConfig:\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = Config()\n  return TrainConfig(**dataclasses.asdict(config))\n"
  },
  {
    "path": "examples/gemma/configs/small.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Default Hyperparameter configuration.\"\"\"\n\nimport dataclasses\n\nfrom train import MeshRules, TrainConfig\n\n\n@dataclasses.dataclass(unsafe_hash=True)\nclass Config:\n  # Path to load or store sentencepiece vocab file.\n  vocab_path: str | None = None\n  # Vocabulary size if `vocab_path` is not given.\n  vocab_size: int = 35_000  # lm1b dataset vocab size: 35913  (Gemma expected vocab size: 262_144)\n  # Maximum number of characters to use for training.\n  max_corpus_chars: int = 10**7\n  # Name of TFDS translation dataset to use.\n  dataset_name: str = 'lm1b'\n  # Optional name of TFDS translation dataset to use for evaluation.\n  eval_dataset_name: str = 'lm1b'\n  # Optional name of TFDS split to use for evaluation.\n  eval_split: str = 'test'\n  # Per device batch size for training.\n  per_device_batch_size: int = 32\n  # Per device batch size for training.\n  eval_per_device_batch_size: int = 32\n\n  # Prompt for language model sampling\n  prompts: tuple[str, ...] = (\n    'Paris is a the capital',\n    'Flax is a',\n    # From train set:\n    'The shutdown was aimed at creating efficiencies as',\n    # -> the plant was already operating at its maximum capacity of 3,000 tonnes of cellulose paste per day\n    'A big theme of this hire is that there are parts of',\n    # -> our operations that to use a pretty trite phrase , need to be taken to the next level ...\n\n    # From test set:\n    'Because of Bear Stearns , many analysts are',\n    # -> raising the odds that a 2008 recession could be worse than expected\n    'Next month , the Brazilian bourse',\n    # -> opens a London office',\n  )\n  # Temperature for top_p sampling.\n  sampling_temperature: float = 0.0\n  # Top-p sampling threshold.\n  sampling_top_p: float = 0.95\n\n  # Number of steps to take during training.\n  num_train_steps: int = 500_000\n  # Number of steps to take during evaluation.\n  num_eval_steps: int = 500\n  # Number of steps to generate predictions.\n  # -1 will use the whole eval dataset.\n  num_predict_steps: int = 50\n  # Base learning rate.\n  learning_rate: float = 0.0016\n  # Linear learning rate warmup.\n  warmup_steps: int = 1000\n  # Cross entropy loss label smoothing.\n  label_smoothing: float = 0.0\n  # Decay factor for AdamW style weight decay.\n  weight_decay: float = 0.1\n  # Maximum length cutoff for training examples.\n  max_target_length: int = 128\n  # Maximum length cutoff for eval examples.\n  max_eval_target_length: int = 512\n\n  # Gemma transformer name.\n  # Possible values defined in transformer.TransformerConfig:\n  # (gemma_2b, gemma_7b, gemma2_2b, gemma2_9b, gemma2_27b, gemma3_1b, gemma3_4b, ...)\n  transformer_name: str | None = None\n  # or alternatively define the model using the dict of parameters\n  transformer_params: dict | None = dataclasses.field(\n    default_factory=lambda: {\n      \"num_layers\": 6,\n      \"embed_dim\": 512,\n      \"hidden_dim\": 2048,\n      \"num_heads\": 4,\n      \"head_dim\": 256,\n      \"num_kv_heads\": 1,\n      \"use_post_attn_norm\": True,\n      \"use_post_ffw_norm\": True,\n      \"use_qk_norm\": True,\n\n      \"attention_types\": (2, 2, 2, 2, 2, 1),  # local_sliding, ..., local_sliding, global\n      \"query_pre_attn_norm\": 1,  # QueryPreAttentionNormalisation.BY_ONE_OVER_SQRT_HEAD_DIM\n\n      \"attn_logits_soft_cap\": None,\n      \"final_logit_softcap\": None,\n      \"sliding_window_size\": 128,\n      \"transpose_gating_einsum\": True,\n      \"local_base_frequency\": 10_000,\n      \"global_base_frequency\": 1_000_000,\n    }\n  )\n\n  # Whether to save model checkpoints.\n  save_checkpoints: bool = True\n  # Whether to restore from existing model checkpoints.\n  restore_checkpoints: bool = True\n  # Save a checkpoint every these number of steps.\n  checkpoint_every_steps: int = 10_000\n  # Frequency of eval during training, e.g. every 1_000 steps.\n  eval_every_steps: int = 5_000\n  # Use bfloat16 mixed precision training instead of float32.\n  use_bfloat16: bool = True\n  # Integer for PRNG random seed.\n  seed: int = 0\n\n  # Parallelism\n  mesh_axes: tuple[str, ...] = ('data', 'fsdp', 'tensor')\n  axis_rules: MeshRules = MeshRules(\n    embed='fsdp',\n    mlp='tensor',\n    kv='tensor',\n    vocab='tensor',\n  )\n  data_sharding: tuple[str, ...] = ('data', 'fsdp')\n\n  # One axis for each parallelism type may hold a placeholder (-1)\n  # value to auto-shard based on available slices and devices.\n  # By default, product of the DCN axes should equal number of slices\n  # and product of the ICI axes should equal number of devices per slice.\n  # ICI (Inter-Chip Interconnection): A high-speed connection between\n  # sets of TPU chips, which form the TPU network.\n  # DCN (Data Center Network): A connection between the TPU networks;\n  # not as fast as ICI.\n  # ICI has around 100x the bandwidth of DCN, but it is not a general\n  # purpose connection, which is why DCN is necessary for scaling to\n  # extremely large ML models.\n  dcn_data_parallelism: int = -1\n  dcn_fsdp_parallelism: int = 1\n  dcn_tensor_parallelism: int = 1\n  ici_data_parallelism: int = 1\n  ici_fsdp_parallelism: int = -1\n  ici_tensor_parallelism: int = 1\n\n  def replace(self, **kwargs):\n    return dataclasses.replace(self, **kwargs)\n\n\ndef get_config() -> TrainConfig:\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = Config()\n  return TrainConfig(**dataclasses.asdict(config))\n"
  },
  {
    "path": "examples/gemma/configs/tiny.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Default Hyperparameter configuration.\"\"\"\n\nimport dataclasses\n\nfrom train import MeshRules, TrainConfig\n\n\n@dataclasses.dataclass(unsafe_hash=True)\nclass Config:\n  # Path to load or store sentencepiece vocab file.\n  vocab_path: str | None = None\n  # Vocabulary size if `vocab_path` is not given.\n  vocab_size: int = 35_000  # lm1b dataset vocab size: 35913  (Gemma expected vocab size: 262_144)\n  # Maximum number of characters to use for training.\n  max_corpus_chars: int = 10**7\n  # Name of TFDS translation dataset to use.\n  dataset_name: str = 'lm1b'\n  # Optional name of TFDS translation dataset to use for evaluation.\n  eval_dataset_name: str = 'lm1b'\n  # Optional name of TFDS split to use for evaluation.\n  eval_split: str = 'test'\n  # Per device batch size for training.\n  per_device_batch_size: int = 32\n  # Per device batch size for training.\n  eval_per_device_batch_size: int = 32\n\n  # Prompt for language model sampling\n  prompts: tuple[str, ...] = (\n    'Paris is a the capital',\n    'Flax is a',\n    # From train set:\n    'The shutdown was aimed at creating efficiencies as',\n    # -> the plant was already operating at its maximum capacity of 3,000 tonnes of cellulose paste per day\n    'A big theme of this hire is that there are parts of',\n    # -> our operations that to use a pretty trite phrase , need to be taken to the next level ...\n\n    # From test set:\n    'Because of Bear Stearns , many analysts are',\n    # -> raising the odds that a 2008 recession could be worse than expected\n    'Next month , the Brazilian bourse',\n    # -> opens a London office',\n  )\n  # Temperature for top_p sampling.\n  sampling_temperature: float = 0.0\n  # Top-p sampling threshold.\n  sampling_top_p: float = 0.95\n\n  # Number of steps to take during training.\n  num_train_steps: int = 500_000\n  # Number of steps to take during evaluation.\n  num_eval_steps: int = 500\n  # Number of steps to generate predictions.\n  # -1 will use the whole eval dataset.\n  num_predict_steps: int = 20\n  # Base learning rate.\n  learning_rate: float = 0.0016\n  # Linear learning rate warmup.\n  warmup_steps: int = 1000\n  # Cross entropy loss label smoothing.\n  label_smoothing: float = 0.0\n  # Decay factor for AdamW style weight decay.\n  weight_decay: float = 0.1\n  # Maximum length cutoff for training examples.\n  max_target_length: int = 128\n  # Maximum length cutoff for eval examples.\n  max_eval_target_length: int = 512\n\n  # Gemma transformer name.\n  # Possible values defined in transformer.TransformerConfig:\n  # (gemma_2b, gemma_7b, gemma2_2b, gemma2_9b, gemma2_27b, gemma3_1b, gemma3_4b, ...)\n  transformer_name: str | None = None\n  # or alternatively define the model using the dict of parameters\n  transformer_params: dict | None = dataclasses.field(\n    default_factory=lambda: {\n      \"num_layers\": 4,\n      \"embed_dim\": 256,\n      \"hidden_dim\": 256 * 4 // 2,  # embed_dim * num_heads // 2\n      \"num_heads\": 4,\n      \"head_dim\": 128,\n      \"num_kv_heads\": 1,\n      \"use_post_attn_norm\": False,\n      \"use_post_ffw_norm\": False,\n      \"attention_types\": (1, 1, 1, 1),  # global * num_layers\n      \"final_logit_softcap\": None,\n    }\n  )\n\n  # Whether to save model checkpoints.\n  save_checkpoints: bool = True\n  # Whether to restore from existing model checkpoints.\n  restore_checkpoints: bool = True\n  # Save a checkpoint every these number of steps.\n  checkpoint_every_steps: int = 10_000\n  # Frequency of eval during training, e.g. every 1_000 steps.\n  eval_every_steps: int = 5_000\n  # Use bfloat16 mixed precision training instead of float32.\n  use_bfloat16: bool = True\n  # Integer for PRNG random seed.\n  seed: int = 0\n\n  # Parallelism\n  mesh_axes: tuple[str, ...] = ('data', 'fsdp', 'tensor')\n  axis_rules: MeshRules = MeshRules(\n    embed='fsdp',\n    mlp='tensor',\n    kv='tensor',\n    vocab='tensor',\n  )\n  data_sharding: tuple[str, ...] = ('data', 'fsdp')\n\n  # One axis for each parallelism type may hold a placeholder (-1)\n  # value to auto-shard based on available slices and devices.\n  # By default, product of the DCN axes should equal number of slices\n  # and product of the ICI axes should equal number of devices per slice.\n  # ICI (Inter-Chip Interconnection): A high-speed connection between\n  # sets of TPU chips, which form the TPU network.\n  # DCN (Data Center Network): A connection between the TPU networks;\n  # not as fast as ICI.\n  # ICI has around 100x the bandwidth of DCN, but it is not a general\n  # purpose connection, which is why DCN is necessary for scaling to\n  # extremely large ML models.\n  dcn_data_parallelism: int = -1\n  dcn_fsdp_parallelism: int = 1\n  dcn_tensor_parallelism: int = 1\n  ici_data_parallelism: int = 1\n  ici_fsdp_parallelism: int = -1\n  ici_tensor_parallelism: int = 1\n\n  def replace(self, **kwargs):\n    return dataclasses.replace(self, **kwargs)\n\n\ndef get_config() -> TrainConfig:\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = Config()\n  return TrainConfig(**dataclasses.asdict(config))\n"
  },
  {
    "path": "examples/gemma/helpers.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Helper functions.\"\"\"\n\nfrom __future__ import annotations\n\nfrom collections.abc import Callable\nfrom typing import Any, TypeVar\n\nimport flax\nfrom flax import nnx\nfrom flax.typing import VariableDict  # pylint: disable=g-importing-member,g-multiple-import\n\nM = TypeVar('M', bound='nnx.Module')\n\n\ndef _flatten_path(path: tuple[str | int, ...]) -> str:\n  def f(item) -> str:\n    if isinstance(item, str):\n      return f'{item}'\n    elif isinstance(item, int):\n      return f'[{item}]'\n    else:\n      raise ValueError(f'Unexpected type {type(item)}')\n\n  return '.'.join([f(item) for item in path]).replace('.[', '[')\n\n\ndef module_from_linen_variables(\n    module_factory: Callable[[], M],\n    variables: VariableDict,\n    map_key_fn: None | (\n        Callable[[tuple[str, ...]], tuple[str | int, ...]]\n    ) = None,\n    assign_val_fn: None | (\n        Callable[\n            [dict[tuple[str, ...], Any], tuple[str | int, ...], VariableDict],\n            dict[tuple[str, ...], Any],\n        ]\n    ) = None,\n) -> M:\n  \"\"\"Returns an `nnx.Module` initialized with the `variables` of a linen module.\n\n  Args:\n    module_factory: A no-args callable that returns an `nnx.Module`.\n    variables: A dictionary of variables.\n    map_key_fn: An optional function for mapping keys in the `variables`\n      dictionary to keys in the `nnx.Module`'s state. If not provided it is\n      assumed that after removing the collection name the keys in the\n      `variables` dictionary are the same as the keys in the `nnx.Module`'s\n      state.\n  \"\"\"\n  if map_key_fn is None:\n\n    def map_key_fn(path: tuple[str, ...]) -> tuple[str | int, ...]:\n      return path[1:] if 'params' in variables else path\n\n  if assign_val_fn is None:\n\n    def assign_val_fn(\n        state: dict[tuple[str, ...], Any],\n        mapped_path: tuple[str | int, ...],\n        val: Any,\n    ) -> dict[tuple[str, ...], Any]:\n      state[mapped_path].set_value(val)\n      return state\n\n  mdl: M = nnx.eval_shape(module_factory)\n  graph_def, state = nnx.split(mdl)\n  state = dict(nnx.to_flat_state(state))\n  for path, val in flax.traverse_util.flatten_dict(variables).items():\n    mapped_path = map_key_fn(path)\n    if mapped_path not in state:\n      raise ValueError(\n          f\"'{mdl.__class__.__name__}.{_flatten_path(mapped_path)}' doesn't \"\n          f' exist (original path={path}).'\n      )\n    state = assign_val_fn(state, mapped_path, val)\n  state = nnx.from_flat_state(state)\n\n  return nnx.merge(graph_def, state)\n"
  },
  {
    "path": "examples/gemma/helpers_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Tests for helpers.\"\"\"\n\nfrom __future__ import annotations\n\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom flax import nnx\nimport flax.linen as nn\nimport helpers\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\n\n\nclass ModuleFromLinenVariablesTest(parameterized.TestCase):\n\n  @parameterized.parameters(\n      dict(\n          inputs_shape=(1, 2, 3, 4),\n          num_features=10,\n          use_bias=True,\n      ),\n      dict(\n          inputs_shape=(10, 5),\n          num_features=4,\n          use_bias=False,\n      ),\n  )\n  def test_same_structure(self, inputs_shape, num_features, use_bias):\n    rng_key = jax.random.PRNGKey(0)\n    rng_inputs, rng_params = jax.random.split(rng_key)\n    inputs = jax.random.normal(rng_inputs, inputs_shape)\n\n    linen_mdl = nn.Dense(\n        features=num_features,\n        use_bias=use_bias,\n    )\n    linen_init_vars = linen_mdl.init(rng_params, jnp.zeros(inputs_shape))\n    linen_output = linen_mdl.apply(\n        linen_init_vars,\n        inputs,\n    )\n\n    mdl = helpers.module_from_linen_variables(\n        module_factory=lambda: nnx.Linear(\n            in_features=inputs_shape[-1],\n            out_features=num_features,\n            use_bias=use_bias,\n            rngs=nnx.Rngs(params=rng_params),\n        ),\n        variables=linen_init_vars,\n    )\n    output = mdl(inputs)\n\n    np.testing.assert_array_equal(output, linen_output)\n\n  @parameterized.parameters(\n      dict(\n          inputs_shape=(1, 2, 3, 4),\n          num_features=(10, 20, 7),\n          use_bias=(False, True, False),\n      ),\n  )\n  def test_different_structure(self, inputs_shape, num_features, use_bias):\n    rng_key = jax.random.PRNGKey(0)\n    rng_inputs, rng_params = jax.random.split(rng_key)\n    inputs = jax.random.normal(rng_inputs, inputs_shape)\n\n    linen_mdl = nn.Sequential([\n        nn.Sequential([\n            nn.BatchNorm(use_running_average=False),\n            nn.Dense(features=f, use_bias=b),\n        ])\n        for f, b in zip(num_features, use_bias)\n    ])\n    linen_init_vars = linen_mdl.init(rng_key, jnp.zeros(inputs_shape))\n    linen_output, linen_vars = linen_mdl.apply(\n        linen_init_vars,\n        inputs,\n        mutable=['batch_stats'],\n    )\n\n    module_factory = lambda: nnx.Sequential(*[\n        nnx.Sequential(\n            nnx.BatchNorm(\n                num_features=in_f,\n                use_running_average=False,\n                rngs=nnx.Rngs(params=rng_params),\n            ),\n            nnx.Linear(\n                in_features=in_f,\n                out_features=out_f,\n                use_bias=b,\n                rngs=nnx.Rngs(params=rng_params),\n            ),\n        )\n        for in_f, out_f, b in zip(in_features, out_features, use_bias)\n    ])\n\n    def _map_key_fn(key: tuple[str, ...]) -> tuple[str | int, ...]:\n      new_key = []\n      for k in key[1:]:\n        if k.startswith('layers_'):\n          prefix, suffix = k.split('layers_')\n          assert not prefix, prefix\n          new_key.append('layers')\n          new_key.append(int(suffix))\n        else:\n          new_key.append(k)\n\n      return tuple(new_key)\n\n    in_features = (inputs_shape[-1], *num_features[:-1])\n    out_features = num_features\n    mdl = helpers.module_from_linen_variables(\n        module_factory=module_factory,\n        variables=linen_init_vars,\n        map_key_fn=_map_key_fn,\n    )\n    output = mdl(inputs)\n\n    np.testing.assert_array_equal(output, linen_output)\n    for i in range(len(num_features)):\n      np.testing.assert_array_equal(\n          mdl.layers[i].layers[0].mean[...],\n          linen_vars['batch_stats'][f'layers_{i}']['layers_0']['mean'],\n      )\n      np.testing.assert_array_equal(\n          mdl.layers[i].layers[0].var[...],\n          linen_vars['batch_stats'][f'layers_{i}']['layers_0']['var'],\n      )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/gemma/input_pipeline.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Input pipeline for a LM1B dataset.\"\"\"\n\nimport os\nfrom typing import Any\n\nimport tokenizer\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\nAUTOTUNE = tf.data.experimental.AUTOTUNE\nFeatures = dict[str, tf.Tensor]\n\n\nclass NormalizeFeatureNamesOp:\n  \"\"\"Normalizes feature names to 'inputs' and 'targets'.\"\"\"\n\n  def __call__(self, features: Features) -> Features:\n    features['inputs'] = features.pop('text')\n    # Unnecessary step used for uniformizing with examples/wmt.\n    features['targets'] = features['inputs']\n    return features\n\n\ndef get_raw_dataset(dataset_name: str, split: str) -> tf.data.Dataset:\n  \"\"\"Loads a raw text dataset and normalizes feature keys.\n\n  Args:\n    dataset_name: TFDS dataset name.\n    split: Split to use. This must be the full split. We shard the split across\n      multiple hosts and currently don't support sharding subsplits.\n\n  Returns:\n    Dataset with source and target language features mapped to 'inputs' and\n    'targets'.\n  \"\"\"\n  split = tfds.split_for_jax_process(split, drop_remainder=True)\n  ds = tfds.load(dataset_name, split=split)\n  ds = ds.map(NormalizeFeatureNamesOp(), num_parallel_calls=AUTOTUNE)\n  return ds\n\n\ndef pack_dataset(\n    dataset: tf.data.Dataset,\n    key2length: int | dict[str, int],\n    keys: list[str] | None = None,\n) -> tf.data.Dataset:\n  \"\"\"Creates a 'packed' version of a dataset on-the-fly.\n\n  Adapted from the mesh-tf implementation.\n\n  This is meant to replace the irritation of having to create a separate\n  \"packed\" version of a dataset to train efficiently on TPU.\n  Each example in the output dataset represents several examples in the\n  input dataset.\n  For each key in the input dataset, two additional keys are created:\n  <key>_segmentation: an int32 tensor identifying the parts\n     representing the original example.\n  <key>_position: an int32 tensor identifying the position within the original\n     example.\n  Example:\n  Two input examples get combined to form an output example.\n  The input examples are:\n  {\"inputs\": [8, 7, 1, 0], \"targets\":[4, 1, 0]}\n  {\"inputs\": [2, 3, 4, 1], \"targets\":[5, 6, 1]}\n  The output example is:\n  {\n                 \"inputs\": [8, 7, 1, 2, 3, 4, 1, 0, 0, 0]\n    \"inputs_segmentation\": [1, 1, 1, 2, 2, 2, 2, 0, 0, 0]\n        \"inputs_position\": [0, 1, 2, 0, 1, 2, 3, 0, 0, 0]\n                \"targets\": [4, 1, 5, 6, 1, 0, 0, 0, 0, 0]\n   \"targets_segmentation\": [1, 1, 2, 2, 2, 0, 0, 0, 0, 0]\n       \"targets_position\": [0, 1, 0, 1, 2, 0, 0, 0, 0, 0]\n  }\n  0 represents padding in both the inputs and the outputs.\n  Sequences in the incoming examples are truncated to length \"length\", and the\n  sequences in the output examples all have fixed (padded) length \"length\".\n\n  Args:\n    dataset: a tf.data.Dataset\n    key2length: an integer, or a dict from feature-key to integer\n    keys: a list of strings (e.g. [\"inputs\", \"targets\"])\n\n  Returns:\n    a tf.data.Dataset\n  \"\"\"\n  shapes = tf.nest.map_structure(lambda spec: spec.shape, dataset.element_spec)\n  if keys is None:\n    keys = list(shapes.keys())\n  for k in keys:\n    if k not in shapes:\n      raise ValueError(\n          'Key %s not found in dataset.  Available keys are %s'\n          % (k, shapes.keys())\n      )\n    if not shapes[k].is_compatible_with(tf.TensorShape([None])):  # type: ignore[wrong-arg-types]\n      raise ValueError('Tensors to be packed must be one-dimensional.')\n  # make sure that the length dictionary contains all keys as well as the\n  # keys suffixed by \"_segmentation\" and \"_position\"\n  if isinstance(key2length, int):\n    key2length = {k: key2length for k in keys}\n  for k in keys:\n    for suffix in ['_segmentation', '_position']:\n      key2length[k + suffix] = key2length[k]\n\n  # trim to length\n  dataset = dataset.map(\n      lambda x: {k: x[k][: key2length[k]] for k in keys},\n      num_parallel_calls=AUTOTUNE,\n  )\n  # Setting batch_size=length ensures that the concatenated sequences (if they\n  # have length >=1) are sufficient to fill at least one packed example.\n  batch_size = max(key2length.values())\n  dataset = dataset.padded_batch(\n      batch_size, padded_shapes={k: [-1] for k in keys}\n  )\n  dataset = _pack_with_tf_ops(dataset, keys, key2length)\n\n  # Set the Tensor shapes correctly since they get lost in the process.\n  def my_fn(x):\n    return {k: tf.reshape(v, [key2length[k]]) for k, v in x.items()}\n\n  return dataset.map(my_fn, num_parallel_calls=AUTOTUNE)\n\n\ndef _pack_with_tf_ops(\n    dataset: tf.data.Dataset, keys: list[str], key2length: dict[str, int]\n) -> tf.data.Dataset:\n  \"\"\"Helper-function for packing a dataset which has already been batched.\n\n  Helper for pack_dataset()  Uses tf.while_loop.\n\n  Args:\n    dataset: a dataset containing padded batches of examples.\n    keys: a list of strings\n    key2length: a dict from feature-key to integer\n\n  Returns:\n    a dataset.\n  \"\"\"\n  empty_example = {}\n  for k in keys:\n    empty_example[k] = tf.zeros([0], dtype=tf.int32)\n    empty_example[k + '_position'] = tf.zeros([0], dtype=tf.int32)\n  keys_etc = empty_example.keys()\n\n  def write_packed_example(partial, outputs):\n    new_partial = empty_example.copy()\n    new_outputs = {}\n    for k in keys_etc:\n      new_outputs[k] = outputs[k].write(\n          outputs[k].size(),\n          tf.pad(partial[k], [[0, key2length[k] - tf.size(partial[k])]]),\n      )\n    return new_partial, new_outputs\n\n  def map_fn(x):\n    \"\"\"Internal function to flat_map over.\n\n    Consumes a batch of input examples and produces a variable number of output\n    examples.\n    Args:\n      x: a single example\n\n    Returns:\n      a tf.data.Dataset\n    \"\"\"\n    partial = empty_example.copy()\n    i = tf.zeros([], dtype=tf.int32)\n    dynamic_batch_size = tf.shape(x[keys[0]])[0]\n    outputs = {}\n    for k in keys:\n      outputs[k] = tf.TensorArray(\n          tf.int32, size=0, dynamic_size=True, element_shape=[key2length[k]]\n      )\n      outputs[k + '_position'] = tf.TensorArray(\n          tf.int32, size=0, dynamic_size=True, element_shape=[key2length[k]]\n      )\n\n    def body_fn(i, partial, outputs):\n      \"\"\"Body function for while_loop.\n\n      Args:\n        i: integer scalar\n        partial: dictionary of Tensor (partially-constructed example)\n        outputs: dictionary of TensorArray\n\n      Returns:\n        A triple containing the new values of the inputs.\n      \"\"\"\n      can_append = True\n      one_example = {}\n      for k in keys:\n        val = tf.cast(x[k][i], tf.int32)\n        val = val[: tf.reduce_sum(tf.cast(tf.not_equal(val, 0), tf.int32))]\n        one_example[k] = val\n      for k in keys:\n        can_append = tf.logical_and(\n            can_append,\n            tf.less_equal(\n                tf.size(partial[k]) + tf.size(one_example[k]), key2length[k]\n            ),\n        )\n\n      def false_fn():\n        return write_packed_example(partial, outputs)\n\n      def true_fn():\n        return partial, outputs\n\n      partial, outputs = tf.cond(can_append, true_fn, false_fn)\n      new_partial = {}\n      for k in keys:\n        new_seq = one_example[k][: key2length[k]]\n        new_seq_len = tf.size(new_seq)\n        new_partial[k] = tf.concat([partial[k], new_seq], 0)\n        new_partial[k + '_position'] = tf.concat(\n            [partial[k + '_position'], tf.range(new_seq_len)], 0\n        )\n      partial = new_partial\n      return i + 1, partial, outputs\n\n    # For loop over all examples in the batch.\n    _, partial, outputs = tf.while_loop(\n        cond=lambda *_: True,\n        body=body_fn,\n        loop_vars=(i, partial, outputs),\n        shape_invariants=(\n            tf.TensorShape([]),\n            {k: tf.TensorShape([None]) for k in keys_etc},  # type: ignore[wrong-arg-types]\n            {k: tf.TensorShape(None) for k in keys_etc},  # type: ignore[wrong-arg-types]\n        ),\n        maximum_iterations=dynamic_batch_size,\n    )\n    _, outputs = write_packed_example(partial, outputs)\n    packed = {k: outputs[k].stack() for k in keys_etc}\n    for k in keys:\n      packed[k + '_segmentation'] = tf.cumsum(\n          tf.cast(tf.equal(packed[k + '_position'], 0), tf.int32), axis=1\n      ) * tf.cast(tf.not_equal(packed[k], 0), tf.int32)\n    return packed\n\n  dataset = dataset.map(map_fn, num_parallel_calls=AUTOTUNE)\n  return dataset.unbatch()\n\n\ndef shift_data_by_truncation(x):\n  # https://github.com/AI-Hypercomputer/maxtext/blob/7fe1de75b3919c0fda00d23ad6cb29def9098362/MaxText/input_pipeline/_input_pipeline_utils.py#L53\n  x['inputs'] = x['inputs'][:-1]\n  x['targets'] = x['targets'][1:]\n  return x\n\n\n# -----------------------------------------------------------------------------\n# Main dataset prep routines.\n# -----------------------------------------------------------------------------\ndef preprocess_data(\n    dataset,\n    shuffle: bool,\n    num_epochs: int | None = 1,\n    pack_examples: bool = True,\n    shuffle_buffer_size: int = 1024,\n    max_length: int = 512,\n    batch_size: int = 256,\n    drop_remainder: bool = True,\n    prefetch_size: int = AUTOTUNE,\n    shift: bool = True,\n):\n  \"\"\"Shuffle and batch/pack the given dataset.\"\"\"\n\n  def length_filter(max_len):\n    def filter_fn(x):\n      source, target = x['inputs'], x['targets']\n      l = tf.maximum(tf.shape(source)[0], tf.shape(target)[0])\n      return tf.less(l, max_len + 1)\n\n    return filter_fn\n\n  if max_length > 0:\n    dataset = dataset.filter(length_filter(max_length))\n\n  if shuffle:\n    dataset = dataset.shuffle(shuffle_buffer_size)\n  dataset = dataset.repeat(num_epochs)\n\n  # Shift inputs for teacher-forced training\n  if shift:\n    dataset = dataset.map(\n        shift_data_by_truncation,\n        num_parallel_calls=AUTOTUNE,\n        deterministic=True,\n    )\n\n  if pack_examples:\n    dataset = pack_dataset(dataset, max_length)\n    dataset = dataset.batch(batch_size, drop_remainder=drop_remainder)\n  else:  # simple (static-shape) padded batching\n    dataset = dataset.padded_batch(\n        batch_size,\n        padded_shapes={'inputs': max_length, 'targets': max_length},\n        padding_values={'inputs': 0, 'targets': 0},\n        drop_remainder=drop_remainder,\n    )\n\n  if prefetch_size:\n    dataset = dataset.prefetch(prefetch_size)\n\n  return dataset\n\n\ndef get_datasets(\n    config: Any,\n    *,\n    n_devices: int,\n    vocab_path: str | None = None,\n):\n  \"\"\"Load and return dataset of batched examples for use during training.\"\"\"\n  if vocab_path is None:\n    vocab_path = os.path.expanduser('~/lm1b_sentencepiece_model')\n\n  train_data = get_raw_dataset(config.dataset_name, 'train')\n\n  if config.eval_dataset_name:\n    eval_dataset_name = config.eval_dataset_name\n  else:\n    eval_dataset_name = config.dataset_name\n  eval_data = get_raw_dataset(eval_dataset_name, config.eval_split)\n\n  # Tokenize data.\n  sp_processor = tokenizer.load_or_train_tokenizer(\n      train_data,\n      vocab_path=vocab_path,\n      vocab_size=config.vocab_size,\n      max_corpus_chars=config.max_corpus_chars,\n  )\n  train_data = train_data.map(\n      tokenizer.TokenizeOp(sp_processor), num_parallel_calls=AUTOTUNE\n  )\n  eval_data = eval_data.map(\n      tokenizer.TokenizeOp(sp_processor), num_parallel_calls=AUTOTUNE\n  )\n\n  batch_size = config.per_device_batch_size * n_devices\n  if config.eval_per_device_batch_size > 0:\n    eval_batch_size = config.eval_per_device_batch_size * n_devices\n  else:\n    eval_batch_size = batch_size\n\n  train_ds = preprocess_data(\n      train_data,\n      shuffle=True,\n      num_epochs=None,\n      pack_examples=True,\n      batch_size=batch_size,\n      max_length=config.max_target_length,\n  )\n\n  eval_ds = preprocess_data(\n      eval_data,\n      shuffle=False,\n      pack_examples=False,\n      batch_size=eval_batch_size,\n      max_length=config.max_eval_target_length,\n  )\n\n  return train_ds, eval_ds, sp_processor\n"
  },
  {
    "path": "examples/gemma/input_pipeline_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport pathlib\nimport sys\nimport tempfile\n\nfrom absl.testing import absltest\nimport tensorflow_datasets as tfds\n\nfrom configs import default\nimport input_pipeline\n\n# We just use different values here to verify that the input pipeline uses the\n# the correct value for the 3 different datasets.\n_TARGET_LENGTH = 32\n_EVAL_TARGET_LENGTH = 48\n\n\nclass InputPipelineTest(absltest.TestCase):\n\n  def setUp(self):\n    super().setUp()\n    if sys.version_info >= (3, 13):\n      self.skipTest('Test (and tensorflow-text) does not suport Python 3.13+')\n    self.train_ds, self.eval_ds = self._get_datasets()\n\n  def _get_datasets(self):\n    config = default.get_config()\n    config.per_device_batch_size = 1\n    config.eval_per_device_batch_size = 2\n    config.vocab_size = 32\n    config.max_corpus_chars = 1000\n    config.max_target_length = _TARGET_LENGTH\n    config.max_eval_target_length = _EVAL_TARGET_LENGTH\n\n    vocab_path = os.path.join(tempfile.mkdtemp(), 'sentencepiece_model')\n\n    # Go two directories up to the root of the flax directory.\n    # \"/path/to/flax/examples/gemma/input_pipeline_test.py\" -> \"/path/to/flax\"\n    flax_root_dir = pathlib.Path(__file__).absolute().parents[2]\n    data_dir = str(flax_root_dir) + '/.tfds/metadata'  # pylint: disable=unused-variable\n\n    with tfds.testing.mock_data(num_examples=128, data_dir=data_dir):\n      train_ds, eval_ds, _ = input_pipeline.get_datasets(\n        n_devices=2, config=config, vocab_path=vocab_path\n      )\n    return train_ds, eval_ds\n\n  def test_train_ds(self):\n    expected_shape = [2, _TARGET_LENGTH]  # 2 devices.\n    # For training we pack multiple short examples in one example.\n    # *_position and *_segmentation indicate the boundaries.\n    for batch in self.train_ds.take(3):\n      self.assertEqual(\n        {k: v.shape.as_list() for k, v in batch.items()},\n        {\n          'inputs': expected_shape,\n          'inputs_position': expected_shape,\n          'inputs_segmentation': expected_shape,\n          'targets': expected_shape,\n          'targets_position': expected_shape,\n          'targets_segmentation': expected_shape,\n        },\n      )\n\n  def test_eval_ds(self):\n    expected_shape = [4, _EVAL_TARGET_LENGTH]  # 2 devices.\n    for batch in self.eval_ds.take(3):\n      self.assertEqual(\n        {k: v.shape.as_list() for k, v in batch.items()},\n        {\n          'inputs': expected_shape,\n          'targets': expected_shape,\n        },\n      )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/gemma/layers.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Base layers.\"\"\"\n\nfrom __future__ import annotations\n\nfrom collections.abc import Sequence\nfrom typing import Any, Union\n\nfrom flax import nnx\nimport jax\nimport jax.numpy as jnp\nfrom jaxtyping import Array, ArrayLike  # pylint: disable=g-importing-member,g-multiple-import\n\n\nShape = Sequence[Union[int, Any]]\n\n\nclass Einsum(nnx.Module):\n  \"\"\"Einsum is a convenience module for parameterized tensor multiplication.\"\"\"\n\n  def __init__(\n      self,\n      einsum_str: str,\n      shape: Shape,\n      *,\n      kernel_init: nnx.Initializer = nnx.initializers.normal(),\n      rngs: nnx.Rngs,\n      dtype: Any = jnp.float32,\n  ):\n    self.einsum_str = einsum_str\n    self.w = nnx.Param(kernel_init(rngs.params(), shape, dtype))\n\n  def __call__(self, x: ArrayLike) -> Array:\n    return jnp.einsum(self.einsum_str, x, self.w[...])\n\n  @property\n  def shape(self) -> Shape:\n    return self.w.shape\n\n\nclass RMSNorm(nnx.Module):\n  \"\"\"RMSNorm layer.\"\"\"\n\n  def __init__(\n      self,\n      dim: int,\n      *,\n      scale_init: nnx.Initializer = nnx.initializers.zeros_init(),\n      rngs: nnx.Rngs,\n      dtype: Any = jnp.float32,\n  ):\n    self.scale = nnx.Param(scale_init(rngs.params(), dim, dtype))\n\n  def __call__(self, x: Array) -> Array:\n    dtype = self.scale.dtype\n    var = jnp.mean(jnp.square(x), axis=-1, keepdims=True)\n    normed_inputs = jnp.asarray(x * jax.lax.rsqrt(var + 1e-06), dtype=dtype)\n    # normed_inputs is a rank-K tensor, K > 1 (K is typically 2 or 3). scale is\n    # a rank-1 tensor. To avoid implicit rank-promotion, reshape scale to\n    # a (1, ..., 1, D) tensor, so the rank of scale matches normed_inputs.\n    scale = jnp.expand_dims(self.scale, axis=range(len(x.shape) - 1))\n    normed_inputs = normed_inputs * (1 + scale)\n    return normed_inputs\n"
  },
  {
    "path": "examples/gemma/layers_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Tests for transformer layers.\"\"\"\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom flax import nnx\nimport layers\nimport jax.numpy as jnp\nimport numpy as np\n\n\nclass EinsumTest(parameterized.TestCase):\n  @parameterized.parameters(\n      dict(\n          inputs_shape=(1, 4),\n          params_shape=(3, 2, 4, 3),\n          eqn='TD,SNDH->STNH',\n          expected_shape=(3, 1, 2, 3),\n      ),\n      dict(\n          inputs_shape=(1, 2, 4),\n          params_shape=(2, 4, 8),\n          eqn='ANH,NHD->AD',\n          expected_shape=(1, 8),\n      ),\n  )\n  def test_einsum(self, inputs_shape, params_shape, eqn, expected_shape):\n    einsum = layers.Einsum(eqn, params_shape, rngs=nnx.Rngs(params=0))\n    output = einsum(\n        jnp.ones(inputs_shape),\n    )\n    self.assertEqual(output.shape, expected_shape)\n\n  @parameterized.parameters(\n      dict(\n          shape=(1, 4),\n      ),\n      dict(\n          shape=(2, 5, 4, 7),\n      ),\n  )\n  def test_shape(self, shape):\n    einsum = layers.Einsum('ij->ji', shape, rngs=nnx.Rngs(params=0))\n    self.assertEqual(einsum.shape, shape)\n\n\nclass RMSNormTest(parameterized.TestCase):\n  @parameterized.parameters(dict(x=[0.1, 0.2], expected=[0.6324429, 1.2648858]))\n  def test_rmsnorm(self, x, expected):\n    x = jnp.array([x])\n    rmsnorm = layers.RMSNorm(x.shape[-1], rngs=nnx.Rngs(params=0))\n    output = rmsnorm(x)\n    np.testing.assert_array_equal(output, jnp.array([expected]))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/gemma/main.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Main file for training Gemma model on the One Billion Word Benchmark dataset.\n\nThis file is intentionally kept short. The majority for logic is in libraries\nthat can be easily tested and imported in Colab.\n\"\"\"\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nfrom clu import platform\nimport train\nimport jax\nfrom ml_collections import config_flags\nimport tensorflow as tf\n\n\nFLAGS = flags.FLAGS\n\nflags.DEFINE_string('workdir', None, 'Directory to store model data.')\nconfig_flags.DEFINE_config_file(\n    'config',\n    'configs/default.py',\n    'File path to the training hyperparameter configuration.',\n    lock_config=True,\n)\nflags.mark_flags_as_required(['workdir'])\n\n\ndef main(argv):\n  if len(argv) > 1:\n    raise app.UsageError('Too many command-line arguments.')\n\n  # Hide any GPUs from TensorFlow. Otherwise TF might reserve memory and make\n  # it unavailable to JAX.\n  tf.config.experimental.set_visible_devices([], 'GPU')\n\n  logging.info('JAX process: %d / %d', jax.process_index(), jax.process_count())\n  logging.info('JAX local devices: %r', jax.local_devices())\n\n  # Add a note so that we can tell which task is which JAX host.\n  # (Depending on the platform task 0 is not guaranteed to be host 0)\n  platform.work_unit().set_task_status(\n      f'process_index: {jax.process_index()}, '\n      f'process_count: {jax.process_count()}'\n  )\n  platform.work_unit().create_artifact(\n      platform.ArtifactType.DIRECTORY, FLAGS.workdir, 'workdir'\n  )\n\n  train.train_and_evaluate(FLAGS.config, FLAGS.workdir)\n\n\nif __name__ == '__main__':\n  jax.config.config_with_absl()\n  app.run(main)\n"
  },
  {
    "path": "examples/gemma/modules.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Transformer sub-modules.\"\"\"\n\nfrom __future__ import annotations\n\nfrom collections.abc import Sequence\nimport enum\nfrom typing import Any, Union\n\nfrom flax import nnx\nimport layers\nimport positional_embeddings\nimport sow_lib\nimport jax\nimport jax.numpy as jnp\nfrom jaxtyping import Array, ArrayLike  # pylint: disable=g-importing-member,g-multiple-import\n\nLayerCache = dict[str, Array]\nShape = Sequence[Union[int, Any]]\n\nK_MASK = -2.3819763e38  # Set to a large negative number.\nDEFAULT_ROPE_BASE_FREQUENCY = 10_000\nDEFAULT_ROPE_SCALE_FACTOR = 1.0\n\n\nclass AttentionType(enum.Enum):\n  GLOBAL = 1\n  LOCAL_SLIDING = 2\n\n\nclass Embedder(nnx.Module):\n  \"\"\"Embedder module.\"\"\"\n\n  def __init__(\n      self,\n      vocab_size: int,\n      embed_dim: int,\n      *,\n      embedding_init: nnx.Initializer = nnx.initializers.normal(),\n      dtype: Any = jnp.float32,\n      rngs: nnx.Rngs,\n  ):\n    self.input_embedding = nnx.Param(\n        embedding_init(rngs.params(), (vocab_size, embed_dim), dtype)\n    )\n\n  def encode(self, x: ArrayLike) -> Array:\n    x = self.input_embedding[(x,)]\n    x *= jnp.sqrt(x.shape[-1]).astype(x.dtype)\n    return x\n\n  def decode(self, x: ArrayLike) -> Array:\n    return jnp.dot(x, self.input_embedding.T)\n\n  @property\n  def embed_dim(self):\n    return self.input_embedding.shape[1]\n\n  @property\n  def num_embed(self):\n    return self.input_embedding.shape[0]\n\n\nclass Attention(nnx.Module):\n  \"\"\"Attention module.\"\"\"\n\n  def __init__(\n      self,\n      num_heads: int,\n      num_kv_heads: int,\n      features: int,\n      head_dim: int,\n      query_pre_attn_scalar: float,\n      attn_type: AttentionType,\n      *,\n      rngs: nnx.Rngs,\n      rope_base_frequency: int = DEFAULT_ROPE_BASE_FREQUENCY,\n      rope_scale_factor: float = DEFAULT_ROPE_SCALE_FACTOR,\n      attn_logits_soft_cap: float | None = None,\n      sliding_window_size: int | None = None,\n      use_qk_norm: bool = False,\n      sow_config: sow_lib.SowConfig = sow_lib.SowConfig(),\n      dtype: Any = jnp.float16,\n      kernel_init: nnx.Initializer = nnx.initializers.normal(),\n      scale_init: nnx.Initializer = nnx.initializers.zeros_init(),\n      attn_vec_einsum_kernel_init: nnx.Initializer | None = None,\n      qkv_einsum_kernel_init: nnx.Initializer | None = None,\n      q_einsum_kernel_init: nnx.Initializer | None = None,\n      kv_einsum_kernel_init: nnx.Initializer | None = None,\n  ):\n    if attn_type == AttentionType.LOCAL_SLIDING and sliding_window_size is None:\n      raise ValueError(\n          '`sliding_window_size` must be set if `attn_type` is Local Sliding.'\n      )\n\n    self.query_pre_attn_scalar = query_pre_attn_scalar\n    self.attn_type = attn_type\n    self.sliding_window_size = sliding_window_size\n    self.attn_logits_soft_cap = attn_logits_soft_cap\n    attn_vec_einsum_kernel_init = attn_vec_einsum_kernel_init if attn_vec_einsum_kernel_init else kernel_init\n    self.attn_vec_einsum = layers.Einsum(\n        einsum_str='BTNH,NHD->BTD',\n        shape=(num_heads, head_dim, features),\n        kernel_init=attn_vec_einsum_kernel_init,\n        dtype=dtype,\n        rngs=rngs,\n    )\n    self.rope_base_frequency = rope_base_frequency\n    self.rope_scale_factor = rope_scale_factor\n    self.use_qk_norm = use_qk_norm\n    self.sow_config = sow_config\n\n    if num_heads == num_kv_heads:\n      qkv_einsum_kernel_init = qkv_einsum_kernel_init if qkv_einsum_kernel_init else kernel_init\n      self.qkv_einsum = layers.Einsum(\n          einsum_str='BTD,SNDH->SBTNH',\n          shape=(3, num_heads, features, head_dim),\n          kernel_init=qkv_einsum_kernel_init,\n          dtype=dtype,\n          rngs=rngs,\n      )\n    else:\n      if num_heads % num_kv_heads != 0:\n        raise ValueError(\n          f\"Number of query heads ({num_heads}) must be divisible by \"\n          f\"number of key/value heads ({num_kv_heads}).\"\n        )\n\n      q_einsum_kernel_init = q_einsum_kernel_init if q_einsum_kernel_init else kernel_init\n      self.q_einsum = layers.Einsum(\n          einsum_str='BTD,NDH->BTNH',\n          shape=(num_heads, features, head_dim),\n          kernel_init=q_einsum_kernel_init,\n          dtype=dtype,\n          rngs=rngs,\n      )\n      kv_einsum_kernel_init = kv_einsum_kernel_init if kv_einsum_kernel_init else kernel_init\n      self.kv_einsum = layers.Einsum(\n          einsum_str='BSD,CKDH->CBSKH',\n          shape=(2, num_kv_heads, features, head_dim),\n          kernel_init=kv_einsum_kernel_init,\n          dtype=dtype,\n          rngs=rngs,\n      )\n\n    if self.use_qk_norm:\n      self._query_norm = layers.RMSNorm(\n        head_dim,\n        scale_init=scale_init,\n        dtype=dtype,\n        rngs=rngs,\n      )\n      self._key_norm = layers.RMSNorm(\n        head_dim,\n        scale_init=scale_init,\n        dtype=dtype,\n        rngs=rngs,\n      )\n\n  def __call__(\n      self,\n      x: Array,\n      segment_pos: Array,\n      cache: LayerCache | None,\n      attn_mask: Array,\n  ) -> tuple[LayerCache | None, Array]:\n    seq_len = x.shape[1]\n\n    if self.use_qkv_einsum:\n      query_proj, key_proj, value_proj = self.qkv_einsum(x)\n    else:\n      query_proj = self.q_einsum(x)\n      key_proj, value_proj = self.kv_einsum(x)\n\n    if self.use_qk_norm:\n      query_proj = self._query_norm(query_proj)\n      key_proj = self._key_norm(key_proj)\n\n    query_proj = positional_embeddings.apply_rope(\n        query_proj,\n        segment_pos,\n        head_dim=self.head_dim,\n        max_wavelength=self.rope_base_frequency,\n        scale_factor=self.rope_scale_factor,\n    )\n    query_scaled = query_proj * self.query_pre_attn_scalar\n    key_proj = positional_embeddings.apply_rope(\n        key_proj,\n        segment_pos,\n        head_dim=self.head_dim,\n        max_wavelength=self.rope_base_frequency,\n        scale_factor=self.rope_scale_factor,\n    )\n\n    # Cache is left aligned.\n    if cache is not None:\n      end_index = cache['end_index'][0]\n      slice_indices = (0, end_index % cache['v'].shape[1], 0, 0)\n      value_proj = jax.lax.dynamic_update_slice(\n          cache['v'],\n          value_proj,\n          slice_indices,\n      )\n      key_proj = jax.lax.dynamic_update_slice(\n          cache['k'], key_proj, slice_indices\n      )\n\n    use_gqa = self.num_heads > self.num_kv_heads and self.num_kv_heads > 1\n    if use_gqa:\n      # Reshape matrices to enable einsums over groups.\n      num_groups = self.num_heads // self.num_kv_heads\n      batch_size, seq_size, _, head_dim = query_scaled.shape\n      query_scaled = query_scaled.reshape(\n        (batch_size, seq_size, self.num_kv_heads, num_groups, head_dim)\n      )\n      logits = jnp.einsum('BTKGH,BSKH->BTKGS', query_scaled, key_proj)\n      logits = logits.reshape(\n        (batch_size, seq_size, self.num_heads, -1)\n      )\n    else:\n      logits = jnp.einsum('BTNH,BSNH->BTNS', query_scaled, key_proj)\n\n    if self.attn_logits_soft_cap is not None:\n      logits = jnp.tanh(logits / self.attn_logits_soft_cap)\n      logits = logits * self.attn_logits_soft_cap\n    if self.attn_type == AttentionType.LOCAL_SLIDING:\n      if self.sliding_window_size is None:\n        raise ValueError(\n            'sliding_window_size must be set if attn_type is Local Sliding.'\n        )\n\n      all_ones = jnp.ones_like(attn_mask)\n      sliding_mask = jnp.triu(\n          all_ones, -1 * self.sliding_window_size + 1\n      ) * jnp.tril(all_ones, self.sliding_window_size - 1)\n      attn_mask = sliding_mask * attn_mask\n\n    padded_logits = jnp.where((jnp.expand_dims(attn_mask, -2)), logits, K_MASK)\n    self.sow_config.maybe_sow_attn_logits_topk(padded_logits, self)\n    probs = jax.nn.softmax(padded_logits, axis=-1).astype(key_proj.dtype)\n\n    if use_gqa:\n      # Reshape matrices to enable einsums over groups.\n      num_groups = self.num_heads // self.num_kv_heads\n      batch_size, seq_size1, _, _ = probs.shape\n      probs = probs.reshape(\n        (batch_size, seq_size1, self.num_kv_heads, num_groups, -1)\n      )\n      encoded = jnp.einsum('BTKGS,BSKH->BTKGH', probs, value_proj)\n      encoded = encoded.reshape(\n        (batch_size, seq_size, self.num_heads, head_dim)\n      )\n    else:\n      encoded = jnp.einsum('BTNS,BSNH->BTNH', probs, value_proj)\n    attn_output = self.attn_vec_einsum(encoded)\n\n    if cache is not None:\n      new_cache = {\n          'v': value_proj,\n          'k': key_proj,\n          'end_index': cache['end_index'] + seq_len,\n      }\n    else:\n      new_cache = None\n\n    return new_cache, attn_output\n\n  @property\n  def head_dim(self):\n    return self.attn_vec_einsum.shape[1]\n\n  @property\n  def num_heads(self):\n    return (\n        self.qkv_einsum.shape[1]\n        if self.use_qkv_einsum\n        else self.q_einsum.shape[0]\n    )\n\n  @property\n  def num_kv_heads(self):\n    return (\n        self.qkv_einsum.shape[1]\n        if self.use_qkv_einsum\n        else self.kv_einsum.shape[1]\n    )\n\n  @property\n  def use_qkv_einsum(self):\n    return hasattr(self, 'qkv_einsum') and self.qkv_einsum is not None\n\n  def init_cache(\n      self,\n      cache_size: int,\n      batch_size: int,\n      dtype: jnp.dtype = jnp.bfloat16,\n  ) -> LayerCache:\n    return {\n        'v': jnp.zeros(\n            (batch_size, cache_size, self.num_kv_heads, self.head_dim),\n            dtype=dtype,\n        ),\n        'k': jnp.zeros(\n            (batch_size, cache_size, self.num_kv_heads, self.head_dim),\n            dtype=dtype,\n        ),\n        'end_index': jnp.zeros((batch_size,), dtype=jnp.int32),\n    }\n\n\nclass FeedForward(nnx.Module):\n  \"\"\"Feed forward module.\"\"\"\n\n  def __init__(\n      self,\n      features: int,\n      hidden_dim: int,\n      *,\n      kernel_init: nnx.Initializer = nnx.initializers.normal(),\n      rngs: nnx.Rngs,\n      sow_config: sow_lib.SowConfig = sow_lib.SowConfig(),\n      dtype: Any = jnp.float32,\n  ):\n    self.gate_proj = nnx.Linear(\n        in_features=features,\n        out_features=hidden_dim,\n        use_bias=False,\n        rngs=rngs,\n        kernel_init=kernel_init,\n        dtype=dtype,\n    )\n    self.up_proj = nnx.Linear(\n        in_features=features,\n        out_features=hidden_dim,\n        use_bias=False,\n        rngs=rngs,\n        kernel_init=kernel_init,\n        dtype=dtype,\n    )\n    self.down_proj = nnx.Linear(\n        in_features=hidden_dim,\n        out_features=features,\n        use_bias=False,\n        rngs=rngs,\n        kernel_init=kernel_init,\n        dtype=dtype,\n    )\n    self.sow_config = sow_config\n\n  def __call__(self, x: ArrayLike) -> Array:\n    ff_gate = self.gate_proj(x)\n    gate_value = nnx.gelu(ff_gate)\n\n    ff1 = self.up_proj(x)\n    activations = gate_value * ff1\n    self.sow_config.maybe_sow_mlp_hidden_topk(activations, self)\n\n    outputs = self.down_proj(activations)\n    return outputs\n\n\nclass Block(nnx.Module):\n  \"\"\"Transformer block.\"\"\"\n\n  def __init__(\n      self,\n      config,  # TransformerConfig\n      attn_type: AttentionType,\n      *,\n      rngs: nnx.Rngs,\n      sow_config: sow_lib.SowConfig = sow_lib.SowConfig(),\n  ):\n    num_heads = config.num_heads\n    num_kv_heads = config.num_kv_heads\n    embed_dim = config.embed_dim\n    head_dim = config.head_dim\n    hidden_dim = config.hidden_dim\n    sliding_window_size = config.sliding_window_size\n    use_post_attn_norm = config.use_post_attn_norm\n    use_post_ffw_norm = config.use_post_ffw_norm\n    query_pre_attn_scalar = config.query_pre_attn_scalar()\n    if attn_type == AttentionType.LOCAL_SLIDING:\n      rope_base_frequency = config.local_base_frequency\n      rope_scale_factor = config.local_scale_factor\n    else:\n      rope_base_frequency = config.global_base_frequency\n      rope_scale_factor = config.global_scale_factor\n\n    attn_logits_soft_cap = config.attn_logits_soft_cap\n    use_qk_norm = config.use_qk_norm\n    dtype = config.dtype\n\n    self.pre_attention_norm = layers.RMSNorm(\n      embed_dim,\n      scale_init=maybe_with_partitioning(\n        nnx.initializers.zeros_init(),\n        config.axis_rules,\n        (\"embed\", ),\n      ),\n      rngs=rngs,\n      dtype=dtype,\n    )\n    self.attn = Attention(\n        num_heads=num_heads,\n        num_kv_heads=num_kv_heads,\n        features=embed_dim,\n        head_dim=head_dim,\n        query_pre_attn_scalar=query_pre_attn_scalar,\n        attn_type=attn_type,\n        rope_base_frequency=rope_base_frequency,\n        rope_scale_factor=rope_scale_factor,\n        attn_logits_soft_cap=attn_logits_soft_cap,\n        sliding_window_size=sliding_window_size,\n        rngs=rngs,\n        use_qk_norm=use_qk_norm,\n        sow_config=sow_config,\n        attn_vec_einsum_kernel_init=maybe_with_partitioning(\n          nnx.initializers.normal(),\n          config.axis_rules,\n          (None, \"embed\", \"kv\"),  # sharded array shape: (num_heads, head_dim, features)\n        ),\n        qkv_einsum_kernel_init=maybe_with_partitioning(\n          nnx.initializers.normal(),\n          config.axis_rules,\n          (None, None, \"embed\", \"kv\"),  # sharded array shape: (3, num_heads, features, head_dim)\n        ),\n        q_einsum_kernel_init=maybe_with_partitioning(\n          nnx.initializers.normal(),\n          config.axis_rules,\n          (None, \"embed\", \"kv\"),  # sharded array shape: (num_heads, features, head_dim)\n        ),\n        kv_einsum_kernel_init=maybe_with_partitioning(\n          nnx.initializers.normal(),\n          config.axis_rules,\n          (None, None, \"embed\", \"kv\"),  # sharded array shape: (2, num_kv_heads, features, head_dim)\n        ),\n        scale_init=maybe_with_partitioning(\n          nnx.initializers.zeros_init(),\n          config.axis_rules,\n          (\"embed\", ),\n        ),\n        dtype=dtype,\n    )\n    if use_post_attn_norm:\n      self.post_attention_norm = layers.RMSNorm(\n        embed_dim,\n        scale_init=maybe_with_partitioning(\n          nnx.initializers.zeros_init(),\n          config.axis_rules,\n          (\"embed\", ),\n        ),\n        rngs=rngs,\n        dtype=dtype,\n      )\n    else:\n      self.post_attention_norm = None\n\n    self.pre_ffw_norm = layers.RMSNorm(\n      embed_dim,\n      scale_init=maybe_with_partitioning(\n        nnx.initializers.zeros_init(),\n        config.axis_rules,\n        (\"embed\", ),\n      ),\n      rngs=rngs,\n      dtype=dtype,\n    )\n    self.mlp = FeedForward(\n        features=embed_dim,\n        hidden_dim=hidden_dim,\n        kernel_init=maybe_with_partitioning(\n          nnx.initializers.normal(),\n          config.axis_rules,\n          (\"embed\", \"mlp\"),\n        ),\n        rngs=rngs,\n        sow_config=sow_config,\n    )\n    if use_post_ffw_norm:\n      self.post_ffw_norm = layers.RMSNorm(\n        embed_dim,\n        scale_init=maybe_with_partitioning(\n          nnx.initializers.zeros_init(),\n          config.axis_rules,\n          (\"embed\", ),\n        ),\n        rngs=rngs,\n        dtype=dtype,\n      )\n    else:\n      self.post_ffw_norm = None\n    self.sow_config = sow_config\n\n  def __call__(\n      self,\n      x: jax.Array,\n      segment_pos: jax.Array,\n      cache: LayerCache | None,\n      attn_mask: jax.Array,\n  ) -> tuple[LayerCache | None, jax.Array]:\n\n    # Attention.\n    attn_inputs = self.pre_attention_norm(x)\n    cache, attn_output = self.attn(\n        attn_inputs,\n        segment_pos,\n        cache,\n        attn_mask,\n    )\n    if self.post_attention_norm is not None:\n      attn_output = self.post_attention_norm(attn_output)\n    x += attn_output\n    self.sow_config.maybe_sow_rs_after_attention(x, self)\n\n    # Feed forward.\n    ffw_inputs = self.pre_ffw_norm(x)\n    ffw_outputs = self.mlp(ffw_inputs)\n    if self.post_ffw_norm is not None:\n      ffw_outputs = self.post_ffw_norm(ffw_outputs)\n    x += ffw_outputs\n    self.sow_config.maybe_sow_rs_after_ffw(x, self)\n\n    return cache, x\n\n  def init_cache(\n      self,\n      cache_size: int,\n      batch_size: int,\n      dtype: jnp.dtype = jnp.bfloat16,\n  ) -> LayerCache:\n    return self.attn.init_cache(\n        cache_size=cache_size,\n        batch_size=batch_size,\n        dtype=dtype,\n    )\n\n\ndef maybe_with_partitioning(fn, axis_rules, axis_rules_args=()):\n  if axis_rules is None:\n    return fn\n  return nnx.with_partitioning(fn, axis_rules(*axis_rules_args))\n"
  },
  {
    "path": "examples/gemma/modules_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Tests for transformer modules.\"\"\"\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom flax import nnx\nimport modules\nimport transformer as transformer_lib\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\n\n\nclass EmbedderTest(parameterized.TestCase):\n\n  @parameterized.parameters(\n      dict(\n          vocab_size=10,\n          embed_dim=4,\n          inputs=[2, 3],\n          expected=[[2.0, 2.0, 2.0, 2.0], [2.0, 2.0, 2.0, 2.0]],\n      ),\n  )\n  def test_encode(self, vocab_size, embed_dim, inputs, expected):\n    embedder = modules.Embedder(\n        vocab_size=vocab_size,\n        embed_dim=embed_dim,\n        rngs=nnx.Rngs(params=0),\n    )\n    embedder.input_embedding[...] = jnp.ones((vocab_size, embed_dim))\n    output = embedder.encode(inputs)\n    np.testing.assert_array_equal(output, jnp.array(expected))\n\n  @parameterized.parameters(\n      dict(\n          vocab_size=5,\n          embed_dim=2,\n          inputs=[[1, 2]],\n          expected=[[3.0, 3.0, 3.0, 3.0, 3.0]],\n      ),\n  )\n  def test_decode(self, vocab_size, embed_dim, inputs, expected):\n    embedder = modules.Embedder(\n        vocab_size=vocab_size,\n        embed_dim=embed_dim,\n        rngs=nnx.Rngs(params=0),\n    )\n    embedder.input_embedding[...] = jnp.ones((vocab_size, embed_dim))\n    output = embedder.decode(jnp.array(inputs))\n    np.testing.assert_array_equal(output, jnp.array(expected))\n\n\nclass AttentionTest(parameterized.TestCase):\n\n  @parameterized.parameters(\n      dict(\n          head_dim=2,\n      ),\n      dict(\n          head_dim=20,\n      ),\n  )\n  def test_head_dim(self, head_dim):\n    attn = modules.Attention(\n        num_heads=4,\n        num_kv_heads=2,\n        features=5,\n        head_dim=head_dim,\n        query_pre_attn_scalar=1.0,\n        attn_type=modules.AttentionType.GLOBAL,\n        rngs=nnx.Rngs(params=0),\n    )\n\n    self.assertEqual(attn.head_dim, head_dim)\n\n  @parameterized.parameters(\n      dict(\n          num_heads=4,\n          num_kv_heads=2,\n          expected_use_qkv_einsum=False,\n      ),\n      dict(\n          num_heads=3,\n          num_kv_heads=3,\n          expected_use_qkv_einsum=True,\n      ),\n  )\n  def test_use_qkv_einsum(\n      self,\n      num_heads,\n      num_kv_heads,\n      expected_use_qkv_einsum,\n  ):\n    attn = modules.Attention(\n        num_heads=num_heads,\n        num_kv_heads=num_kv_heads,\n        features=5,\n        head_dim=8,\n        query_pre_attn_scalar=1.0,\n        attn_type=modules.AttentionType.GLOBAL,\n        rngs=nnx.Rngs(params=0),\n    )\n\n    self.assertEqual(attn.use_qkv_einsum, expected_use_qkv_einsum)\n    self.assertEqual(hasattr(attn, 'q_einsum'), not expected_use_qkv_einsum)\n    self.assertEqual(hasattr(attn, 'kv_einsum'), not expected_use_qkv_einsum)\n\n  @parameterized.parameters(\n      dict(\n          num_heads=2,\n          head_dim=4,\n          features=8,\n          segment_pos=0,\n          cache_size=3,\n          batch_size=2,\n          expected_cache_shape=(2, 3, 2, 4),\n          expected_output_shape=(2, 1, 8),\n      ),\n  )\n  def test_attention(\n      self,\n      num_heads,\n      head_dim,\n      features,\n      segment_pos,\n      cache_size,\n      batch_size,\n      expected_cache_shape,\n      expected_output_shape,\n  ):\n    attn_mask = jnp.ones((batch_size, 1, cache_size))\n    attn = modules.Attention(\n        num_heads,\n        num_heads,\n        features,\n        head_dim,\n        query_pre_attn_scalar=1.0,\n        attn_type=modules.AttentionType.GLOBAL,\n        rngs=nnx.Rngs(params=0),\n    )\n    cache = attn.init_cache(\n        cache_size=cache_size,\n        batch_size=batch_size,\n        dtype=jnp.float32,\n    )\n    x = jnp.ones((batch_size, 1, features))\n    cache, output = attn(x, jnp.array([[segment_pos]]), cache, attn_mask)\n\n    self.assertEqual(cache['k'].shape, expected_cache_shape)\n    self.assertEqual(output.shape, expected_output_shape)\n\n  @parameterized.parameters(\n      dict(\n          sliding_window_size=2,\n      ),\n  )\n  def test_sliding_window(self, sliding_window_size):\n    num_heads = 2\n    head_dim = 4\n    features = 8\n    segment_pos = 0\n    cache_size = 3\n    batch_size = 2\n    attn_mask = jnp.ones((batch_size, 1, cache_size))\n    x = jnp.ones((batch_size, 1, features))\n    attn = modules.Attention(\n        num_heads,\n        num_heads,\n        features,\n        head_dim,\n        query_pre_attn_scalar=1.0,\n        attn_type=modules.AttentionType.GLOBAL,\n        rngs=nnx.Rngs(params=0),\n    )\n    cache = attn.init_cache(\n        cache_size=cache_size,\n        batch_size=batch_size,\n        dtype=jnp.float32,\n    )\n    _, output = attn(x, jnp.array([[segment_pos]]), cache, attn_mask)\n    sliding_attn = modules.Attention(\n        num_heads,\n        num_heads,\n        features,\n        head_dim,\n        query_pre_attn_scalar=1.0,\n        attn_type=modules.AttentionType.LOCAL_SLIDING,\n        sliding_window_size=sliding_window_size,\n        rngs=nnx.Rngs(params=0),\n    )\n    _, sliding_output = sliding_attn(\n        x, jnp.array([[segment_pos]]), cache, attn_mask\n    )\n\n    self.assertFalse((output == sliding_output).all())\n\n\nclass FeedForwardTest(parameterized.TestCase):\n\n  @parameterized.parameters(\n      dict(\n          features=2,\n          hidden_dim=3,\n          batch_size=2,\n          expected_val=[11.72758674, 47.99916],\n          expected_shape=(2, 1, 2),\n      ),\n  )\n  def test_ffw(\n      self, features, hidden_dim, batch_size, expected_val, expected_shape\n  ):\n    inputs = jnp.arange(1, batch_size+1)[:, None, None]\n    inputs = jnp.repeat(inputs, features, axis=-1)\n    ffw = modules.FeedForward(\n        features=features,\n        hidden_dim=hidden_dim,\n        rngs=nnx.Rngs(params=0),\n    )\n    ffw.gate_proj.kernel[...] = jnp.ones((features, hidden_dim))\n    ffw.up_proj.kernel[...] = jnp.ones((features, hidden_dim))\n    ffw.down_proj.kernel[...] = jnp.ones((hidden_dim, features))\n\n    with jax.default_matmul_precision('float32'):\n      outputs = ffw(inputs)\n\n    np.testing.assert_array_almost_equal(outputs[:, 0, 0], expected_val)\n    self.assertEqual(outputs.shape, expected_shape)\n\n\nclass BlockTest(parameterized.TestCase):\n\n  @parameterized.parameters(\n      dict(\n          num_heads=2,\n          embed_dim=4,\n          head_dim=6,\n          cache_size=3,\n          batch_size=2,\n          use_post_attn_norm=False,\n          use_post_ffw_norm=False,\n          expected_cache_shape=(2, 3, 2, 6),\n          expected_output_shape=(2, 1, 4),\n      ),\n  )\n  def test_block(\n      self,\n      num_heads,\n      embed_dim,\n      head_dim,\n      cache_size,\n      batch_size,\n      use_post_attn_norm,\n      use_post_ffw_norm,\n      expected_cache_shape,\n      expected_output_shape,\n  ):\n    inputs = jnp.ones((batch_size, 1, embed_dim))\n    attn_mask = jnp.ones((batch_size, 1, cache_size))\n\n    config = transformer_lib.TransformerConfig(\n        num_heads=num_heads,\n        num_kv_heads=num_heads,\n        embed_dim=embed_dim,\n        head_dim=head_dim,\n        hidden_dim=1,\n        use_post_attn_norm=use_post_attn_norm,\n        use_post_ffw_norm=use_post_ffw_norm,\n        final_logit_softcap=None,\n        num_layers=-1,\n        num_embed=-1,\n        attention_types=[],\n    )\n\n    block = modules.Block(\n        config,\n        attn_type=modules.AttentionType.GLOBAL,\n        rngs=nnx.Rngs(params=0),\n    )\n    cache = block.init_cache(\n        cache_size=cache_size,\n        batch_size=batch_size,\n        dtype=jnp.float32,\n    )\n\n    new_cache, outputs = block(inputs, jnp.array([[0]]), cache, attn_mask)\n\n    self.assertEqual(block.post_attention_norm is not None, use_post_attn_norm)\n    self.assertEqual(new_cache['k'].shape, expected_cache_shape)\n    self.assertEqual(outputs.shape, expected_output_shape)\n\n  @parameterized.parameters(\n      dict(\n          num_heads=1,\n          embed_dim=1,\n          head_dim=2,\n          cache_size=1,\n          batch_size=1,\n      ),\n  )\n  def test_post_attention_norm(\n      self,\n      num_heads,\n      embed_dim,\n      head_dim,\n      cache_size,\n      batch_size,\n  ):\n    inputs = jnp.ones((batch_size, 1, embed_dim))\n    attn_mask = jnp.ones((batch_size, 1, cache_size))\n\n    normed_block_config = transformer_lib.TransformerConfig(\n        num_heads=num_heads,\n        num_kv_heads=num_heads,\n        embed_dim=embed_dim,\n        head_dim=head_dim,\n        hidden_dim=1,\n        use_post_attn_norm=True,\n        use_post_ffw_norm=False,\n        final_logit_softcap=None,\n        num_layers=-1,\n        num_embed=-1,\n        attention_types=[],\n    )\n    normed_block = modules.Block(\n        normed_block_config,\n        attn_type=modules.AttentionType.GLOBAL,\n        rngs=nnx.Rngs(params=0),\n    )\n\n    unnormed_block_config = transformer_lib.TransformerConfig(\n        num_heads=num_heads,\n        num_kv_heads=num_heads,\n        embed_dim=embed_dim,\n        head_dim=head_dim,\n        hidden_dim=1,\n        use_post_attn_norm=False,\n        use_post_ffw_norm=False,\n        final_logit_softcap=None,\n        num_layers=-1,\n        num_embed=-1,\n        attention_types=[],\n    )\n    unnormed_block = modules.Block(\n        unnormed_block_config,\n        attn_type=modules.AttentionType.GLOBAL,\n        rngs=nnx.Rngs(params=0),\n    )\n\n    # Ok to use the same cache for both blocks.\n    cache = normed_block.init_cache(\n        cache_size=cache_size,\n        batch_size=batch_size,\n        dtype=jnp.float32,\n    )\n\n    all_outputs = []\n    for block in (normed_block, unnormed_block):\n      _, outputs = block(inputs, jnp.array([[0]]), cache, attn_mask)\n      all_outputs.append(outputs)\n\n    normed_output, unnormed_output = all_outputs  # pylint: disable=unbalanced-tuple-unpacking\n    self.assertTrue(jnp.not_equal(normed_output, unnormed_output).all())\n\n  @parameterized.parameters(\n      dict(\n          num_heads=1,\n          embed_dim=1,\n          head_dim=2,\n          cache_size=1,\n          batch_size=1,\n      ),\n  )\n  def test_post_ffw_norm(\n      self,\n      num_heads,\n      embed_dim,\n      head_dim,\n      cache_size,\n      batch_size,\n  ):\n    inputs = jnp.ones((batch_size, 1, embed_dim))\n    attn_mask = jnp.ones((batch_size, 1, cache_size))\n\n    normed_block_config = transformer_lib.TransformerConfig(\n        num_heads=num_heads,\n        num_kv_heads=num_heads,\n        embed_dim=embed_dim,\n        head_dim=head_dim,\n        hidden_dim=1,\n        use_post_attn_norm=False,\n        use_post_ffw_norm=True,\n        final_logit_softcap=None,\n        num_layers=-1,\n        num_embed=-1,\n        attention_types=[],\n    )\n    normed_block = modules.Block(\n        normed_block_config,\n        attn_type=modules.AttentionType.GLOBAL,\n        rngs=nnx.Rngs(params=0),\n    )\n\n    unnormed_block_config = transformer_lib.TransformerConfig(\n        num_heads=num_heads,\n        num_kv_heads=num_heads,\n        embed_dim=embed_dim,\n        head_dim=head_dim,\n        hidden_dim=1,\n        use_post_attn_norm=False,\n        use_post_ffw_norm=False,\n        final_logit_softcap=None,\n        num_layers=-1,\n        num_embed=-1,\n        attention_types=[],\n    )\n    unnormed_block = modules.Block(\n        unnormed_block_config,\n        attn_type=modules.AttentionType.GLOBAL,\n        rngs=nnx.Rngs(params=0),\n    )\n\n    # Ok to use the same cache for both blocks.\n    cache = normed_block.init_cache(\n        cache_size=cache_size,\n        batch_size=batch_size,\n        dtype=jnp.float32,\n    )\n\n    all_outputs = []\n    for block in (normed_block, unnormed_block):\n      _, outputs = block(inputs, jnp.array([[0]]), cache, attn_mask)\n      all_outputs.append(outputs)\n\n    normed_output, unnormed_output = all_outputs  # pylint: disable=unbalanced-tuple-unpacking\n    print(normed_output.shape, unnormed_output.shape)\n    print(f\"{normed_output=}\")\n    print(f\"{unnormed_output=}\")\n    self.assertTrue(jnp.not_equal(normed_output, unnormed_output).all())\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/gemma/params.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Utils for loading Gemma params.\"\"\"\n\nfrom collections.abc import Mapping\nimport functools\nfrom typing import Any\n\nimport jax\nimport jax.numpy as jnp\nimport orbax.checkpoint\n\nParams = Mapping[str, Any]\n\n\ndef load_and_format_params(path: str) -> Params:\n  \"\"\"Loads parameters and formats them for compatibility.\"\"\"\n  params = load_params(path)\n  param_state = jax.tree.map(jnp.array, params)\n  remapped_params = param_remapper(param_state)\n  nested_params = nest_params(remapped_params)\n  return nested_params\n\n\ndef load_metadata(path: str) -> Any | None:\n  \"\"\"Loads metadata from a checkpoint path.\"\"\"\n  checkpointer = orbax.checkpoint.PyTreeCheckpointer()\n  metadata = checkpointer.metadata(path)\n  return metadata\n\n\n@functools.cache\ndef load_params(path: str) -> Params:\n  \"\"\"Loads parameters from a checkpoint path.\"\"\"\n  checkpointer = orbax.checkpoint.PyTreeCheckpointer()\n  params = checkpointer.restore(path)\n  return params\n\n\ndef param_remapper(orig_params: Params) -> Params:\n  \"\"\"Remaps params to new module layout.\n\n  This is needed here because the model definition  does not have a separate\n  `mlp` module.\n\n  Args:\n    orig_params: original dict of parameters in Gemma format.\n\n  Returns:\n    dict of params with different names.\n  \"\"\"\n  new_params = {}\n  for k, v in orig_params.items():\n    if 'mlp/' in k:\n      layer_name, param = k.rsplit('/', maxsplit=1)\n      if layer_name not in new_params:\n        new_params[layer_name] = {}\n      if 'w' in v:\n        new_params[layer_name][param] = v['w']\n    else:\n      new_params[k] = v\n  return new_params\n\n\ndef nest_params(params: Params) -> Params:\n  \"\"\"Nests params as a dict of dicts rather than a flat dict.\"\"\"\n  nested_params = {}\n  for path, param in params.items():\n    *path, leaf = path.split('/')\n    subdict = nested_params\n    for key in path:\n      subdict = subdict.setdefault(key, {})\n    subdict[leaf] = param\n  return nested_params\n"
  },
  {
    "path": "examples/gemma/positional_embeddings.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Utils for positional embeddings (including RoPE).\"\"\"\n\nimport jax\nimport jax.numpy as jnp\n\n_MAX_WAVELENGTH = 10_000\n\n\ndef add_positional_embedding(\n    input_embedding: jax.Array,\n    position: int,\n    max_wavelength: int = _MAX_WAVELENGTH,\n) -> jax.Array:\n  \"\"\"Adds positional embeddings to input embeddings.\"\"\"\n  embed_dim = input_embedding.shape[-1]\n  num_timescales = embed_dim // 2\n  log_timescale_increment = jnp.log(float(max_wavelength)) / jnp.maximum(\n      jnp.asarray(num_timescales, dtype=jnp.float32) - 1, 1\n  )\n  inv_timescales = jnp.exp(\n      jnp.arange(num_timescales, dtype=jnp.float32) * -log_timescale_increment\n  )\n  scaled_time = position * inv_timescales\n  signal = jnp.concatenate([jnp.sin(scaled_time), jnp.cos(scaled_time)])\n  signal = jnp.pad(signal, [[0, jnp.mod(embed_dim, 2)]])\n  position_embedding = signal.astype(jnp.float32)\n\n  return input_embedding + position_embedding\n\n\ndef apply_rope(\n    inputs: jax.Array,  # [B, L]\n    positions: jax.Array,  # [B, L]\n    head_dim: int,\n    max_wavelength: int = _MAX_WAVELENGTH,\n    scale_factor: float = 1.0,\n) -> jax.Array:\n  \"\"\"Applies RoPE.\"\"\"\n  fraction = 2 * jnp.arange(0, head_dim // 2) / head_dim\n  timescale = max_wavelength**fraction\n\n  sinusoid_inp = (\n      positions[..., jnp.newaxis] / timescale[jnp.newaxis, jnp.newaxis, :]\n  )\n  sinusoid_inp = sinusoid_inp[..., jnp.newaxis, :]\n  if scale_factor < 1.0:\n    raise ValueError(f'scale_factor must be >= 1.0, got {scale_factor}')\n  sinusoid_inp /= scale_factor\n\n  sin = jnp.sin(sinusoid_inp)\n  cos = jnp.cos(sinusoid_inp)\n\n  first_half, second_half = jnp.split(inputs, 2, axis=-1)\n  first_part = first_half * cos - second_half * sin\n  second_part = second_half * cos + first_half * sin\n  out = jnp.concatenate([first_part, second_part], axis=-1)\n  return out.astype(inputs.dtype)\n"
  },
  {
    "path": "examples/gemma/positional_embeddings_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Tests for the positional embeddings utilities.\"\"\"\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nimport positional_embeddings\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\n\n# positional_embeddings.py uses implicit rank broadcast and needs this config to\n# be 'allow', while the rest of Flax can use jax_numpy_rank_promotion=raise.\njax.config.update('jax_numpy_rank_promotion', 'allow')\n\n\nclass PositionalEmbeddingsTest(parameterized.TestCase):\n\n  @parameterized.parameters(\n      dict(\n          input_embedding_shape=(2, 1, 1, 5),\n          position=3,\n          max_wavelength=100,\n          expected=[[[[1.1411201, 1.0299965, 0.0100075, 1.99955, 1.0]]],\n                    [[[1.1411201, 1.0299965, 0.0100075, 1.99955, 1.0]]]]\n      )\n  )\n  def test_adds_positional_embeddings(\n      self, input_embedding_shape, position, max_wavelength, expected\n  ):\n    outputs = positional_embeddings.add_positional_embedding(\n        jnp.ones(input_embedding_shape), position, max_wavelength\n    )\n    np.testing.assert_array_almost_equal(outputs, jnp.array(expected))\n\n  @parameterized.parameters(\n      dict(\n          input_embedding_shape=(2, 1, 2, 4),\n          position=3,\n          head_dim=4,\n          max_wavelength=100,\n          expected=[\n              [[\n                  [-1.1311126, 0.6598157, -0.8488725, 1.2508571],\n                  [-1.1311126, 0.6598157, -0.8488725, 1.2508571],\n              ]],\n              [[\n                  [-1.1311126, 0.6598157, -0.8488725, 1.2508571],\n                  [-1.1311126, 0.6598157, -0.8488725, 1.2508571],\n              ]],\n          ],\n      )\n  )\n  def test_rope_positional_embeddings(\n      self, input_embedding_shape, position, head_dim, max_wavelength, expected\n  ):\n    outputs = positional_embeddings.apply_rope(\n        jnp.ones(input_embedding_shape),\n        jnp.array([[position]]),\n        head_dim,\n        max_wavelength,\n    )\n    np.testing.assert_array_almost_equal(outputs, jnp.array(expected))\n\n\nif __name__ == \"__main__\":\n  absltest.main()\n"
  },
  {
    "path": "examples/gemma/requirements.txt",
    "content": "absl-py~=2.2\nclu==0.0.12\nflax~=0.10\njax~=0.6\nmlcroissant~=1.0\nnumpy~=2.1\noptax~=0.2\nsentencepiece~=0.2\njaxtyping~=0.3\ntensorflow~=2.19\ntensorflow-datasets~=4.9\ntensorflow-text~=2.19"
  },
  {
    "path": "examples/gemma/sampler.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Sampler for Gemma transformer.\n\nAn example of a sampling class for a Gemma model.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom collections.abc import Sequence\nimport dataclasses\n\nimport flax\nfrom flax import nnx\nimport modules\nimport sow_lib\nimport transformer as transformer_lib\nfrom flax.nnx import graph\nfrom flax.nnx import statelib\nimport jax\nimport jax.numpy as jnp\n\nimport sentencepiece as spm\n\n\ndef _sample_top_p(probs: jnp.ndarray, p: float, key: jax.Array) -> jnp.ndarray:\n  \"\"\"Sample a token using top-p sampling.\"\"\"\n  probs_sorted, indices = jax.lax.top_k(probs, k=probs.shape[-1])\n  cumsum_probs = jnp.cumsum(probs_sorted, axis=-1)\n  mask = cumsum_probs - probs_sorted > p\n  probs_sorted = jnp.where(mask, 0.0, probs_sorted)\n  probs_sorted /= jnp.sum(probs_sorted, axis=-1, keepdims=True)\n\n  next_token = jax.random.categorical(key, logits=jnp.log(probs_sorted))\n\n  next_token = jnp.take_along_axis(indices, next_token[..., None], axis=-1)\n  next_token = jnp.squeeze(next_token, axis=-1)\n  return next_token\n\n\ndef _compute_attention_masks(\n    time_step: jax.Array, seq_len: int, input_mask: jax.Array\n) -> jax.Array:\n  \"\"\"Computes causal attention mask.\"\"\"\n  batch_size = input_mask.shape[0]\n  batch_time_step = jnp.full((batch_size, 1), time_step, dtype=jnp.uint32)\n  causal_padding = jnp.greater(\n      jnp.expand_dims(jnp.arange(seq_len), 0), batch_time_step\n  )\n  max_seq_len = min(input_mask.shape[-1], seq_len)\n  input_mask = jax.lax.dynamic_slice(\n      input_mask,\n      (0, jnp.maximum(time_step - seq_len + 1, 0)),\n      (batch_size, max_seq_len),\n  )\n  input_mask = (\n      jnp.zeros((batch_size, seq_len), dtype=jnp.bool_)\n      .at[:, :max_seq_len]\n      .set(input_mask)\n  )\n\n  causal_padding = jnp.logical_or(causal_padding, input_mask)\n  attention_mask = causal_padding[:, jnp.newaxis, :].astype(jnp.bool_)\n\n  return ~attention_mask\n\n\n@flax.struct.dataclass\nclass _SamplingState:\n  \"\"\"Internal sampling state.\"\"\"\n\n  # Decoding step.\n  decoding_step: jnp.int32\n\n  # Number of tokens in the prompt.\n  num_input_tokens: jnp.ndarray  # [B]\n\n  # Fixed-size buffer for accumulating the output tokens.\n  token_buffer: jnp.ndarray  # [B, L]\n\n  # Position indices, based on ignoring pad tokens.\n  positions: jnp.ndarray  # [B, L]\n\n  # Model state for conditioning the model on autoregressively.\n  cache: dict[str, modules.LayerCache]\n\n  # Is decoding done on the given sequence?\n  done: jnp.ndarray  # [B]\n\n  # Total sampling steps (including the prompt).\n  total_sampling_steps: int\n\n  # Fixed-size buffer for accumulating the output logits.\n  logits_buffer: jnp.ndarray | None  # [B, L, V]\n\n  # List of tokens that are forbidden to be generated.\n  forbidden_token_ids: Sequence[int] | None\n\n  # Intermediate activations from the model if requested.\n  intermediates: sow_lib.TransformerIntermediates | None\n\n  # Random seed for sampling.\n  seed: jax.Array\n\n  # Tempurature for top_p sampling.\n  temperature: float = flax.struct.field(pytree_node=False)\n\n  # Top-p sampling threshold.\n  top_p: float = flax.struct.field(pytree_node=False)\n\n\n@dataclasses.dataclass\nclass SamplerOutput:\n  \"\"\"Output of the sampler.\"\"\"\n\n  # Decoded samples from the model.\n  text: list[str]\n\n  # Per-step logits used during sampling.\n  logits: list[list[float]]\n\n  # Tokens corresponding to the generated samples.\n  tokens: list[list[int]]\n\n  # Intermediate activations from the model if requested.\n  intermediates: sow_lib.TransformerIntermediates | None = None\n\n\nclass Sampler:\n  \"\"\"Sampler for gemma transformer.\"\"\"\n\n  def __init__(\n      self,\n      transformer: transformer_lib.Transformer,\n      vocab: spm.SentencePieceProcessor,\n      cache_size: int = 1024,\n  ):\n    \"\"\"Initializes a sampler for a Gemma model.\n\n    Args:\n      transformer: an instance of the Gemma transformer.\n      vocab: vocabulary of the given model.\n      cache_size: size of the cache for the transformer.\n    \"\"\"\n    self.vocab = vocab\n    self.cache_size = cache_size\n    graphdef, state = nnx.split(transformer)\n    self._transformer_graphdef: graph.NodeDef = graphdef\n    self._transformer_state: statelib.State = state\n    # we separate out state and graph def so that the state can be passed as an\n    # argument to _sample_fn, resulting in it not being treated as a static\n    # arg. This greatly reduces the size of the HLO and reduces compile time\n    self._compiled_sample_fn = jax.jit(self._sample_fn)\n\n  @property\n  def transformer(self) -> transformer_lib.Transformer:\n    return nnx.merge(self._transformer_graphdef, self._transformer_state)\n\n  @property\n  def transformer_state(self) -> statelib.State:\n    return self._transformer_state\n\n  @transformer_state.setter\n  def transformer_state(self, state: statelib.State) -> statelib.State:\n    def check_tree_structure(tree1, tree2):\n      if jax.tree_util.tree_structure(tree1) != jax.tree_util.tree_structure(\n          tree2\n      ):\n        raise ValueError(\n            \"New state must have the same structure as the old state.\"\n        )\n\n      def check_shape_dtype(x, y):\n        return jnp.shape(x) == jnp.shape(y) and jnp.dtype(x) == jnp.dtype(y)\n\n      if not all(\n          jax.tree_util.tree_leaves(\n              jax.tree_util.tree_map(check_shape_dtype, tree1, tree2)\n          )\n      ):\n        raise ValueError(\n            \"New state must have the same shape and dtype as the old state.\"\n        )\n\n    check_tree_structure(self._transformer_state, state)\n    self._transformer_state = state\n\n  @property\n  def dtype(self) -> jnp.dtype:\n    return jax.tree_util.tree_leaves(\n        nnx.to_flat_state(self._transformer_state)\n    )[0].dtype\n\n  def _sample_step(\n      self, params: statelib.State, sampler_state: _SamplingState\n  ) -> _SamplingState:\n    \"\"\"Performs a single sampling step.\"\"\"\n    batch_size = sampler_state.token_buffer.shape[0]\n    decoding_step = jnp.asarray(sampler_state.decoding_step, dtype=jnp.int32)\n    last_token = sampler_state.token_buffer[:, decoding_step]\n    input_mask = sampler_state.token_buffer == self.vocab.pad_id()\n    attention_mask = _compute_attention_masks(\n        decoding_step, self.cache_size, input_mask\n    )\n    step_positions = jnp.expand_dims(\n        sampler_state.positions[:, decoding_step], -1\n    )\n    last_token = last_token.reshape((batch_size, 1))\n\n    transformer = nnx.merge(self._transformer_graphdef, params)\n    logits, cache = transformer(\n        last_token,\n        step_positions,\n        sampler_state.cache,\n        attention_mask,\n    )\n    if sampler_state.forbidden_token_ids:\n      logits = logits.at[:, :, sampler_state.forbidden_token_ids].set(-jnp.inf)\n\n    def sample_top_p(logits, key):\n      probs = jax.nn.softmax(logits[:, -1] / sampler_state.temperature, axis=-1)\n      next_token = _sample_top_p(probs, sampler_state.top_p, key)\n      return next_token\n\n    def sample_best(logits):\n      next_token = jnp.argmax(logits, axis=-1)\n      next_token = next_token[:, 0]\n      return next_token\n\n    if sampler_state.temperature > 0:\n      key = jax.random.fold_in(sampler_state.seed, decoding_step)\n      next_token_candidate = sample_top_p(logits, key)\n    else:\n      next_token_candidate = sample_best(logits)\n\n    next_token_candidate = jnp.where(\n        decoding_step < sampler_state.num_input_tokens - 1,\n        sampler_state.token_buffer[:, decoding_step + 1],\n        next_token_candidate,\n    )\n\n    token_buffer = sampler_state.token_buffer.at[:, decoding_step + 1].set(\n        next_token_candidate\n    )\n\n    if sampler_state.logits_buffer is not None:\n      next_logits = jnp.squeeze(logits, 1)\n      logits_buffer = sampler_state.logits_buffer.at[:, decoding_step + 1].set(\n          next_logits\n      )\n    else:\n      logits_buffer = sampler_state.logits_buffer\n\n    if sampler_state.intermediates is not None:\n      sampler_state.intermediates.merge(decoding_step, transformer)\n\n    done = sampler_state.done | jnp.equal(\n        token_buffer[:, decoding_step + 1], self.vocab.eos_id()\n    )\n\n    return _SamplingState(\n        decoding_step=sampler_state.decoding_step + 1,\n        num_input_tokens=sampler_state.num_input_tokens,\n        token_buffer=token_buffer,\n        positions=sampler_state.positions,\n        logits_buffer=logits_buffer,\n        cache=cache,\n        done=done,\n        total_sampling_steps=sampler_state.total_sampling_steps,\n        forbidden_token_ids=sampler_state.forbidden_token_ids,\n        intermediates=sampler_state.intermediates,\n        temperature=sampler_state.temperature,\n        top_p=sampler_state.top_p,\n        seed=sampler_state.seed,\n    )\n\n  def init_sample_state(\n      self,\n      all_input_ids: list[jax.Array],\n      total_sampling_steps: int,\n      include_logits: bool,\n      forbidden_token_ids: Sequence[int] | None,\n      temperature: float,\n      top_p: float,\n      seed: jax.Array,\n  ) -> _SamplingState:\n    \"\"\"Initializes the sampling state given input prompts.\"\"\"\n    batch_size = len(all_input_ids)\n    num_input_tokens = [len(input_ids) for input_ids in all_input_ids]\n    buffer_size = total_sampling_steps + 1\n\n    token_buffer = jnp.full(\n        (\n            batch_size,\n            buffer_size,\n        ),\n        self.vocab.pad_id(),\n        dtype=jnp.int32,\n    )\n    input_mask = jnp.ones_like(token_buffer, dtype=jnp.bool_)\n    for i, (input_ids, num_tokens) in enumerate(\n        zip(all_input_ids, num_input_tokens)\n    ):\n      token_buffer = token_buffer.at[i, :num_tokens].set(input_ids)\n      input_mask = input_mask.at[i, :num_tokens].set(\n          input_ids != self.vocab.pad_id()\n      )\n    positions = transformer_lib.build_positions_from_mask(input_mask)\n\n    done = jnp.zeros((batch_size,), dtype=jnp.bool_)\n\n    if include_logits:\n      logits_buffer = jnp.zeros(\n          (batch_size, buffer_size, self.transformer.num_embed),\n          dtype=jnp.float32,\n      )\n    else:\n      logits_buffer = None\n\n    return _SamplingState(\n        decoding_step=0,\n        num_input_tokens=jnp.array(num_input_tokens, dtype=jnp.int32),\n        token_buffer=token_buffer,\n        positions=positions,\n        logits_buffer=logits_buffer,\n        cache=self.transformer.init_cache(\n            cache_size=self.cache_size,\n            batch_size=batch_size,\n            dtype=self.dtype,\n        ),\n        done=done,\n        total_sampling_steps=total_sampling_steps,\n        forbidden_token_ids=forbidden_token_ids,\n        intermediates=self.transformer.init_intermediates(\n            batch_size, buffer_size, self.transformer.sow_config\n        ),\n        temperature=temperature,\n        top_p=top_p,\n        seed=seed,\n    )\n\n  def tokenize(self, input_string: str) -> jax.Array:\n    \"\"\"Tokenizes the input string.\"\"\"\n    input_ids = self.vocab.EncodeAsIds(input_string)\n    input_ids = jnp.array(\n        [self.vocab.bos_id()] + jnp.array(input_ids).tolist(), dtype=jnp.int32\n    )\n    return input_ids\n\n  def mask_tokens_after_eos_ids(self, token_buffer):\n    \"\"\"Mask token IDs after the EOS token with the padding ID.\"\"\"\n    eos_id = self.vocab.eos_id()\n    eos_exists = jnp.any(jnp.equal(token_buffer, eos_id), axis=-1)\n    eos_indices = jnp.where(\n        eos_exists,\n        jnp.argmax(jnp.equal(token_buffer, eos_id), axis=-1),\n        token_buffer.shape[-1],\n    )\n    mask = jnp.less_equal(\n        jnp.arange(token_buffer.shape[-1]), eos_indices[:, None]\n    )\n    masked_token_buffer = token_buffer * mask + self.vocab.pad_id() * (1 - mask)\n\n    return masked_token_buffer\n\n  def _sample_fn(\n      self,\n      params: statelib.State,\n      initial_sampling_state: _SamplingState,\n  ) -> _SamplingState:\n    \"\"\"Internal sampling function (to be jitted).\"\"\"\n\n    def sample_with_params(sampler_state: _SamplingState):\n      return self._sample_step(params, sampler_state)\n\n    def cond_fn(sampler_state: _SamplingState):\n      return (\n          sampler_state.decoding_step < sampler_state.total_sampling_steps\n      ) & jnp.any(jnp.logical_not(sampler_state.done))\n\n    return jax.lax.while_loop(\n        cond_fn, sample_with_params, initial_sampling_state\n    )\n\n  def __call__(\n      self,\n      input_strings: Sequence[str],\n      total_generation_steps: int,\n      echo: bool = False,\n      return_logits: bool = True,\n      forbidden_tokens: Sequence[str] | None = None,\n      temperature: float = 0.0,\n      top_p: float = 0.95,\n      seed: jax.Array | None = None,\n  ) -> SamplerOutput:\n    \"\"\"Samples a completion of the input string.\n\n    Args:\n      input_strings: input prompts to feed to the model for sampling.\n      total_generation_steps: number of generation steps. will correspond to the\n        longest prompt in the batch.\n      echo: whether to return the prompt as part of the output sample.\n      return_logits: whether to return per-step logits used during generation.\n      forbidden_tokens: list of tokens that are forbidden to be generated. Each\n        token must map to a single token id in the vocab.\n      temperature: temperature for sampling.\n      top_p: top-p sampling threshold.\n      seed: random seed for sampling.\n\n    Returns:\n      sampler_output: A SamplerOutput object containing the generated samples.\n    \"\"\"\n    forbidden_token_ids = None\n    if forbidden_tokens is not None:\n      forbidden_token_ids = []\n      for token in forbidden_tokens:\n        token_id = self.vocab.EncodeAsIds(token)\n        if len(token_id) != 1:\n          raise ValueError(\n              \"Forbidden tokens must map to single token ids in the vocab.\"\n          )\n        forbidden_token_ids.extend(token_id)\n      forbidden_token_ids = tuple(forbidden_token_ids)\n    all_input_ids = [self.tokenize(x) for x in input_strings]\n    max_input_length = max(len(input_ids) for input_ids in all_input_ids)\n    total_sampling_steps = max_input_length + total_generation_steps\n\n    if seed is None:\n      seed = jax.random.PRNGKey(0)\n    initial_sampling_state = self.init_sample_state(\n        all_input_ids,\n        include_logits=return_logits,\n        total_sampling_steps=total_sampling_steps,\n        forbidden_token_ids=forbidden_token_ids,\n        temperature=temperature,\n        top_p=top_p,\n        seed=seed,\n    )\n\n    sampling_state = self._compiled_sample_fn(\n        self._transformer_state, initial_sampling_state\n    )\n\n    masked_token_buffer = self.mask_tokens_after_eos_ids(\n        sampling_state.token_buffer\n    )\n\n    out_tokens = []\n    out_logits = []\n    for i, (token_buffer, num_tokens) in enumerate(\n        zip(\n            masked_token_buffer,\n            sampling_state.num_input_tokens,\n        )\n    ):\n      start_idx = 0 if echo else num_tokens\n      out_tokens.append(token_buffer[start_idx:total_sampling_steps].tolist())\n      if return_logits:\n        logits_buffer = sampling_state.logits_buffer[i]\n        out_logits.append(\n            logits_buffer[start_idx:total_sampling_steps].tolist()\n        )\n\n    decoded_outputs = [self.vocab.DecodeIds(tokens) for tokens in out_tokens]\n\n    if sampling_state.intermediates is not None:\n      sampling_state.intermediates.trim(total_sampling_steps)\n\n    result = SamplerOutput(\n        text=decoded_outputs,\n        logits=out_logits,\n        tokens=out_tokens,\n        intermediates=sampling_state.intermediates,\n    )\n    return result\n"
  },
  {
    "path": "examples/gemma/sampler_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Minimal test for sampler.\"\"\"\n\nimport os\nfrom collections.abc import Iterable\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom flax import nnx\nimport modules\nimport params as params_lib\nimport sampler as sampler_lib\nimport sow_lib\nimport transformer as transformer_lib\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\n\nimport sentencepiece as spm\n\n\nclass MockVocab(spm.SentencePieceProcessor):\n\n  def __init__(self):\n    super().__init__()\n    self._start_id = 3\n    self._mapping_text_to_id = {\n        '<pad>': 0,\n        '<s>': 1,\n        '</s>': 2,\n        'input': 3,\n        'string': 4,\n        'hello': 5,\n        'world': 6,\n        'Hello': 7,\n        'there': 8,\n        '!': 9,\n        'My': 10,\n        'name': 11,\n        'is': 12,\n        'Morgane': 13,\n    }\n    self._vocab_size = len(self._mapping_text_to_id)\n\n  def pad_id(self) -> int:\n    return 0\n\n  def bos_id(self) -> int:\n    return 1\n\n  def eos_id(self) -> int:\n    return 2\n\n  def GetPieceSize(self) -> int:  # pylint: disable=invalid-name\n    return self._vocab_size\n\n  def DecodeIds(self, ids: Iterable[int]) -> str:  # pylint: disable=invalid-name\n    reverse_mapping = {v: k for k, v in self._mapping_text_to_id.items()}\n    return ' '.join(reverse_mapping[e] for e in ids)\n\n  def EncodeAsIds(self, text: str) -> list[int]:  # pylint: disable=invalid-name\n    words = text.split(' ')\n    return [self._mapping_text_to_id[word] for word in words]\n\n\nclass SamplerTest(parameterized.TestCase):\n\n  def assertReasonableTensor(self, array, expected_shape=None):\n    self.assertIsNotNone(array)\n    if expected_shape is not None:\n      self.assertEqual(array.shape, expected_shape)\n\n  def test_samples(self):\n    vocab = MockVocab()\n    num_layers = 6\n    transformer_config = transformer_lib.TransformerConfig(  # pytype: disable=wrong-arg-types\n        num_layers=num_layers,\n        num_embed=vocab.GetPieceSize(),\n        embed_dim=768,\n        hidden_dim=6144,\n        num_heads=4,\n        num_kv_heads=4,\n        head_dim=256,\n        final_logit_softcap=None,\n        attention_types=[modules.AttentionType.GLOBAL] * num_layers,\n        attn_logits_soft_cap=None,\n        use_post_attn_norm=None,\n        use_post_ffw_norm=None,\n    )\n    transformer = transformer_lib.Transformer(\n        transformer_config, rngs=nnx.Rngs(params=0)\n    )\n    sampler = sampler_lib.Sampler(\n        transformer=transformer,\n        vocab=vocab,\n        cache_size=1024,\n    )\n\n    result = sampler(['input string', 'hello world'], total_generation_steps=10)\n    self.assertIsNotNone(result)\n\n    top_p_result = sampler(\n        ['input string', 'hello world'],\n        total_generation_steps=10,\n        temperature=9,\n        top_p=0.95,\n    )\n    self.assertIsNotNone(top_p_result)\n    self.assertNotEqual(result.text, top_p_result.text)\n\n    top_p_result_2 = sampler(\n        ['input string', 'hello world'],\n        total_generation_steps=10,\n        temperature=9,\n        top_p=0.95,\n        seed=jax.random.PRNGKey(42),\n    )\n    self.assertIsNotNone(top_p_result_2)\n    self.assertNotEqual(top_p_result.text, top_p_result_2.text)\n\n  def test_state_update(self):\n    vocab = MockVocab()\n    num_layers = 6\n    transformer_config = transformer_lib.TransformerConfig(  # pytype: disable=wrong-arg-types\n        num_layers=num_layers,\n        num_embed=vocab.GetPieceSize(),\n        embed_dim=768,\n        hidden_dim=6144,\n        num_heads=4,\n        num_kv_heads=4,\n        head_dim=256,\n        final_logit_softcap=None,\n        attention_types=[modules.AttentionType.GLOBAL] * num_layers,\n        attn_logits_soft_cap=None,\n        use_post_attn_norm=None,\n        use_post_ffw_norm=None,\n    )\n    transformer = transformer_lib.Transformer(\n        transformer_config, rngs=nnx.Rngs(params=0)\n    )\n    sampler = sampler_lib.Sampler(\n        transformer=transformer,\n        vocab=vocab,\n        cache_size=1024,\n    )\n    input_strings = ['input string', 'hello world']\n    original_logits = sampler(input_strings, total_generation_steps=10).logits\n\n    new_transformer = transformer_lib.Transformer(\n        transformer_config, rngs=nnx.Rngs(params=42)\n    )\n    sampler.transformer_state = nnx.state(new_transformer, nnx.Param)\n    new_logits = sampler(input_strings, total_generation_steps=10).logits\n    with self.assertRaises(AssertionError):\n      np.testing.assert_allclose(\n          original_logits, new_logits, atol=1e-1, rtol=1e-1\n      )\n\n  def test_invalid_state_update(self):\n    vocab = MockVocab()\n\n    def make_config(num_layers, embed_dim):\n      return transformer_lib.TransformerConfig(  # pytype: disable=wrong-arg-types\n          num_layers=num_layers,\n          num_embed=vocab.GetPieceSize(),\n          embed_dim=embed_dim,\n          hidden_dim=6144,\n          num_heads=4,\n          num_kv_heads=4,\n          head_dim=256,\n          final_logit_softcap=None,\n          attention_types=[modules.AttentionType.GLOBAL] * num_layers,\n          attn_logits_soft_cap=None,\n          use_post_attn_norm=None,\n          use_post_ffw_norm=None,\n      )\n\n    transformer = transformer_lib.Transformer(\n        make_config(num_layers=6, embed_dim=768), rngs=nnx.Rngs(params=0)\n    )\n    sampler = sampler_lib.Sampler(\n        transformer=transformer,\n        vocab=vocab,\n        cache_size=1024,\n    )\n\n    new_transformer = transformer_lib.Transformer(\n        make_config(num_layers=3, embed_dim=768), rngs=nnx.Rngs(params=42)\n    )\n    with self.assertRaisesRegex(\n        ValueError, '.*must have the same structure.*'\n    ):\n      sampler.transformer_state = nnx.state(new_transformer, nnx.Param)\n\n    new_transformer = transformer_lib.Transformer(\n        make_config(num_layers=6, embed_dim=1024), rngs=nnx.Rngs(params=42)\n    )\n    with self.assertRaisesRegex(\n        ValueError, '.*must have the same shape and dtype.*'\n    ):\n      sampler.transformer_state = nnx.state(new_transformer, nnx.Param)\n\n  def test_forbidden_tokens(self):\n    vocab = MockVocab()\n    transformer_config = transformer_lib.TransformerConfig(  # pytype: disable=wrong-arg-types\n        num_layers=0,\n        num_embed=vocab.GetPieceSize(),\n        embed_dim=32,\n        hidden_dim=64,\n        num_heads=4,\n        num_kv_heads=1,\n        head_dim=64,\n        final_logit_softcap=None,\n        attention_types=[],\n        use_post_attn_norm=None,\n        use_post_ffw_norm=None,\n    )\n    transformer = transformer_lib.Transformer(\n        transformer_config, rngs=nnx.Rngs(params=0)\n    )\n    # Pre-cook the embedding matrix so that the output is deterministic.\n    transformer.embedder.input_embedding.set_value(jnp.eye(\n        vocab.GetPieceSize(), 32\n    ))\n    sampler = sampler_lib.Sampler(\n        transformer=transformer,\n        vocab=vocab,\n        cache_size=8,\n    )\n\n    # First, we check that the sampler would produce the tokens that we are\n    # trying to forbid.\n    result1 = sampler(\n        ['input string', 'hello world'],\n        total_generation_steps=10,\n        forbidden_tokens=None,\n    )\n    self.assertIn('string', result1.text[0])\n    self.assertIn('world', result1.text[1])\n\n    # Then, we check that the sampler does not produce the forbidden tokens.\n    result2 = sampler(\n        ['input string', 'hello world'],\n        total_generation_steps=10,\n        forbidden_tokens=['string', 'world'],\n    )\n    for output in result2.text:\n      self.assertNotIn('string', output)\n      self.assertNotIn('world', output)\n\n  def test_forward_equivalence(self):\n    vocab = MockVocab()\n    num_layers = 2\n    transformer_config = transformer_lib.TransformerConfig(  # pytype: disable=wrong-arg-types\n        num_layers=num_layers,\n        num_embed=vocab.GetPieceSize(),\n        embed_dim=32,\n        hidden_dim=64,\n        num_heads=4,\n        num_kv_heads=1,\n        head_dim=64,\n        final_logit_softcap=None,\n        attention_types=[modules.AttentionType.GLOBAL] * num_layers,\n        use_post_attn_norm=None,\n        use_post_ffw_norm=None,\n    )\n\n    transformer = transformer_lib.Transformer(\n        transformer_config, rngs=nnx.Rngs(params=0)\n    )\n    raw_input = 'Hello there ! My name is Morgane <pad>'\n    token_input = jnp.asarray(\n        [vocab.bos_id()] + vocab.EncodeAsIds(raw_input)\n    ).reshape((1, -1))\n    batch_size = 1\n    cache_size = 9\n    cache = transformer.init_cache(\n        cache_size=cache_size,\n        batch_size=batch_size,\n        dtype=jnp.float32,\n    )\n    input_mask = token_input != vocab.pad_id()\n    positions = transformer_lib.build_positions_from_mask(input_mask)\n    attention_mask = transformer_lib.make_causal_attn_mask(input_mask)\n\n    n_input_tokens = token_input.shape[1]\n\n    output_forward, _ = transformer(\n        last_tokens=token_input,\n        positions=positions,\n        cache=cache,\n        attention_mask=attention_mask,\n    )\n    output_forward = output_forward[0, :n_input_tokens]\n\n    sampler = sampler_lib.Sampler(\n        transformer=transformer,\n        vocab=vocab,\n        cache_size=cache_size,\n    )\n\n    output_transformer = sampler(\n        [raw_input],\n        total_generation_steps=10,\n        echo=True,\n    )\n    out_logits = np.array(output_transformer.logits)[0, 1 : n_input_tokens + 1]\n\n    np.testing.assert_almost_equal(output_forward, out_logits)\n\n  def test_sampler_init_sample_state(self):\n    vocab = MockVocab()\n    transformer_config = transformer_lib.TransformerConfig(  # pytype: disable=wrong-arg-types\n        num_layers=0,\n        num_embed=vocab.GetPieceSize(),\n        embed_dim=32,\n        hidden_dim=64,\n        num_heads=4,\n        num_kv_heads=1,\n        head_dim=64,\n        final_logit_softcap=None,\n        attention_types=[],\n        use_post_attn_norm=None,\n        use_post_ffw_norm=None,\n    )\n    transformer = transformer_lib.Transformer(\n        transformer_config, rngs=nnx.Rngs(params=0)\n    )\n    sampler = sampler_lib.Sampler(\n        transformer=transformer,\n        vocab=vocab,\n        cache_size=8,\n    )\n\n    input_strings = ['<pad> hello world', 'input string <pad>']\n    all_input_ids = [sampler.tokenize(x) for x in input_strings]\n    total_sampling_steps = 5\n    sample_state = sampler.init_sample_state(\n        all_input_ids,\n        total_sampling_steps=total_sampling_steps,\n        include_logits=True,\n        forbidden_token_ids=None,\n        temperature=0.0,\n        top_p=0.95,\n        seed=jax.random.PRNGKey(0),\n    )\n\n    # Check that the position indices correctly ignore padding\n    self.assertListEqual(list(sample_state.positions[0]), [0, 0, 1, 2, 3, 4])\n    self.assertListEqual(list(sample_state.positions[1]), [0, 1, 2, 2, 3, 4])\n\n  def test_sampler_mask_tokens_after_eos_ids(self):\n    vocab = MockVocab()\n    transformer_config = transformer_lib.TransformerConfig(  # pytype: disable=wrong-arg-types\n        num_layers=0,\n        num_embed=vocab.GetPieceSize(),\n        embed_dim=32,\n        hidden_dim=64,\n        num_heads=4,\n        num_kv_heads=1,\n        head_dim=64,\n        final_logit_softcap=None,\n        attention_types=[],\n        use_post_attn_norm=None,\n        use_post_ffw_norm=None,\n    )\n    transformer = transformer_lib.Transformer(\n        transformer_config, rngs=nnx.Rngs(params=0)\n    )\n    sampler = sampler_lib.Sampler(\n        transformer=transformer,\n        vocab=vocab,\n        cache_size=8,\n    )\n\n    input_strings = ['hello world </s> hello world', 'input string </s> hello']\n    all_input_ids = [sampler.tokenize(x) for x in input_strings]\n    total_sampling_steps = 5\n    sample_state = sampler.init_sample_state(\n        all_input_ids,\n        total_sampling_steps=total_sampling_steps,\n        include_logits=True,\n        forbidden_token_ids=None,\n        temperature=0.0,\n        top_p=0.95,\n        seed=jax.random.PRNGKey(0),\n    )\n\n    masked_token_buffer = sampler.mask_tokens_after_eos_ids(\n        sample_state.token_buffer\n    )\n\n    self.assertListEqual(list(masked_token_buffer[0]), [1, 5, 6, 2, 0, 0])\n    self.assertListEqual(list(masked_token_buffer[1]), [1, 3, 4, 2, 0, 0])\n\n  def test_sampler_sows_intermediates(self):\n    vocab = MockVocab()\n    num_layers = 3\n    config = transformer_lib.TransformerConfig(  # pytype: disable=wrong-arg-types\n        num_layers=num_layers,\n        num_embed=vocab.GetPieceSize(),\n        embed_dim=64,\n        hidden_dim=128,\n        num_heads=2,\n        num_kv_heads=1,\n        head_dim=64,\n        final_logit_softcap=None,\n        attention_types=[modules.AttentionType.GLOBAL] * num_layers,\n        use_post_attn_norm=None,\n        attn_logits_soft_cap=None,\n        use_post_ffw_norm=None,\n    )\n    sow_config = sow_lib.SowConfig(\n        embeddings=True,\n        rs_after_attention=False,  # This should results in a None value.\n        rs_after_ffw=True,\n        attn_logits_topk=5,\n        mlp_hidden_topk=11,\n    )\n    transformer = transformer_lib.Transformer(\n        config, rngs=nnx.Rngs(params=0), sow_config=sow_config\n    )\n    sampler = sampler_lib.Sampler(\n        transformer=transformer,\n        vocab=vocab,\n    )\n    raw_input = ['input string', 'hello world']\n\n    result = sampler(raw_input, total_generation_steps=10)\n    input_length = max([len(vocab.EncodeAsIds(i)) for i in raw_input])\n    input_length += 1  # +1 for BOS token\n    output_length = max(len(tokens) for tokens in result.tokens)\n    length = input_length + output_length\n    self.assertIsNotNone(result)\n    intermediates = result.intermediates\n    self.assertIsNotNone(intermediates)\n    self.assertReasonableTensor(\n        intermediates.embeddings,\n        expected_shape=(2, length, config.embed_dim),\n    )\n    # Verify that the intermediates are different for two different steps.\n    self.assertNotAlmostEqual(\n        jnp.sum(intermediates.embeddings[:, 1, ...]),\n        jnp.sum(intermediates.embeddings[:, 2, ...]),\n    )\n    # Verify that the intermediates are filled in for each layer.\n    self.assertLen(intermediates.layers, config.num_layers)\n    for layer in intermediates.layers:\n      # For the requested intermediates we check the shape and that values are\n      # not all zeros, which was the initial value.\n      self.assertReasonableTensor(\n          layer.rs_after_ffw,\n          expected_shape=(2, length, config.embed_dim),\n      )\n      self.assertReasonableTensor(\n          layer.attn_logits_topk_values,\n          expected_shape=(\n              2,\n              length,\n              config.num_heads,\n              sow_config.attn_logits_topk,\n          ),\n      )\n      self.assertReasonableTensor(\n          layer.attn_logits_topk_indices,\n          expected_shape=(\n              2,\n              length,\n              config.num_heads,\n              sow_config.attn_logits_topk,\n          ),\n      )\n      self.assertReasonableTensor(\n          layer.mlp_hidden_topk_values,\n          expected_shape=(2, length, sow_config.mlp_hidden_topk),\n      )\n      self.assertReasonableTensor(\n          layer.mlp_hidden_topk_indices,\n          expected_shape=(2, length, sow_config.mlp_hidden_topk),\n      )\n      # For the none requested intermediates we want to have None values.\n      self.assertIsNone(layer.rs_after_attention)\n\n  def test_compute_attention_mask(self):\n    # Check that the input mask is correctly applied when total sampling steps\n    # is lower than the max cache length.\n    input_mask = jnp.array([[1, 1, 0, 0, 0], [1, 1, 0, 1, 0]], dtype=jnp.bool_)\n    seq_len = 8\n    time_step = jnp.asarray(4, dtype=jnp.int32)\n    attn_mask = sampler_lib._compute_attention_masks(\n        time_step, seq_len, input_mask\n    )\n    expected_attn_mask = jnp.array(\n        [[0, 0, 1, 1, 1, 0, 0, 0], [0, 0, 1, 0, 1, 0, 0, 0]], dtype=jnp.bool_\n    )\n\n    self.assertTrue((attn_mask.squeeze(1) == expected_attn_mask).all())\n\n    # Check that the input mask is correctly applied when total sampling steps\n    # is *longer* than the max cache length.\n    seq_len = 4\n    time_step = jnp.asarray(4, dtype=jnp.int32)\n    attn_mask = sampler_lib._compute_attention_masks(\n        time_step, seq_len, input_mask\n    )\n    expected_attn_mask = jnp.array(\n        [[0, 1, 1, 1], [0, 1, 0, 1]], dtype=jnp.bool_\n    )\n\n    self.assertTrue((attn_mask.squeeze(1) == expected_attn_mask).all())\n\n  @parameterized.parameters(\n    {\"url\": \"google/gemma/flax/2b\"},\n    {\"url\": \"google/gemma-2/flax/gemma2-2b\"},\n    {\"url\": \"google/gemma-3/flax/gemma3-1b\"},\n  )\n  def test_models_from_kaggle(self, url):\n    # A smoke test based on guide/gemma.md to ensure models are working correctly\n    # Check Kaggle creds as env var otherwise skip the test\n    has_kaggle_creds = all(k in os.environ for k in [\"KAGGLE_USERNAME\", \"KAGGLE_KEY\"])\n    try:\n      import kagglehub\n\n      has_kagglehub_dep = True\n    except ModuleNotFoundError:\n      has_kagglehub_dep = False\n\n    if not (has_kaggle_creds and has_kagglehub_dep):\n      self.skipTest('Skip the test as no Kaggle deps/creds')\n\n    variant = url.split(\"/\")[-1]\n    weights_dir = kagglehub.model_download(url)\n    ckpt_path = f\"{weights_dir}/{variant}\"\n    vocab_path = f\"{weights_dir}/tokenizer.model\"\n\n    vocab = spm.SentencePieceProcessor()\n    vocab.Load(vocab_path)\n\n    params = params_lib.load_and_format_params(ckpt_path)\n    transformer = transformer_lib.Transformer.from_params(params)\n    sampler = sampler_lib.Sampler(\n      transformer=transformer,\n      vocab=vocab,\n    )\n\n    input_batch = [\n        \"# Python function to compute a square of the input number\",\n    ]\n    out_data = sampler(\n      input_strings=input_batch,\n      total_generation_steps=50,\n    )\n    assert \"def square(\" in out_data.text[0], out_data.text[0]\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/gemma/sow_lib.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Utilities for sowing intermediate activations.\"\"\"\n\nimport dataclasses\nfrom flax import nnx\nimport jax\nimport jax.numpy as jnp\n\n\n@jax.tree_util.register_dataclass\n@dataclasses.dataclass\nclass LayerIntermediates:\n  \"\"\"Intermediate activations for a single layer.\"\"\"\n\n  # Dense residual stream activations.\n  rs_after_attention: jax.Array | None = None\n  rs_after_ffw: jax.Array | None = None\n\n  # Sparse representations for large activations.\n  mlp_hidden_topk_values: jax.Array | None = None\n  mlp_hidden_topk_indices: jax.Array | None = None\n  attn_logits_topk_values: jax.Array | None = None\n  attn_logits_topk_indices: jax.Array | None = None\n\n  def merge(self, decoding_step, layer: nnx.Module):\n    \"\"\"Merges the intermediate activations from one step.\"\"\"\n\n    for field in dataclasses.fields(self.__class__):\n      value = getattr(self, field.name)\n      if value is None:\n        continue\n      # We put mlp and attn intermediates into this class without any further\n      # nesting. So we have to retrieve the intermediates from the correct\n      # sub-module.\n      try:\n        if field.name.startswith('attn_'):\n          step_value = getattr(\n              layer.attn, field.name.replace('attn_', '')\n          )[0]\n        elif field.name.startswith('mlp_'):\n          step_value = getattr(layer.mlp, field.name.replace('mlp_', ''))[0]\n        else:\n          step_value = getattr(layer, field.name)[0]\n      except AttributeError as exc:\n        raise ValueError(\n            f'Intermediate {field.name} is not in the step intermediates.'\n        ) from exc\n      # This logic is the same for all intermediates. The second dimenions is\n      # the length dimension, where we want to merge the intermediates from\n      # multiple steps.\n      setattr(\n          self,\n          field.name,\n          value.at[:, decoding_step + 1].set(step_value[:, 0, ...]),\n      )\n\n  def trim(self, max_length: int):\n    \"\"\"Trims the intermediate activations to the given length.\"\"\"\n    for field in dataclasses.fields(self.__class__):\n      value = getattr(self, field.name)\n      if value is not None:\n        setattr(self, field.name, value[:, :max_length, ...])\n\n\n@jax.tree_util.register_dataclass\n@dataclasses.dataclass\nclass TransformerIntermediates:\n  \"\"\"Intermediate activations of a transformer network.\"\"\"\n\n  # Embeddings of the input tokens.\n  embeddings: jax.Array | None = None\n\n  # Intermediate activations of each layer.\n  layers: list[LayerIntermediates] = dataclasses.field(default_factory=list)\n\n  def merge(self, decoding_step, transformer: nnx.Module):\n    \"\"\"Merges the intermediate activations from one step.\"\"\"\n    if self.embeddings is not None:\n      try:\n        self.embeddings = self.embeddings.at[:, decoding_step + 1, ...].set(\n            transformer.embeddings[0][:, 0, ...]\n        )\n      except AttributeError as exc:\n        raise ValueError(\n            'Embeddings are not in the step intermediates.'\n        ) from exc\n    if len(self.layers) != len(transformer.layers):\n      raise ValueError(\n          'Number of layers in the transformer and intermediates do not match.'\n      )\n    for layer_intermediates, layer_module in zip(\n        self.layers, transformer.layers\n    ):\n      layer_intermediates.merge(decoding_step, layer_module)\n\n  def trim(self, max_length: int):\n    \"\"\"Trims the intermediate activations to the given length.\"\"\"\n    if self.embeddings is not None:\n      self.embeddings = self.embeddings[:, :max_length, ...]\n    for layer in self.layers:\n      layer.trim(max_length)\n\n\n@dataclasses.dataclass(frozen=True)\nclass SowConfig:\n  \"\"\"Module for sowing intermediate activations.\"\"\"\n\n  # Whether to sow embeddings.\n  embeddings: bool = False\n\n  # Whether to sow activations after each attention block (in residual stream).\n  rs_after_attention: bool = False\n\n  # Whether to sow activations after each FFW block (in residual stream).\n  # This is the same as the residual stream activations after a whole layer.\n  rs_after_ffw: bool = False\n\n  # If non-zero, top-k activations in a ffw hidden layer are sowed.\n  # We use a sparse representation here to save memory.\n  mlp_hidden_topk: int = 0\n\n  # If non-zero, top-k attention logits are sowed.\n  # We use a sparse representation here to save memory.\n  attn_logits_topk: int = 0\n\n  def maybe_sow_embeddings(\n      self,\n      embeddings: jax.Array,\n      module: nnx.Module,\n  ):\n    \"\"\"Sows embeddings if configured.\"\"\"\n    if self.embeddings:\n      module.sow(nnx.Intermediate, 'embeddings', embeddings)\n\n  def maybe_sow_rs_after_attention(\n      self,\n      activations: jax.Array,\n      module: nnx.Module,\n  ):\n    \"\"\"Sows activations after attention if configured.\"\"\"\n    if self.rs_after_attention:\n      module.sow(nnx.Intermediate, 'rs_after_attention', activations)\n\n  def maybe_sow_rs_after_ffw(\n      self,\n      activations: jax.Array,\n      module: nnx.Module,\n  ):\n    \"\"\"Sows activations after FFW if configured.\"\"\"\n    if self.rs_after_ffw:\n      module.sow(nnx.Intermediate, 'rs_after_ffw', activations)\n\n  def maybe_sow_mlp_hidden_topk(\n      self,\n      activations: jax.Array,\n      module: nnx.Module,\n  ):\n    \"\"\"Sows top-absolute-k activations in a mlp hidden layer if configured.\"\"\"\n    if self.mlp_hidden_topk:\n      _, indices = jax.lax.top_k(jnp.abs(activations), self.mlp_hidden_topk)\n      values = jnp.take_along_axis(activations, indices, axis=-1)\n      module.sow(nnx.Intermediate, 'hidden_topk_values', values)\n      module.sow(nnx.Intermediate, 'hidden_topk_indices', indices)\n\n  def maybe_sow_attn_logits_topk(\n      self,\n      logits: jax.Array,\n      module: nnx.Module,\n  ):\n    \"\"\"Sows top-k attention logits if configured.\"\"\"\n    if self.attn_logits_topk:\n      values, indices = jax.lax.top_k(logits, self.attn_logits_topk)\n      module.sow(nnx.Intermediate, 'logits_topk_values', values)\n      module.sow(nnx.Intermediate, 'logits_topk_indices', indices)\n"
  },
  {
    "path": "examples/gemma/tokenizer.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Provides op for tokenizing a dataset.\"\"\"\n\nfrom collections.abc import Iterable\nimport dataclasses\nimport os\nimport sys\nimport tempfile\nimport time\nfrom typing import Any\n\nfrom absl import logging\nimport jax\nimport tensorflow as tf\n\nfrom sentencepiece import SentencePieceProcessor  # pylint: disable=g-importing-member\nfrom sentencepiece import SentencePieceTrainer  # pylint: disable=g-importing-member\n\nif sys.version_info < (3, 13):\n  import tensorflow_text as tftxt\n\nFeatures = dict[str, tf.Tensor]\n\n\ndef _dump_chars_to_textfile(\n    dataset: tf.data.Dataset,\n    maxchars: int = int(1e7),\n    data_keys=('inputs', 'targets'),\n) -> tuple[str, int]:\n  \"\"\"Write part of a TFDS sentence dataset to lines in a text file.\n\n  Args:\n    dataset: tf.dataset containing string-data.\n    maxchars: int: approximate number of characters to save from dataset.\n    data_keys: Tuple[str]: what keys in dataset to dump from.\n\n  Returns:\n    name of temp file with dataset bytes, exact number of characters dumped.\n  \"\"\"\n  char_count = 0\n  ds_iter = dataset.as_numpy_iterator()\n  with tempfile.NamedTemporaryFile(\n      delete=False, prefix='/tmp/ds_chars'\n  ) as outfp:\n    while char_count < maxchars:\n      example = next(ds_iter)\n      for k in data_keys:\n        line = example[k] + b'\\n'\n        char_count += len(line)\n        outfp.write(line)\n  return outfp.name, char_count\n\n\ndef _train_sentencepiece(\n    dataset: tf.data.Dataset,\n    *,\n    vocab_size: int,\n    maxchars: int = int(1e7),\n    model_path: str,\n    model_type: str = 'unigram',\n    character_coverage: float = 1.0,\n    data_keys=('inputs', 'targets'),\n    pad_id: int = 0,\n    eos_id: int = 1,\n    bos_id: int = 2,\n    unk_id: int = 3,\n):\n  \"\"\"Train SentencePiece tokenizer from subset of tf dataset.\n\n  Args:\n    dataset: tf.dataset\n    vocab_size: int: size of vocab tokens to train.\n    maxchars: int: number of characters to use for sentencepiece training.\n    model_path: str: path of model file to save vocab model to.\n    model_type: str: type of sentencepiece vocab to train.\n    character_coverage: amount of characters covered by the model, good defaults\n      are 0.9995 for languages with rich character set like Japanese or Chinese\n      and 1.0 for other languages with small character set.\n    data_keys: Tuple[str]: keys of dataset to use for training.\n    pad_id: int: pad piece id\n    eos_id: int: end of sentence piece id\n    bos_id: int: begin of sentence piece id\n    unk_id: int: unknown piece id\n\n  Returns:\n    path to the trained sentencepiece vocabulary model.\n  \"\"\"\n  if model_path.startswith('gs://'):\n    abs_model_path = model_path\n  else:\n    abs_model_path = os.path.abspath(os.path.expanduser(model_path))\n  fname, _ = _dump_chars_to_textfile(\n      dataset, maxchars=maxchars, data_keys=data_keys\n  )\n  with tempfile.NamedTemporaryFile(\n      delete=False, prefix='/tmp/sp_tmp'\n  ) as model_fp:\n    pass  # we just want a prefix'd tmp-filename\n  argstr = ' '.join([\n      f'--input={fname}',\n      f'--vocab_size={vocab_size}',\n      f'--character_coverage={character_coverage}',\n      f'--model_prefix={model_fp.name}',\n      f'--model_type={model_type}',\n      # Setup ids for PAD, EOS, BOS, UNK as 0, 1, 2, 3\n      # Default values:\n      # --unk_id (Override UNK (<unk>) id.)  type: int32 default: 0\n      # --bos_id (Override BOS (<s>) id. Set -1 to disable BOS.)  type: int32 default: 1\n      # --eos_id (Override EOS (</s>) id. Set -1 to disable EOS.)  type: int32 default: 2\n      # --pad_id (Override PAD (<pad>) id. Set -1 to disable PAD.)  type: int32 default: -1\n      # https://github.com/google/sentencepiece/blob/master/doc/options.md\n      f'--pad_id={pad_id}',\n      f'--bos_id={bos_id}',\n      f'--eos_id={eos_id}',\n      f'--unk_id={unk_id}',\n  ])\n  SentencePieceTrainer.Train(argstr)\n  if jax.process_index() == 0:\n    # Use an intermediate filename that is renamed to the target name to address\n    # create and fill delays.\n    copy_rename_path = abs_model_path + '.rntmp'\n    tf.io.gfile.copy(model_fp.name + '.model', copy_rename_path, overwrite=True)\n    tf.io.gfile.rename(copy_rename_path, abs_model_path, overwrite=True)\n    logging.info('copied %s to %s', model_fp.name + '.model', abs_model_path)\n  else:\n    while not tf.io.gfile.exists(abs_model_path):\n      time.sleep(1)\n    time.sleep(1)\n  return abs_model_path\n\n\ndef _load_sentencepiece_tokenizer(\n    model_path: str,\n    add_bos: bool = False,\n    add_eos: bool = True,\n    reverse: bool = False,\n):\n  \"\"\"Load a tf-text SentencePiece tokenizer from given model filepath.\"\"\"\n  with tf.io.gfile.GFile(model_path, 'rb') as model_fp:\n    sp_model = model_fp.read()\n  sp_tokenizer = tftxt.SentencepieceTokenizer(\n      model=sp_model, add_bos=add_bos, add_eos=add_eos, reverse=reverse\n  )\n  return sp_tokenizer\n\n\ndef load_or_train_tokenizer(\n    dataset: tf.data.Dataset,\n    *,\n    vocab_path: str,\n    vocab_size: int,\n    max_corpus_chars: int,\n    data_keys: tuple[str, str] = ('inputs', 'targets'),\n):\n  \"\"\"Loads the tokenizer at `vocab_path` or trains a one from `dataset`.\"\"\"\n  try:\n    return _load_sentencepiece_tokenizer(vocab_path)\n  except tf.errors.NotFoundError:\n    logging.info('SentencePiece vocab not found, building one from data.')\n    vocab_path = _train_sentencepiece(\n        dataset,\n        vocab_size=vocab_size,\n        maxchars=max_corpus_chars,\n        model_path=vocab_path,\n        data_keys=data_keys,\n    )\n    return _load_sentencepiece_tokenizer(vocab_path)\n\n\n@dataclasses.dataclass\nclass TokenizeOp:\n  sp_tokenizer: Any\n  data_keys: Iterable[str] = ('inputs', 'targets')\n\n  def __call__(self, features: Features) -> Features:\n    for k in self.data_keys:\n      features[k] = self.sp_tokenizer.tokenize(features[k])\n    return features\n\n\ndef load_sentencepiece_processor(vocab_path: str):\n  spp = SentencePieceProcessor()\n  spp.Load(vocab_path)\n  return spp\n"
  },
  {
    "path": "examples/gemma/train.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Language Modeling example.\n\nThis script trains a Transformer on a LM1B dataset.\n\"\"\"\n\n# pytype: disable=wrong-arg-count\n# pytype: disable=attribute-error\n\nimport dataclasses\nimport os\nfrom typing import Any\n\nfrom absl import logging\nfrom clu import metric_writers\nfrom clu import periodic_actions\nfrom flax import nnx\nimport input_pipeline\nimport sampler as sampler_lib\nimport tokenizer\nimport transformer as transformer_lib\nimport utils\nfrom flax.training import checkpoints\nfrom flax.training import common_utils\nimport jax\nfrom jax import random\nimport jax.numpy as jnp\nimport numpy as np\nimport optax\nimport tensorflow as tf\n\n\n@dataclasses.dataclass(unsafe_hash=True)\nclass MeshRules:\n  embed: str | None = None\n  mlp: str | None = None\n  kv: str | None = None\n  vocab: str | None = None\n\n  def __call__(self, *keys: str) -> tuple[str, ...]:\n    return tuple(\n        getattr(self, key) if key is not None else None for key in keys\n    )\n\n\n@dataclasses.dataclass(unsafe_hash=True)\nclass TrainConfig:\n  \"\"\"Configuration for training a gemma model.\"\"\"\n\n  # Path to load or store sentencepiece vocab file.\n  vocab_path: str | None\n  # Vocabulary size if `vocab_path` is not given.\n  vocab_size: int\n  # Maximum number of characters to use for training.\n  max_corpus_chars: int\n  # Name of TFDS translation dataset to use.\n  dataset_name: str\n  # Optional name of TFDS translation dataset to use for evaluation.\n  eval_dataset_name: str\n  # Optional name of TFDS split to use for evaluation.\n  eval_split: str\n  # Per device batch size for training.\n  per_device_batch_size: int\n  # Per device batch size for training.\n  eval_per_device_batch_size: int\n\n  # Prompt for language model sampling\n  prompts: tuple[str, ...]\n  # Temperature for top_p sampling.\n  sampling_temperature: float\n  # Top-p sampling threshold.\n  sampling_top_p: float\n\n  # Number of steps to take during training.\n  num_train_steps: int\n  # Number of steps to take during evaluation.\n  # Large enough to evaluate all samples: 306_688 / (32 * 8) = 1198\n  num_eval_steps: int\n  # Number of steps to generate predictions.\n  # -1 will use the whole eval dataset.\n  num_predict_steps: int\n  # Base learning rate.\n  learning_rate: float\n  # Linear learning rate warmup.\n  warmup_steps: int\n  # Cross entropy loss label smoothing.\n  label_smoothing: float\n  # Decay factor for AdamW style weight decay.\n  weight_decay: float\n  # Maximum length cutoff for training examples.\n  max_target_length: int\n  # Maximum length cutoff for eval examples.\n  max_eval_target_length: int\n\n  # Gemma transformer name.\n  # Possible values defined in transformer.TransformerConfig:\n  # (gemma_2b, gemma_7b, gemma2_2b, gemma2_9b, gemma2_27b, gemma3_1b, gemma3_4b,\n  # ...)\n  transformer_name: str | None\n  # or alternatively define the model using the dict of parameters\n  transformer_params: dict[Any, Any] | None\n\n  # Whether to save model checkpoints.\n  save_checkpoints: bool\n  # Whether to restore from existing model checkpoints.\n  restore_checkpoints: bool\n  # Save a checkpoint every these number of steps.\n  checkpoint_every_steps: int\n  # Frequency of eval during training, e.g. every 1_000 steps.\n  eval_every_steps: int\n  # Use bfloat16 mixed precision training instead of float32.\n  use_bfloat16: bool\n  # Integer for PRNG random seed.\n  seed: int\n\n  # Parallelism\n  mesh_axes: tuple[str, ...]\n  axis_rules: MeshRules\n  data_sharding: tuple[str, ...]\n\n  # One axis for each parallelism type may hold a placeholder (-1)\n  # value to auto-shard based on available slices and devices.\n  # By default, product of the DCN axes should equal number of slices\n  # and product of the ICI axes should equal number of devices per slice.\n  # ICI (Inter-Chip Interconnection): A high-speed connection between\n  # sets of TPU chips, which form the TPU network.\n  # DCN (Data Center Network): A connection between the TPU networks;\n  # not as fast as ICI.\n  # ICI has around 100x the bandwidth of DCN, but it is not a general\n  # purpose connection, which is why DCN is necessary for scaling to\n  # extremely large ML models.\n  dcn_data_parallelism: int = -1\n  dcn_fsdp_parallelism: int = 1\n  dcn_tensor_parallelism: int = 1\n  ici_data_parallelism: int = 1\n  ici_fsdp_parallelism: int = -1\n  ici_tensor_parallelism: int = 1\n\n  def replace(self, **kwargs):\n    return dataclasses.replace(self, **kwargs)\n\n  def __post_init__(self):\n    if isinstance(self.axis_rules, dict):\n      self.axis_rules = MeshRules(**self.axis_rules)\n\n\ndef rsqrt_schedule(\n    init_value: float,\n    shift: int = 0,\n):\n  \"\"\"Applies a reverse square-root schedule.\n\n  The reverse square root schedule is simply `lr = init_value / sqrt(step)`.\n\n  Args:\n    init_value: Base learning rate (before applying the rsqrt schedule).\n    shift: How many steps the rsqrt should be shifted. Shifting the rsqrt\n      schedule makes it less steep in the beginning (close to 0).\n\n  Returns:\n    A schedule that applies the reverse square root.\n  \"\"\"\n\n  def schedule(count):\n    return init_value * (count + shift) ** -0.5 * shift**0.5\n\n  return schedule\n\n\ndef create_learning_rate_schedule(learning_rate: float, warmup_steps: int):\n  \"\"\"Creates a rsqrt schedule with linear warmup.\"\"\"\n  return optax.join_schedules(\n      [\n          optax.linear_schedule(\n              init_value=0,\n              end_value=learning_rate,\n              transition_steps=warmup_steps,\n          ),\n          rsqrt_schedule(init_value=learning_rate, shift=warmup_steps),\n      ],\n      boundaries=[warmup_steps],\n  )\n\n\ndef compute_weighted_cross_entropy(\n    logits, targets, weights=None, label_smoothing=0.0\n):\n  \"\"\"Compute weighted cross entropy and entropy for log probs and targets.\n\n  Args:\n   logits: [batch, length, num_classes] float array.\n   targets: categorical targets [batch, length] int array.\n   weights: None or array of shape [batch, length].\n   label_smoothing: label smoothing constant, used to determine the on and off\n     values.\n\n  Returns:\n    Tuple of scalar loss and batch normalizing factor.\n  \"\"\"\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        'Incorrect shapes. Got shape %s logits and %s targets'\n        % (str(logits.shape), str(targets.shape))\n    )\n  vocab_size = logits.shape[-1]\n  confidence = 1.0 - label_smoothing\n  low_confidence = (1.0 - confidence) / (vocab_size - 1)\n  normalizing_constant = -(\n      confidence * jnp.log(confidence)\n      + (vocab_size - 1) * low_confidence * jnp.log(low_confidence + 1e-20)\n  )\n  soft_targets = common_utils.onehot(\n      targets, vocab_size, on_value=confidence, off_value=low_confidence\n  )\n\n  loss = -jnp.sum(soft_targets * nnx.log_softmax(logits), axis=-1)\n  loss = loss - normalizing_constant\n\n  normalizing_factor = np.prod(targets.shape)\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_weighted_accuracy(logits, targets, weights=None):\n  \"\"\"Compute weighted accuracy for log probs and targets.\n\n  Args:\n   logits: [batch, length, num_classes] float array.\n   targets: categorical targets [batch, length] int array.\n   weights: None or array of shape [batch, length]\n\n  Returns:\n    Tuple of scalar loss and batch normalizing factor.\n  \"\"\"\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        'Incorrect shapes. Got shape %s logits and %s targets'\n        % (str(logits.shape), str(targets.shape))\n    )\n  loss = jnp.equal(jnp.argmax(logits, axis=-1), targets)\n  normalizing_factor = np.prod(logits.shape[:-1])\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_metrics(logits, labels, weights, label_smoothing=0.0):\n  \"\"\"Compute summary metrics.\"\"\"\n  loss, weight_sum = compute_weighted_cross_entropy(\n      logits, labels, weights, label_smoothing\n  )\n  acc, _ = compute_weighted_accuracy(logits, labels, weights)\n  metrics = {\n      'loss': loss,\n      'accuracy': acc,\n      'denominator': weight_sum,\n  }\n  return metrics\n\n\n# Primary training / eval / decode step functions.\n# -----------------------------------------------------------------------------\n\n\ndef train_step(\n    state: utils.TrainState,\n    batch,\n    learning_rate_fn,\n    label_smoothing=0.0,\n):\n  \"\"\"Perform a single training step.\"\"\"\n  # X_position and X_segmentation are needed only when using \"packed examples\"\n  # where multiple sequences are packed into the same example with this\n  # metadata.\n  # if such features are not present they are ignored and the example is treated\n  # like a normal, unpacked sequence example.\n  train_keys = ['inputs', 'inputs_position', 'inputs_segmentation', 'targets']\n  (inputs, inputs_positions, inputs_segmentation, targets) = (\n      batch.get(k, None) for k in train_keys\n  )\n\n  # TODO: this should be defined globally\n  pad_id = 0\n  weights = jnp.where(inputs > pad_id, 1, 0).astype(jnp.float32)\n  input_mask = inputs > pad_id\n  attention_mask = transformer_lib.make_causal_attn_mask(\n      input_mask\n  )  # (B, L, L)\n  # inputs_segmentation: (B, L)\n  mask = (\n      inputs_segmentation[:, :, None] == inputs_segmentation[:, None, :]\n  )  # (B, L, L)\n  attention_mask = jnp.logical_and(mask, attention_mask)\n\n  def loss_fn(params):\n    \"\"\"loss function used for training.\"\"\"\n    module = nnx.merge(state.graphdef, params)\n\n    logits, _ = module(\n        inputs,\n        positions=inputs_positions,\n        attention_mask=attention_mask,\n        cache=None,\n    )\n\n    loss, weight_sum = compute_weighted_cross_entropy(\n        logits, targets, weights, label_smoothing\n    )\n    mean_loss = loss / weight_sum\n    return mean_loss, logits\n\n  step = state.step\n  lr = learning_rate_fn(step)\n  grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n  (_, logits), grads = grad_fn(state.params)\n  new_state = state.apply_gradients(grads=grads)\n  metrics = compute_metrics(logits, targets, weights)\n  metrics['learning_rate'] = lr\n\n  return new_state, metrics\n\n\ndef eval_step(\n    params: nnx.State,\n    batch,\n    graphdef: nnx.GraphDef[transformer_lib.Transformer],\n    label_smoothing=0.0,\n):\n  \"\"\"Calculate evaluation metrics on a batch.\"\"\"\n  inputs, targets = batch['inputs'], batch['targets']\n\n  # TODO: this should be defined globally\n  pad_id = 0\n  weights = jnp.where(inputs > pad_id, 1, 0).astype(jnp.float32)\n  input_mask = inputs > pad_id\n  inputs_positions = transformer_lib.build_positions_from_mask(input_mask)\n  attention_mask = transformer_lib.make_causal_attn_mask(input_mask)\n\n  module = nnx.merge(graphdef, params)\n  logits, _ = module(\n      inputs,\n      positions=inputs_positions,\n      attention_mask=attention_mask,\n      cache=None,\n  )\n\n  return compute_metrics(logits, targets, weights, label_smoothing)\n\n\ndef evaluate(\n    *,\n    jit_eval_step,\n    state: utils.TrainState,\n    eval_ds: tf.data.Dataset,\n    num_eval_steps: int,\n):\n  \"\"\"Evaluate the target an return a dictionary with the metrics.\"\"\"\n  logging.info('Gathering evaluation metrics.')\n  eval_metrics = []\n  eval_iter = iter(eval_ds)  # pytype: disable=wrong-arg-types\n  for _, eval_batch in zip(range(num_eval_steps), eval_iter):\n    eval_batch = jax.tree.map(lambda x: x._numpy(), eval_batch)  # pylint: disable=protected-access\n    metrics = jit_eval_step(state.params, eval_batch, state.graphdef)\n    eval_metrics.append(metrics)\n  eval_metrics = common_utils.stack_forest(eval_metrics)\n  eval_metrics_sums = jax.tree.map(jnp.sum, eval_metrics)\n  eval_denominator = eval_metrics_sums.pop('denominator')\n  eval_summary = jax.tree.map(\n      lambda x: x / eval_denominator,  # pylint: disable=cell-var-from-loop\n      eval_metrics_sums,\n  )\n  return eval_summary\n\n\ndef train_and_evaluate(config: TrainConfig, workdir: str):\n  \"\"\"Runs a training and evaluation loop.\n\n  Args:\n    config: Configuration to use.\n    workdir: Working directory for checkpoints and TF summaries. If this\n      contains checkpoint training will be resumed from the latest checkpoint.\n  \"\"\"\n  workdir = os.path.abspath(workdir)\n  tf.io.gfile.makedirs(workdir)\n\n  vocab_path = config.vocab_path\n  if vocab_path is None:\n    vocab_path = os.path.join(workdir, 'sentencepiece_model')\n    config.vocab_path = vocab_path\n  tf.io.gfile.makedirs(os.path.split(vocab_path)[0])\n\n  # Load Dataset\n  # ---------------------------------------------------------------------------\n  logging.info('Initializing dataset.')\n  train_ds, eval_ds, encoder = input_pipeline.get_datasets(\n      n_devices=jax.local_device_count(), config=config, vocab_path=vocab_path\n  )\n\n  train_iter = iter(train_ds)\n  vocab_size = int(encoder.vocab_size())\n\n  logging.info('Initializing model, optimizer, and step functions.')\n  # Build Model and Optimizer\n  # ---------------------------------------------------------------------------\n  if config.transformer_name is not None:\n    model_config = transformer_lib.TransformerConfig.from_version_name(\n        config.transformer_name,\n        num_embed=vocab_size,\n        dtype=jnp.bfloat16 if config.use_bfloat16 else jnp.float32,\n        axis_rules=config.axis_rules,\n    )\n  else:\n    assert config.transformer_params is not None\n    model_config = transformer_lib.TransformerConfig.from_dict(\n        **config.transformer_params,\n        num_embed=vocab_size,\n        dtype=jnp.bfloat16 if config.use_bfloat16 else jnp.float32,\n        axis_rules=config.axis_rules,\n    )\n\n  # Mesh definition\n  devices_array = utils.create_device_mesh(config)\n  mesh = jax.sharding.Mesh(devices_array, config.mesh_axes)\n\n  start_step = 0\n  rng = jax.random.PRNGKey(config.seed)\n  rng, init_rng = jax.random.split(rng)\n  _, inference_rng = random.split(rng)\n\n  def constructor(config: transformer_lib.TransformerConfig, key: jax.Array):\n    return transformer_lib.Transformer(config, rngs=nnx.Rngs(params=key))\n\n  learning_rate_fn = create_learning_rate_schedule(\n      learning_rate=config.learning_rate, warmup_steps=config.warmup_steps\n  )\n\n  optimizer = optax.adamw(\n      learning_rate_fn,\n      b1=0.9,\n      b2=0.98,\n      eps=1e-9,\n      weight_decay=config.weight_decay,\n  )\n\n  state, state_sharding = utils.setup_initial_state(\n      constructor, optimizer, model_config, init_rng, mesh\n  )\n  data_sharding = jax.NamedSharding(mesh, jax.P(config.data_sharding))\n\n  if config.restore_checkpoints:\n    # Restore unreplicated optimizer + model state from last checkpoint.\n    state = checkpoints.restore_checkpoint(workdir, state)\n    # Grab last step.\n    start_step = int(state.step)\n\n  writer = metric_writers.create_default_writer(\n      workdir, just_logging=jax.process_index() > 0\n  )\n  if start_step == 0:\n    writer.write_hparams(dataclasses.asdict(config))\n\n  # compile multidevice versions of train/eval/predict step fn.\n  jit_train_step = jax.jit(\n      train_step,\n      in_shardings=(\n          state_sharding,\n          data_sharding,\n      ),  # type: ignore\n      out_shardings=(state_sharding, None),  # type: ignore\n      static_argnames=('learning_rate_fn', 'label_smoothing'),\n      donate_argnums=0,\n  )\n\n  jit_eval_step = jax.jit(\n      eval_step,\n      in_shardings=(\n          state_sharding.params,\n          data_sharding,\n      ),  # type: ignore\n      out_shardings=None,  # type: ignore\n      static_argnames=('graphdef', 'label_smoothing'),\n  )\n\n  vocab = tokenizer.load_sentencepiece_processor(vocab_path)\n  sampler = sampler_lib.Sampler(\n      transformer=nnx.merge(state.graphdef, state.params),\n      vocab=vocab,\n      cache_size=1024,\n  )\n\n  # Main Train Loop\n  # ---------------------------------------------------------------------------\n\n  # We init the first set of dropout PRNG keys, but update it afterwards inside\n  # the main pmap'd training update for performance.\n  logging.info('Starting training loop.')\n  hooks = []\n  report_progress = periodic_actions.ReportProgress(\n      num_train_steps=config.num_train_steps, writer=writer\n  )\n  if jax.process_index() == 0:\n    hooks += [\n        report_progress,\n        periodic_actions.Profile(logdir=workdir, num_profile_steps=5),\n    ]\n  train_metrics = []\n  with metric_writers.ensure_flushes(writer):\n    for step in range(start_step, config.num_train_steps):\n      is_last_step = step == config.num_train_steps - 1\n\n      # Shard data to devices and do a training step.\n      with jax.profiler.StepTraceAnnotation('train', step_num=step):\n        with report_progress.timed('data'):\n          batch = next(train_iter)\n          batch = jax.tree.map(\n              lambda x: jnp.asarray(x, device=data_sharding), batch\n          )\n\n        with report_progress.timed('train_step'):\n          state, metrics = jit_train_step(state, batch, learning_rate_fn, 0.0)\n        train_metrics.append(metrics)\n\n      # Quick indication that training is happening.\n      logging.log_first_n(logging.INFO, 'Finished training step %d.', 5, step)\n      for h in hooks:\n        h(step)\n\n      # Write batch loss and lr every step to TB\n      # without overwhelming the stdout:\n      if jax.process_index() == 0:\n        tb_writer = writer._writers[-1]  # pylint: disable=protected-access\n        lr = train_metrics[-1]['learning_rate']\n        train_batch_loss = train_metrics[-1]['loss']\n        denominator = train_metrics[-1]['denominator']\n        tb_writer.write_scalars(\n            step,\n            {\n                'train_learning_rate': lr,\n                'train_loss': train_batch_loss / denominator,\n            },\n        )\n\n      # Periodic metric handling.\n      if (step > 0 and step % config.eval_every_steps == 0) or is_last_step:\n        with report_progress.timed('training_metrics'):\n          logging.info('Gathering training metrics.')\n          train_metrics = common_utils.stack_forest(train_metrics)\n          # Remove learning_rate from the summary\n          _ = train_metrics.pop('learning_rate')\n          metrics_sums = jax.tree.map(jnp.sum, train_metrics)\n          denominator = metrics_sums.pop('denominator')\n          summary = jax.tree.map(lambda x: x / denominator, metrics_sums)  # pylint: disable=cell-var-from-loop\n          summary['perplexity'] = jnp.clip(jnp.exp(summary['loss']), max=1.0e4)\n          summary = {'train_' + k: v for k, v in summary.items()}\n          writer.write_scalars(step, summary)\n          train_metrics = []\n\n        with report_progress.timed('generate_text'):\n          # update sampler's transformer state:\n          sampler.transformer_state = state.params\n          exemplars = sampler(\n              config.prompts,\n              total_generation_steps=config.num_predict_steps,\n              temperature=config.sampling_temperature,\n              top_p=config.sampling_top_p,\n              seed=inference_rng,\n              echo=True,\n          )\n          writer.write_texts(step, {'samples': exemplars.text[0]})\n\n        with report_progress.timed('eval'):\n          eval_results = evaluate(\n              jit_eval_step=jit_eval_step,\n              state=state,\n              eval_ds=eval_ds,\n              num_eval_steps=config.num_eval_steps,\n          )\n          # (clipped) perplexity after averaging log-perplexity\n          eval_results['perplexity'] = jnp.clip(\n              jnp.exp(eval_results['loss']), max=1.0e4\n          )\n          writer.write_scalars(\n              step, {'eval_' + k: v for k, v in eval_results.items()}\n          )\n\n      # Save a checkpoint on one host after every checkpoint_freq steps.\n      save_checkpoint = (\n          step % config.checkpoint_every_steps == 0 or is_last_step\n      )\n      if config.save_checkpoints and save_checkpoint:\n        logging.info('Saving checkpoint step %d.', step)\n        with report_progress.timed('checkpoint'):\n          checkpoints.save_checkpoint_multiprocess(workdir, state, step)\n"
  },
  {
    "path": "examples/gemma/transformer.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Gemma transformer.\"\"\"\n\nfrom __future__ import annotations\n\nfrom collections.abc import Iterable\nimport dataclasses\nimport enum\nimport functools\nfrom typing import Any\n\nfrom flax import nnx\nimport helpers\nimport layers\nimport modules\nimport params as params_lib\nimport sow_lib\nimport jax.numpy as jnp\nfrom jaxtyping import Array  # pylint: disable=g-importing-member,g-multiple-import\n\nCache = dict[str, modules.LayerCache]\n\n\ndef make_attention_layers_types(\n    pattern: tuple[modules.AttentionType, ...],\n    num_layers: int,\n) -> tuple[modules.AttentionType, ...]:\n  \"\"\"Returns the list of attention types for every layers.\"\"\"\n\n  pattern_size = len(pattern)\n  out = pattern * (num_layers // pattern_size)\n  if num_layers % pattern_size != 0:\n    out += pattern[: num_layers % pattern_size]\n  return tuple(out)\n\n\nclass QueryPreAttentionNormalisation(enum.Enum):\n  \"\"\"Initialization strategy.\"\"\"\n\n  # Whether to scale the query by 1/sqrt(head_dim)\n  BY_ONE_OVER_SQRT_HEAD_DIM = enum.auto()\n\n  # Whether to scale the query by `embed_dim // num_heads`\n  BY_EMBED_DIM_DIV_NUM_HEADS = enum.auto()\n\n  # Whether to scale the query by `1/sqrt(embed_dim // num_heads)`\n  BY_ONE_OVER_SQRT_EMBED_DIM_DIV_NUM_HEADS = enum.auto()\n\n\n_NUM_LAYERS_GEMMA_2B = 18\n_NUM_LAYERS_GEMMA_7B = 28\n_NUM_LAYERS_GEMMA2_2B = 26\n_NUM_LAYERS_GEMMA2_9B = 42\n_NUM_LAYERS_GEMMA2_27B = 46\n_NUM_LAYERS_GEMMA3_1B = 26\n_NUM_LAYERS_GEMMA3_4B = 34\n_NUM_LAYERS_GEMMA3_12B = 48\n_NUM_LAYERS_GEMMA3_27B = 62\nGEMMA3_ATTENTION_PATTERN = (\n    modules.AttentionType.LOCAL_SLIDING,\n    modules.AttentionType.LOCAL_SLIDING,\n    modules.AttentionType.LOCAL_SLIDING,\n    modules.AttentionType.LOCAL_SLIDING,\n    modules.AttentionType.LOCAL_SLIDING,\n    modules.AttentionType.GLOBAL,\n)\n\n\n@dataclasses.dataclass(frozen=True)\nclass TransformerConfig:\n  \"\"\"Configuration for the gemma transformer.\"\"\"\n\n  num_layers: int\n  num_embed: int\n  embed_dim: int\n  hidden_dim: int\n  num_heads: int\n  head_dim: int\n  num_kv_heads: int\n  final_logit_softcap: float | None\n  use_post_attn_norm: bool\n  use_post_ffw_norm: bool\n  attention_types: Iterable[modules.AttentionType]\n  query_pre_attn_norm: QueryPreAttentionNormalisation = (\n      QueryPreAttentionNormalisation.BY_ONE_OVER_SQRT_HEAD_DIM\n  )\n  attn_logits_soft_cap: float | None = None\n  transpose_gating_einsum: bool = False\n  local_base_frequency: int = modules.DEFAULT_ROPE_BASE_FREQUENCY\n  global_base_frequency: int = modules.DEFAULT_ROPE_BASE_FREQUENCY\n  local_scale_factor: float = modules.DEFAULT_ROPE_SCALE_FACTOR\n  global_scale_factor: float = modules.DEFAULT_ROPE_SCALE_FACTOR\n  use_qk_norm: bool = False\n  sliding_window_size: int | None = None\n  dtype: Any = jnp.float32\n  axis_rules: Any | None = None\n\n  def query_pre_attn_scalar(self) -> float:\n    \"\"\"Returns the scalar to multiply the query by before attention.\"\"\"\n    match self.query_pre_attn_norm:\n      case QueryPreAttentionNormalisation.BY_EMBED_DIM_DIV_NUM_HEADS:\n        return self.embed_dim // self.num_heads\n      case QueryPreAttentionNormalisation.BY_ONE_OVER_SQRT_EMBED_DIM_DIV_NUM_HEADS:  # pylint: disable=line-too-long\n        return (self.embed_dim // self.num_heads) ** -0.5\n      case QueryPreAttentionNormalisation.BY_ONE_OVER_SQRT_HEAD_DIM | _:\n        return self.head_dim**-0.5\n\n  @classmethod\n  def from_path(cls, path: str) -> TransformerConfig:\n    \"\"\"Creates a TransformerConfig from loaded parameters.\"\"\"\n    params = params_lib.load_params(path)\n\n    return cls.from_params(params)\n\n  @classmethod\n  def from_params(cls, params: params_lib.Params) -> TransformerConfig:\n    \"\"\"Creates a TransformerConfig from loaded parameters.\n\n    Args:\n      params: Model parameters\n\n    Returns:\n      TransformerConfig.\n    \"\"\"\n\n    # Post Attn Norm is only used starting from Gemma 2.\n    use_post_attn_norm = (\n        'post_attention_norm' in params['transformer']['layer_0']\n    )\n\n    # QK Norm is only used starting from Gemma 3.\n    use_qk_norm = '_query_norm' in params['transformer']['layer_0']['attn']\n\n    # Num layers will give use the model size.\n    layer_names = [\n        name for name in params['transformer'].keys() if 'layer' in name\n    ]\n    layer_names = [name.replace('layer_', '') for name in layer_names]\n    num_layers = max([int(layer) for layer in layer_names]) + 1\n\n    if not use_post_attn_norm:  # Gemma 1.\n      if num_layers == _NUM_LAYERS_GEMMA_2B:\n        return cls.gemma_2b()\n      if num_layers == _NUM_LAYERS_GEMMA_7B:\n        return cls.gemma_7b()\n      raise ValueError(\n          'Guessing Gemma 1 model, but could not determine size from params.'\n      )\n    elif not use_qk_norm:  # Gemma 2.\n      if num_layers == _NUM_LAYERS_GEMMA2_2B:\n        return cls.gemma2_2b()\n      if num_layers == _NUM_LAYERS_GEMMA2_9B:\n        return cls.gemma2_9b()\n      if num_layers == _NUM_LAYERS_GEMMA2_27B:\n        return cls.gemma2_27b()\n      raise ValueError(\n          'Guessing Gemma 2 model but could not determine size from params.'\n      )\n    else:  # Gemma 3.\n      if num_layers == _NUM_LAYERS_GEMMA3_1B:\n        return cls.gemma3_1b()\n      if num_layers == _NUM_LAYERS_GEMMA3_4B:\n        return cls.gemma3_4b()\n      if num_layers == _NUM_LAYERS_GEMMA3_12B:\n        return cls.gemma3_12b()\n      if num_layers == _NUM_LAYERS_GEMMA3_27B:\n        return cls.gemma3_27b()\n\n    raise ValueError('Could not determine Gemma variant from params.')\n\n  @classmethod\n  def from_version_name(cls, name: str, **override) -> TransformerConfig:\n    possible_names = (\n      \"gemma_2b\", \"gemma_7b\",\n      \"gemma2_2b\", \"gemma2_9b\", \"gemma2_27b\",\n      \"gemma3_1b\", \"gemma3_4b\", \"gemma3_12b\", \"gemma3_27b\",\n    )\n    if name not in possible_names:\n      raise ValueError(\n        f'Unknown version name: {name}. '\n        f'Please choose one of the following: {possible_names}'\n      )\n    if hasattr(cls, name):\n      model_config = getattr(cls, name)(**override)\n      return model_config\n    else:\n      raise RuntimeError(\n        'Something wrong in TransformerConfig code. '\n        f'No attribute {name} in TransformerConfig'\n      )\n\n  @classmethod\n  def from_dict(cls, **config: Any) -> TransformerConfig:\n    # Deserialize query_pre_attn_norm values:\n    if \"query_pre_attn_norm\" in config:\n      config[\"query_pre_attn_norm\"] = QueryPreAttentionNormalisation(config[\"query_pre_attn_norm\"])\n    else:\n      config[\"query_pre_attn_norm\"] = QueryPreAttentionNormalisation.BY_ONE_OVER_SQRT_HEAD_DIM\n    return cls(**config)\n\n  @classmethod\n  def gemma_2b(cls, **override) -> TransformerConfig:\n    num_layers = _NUM_LAYERS_GEMMA_2B\n    config = {\n      'num_layers': num_layers,\n      'num_embed': 256128,\n      'embed_dim': 2048,\n      'hidden_dim': 16384,\n      'num_heads': 8,\n      'head_dim': 256,\n      'num_kv_heads': 1,\n      'final_logit_softcap': None,\n      'attention_types': (modules.AttentionType.GLOBAL,) * num_layers,\n      'use_post_attn_norm': False,\n      'use_post_ffw_norm': False,\n    }\n    for key, value in override.items():\n      config[key] = value\n    return cls(**config)\n\n  @classmethod\n  def gemma_7b(cls, **override):\n    num_layers = _NUM_LAYERS_GEMMA_7B\n    config = {\n      \"num_layers\": num_layers,\n      \"num_embed\": 256128,\n      \"embed_dim\": 3072,\n      \"hidden_dim\": 24576,\n      \"num_heads\": 16,\n      \"head_dim\": 256,\n      \"num_kv_heads\": 16,\n      \"final_logit_softcap\": None,\n      \"attention_types\": (modules.AttentionType.GLOBAL,) * num_layers,\n      \"use_post_attn_norm\": False,\n      \"use_post_ffw_norm\": False,\n    }\n    for key, value in override.items():\n      config[key] = value\n    return cls(**config)\n\n  @classmethod\n  def gemma2_2b(cls, **override):\n    num_layers = _NUM_LAYERS_GEMMA2_2B\n    config = {\n      'num_layers': num_layers,\n      'num_embed': 256128,\n      'embed_dim': 2304,\n      'hidden_dim': 9216,\n      'num_heads': 8,\n      'head_dim': 256,\n      'num_kv_heads': 4,\n      'final_logit_softcap': 30.0,\n      'attention_types': (\n        modules.AttentionType.LOCAL_SLIDING,\n        modules.AttentionType.GLOBAL,\n      )\n      * int(num_layers / 2),\n      'use_post_attn_norm': True,\n      'use_post_ffw_norm': True,\n      'query_pre_attn_norm': QueryPreAttentionNormalisation.BY_ONE_OVER_SQRT_HEAD_DIM,\n      'attn_logits_soft_cap': 50.0,\n      'sliding_window_size': 4096,\n    }\n    for key, value in override.items():\n      config[key] = value\n    return cls(**config)\n\n  @classmethod\n  def gemma2_9b(cls, **override):\n    num_layers = _NUM_LAYERS_GEMMA2_9B\n    config = {\n      \"num_layers\": num_layers,\n      \"num_embed\": 256128,\n      \"embed_dim\": 3584,\n      \"hidden_dim\": 28672,\n      \"num_heads\": 16,\n      \"head_dim\": 256,\n      \"num_kv_heads\": 8,\n      \"final_logit_softcap\": 30.0,\n      \"attention_types\": (\n          modules.AttentionType.LOCAL_SLIDING,\n          modules.AttentionType.GLOBAL,\n      ) * int(num_layers / 2),\n      \"use_post_attn_norm\": True,\n      \"use_post_ffw_norm\": True,\n      \"attn_logits_soft_cap\": 50.0,\n      \"sliding_window_size\": 4096,\n    }\n    for key, value in override.items():\n      config[key] = value\n    return cls(**config)\n\n  @classmethod\n  def gemma2_27b(cls, **override):\n    num_layers = _NUM_LAYERS_GEMMA2_27B\n    config = {\n      \"num_layers\": num_layers,\n      \"num_embed\": 256128,\n      \"embed_dim\": 4608,\n      \"hidden_dim\": 72728,\n      \"num_heads\": 32,\n      \"head_dim\": 128,\n      \"num_kv_heads\": 16,\n      \"final_logit_softcap\": 30.0,\n      \"use_post_attn_norm\": True,\n      \"use_post_ffw_norm\": True,\n      \"attention_types\": (\n          modules.AttentionType.LOCAL_SLIDING,\n          modules.AttentionType.GLOBAL,\n      ) * int(num_layers / 2),\n      \"attn_logits_soft_cap\": 50.0,\n      \"sliding_window_size\": 4096,\n    }\n    for key, value in override.items():\n      config[key] = value\n    return cls(**config)\n\n  @classmethod\n  def gemma3_1b(cls, **override):\n    num_layers = _NUM_LAYERS_GEMMA3_1B\n    config = {\n      \"num_layers\": num_layers,\n      \"final_logit_softcap\": None,\n      \"num_embed\": 262144,\n      \"embed_dim\": 1152,\n      \"hidden_dim\": 6 * 1152,\n      \"num_heads\": 4,\n      \"head_dim\": 256,\n      \"num_kv_heads\": 1,\n      \"use_post_attn_norm\": True,\n      \"use_post_ffw_norm\": True,\n      \"use_qk_norm\": True,\n      \"attention_types\": make_attention_layers_types(\n          GEMMA3_ATTENTION_PATTERN, num_layers\n      ),\n      \"query_pre_attn_norm\": QueryPreAttentionNormalisation.BY_ONE_OVER_SQRT_HEAD_DIM,\n      \"attn_logits_soft_cap\": None,\n      \"sliding_window_size\": 512,\n      \"transpose_gating_einsum\": True,\n      \"local_base_frequency\": 10_000,\n      \"global_base_frequency\": 1_000_000,\n    }\n    for key, value in override.items():\n      config[key] = value\n    return cls(**config)\n\n  @classmethod\n  def gemma3_4b(cls, **override):\n    num_layers = _NUM_LAYERS_GEMMA3_4B\n    config = {\n      \"num_layers\": num_layers,\n      \"final_logit_softcap\": None,\n      \"num_embed\": 262_144,\n      \"embed_dim\": 2560,\n      \"hidden_dim\": 2560 * 8 // 2,\n      \"num_heads\": 8,\n      \"head_dim\": 256,\n      \"num_kv_heads\": 4,\n      \"use_post_attn_norm\": True,\n      \"use_post_ffw_norm\": True,\n      \"use_qk_norm\": True,\n      \"attention_types\": make_attention_layers_types(\n          GEMMA3_ATTENTION_PATTERN, num_layers\n      ),\n      \"query_pre_attn_norm\": QueryPreAttentionNormalisation.BY_ONE_OVER_SQRT_HEAD_DIM,\n      \"attn_logits_soft_cap\": None,\n      \"sliding_window_size\": 1024,\n      \"transpose_gating_einsum\": True,\n      \"local_base_frequency\": 10_000,\n      \"global_base_frequency\": 1_000_000,\n      \"global_scale_factor\": 8.0,\n    }\n    for key, value in override.items():\n      config[key] = value\n    return cls(**config)\n\n  @classmethod\n  def gemma3_12b(cls, **override):\n    num_layers = _NUM_LAYERS_GEMMA3_12B\n    config = {\n      \"num_layers\": num_layers,\n      \"final_logit_softcap\": None,\n      \"num_embed\": 262144,\n      \"embed_dim\": 30 * 128,\n      \"hidden_dim\": 8 * 30 * 128 // 2,\n      \"num_heads\": 16,\n      \"head_dim\": 256,\n      \"num_kv_heads\": 8,\n      \"use_post_attn_norm\": True,\n      \"use_post_ffw_norm\": True,\n      \"use_qk_norm\": True,\n      \"attention_types\": make_attention_layers_types(\n        GEMMA3_ATTENTION_PATTERN, num_layers\n      ),\n      \"query_pre_attn_norm\": QueryPreAttentionNormalisation.BY_ONE_OVER_SQRT_HEAD_DIM,\n      \"attn_logits_soft_cap\": None,\n      \"sliding_window_size\": 1024,\n      \"transpose_gating_einsum\": True,\n      \"local_base_frequency\": 10_000,\n      \"global_base_frequency\": 1_000_000,\n      \"global_scale_factor\": 8.0,\n    }\n    for key, value in override.items():\n      config[key] = value\n    return cls(**config)\n\n  @classmethod\n  def gemma3_27b(cls, **override):\n    num_layers = _NUM_LAYERS_GEMMA3_27B\n    config = {\n      \"num_layers\": num_layers,\n      \"final_logit_softcap\": None,\n      \"num_embed\": 262144,\n      \"embed_dim\": 5376,\n      \"hidden_dim\": 5376 * 8 // 2,\n      \"num_heads\": 32,\n      \"head_dim\": 128,\n      \"num_kv_heads\": 16,\n      \"use_post_attn_norm\": True,\n      \"use_post_ffw_norm\": True,\n      \"use_qk_norm\": True,\n      \"attention_types\": make_attention_layers_types(\n          GEMMA3_ATTENTION_PATTERN, num_layers\n      ),\n      \"query_pre_attn_norm\": QueryPreAttentionNormalisation.BY_ONE_OVER_SQRT_EMBED_DIM_DIV_NUM_HEADS,\n      \"attn_logits_soft_cap\": None,\n      \"sliding_window_size\": 1024,\n      \"transpose_gating_einsum\": True,\n      \"local_base_frequency\": 10_000,\n      \"global_base_frequency\": 1_000_000,\n      \"global_scale_factor\": 8.0,\n    }\n    for key, value in override.items():\n      config[key] = value\n    return cls(**config)\n\n  def __post_init__(self):\n      if self.num_heads != self.num_kv_heads:\n        if self.num_heads % self.num_kv_heads != 0:\n          raise ValueError(\n            f\"Number of query heads ({self.num_heads}) must be divisible by \"\n            f\"number of key/value heads ({self.num_kv_heads}).\"\n          )\n\n\ndef _map_linen_var_names(key: tuple[str, ...]) -> tuple[str | int, ...]:\n  \"\"\"Maps linen variable names to nnx variable names.\"\"\"\n  new_key = []\n  for k in key:\n    if k.startswith('layer_'):\n      prefix, suffix = k.split('layer_')\n      assert not prefix, prefix\n      new_key.append('layers')\n      new_key.append(int(suffix))\n    elif k == 'gating_einsum':\n      new_key.append('gate_proj')\n      new_key.append('kernel')\n    elif k == 'linear':\n      new_key.append('down_proj')\n      new_key.append('kernel')\n    else:\n      new_key.append(k)\n\n  return tuple(new_key)\n\n\ndef _assign_linen_params_to_nnx_state(\n    state: dict[tuple[str, ...], Any],\n    mapped_path: tuple[str | int, ...],\n    val: Any,\n    transpose_gating_einsum: bool,\n) -> dict[tuple[str, ...], Any]:\n  \"\"\"Splits and maybe transposes gate_proj.\"\"\"\n  if 'gate_proj' in mapped_path:\n    if transpose_gating_einsum:\n      val = jnp.swapaxes(val, 1, 2)\n    state[mapped_path].set_value(val[0])\n    state[mapped_path[:-2] + ('up_proj', 'kernel')].set_value(val[1])\n  else:\n    state[mapped_path].set_value(val)\n  return state\n\n\nclass Transformer(nnx.Module):\n  \"\"\"Gemma transformer.\"\"\"\n\n  @classmethod\n  def from_params(\n      cls,\n      params: params_lib.Params,\n      config: None | TransformerConfig = None,\n      sow_config: sow_lib.SowConfig = sow_lib.SowConfig(),\n  ) -> Transformer:\n    if config is None:\n      config = TransformerConfig.from_params(params)\n    assign_val_fn = functools.partial(\n        _assign_linen_params_to_nnx_state,\n        transpose_gating_einsum=config.transpose_gating_einsum,\n    )\n    return helpers.module_from_linen_variables(\n        module_factory=lambda: cls(\n            config, rngs=nnx.Rngs(params=0), sow_config=sow_config\n        ),\n        variables=params['transformer'],\n        map_key_fn=_map_linen_var_names,\n        assign_val_fn=assign_val_fn,\n    )\n\n  def __init__(\n      self,\n      config: TransformerConfig,\n      *,\n      rngs: nnx.Rngs,\n      sow_config: sow_lib.SowConfig = sow_lib.SowConfig(),\n  ):\n    self.embedder = modules.Embedder(\n        vocab_size=config.num_embed,\n        embed_dim=config.embed_dim,\n        embedding_init=modules.maybe_with_partitioning(\n          nnx.initializers.normal(),\n          config.axis_rules,\n          (\"vocab\", \"embed\"),\n        ),\n        dtype=config.dtype,\n        rngs=rngs,\n    )\n    self.layers = nnx.List([\n        modules.Block(\n          config=config,\n          attn_type=attn_type,\n          sow_config=sow_config,\n          rngs=rngs,\n        )\n        for _, attn_type in zip(\n            range(config.num_layers), config.attention_types\n        )\n    ])\n    self.final_norm = layers.RMSNorm(\n      config.embed_dim,\n      scale_init=modules.maybe_with_partitioning(\n        nnx.initializers.zeros_init(),\n        config.axis_rules,\n        (\"embed\", ),\n      ),\n      rngs=rngs,\n    )\n    self.final_logits_softcap = config.final_logit_softcap\n    self.sow_config = sow_config\n\n  def __call__(\n      self,\n      last_tokens: Array,  # [B, L]\n      positions: Array,  # [B, L]\n      cache: Cache | None,  # (sequence length L')\n      attention_mask: Array,  # [B, L, L']\n  ) -> tuple[Array, Cache | None]:\n    \"\"\"Transformer forward pass.\n\n    You can run this forward pass two ways: with or without an attention kv\n    cache.\n\n    Args:\n      last_tokens: input sequence of tokens.\n      positions: input absolute positions.\n      cache: Attention KV cache or None.\n      attention_mask: transformer input mask.\n\n    Returns:\n      predicted_logits, new_cache\n\n      predicted_logits: output logits predicted by the model\n      new_cache: updated cache if the input cache is not None, None elsewhere.\n    \"\"\"\n    new_cache = None if cache is None else {}\n    x = self.embedder.encode(last_tokens)\n    self.sow_config.maybe_sow_embeddings(x, self)\n    for i, layer in enumerate(self.layers):\n      layer_name = f'layer_{i}'\n      layer_cache = cache[layer_name] if cache else None\n      layer_cache, x = layer(\n          x,\n          positions,\n          layer_cache,\n          attention_mask,\n      )\n      if cache is not None:\n        new_cache[layer_name] = layer_cache  # pytype: disable=container-type-mismatch\n\n    x = self.final_norm(x)\n    logits = self.embedder.decode(x)\n\n    if self.final_logits_softcap is not None:\n      logits /= self.final_logits_softcap\n      logits = jnp.tanh(logits) * self.final_logits_softcap\n\n    return logits, new_cache  # pytype: disable=bad-return-type\n\n  @property\n  def embed_dim(self) -> int:\n    return self.embedder.embed_dim\n\n  @property\n  def num_embed(self) -> int:\n    return self.embedder.num_embed\n\n  @property\n  def num_layers(self) -> int:\n    return len(self.layers)\n\n  def init_cache(\n      self,\n      cache_size: int,\n      batch_size: int,\n      dtype: jnp.dtype = jnp.bfloat16,\n  ) -> Cache:\n    \"\"\"Initializes a new Transformer cache.\"\"\"\n    return {\n        f'layer_{i}': self.layers[i].init_cache(\n            cache_size=cache_size,\n            batch_size=batch_size,\n            dtype=dtype,\n        )\n        for i in range(self.num_layers)\n    }\n\n  def init_intermediates(\n      self,\n      batch_size: int,\n      buffer_size: int,\n      sow_config: sow_lib.SowConfig,\n      dtype: jnp.dtype = jnp.float32,\n  ) -> sow_lib.TransformerIntermediates:\n    \"\"\"Initializes the intermediate activations that will be filled.\"\"\"\n    intermediates = sow_lib.TransformerIntermediates()\n    residual_stream_dummy = jnp.zeros(\n        (batch_size, buffer_size, self.embed_dim),\n        dtype=dtype,\n    )\n    if sow_config.embeddings:\n      intermediates.embeddings = residual_stream_dummy\n    for layer in self.layers:\n      layer_intermediates = sow_lib.LayerIntermediates()\n      if sow_config.rs_after_attention:\n        layer_intermediates.rs_after_attention = residual_stream_dummy\n      if sow_config.rs_after_ffw:\n        layer_intermediates.rs_after_ffw = residual_stream_dummy\n      if sow_config.attn_logits_topk:\n        shape = (\n            batch_size,\n            buffer_size,\n            layer.attn.num_heads,\n            sow_config.attn_logits_topk,\n        )\n        layer_intermediates.attn_logits_topk_values = jnp.zeros(\n            shape,\n            dtype=dtype,\n        )\n        layer_intermediates.attn_logits_topk_indices = jnp.zeros(\n            shape,\n            dtype=jnp.int32,\n        )\n      if sow_config.mlp_hidden_topk:\n        shape = (\n            batch_size,\n            buffer_size,\n            sow_config.mlp_hidden_topk,\n        )\n        layer_intermediates.mlp_hidden_topk_values = jnp.zeros(\n            shape,\n            dtype=dtype,\n        )\n        layer_intermediates.mlp_hidden_topk_indices = jnp.zeros(\n            shape,\n            dtype=jnp.int32,\n        )\n      intermediates.layers.append(layer_intermediates)\n    return intermediates\n\n\ndef make_causal_attn_mask(\n    input_mask: Array,\n) -> Array:\n  \"\"\"Attention mask in batch mode.\n\n  Args:\n    input_mask: Input mask for the input. True for non-padded tokens only, else\n      False.\n\n  Returns:\n    Attention mask.\n  \"\"\"\n  seq_len = input_mask.shape[-1]\n  attn_mask = input_mask[..., None, :]\n  causal_mask = jnp.tril(jnp.ones((seq_len, seq_len), dtype=jnp.bool_))\n  # Prefixes can be attended by all tokens\n  attn_mask *= causal_mask[None, ...]\n  return attn_mask\n\n\ndef build_positions_from_mask(input_mask: Array) -> Array:\n  \"\"\"Computes the `positions` from the `input_mask`.\n\n  Args:\n    input_mask: The tokens `input_mask`, True for non-padded tokens only.\n\n  Returns:\n    The indices to use for RoPE and absolute position encodings for the given\n    input mask.\n  \"\"\"\n  positions = jnp.cumsum(input_mask, axis=-1)\n  # Subtract one for all positions from the first valid one as they are\n  # 0-indexed\n  return positions - (positions >= 1)\n"
  },
  {
    "path": "examples/gemma/transformer_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ============================================================================\n\"\"\"Tests for the Gemma transformer.\"\"\"\n\nfrom collections import defaultdict\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom flax import nnx\nimport modules\nimport sow_lib\nimport transformer as transformer_lib\nimport jax.numpy as jnp\nimport numpy as np\n\n\ndef create_fake_params(config: transformer_lib.TransformerConfig):\n  def nested_defaultdict():\n    return defaultdict(nested_defaultdict)\n\n  res = nested_defaultdict()\n  res['transformer'] = nested_defaultdict()\n  params = res['transformer']\n  # 1. embedding params\n  params['embedder']['input_embedding'] = jnp.ones(\n      (config.num_embed, config.embed_dim)\n  )\n  # 2. final norm params\n  params['final_norm'] = {'scale': jnp.ones((config.embed_dim,))}\n\n  # 3. attention block params\n  for layer_idx in range(config.num_layers):\n    params[f'layer_{layer_idx}']['attn']['attn_vec_einsum']['w'] = jnp.ones(\n        (config.num_heads, config.head_dim, config.embed_dim)\n    )\n    if config.num_heads == config.num_kv_heads:\n      params[f'layer_{layer_idx}']['attn']['qkv_einsum']['w'] = jnp.ones(\n          (3, config.num_heads, config.embed_dim, config.head_dim)\n      )\n    else:\n      params[f'layer_{layer_idx}']['attn']['q_einsum']['w'] = jnp.ones(\n          (config.num_heads, config.embed_dim, config.head_dim)\n      )\n      params[f'layer_{layer_idx}']['attn']['kv_einsum']['w'] = jnp.ones(\n          (2, config.num_kv_heads, config.embed_dim, config.head_dim)\n      )\n\n    # 4. feedforward block params\n    params[f'layer_{layer_idx}']['mlp']['gating_einsum'] = jnp.ones(\n        (2, config.embed_dim, config.hidden_dim)\n    )\n    params[f'layer_{layer_idx}']['mlp']['linear'] = jnp.ones(\n        (config.hidden_dim, config.embed_dim)\n    )\n\n    # 5. layer norm params\n    params[f'layer_{layer_idx}']['pre_attention_norm']['scale'] = jnp.ones((\n        config.embed_dim,\n    ))\n    params[f'layer_{layer_idx}']['pre_ffw_norm']['scale'] = jnp.ones((\n        config.embed_dim,\n    ))\n\n    if config.use_post_attn_norm:\n      params[f'layer_{layer_idx}']['post_attention_norm']['scale'] = jnp.ones((\n          config.embed_dim,\n      ))\n    if config.use_post_ffw_norm:\n      params[f'layer_{layer_idx}']['post_ffw_norm']['scale'] = jnp.ones((\n          config.embed_dim,\n      ))\n  return res\n\n\nclass TransformerTest(parameterized.TestCase):\n\n  @parameterized.parameters(\n      # Prime number to ease shape tracing\n      dict(\n          num_layers=3,\n          num_embed=17,\n          embed_dim=2,\n          num_heads=2,\n          num_kv_heads=2,\n          hidden_dim=11,\n          head_dim=8,\n          cache_size=29,\n          batch_size=7,\n          sequence_length=18,\n          expected_outputs_shape=(7, 18, 17),  # batch_size, seq_size, num_embed\n          expected_cache_shape=(7, 29, 2, 8),  # batch_size, cache_size, num_kv_heads, head_dim\n      ),\n      dict(\n          num_layers=3,\n          num_embed=4,\n          embed_dim=2,\n          num_heads=2,\n          num_kv_heads=1,\n          hidden_dim=4,\n          head_dim=4,\n          cache_size=2,\n          batch_size=1,\n          sequence_length=1,\n          expected_outputs_shape=(1, 1, 4),  # batch_size, seq_size, num_embed\n          expected_cache_shape=(1, 2, 1, 4),  # batch_size, cache_size, num_kv_heads, head_dim\n      ),\n      dict(\n          num_layers=3,\n          num_embed=7,\n          embed_dim=5,\n          num_heads=4,\n          num_kv_heads=2,\n          hidden_dim=6,\n          head_dim=8,\n          cache_size=9,\n          batch_size=1,\n          sequence_length=1,\n          expected_outputs_shape=(1, 1, 7),  # batch_size, seq_size, num_embed\n          expected_cache_shape=(1, 9, 2, 8),  # batch_size, cache_size, num_kv_heads, head_dim\n      ),\n  )\n  def test_transformer(\n      self,\n      num_layers,\n      num_embed,\n      embed_dim,\n      num_heads,\n      num_kv_heads,\n      hidden_dim,\n      head_dim,\n      cache_size,\n      batch_size,\n      sequence_length,\n      expected_outputs_shape,\n      expected_cache_shape,\n  ):\n\n    config = transformer_lib.TransformerConfig(\n        num_layers=num_layers,\n        num_embed=num_embed,\n        embed_dim=embed_dim,\n        hidden_dim=hidden_dim,\n        num_heads=num_heads,\n        head_dim=head_dim,\n        num_kv_heads=num_kv_heads,\n        final_logit_softcap=None,\n        attention_types=[modules.AttentionType.GLOBAL] * num_layers,\n        use_post_attn_norm=False,\n        use_post_ffw_norm=False,\n    )\n    attention_mask = jnp.ones((batch_size, 1, cache_size), dtype=jnp.bool)\n    transformer = transformer_lib.Transformer(\n        config=config, rngs=nnx.Rngs(params=0)\n    )\n    cache = transformer.init_cache(\n        cache_size=cache_size,\n        batch_size=batch_size,\n        dtype=jnp.float32,\n    )\n\n    outputs, cache = transformer(\n        jnp.tile(jnp.arange(sequence_length), (batch_size, 1)),\n        jnp.tile(jnp.arange(sequence_length), (batch_size, 1)),\n        cache,\n        attention_mask,\n    )\n\n    self.assertEqual(outputs.shape, expected_outputs_shape)\n    self.assertEqual(cache['layer_0']['v'].shape, expected_cache_shape)\n\n  @parameterized.parameters(\n      ('final_logit_softcap',),\n      ('attn_logits_soft_cap',),\n  )\n  def test_logit_softcap(\n      self,\n      soft_cap_arg,\n  ):\n    cache_size = 2\n    batch_size = 1\n    soft_cap_val = 0.001\n\n    attention_mask = jnp.ones((batch_size, 1, cache_size), dtype=jnp.bool)\n\n    params = dict(\n        num_layers=3,\n        num_embed=4,\n        embed_dim=2,\n        num_heads=2,\n        num_kv_heads=1,\n        hidden_dim=4,\n        head_dim=4,\n        attention_types=[modules.AttentionType.GLOBAL] * 3,\n        use_post_attn_norm=False,\n        use_post_ffw_norm=False,\n    )\n\n    no_soft_cap_args = {\n        'final_logit_softcap': None,\n        'attn_logits_soft_cap': None,\n    }\n\n    soft_cap_args = no_soft_cap_args.copy()\n    soft_cap_args[soft_cap_arg] = soft_cap_val\n\n    config_soft_cap = transformer_lib.TransformerConfig(\n        **(params | soft_cap_args)\n    )\n    config_no_soft_cap = transformer_lib.TransformerConfig(\n        **(params | no_soft_cap_args)\n    )\n\n    all_outputs = []\n    for config in [config_soft_cap, config_no_soft_cap]:\n      transformer = transformer_lib.Transformer(\n          config=config, rngs=nnx.Rngs(params=1)\n      )\n      cache = transformer.init_cache(\n          cache_size=cache_size,\n          batch_size=batch_size,\n          dtype=jnp.float32,\n      )\n\n      outputs, _ = transformer(\n          jnp.array([[1]]), jnp.array([[1]]), cache, attention_mask\n      )\n      all_outputs.append(outputs)\n\n    soft_cap_outputs, no_soft_cap_outputs = all_outputs  # pylint: disable=unbalanced-tuple-unpacking\n\n    # Ensure that values aren't equal coming out of computation\n    self.assertFalse((soft_cap_outputs == no_soft_cap_outputs).all())\n\n    # Run soft capping manually\n    manual_soft_cap_logits = no_soft_cap_outputs / soft_cap_val\n    manual_soft_cap_logits = jnp.tanh(manual_soft_cap_logits) * soft_cap_val\n\n    np.testing.assert_array_almost_equal(\n        manual_soft_cap_logits, soft_cap_outputs, 1e-5\n    )\n\n  @parameterized.parameters([\n      dict(\n          config=transformer_lib.TransformerConfig(\n              num_layers=2,\n              num_embed=0,  # unused\n              embed_dim=0,  # unused\n              hidden_dim=0,  # unused\n              num_heads=3,\n              head_dim=4,\n              num_kv_heads=3,\n              final_logit_softcap=None,\n              attention_types=[modules.AttentionType.GLOBAL] * 2,\n              use_post_attn_norm=False,\n              use_post_ffw_norm=False,\n          ),\n          cache_size=2,\n          keys=['layer_0', 'layer_1'],\n          k_shape=(1, 2, 3, 4),\n          v_shape=(1, 2, 3, 4),\n      )\n  ])\n  def test_creates_cache(self, config, cache_size, keys, k_shape, v_shape):\n    transformer = transformer_lib.Transformer(\n        config=config, rngs=nnx.Rngs(params=0)\n    )\n    cache = transformer.init_cache(\n        cache_size=cache_size,\n        batch_size=1,\n        dtype=jnp.float32,\n    )\n    self.assertEqual(list(cache.keys()), keys)\n    self.assertEqual(cache['layer_0']['k'].shape, k_shape)\n    self.assertEqual(cache['layer_0']['v'].shape, v_shape)\n\n  @parameterized.parameters([\n      dict(\n          batch_size=1,\n          seq_size=4,\n          config=transformer_lib.TransformerConfig(\n              num_layers=2,\n              num_embed=4,  # unused\n              embed_dim=2,\n              hidden_dim=12,  # unused\n              num_heads=3,\n              head_dim=4,\n              num_kv_heads=3,\n              final_logit_softcap=None,\n              attention_types=[modules.AttentionType.GLOBAL] * 2,\n              use_post_attn_norm=False,\n              use_post_ffw_norm=False,\n          ),\n      )\n  ])\n  def test_forward_no_cache(\n      self,\n      batch_size: int,\n      seq_size: int,\n      config: transformer_lib.TransformerConfig,\n  ):\n    cache_size = 6\n\n    token_input = jnp.ones((batch_size, seq_size), dtype=jnp.int32)\n    transformer = transformer_lib.Transformer(\n        config=config, rngs=nnx.Rngs(params=0)\n    )\n    empty_cache = transformer.init_cache(\n        cache_size=cache_size,\n        batch_size=batch_size,\n        dtype=jnp.float32,\n    )\n    attention_mask = jnp.ones(\n        (batch_size, seq_size, cache_size), dtype=jnp.bool\n    )\n    positions = transformer_lib.build_positions_from_mask(token_input != 0)\n\n    output_cache, _ = transformer(\n        token_input, positions, empty_cache, attention_mask\n    )\n\n    attention_mask = jnp.ones((batch_size, seq_size, seq_size), dtype=jnp.bool)\n    output_none, cache_none = transformer(\n        token_input, positions, None, attention_mask\n    )\n\n    self.assertIsNone(cache_none)\n    np.testing.assert_array_almost_equal(output_cache, output_none, 1e-5)\n\n  def test_attention_types(\n      self,\n  ):\n\n    config = transformer_lib.TransformerConfig(\n        num_layers=2,\n        num_embed=4,\n        embed_dim=2,\n        hidden_dim=12,\n        num_heads=3,\n        head_dim=4,\n        num_kv_heads=3,\n        final_logit_softcap=None,\n        attention_types=[modules.AttentionType.GLOBAL] * 2,\n        use_post_attn_norm=False,\n        use_post_ffw_norm=False,\n    )\n    transformer = transformer_lib.Transformer(\n        config=config, rngs=nnx.Rngs(params=0)\n    )\n    cache = transformer.init_cache(\n        cache_size=6,\n        batch_size=1,\n        dtype=jnp.float32,\n    )\n    self.assertTrue(cache)\n\n  @parameterized.parameters(\n      dict(\n          config=transformer_lib.TransformerConfig(\n              num_layers=2,\n              num_embed=4,\n              embed_dim=2,\n              hidden_dim=12,\n              num_heads=3,\n              head_dim=4,\n              num_kv_heads=3,\n              final_logit_softcap=None,\n              attention_types=[modules.AttentionType.GLOBAL] * 2,\n              use_post_attn_norm=False,\n              use_post_ffw_norm=False,\n          ),\n      ),\n      dict(\n          config=transformer_lib.TransformerConfig(\n              num_layers=2,\n              num_embed=4,\n              embed_dim=2,\n              hidden_dim=12,\n              num_heads=3,\n              head_dim=4,\n              num_kv_heads=3,\n              final_logit_softcap=None,\n              attention_types=[modules.AttentionType.GLOBAL] * 2,\n              use_post_attn_norm=True,\n              use_post_ffw_norm=True,\n          ),\n      ),\n      dict(\n          config=transformer_lib.TransformerConfig(\n              num_layers=2,\n              num_embed=4,\n              embed_dim=5,\n              hidden_dim=12,\n              num_heads=6,\n              head_dim=4,\n              num_kv_heads=3,\n              final_logit_softcap=None,\n              attention_types=[modules.AttentionType.GLOBAL] * 2,\n              use_post_attn_norm=True,\n              use_post_ffw_norm=True,\n          ),\n      ),\n  )\n  def test_load_from_params(self, config):\n    params = create_fake_params(config)\n    transformer = transformer_lib.Transformer.from_params(params, config)\n    logits, _ = transformer(\n        last_tokens=jnp.tile(jnp.arange(3), (2, 1)),\n        positions=jnp.tile(jnp.arange(3), (2, 1)),\n        cache=None,\n        attention_mask=jnp.ones((2, 1, 3), dtype=jnp.bool),\n    )\n    self.assertEqual(logits.shape, (2, 3, 4))\n\n  @parameterized.parameters([\n      sow_lib.SowConfig(embeddings=True),\n      sow_lib.SowConfig(rs_after_attention=True),\n      sow_lib.SowConfig(rs_after_ffw=True),\n      sow_lib.SowConfig(attn_logits_topk=5),\n      sow_lib.SowConfig(mlp_hidden_topk=11),\n  ])\n  def test_sow_intermediates(self, sow_config):\n    batch_size = 3\n    sequence_length = 7\n    num_layers = 2\n    config = transformer_lib.TransformerConfig(\n        num_layers=num_layers,\n        num_embed=4,\n        embed_dim=48,\n        hidden_dim=12,\n        num_heads=1,\n        head_dim=4,\n        num_kv_heads=1,\n        final_logit_softcap=None,\n        use_post_attn_norm=False,\n        use_post_ffw_norm=False,\n        attention_types=[modules.AttentionType.GLOBAL] * num_layers,\n    )\n    attention_mask = jnp.ones(\n        (batch_size, sequence_length, sequence_length), dtype=jnp.bool\n    )\n    transformer = transformer_lib.Transformer(\n        config=config, rngs=nnx.Rngs(params=0), sow_config=sow_config\n    )\n    transformer(\n        jnp.tile(jnp.arange(sequence_length), (batch_size, 1)),\n        jnp.tile(jnp.arange(sequence_length), (batch_size, 1)),\n        None,\n        attention_mask,\n    )\n\n    if sow_config.embeddings:\n      self.assertTrue(hasattr(transformer, 'embeddings'))\n      embeddings = transformer.embeddings[0]\n      self.assertEqual(\n          embeddings.shape,\n          (batch_size, sequence_length, config.embed_dim),\n      )\n    else:\n      self.assertFalse(hasattr(transformer, 'embeddings'))\n\n    for layer in transformer.layers:\n      if sow_config.rs_after_attention:\n        self.assertTrue(hasattr(layer, 'rs_after_attention'))\n        rs_after_attention = layer.rs_after_attention[0]\n        self.assertIsNotNone(rs_after_attention)\n        self.assertEqual(\n            rs_after_attention.shape,\n            (batch_size, sequence_length, config.embed_dim),\n        )\n      else:\n        self.assertFalse(hasattr(layer, 'rs_after_attention'))\n      if sow_config.rs_after_ffw:\n        self.assertTrue(hasattr(layer, 'rs_after_ffw'))\n        rs_after_ffw = layer.rs_after_ffw[0]\n        self.assertIsNotNone(rs_after_ffw)\n        self.assertEqual(\n            rs_after_ffw.shape,\n            (batch_size, sequence_length, config.embed_dim),\n        )\n      else:\n        self.assertFalse(hasattr(layer, 'rs_after_ffw'))\n      if sow_config.attn_logits_topk:\n        self.assertTrue(hasattr(layer.attn, 'logits_topk_values'))\n        attn_logits_topk_values = layer.attn.logits_topk_values[0]\n        self.assertIsNotNone(attn_logits_topk_values)\n        self.assertEqual(\n            attn_logits_topk_values.shape,\n            (\n                batch_size,\n                sequence_length,\n                config.num_heads,\n                sow_config.attn_logits_topk,\n            ),\n        )\n        self.assertTrue(hasattr(layer.attn, 'logits_topk_indices'))\n        attn_logits_topk_indices = layer.attn.logits_topk_indices[0]\n        self.assertIsNotNone(attn_logits_topk_indices)\n        self.assertEqual(\n            attn_logits_topk_indices.shape,\n            (\n                batch_size,\n                sequence_length,\n                config.num_heads,\n                sow_config.attn_logits_topk,\n            ),\n        )\n      else:\n        self.assertFalse(hasattr(layer.attn, 'logits_topk_values'))\n        self.assertFalse(hasattr(layer.attn, 'logits_topk_indices'))\n      if sow_config.mlp_hidden_topk:\n        self.assertTrue(hasattr(layer.mlp, 'hidden_topk_values'))\n        ffw_hidden_topk_values = layer.mlp.hidden_topk_values[0]\n        self.assertIsNotNone(ffw_hidden_topk_values)\n        self.assertEqual(\n            ffw_hidden_topk_values.shape,\n            (\n                batch_size,\n                sequence_length,\n                sow_config.mlp_hidden_topk,\n            ),\n        )\n        self.assertTrue(hasattr(layer.mlp, 'hidden_topk_indices'))\n        ffw_hidden_topk_indices = layer.mlp.hidden_topk_indices[0]\n        self.assertIsNotNone(ffw_hidden_topk_indices)\n        self.assertEqual(\n            ffw_hidden_topk_indices.shape,\n            (\n                batch_size,\n                sequence_length,\n                sow_config.mlp_hidden_topk,\n            ),\n        )\n      else:\n        self.assertFalse(hasattr(layer.mlp, 'hidden_topk_values'))\n        self.assertFalse(hasattr(layer.mlp, 'hidden_topk_indices'))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/gemma/utils.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Copied over from MaxText\n# (https://github.com/google/maxtext/blob/main/MaxText/max_utils.py).\n\"\"\"Provides utilities for training the Flax gemma example.\"\"\"\n\nfrom collections.abc import Callable\nimport logging\nfrom typing import Any\n\nfrom flax import nnx\nimport transformer\nfrom flax.training import train_state\nimport jax\nfrom jax.experimental import mesh_utils\nimport jax.numpy as jnp\nimport numpy as np\n\n\nDtype = Any\nShape = tuple[int, ...]\n\n\nclass TrainState(train_state.TrainState):\n  graphdef: nnx.GraphDef[transformer.Transformer]\n\n\n# Mesh utils.\n# -----------------------------------------------------------------------------\n\n\ndef create_device_mesh(config: Any):\n  \"\"\"Creates a device mesh with each slice in its own data parallel group.\n\n  If there is only one slice, uses two replicas.\n\n  Args:\n    config: The training configuration.\n  Returns:\n    The device mesh.\n  \"\"\"\n  devices = jax.devices()\n  num_devices = len(devices)\n  try:\n    num_slices = 1 + max([d.slice_index for d in devices])\n  except AttributeError:\n    num_slices = 1\n  num_devices_per_slice = num_devices // num_slices\n  logging.info(f'Devices: {devices}')  # pylint: disable=logging-fstring-interpolation\n  logging.info(f'Number of devices: {num_devices}')  # pylint: disable=logging-fstring-interpolation\n\n  multi_slice_env = hasattr(jax.devices()[0], 'slice_index')\n\n  dcn_parallelism = [\n      config.dcn_data_parallelism,\n      config.dcn_fsdp_parallelism,\n      config.dcn_tensor_parallelism,\n  ]\n  ici_parallelism = [\n      config.ici_data_parallelism,\n      config.ici_fsdp_parallelism,\n      config.ici_tensor_parallelism,\n  ]\n\n  # Find possible unspecified parallelisms\n  dcn_parallelism = fill_unspecified_mesh_axes(\n      dcn_parallelism, num_slices, 'DCN'\n  )\n  ici_parallelism = fill_unspecified_mesh_axes(\n      ici_parallelism, num_devices_per_slice, 'ICI'\n  )\n\n  if multi_slice_env:\n    mesh = mesh_utils.create_hybrid_device_mesh(\n        ici_parallelism, dcn_parallelism\n    )\n  else:\n    mesh = mesh_utils.create_device_mesh(ici_parallelism)\n\n  logging.info(f'Decided on mesh: {mesh}')  # pylint: disable=logging-fstring-interpolation\n  logging.info(f'Mesh shape: {mesh.shape}')  # pylint: disable=logging-fstring-interpolation\n\n  return mesh\n\n\ndef fill_unspecified_mesh_axes(\n    parallelism_vals, target_product, parallelism_type\n):\n  \"\"\"Evaluates unspecified DCN/ICI parallelism values.\"\"\"\n  if -1 in parallelism_vals:\n    assert parallelism_vals.count(-1) == 1, (\n        f'Found unspecified values (-1) for more than one {parallelism_type}   '\n        '   parallelism axis. At most one axis can be unspecified.'\n    )\n\n    determined_val = target_product / np.prod(parallelism_vals) * -1\n\n    assert determined_val >= 1 and determined_val.is_integer, (\n        'Unspecified value unable to be determined with the given     '\n        f' {parallelism_type} parallelism values'\n    )\n\n    parallelism_vals[parallelism_vals.index(-1)] = int(determined_val)\n\n  target_type = 'slices' if parallelism_type == 'DCN' else 'devices per slice'\n\n  assert np.prod(parallelism_vals) == target_product, (\n      f'Number of {target_type} {target_product} does not match    the product'\n      f' of the {parallelism_type} parallelism {np.prod(parallelism_vals)}'\n  )\n\n  return parallelism_vals\n\n\n# State initialization utils.\n# -----------------------------------------------------------------------------\n\n\ndef _to_array(x):\n  if not isinstance(x, jax.Array):\n    x = jnp.asarray(x)\n  return x\n\n\ndef setup_initial_state(\n    constructor: Callable[\n        [transformer.TransformerConfig, jax.Array], transformer.Transformer\n    ],\n    tx,\n    config: transformer.TransformerConfig,\n    rng: jax.Array,\n    mesh: jax.sharding.Mesh,\n) -> tuple[TrainState, TrainState]:\n  \"\"\"We initialize train state, optionally loading from checkpoint.\n\n  Args:\n    constructor: the model constructor\n    tx: the optax.GradientTransformation\n    config: config object\n    rng: jax.prng key\n    mesh: jax.devices() mesh\n\n  Returns:\n    state: the initialized train state\n    state_mesh_annotations: the mesh annotations for the train state\n  \"\"\"\n\n  @jax.jit\n  def sharded_init():\n    model = constructor(config, rng)\n    graphdef, params = nnx.split(model, nnx.Param)\n    state = TrainState.create(\n        apply_fn=graphdef.apply,\n        params=params,\n        tx=tx,\n        graphdef=graphdef,\n    )\n    state = jax.tree.map(_to_array, state)\n    state_spec = nnx.get_partition_spec(state)\n    state = jax.lax.with_sharding_constraint(state, state_spec)\n    return state\n\n  # Initialization\n  with jax.set_mesh(mesh):\n    state = sharded_init()\n\n  state_sharding = nnx.get_named_sharding(state, mesh)\n  return state, state_sharding\n"
  },
  {
    "path": "examples/imagenet/README.md",
    "content": "## ImageNet classification\n\nTrains a ResNet50 model ([He *et al.*, 2016]) for the ImageNet classification task\n([Russakovsky *et al.*, 2015]).\n\nThis example uses linear learning rate warmup and cosine learning rate schedule.\n\n[He *et al.*, 2016]: https://arxiv.org/abs/1512.03385\n[Russakovsky *et al.*, 2015]: https://arxiv.org/abs/1409.0575\n\nYou can run this code and even modify it directly in Google Colab, no\ninstallation required:\n\nhttps://colab.research.google.com/github/google/flax/blob/main/examples/imagenet/imagenet.ipynb\n\nThe Colab also demonstrates how to load pretrained checkpoints from Cloud\nstorage at\n[gs://flax_public/examples/imagenet/](https://console.cloud.google.com/storage/browser/flax_public/examples/imagenet)\n\nTable of contents:\n\n- [Requirements](#requirements)\n- [Example runs](#example-runs)\n- [Running locally](#running-locally)\n  - [Overriding parameters on the command line](#overriding-parameters-on-the-command-line)\n- [Running fake data benchmarks](#running-fake-data-benchmarks)\n- [Running on Cloud](#running-on-cloud)\n  - [Preparing the dataset](#preparing-the-dataset)\n  - [Google Cloud TPU](#google-cloud-tpu)\n  - [Google Cloud GPU](#google-cloud-gpu)\n\n### Requirements\n\n* TensorFlow dataset `imagenet2012:5.*.*`\n* `≈180GB` of RAM if you want to cache the dataset in memory for faster IO\n\n### Example runs\n\nWhile the example should run on a variety of hardware,\nwe have tested the following GPU and TPU configurations:\n\n|          Name           | Steps  | Walltime | Top-1 accuracy |                                                                       Metrics                                                                        |                                                                               Workdir                                                                                |\n| :---------------------- | -----: | :------- | :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| TPU v3-32                | 125100 | 2.1h     | 76.54%         | [tfhub.dev](https://tensorboard.dev/experiment/GhPHRoLzTqu7c8vynTk6bg/)                                                                              | [gs://flax_public/examples/imagenet/tpu_v3_32](https://console.cloud.google.com/storage/browser/flax_public/examples/imagenet/tpu_v3_32)                                         |\n| TPU v2-32                | 125100 | 2.5h     | 76.67%         | [tfhub.dev](https://tensorboard.dev/experiment/qBJ7T9VPSgO5yeb0HAKbIA/)                                                                              | [gs://flax_public/examples/imagenet/tpu_v2_32](https://console.cloud.google.com/storage/browser/flax_public/examples/imagenet/tpu_v2_32)                                         |\n| TPU v3-8                | 125100 | 4.4h     | 76.37%         | [tfhub.dev](https://tensorboard.dev/experiment/JwxRMYrsR4O6V6fnkn3dmg/)                                                                              | [gs://flax_public/examples/imagenet/tpu](https://console.cloud.google.com/storage/browser/flax_public/examples/imagenet/tpu)                                         |\n| v100_x8                 | 250200 | 13.2h    | 76.72%         | [tfhub.dev](https://tensorboard.dev/experiment/venzpsNXR421XLkvvzSkqQ/#scalars&_smoothingWeight=0&regexInput=%5Eimagenet/v100_x8%24)                 | [gs://flax_public/examples/imagenet/v100_x8](https://console.cloud.google.com/storage/browser/flax_public/examples/imagenet/v100_x8)                                 |\n| v100_x8_mixed_precision |  62500 | 4.3h     | 76.27%         | [tfhub.dev](https://tensorboard.dev/experiment/venzpsNXR421XLkvvzSkqQ/#scalars&_smoothingWeight=0&regexInput=%5Eimagenet/v100_x8_mixed_precision%24) | [gs://flax_public/examples/imagenet/v100_x8_mixed_precision](https://console.cloud.google.com/storage/browser/flax_public/examples/imagenet/v100_x8_mixed_precision) |\n\n\n### Running locally\n\n```shell\npython main.py --workdir=./imagenet --config=configs/default.py\n```\n\n#### Overriding parameters on the command line\n\nSpecify a hyperparameter configuration by the means of setting `--config` flag.\nConfiguration flag is defined using\n[config_flags](https://github.com/google/ml_collections/tree/master#config-flags).\n`config_flags` allows overriding configuration fields. This can be done as\nfollows:\n\n```shell\npython main.py --workdir=./imagenet_default --config=configs/default.py \\\n--config.num_epochs=100\n```\n\n### Running fake data benchmarks\n\nExecute the following code with `flax/examples/imagenet` as your current directory:\n```shell\nbash ../../tests/download_dataset_metadata.sh\npython imagenet_fake_data_benchmark.py\n```\n\nIf you get an error like this:\n```shell\nCloning into 'datasets'...\nfatal: cannot change to 'https://github.com/tensorflow/datasets/': No such file or directory\nerror: failed to initialize sparse-checkout\n```\nThis mean your git version is outdated. Just update it and re-run.\n\n### Running on Cloud\n\n#### Preparing the dataset\n\nFor running the ResNet50 model on imagenet dataset,\nyou first need to prepare the `imagenet2012` dataset.\nDownload the data from http://image-net.org/ as described in the\n[tensorflow_datasets catalog](https://www.tensorflow.org/datasets/catalog/imagenet2012).\nThen point the environment variable `$IMAGENET_DOWNLOAD_PATH`\nto the directory where the downloads are stored and prepare the dataset\nby running\n\n```shell\npython -c \"\nimport tensorflow_datasets as tfds\ntfds.builder('imagenet2012').download_and_prepare(\n    download_config=tfds.download.DownloadConfig(\n        manual_dir='$IMAGENET_DOWNLOAD_PATH'))\n\"\n```\n\nThe contents of the directory `~/tensorflow_datasets` should be copied to your\ngcs bucket. Point the environment variable `GCS_TFDS_BUCKET` to your bucket and\nrun the following command:\n\n```shell\ngcloud storage cp --recursive ~/tensorflow_datasets gs://$GCS_TFDS_BUCKET/datasets\n```\n\n#### Google Cloud TPU\n\nSee below for commands to set up a single VM with 8 TPUs attached\n(`--accelerator-type v3-8`), or for an entire TPU slice spanning multiple\nVMs (e.g. `--accelerator-type v3-32`). For more details about how to set up and\nuse TPUs, refer to Cloud docs for\n[single VM setup](https://cloud.google.com/tpu/docs/jax-quickstart-tpu-vm)\nand [pod slice setup](https://cloud.google.com/tpu/docs/jax-quickstart-tpu-vm).\n\nFirst create a single TPUv3-8 VM and connect to it:\n\n```\nZONE=us-central1-a\nTPU_TYPE=v3-8\nVM_NAME=imagenet\n\ngcloud alpha compute tpus tpu-vm create $VM_NAME \\\n    --zone $ZONE \\\n    --accelerator-type $TPU_TYPE \\\n    --version v2-alpha\n\ngcloud alpha compute tpus tpu-vm ssh $VM_NAME --zone $ZONE -- \\\n    -L 6006:localhost:6006\n```\n\nWhen connected install JAX:\n\n```\npip install \"jax[tpu]>=0.2.21\" \\\n    -f https://storage.googleapis.com/jax-releases/libtpu_releases.html\n```\n\nThen install Flax + the example dependencies:\n\n```\ngit clone --depth=1 --branch=main https://github.com/google/flax\ncd flax\npip install -e .\ncd examples/imagenet\npip install -r requirements.txt\n```\n\nAnd finally start the training:\n\n```\nexport TFDS_DATA_DIR=gs://$GCS_TFDS_BUCKET/datasets\npython3 main.py --workdir=$HOME/logs/imagenet_tpu --config=configs/tpu.py \\\n    --jax_backend_target=\"grpc://192.168.0.2:8470\"\n```\n\nNote that you might want to set `TFDS_DATA_DIR` as explained above. You probably\nalso want to start the long-running command above in a `tmux` session and start\nsome monitoring in a separate pane (note that we forwarded port 6006 locally\nabove):\n\n```\ntensorboard --logdir=$HOME/logs\n```\n\nWhen running on pod slices, after creating the TPU VM, there are different ways\nof running the training in SPMD fashion on the hosts connected to the TPUs that\nmake up the slice. We simply send the same installation/execution shell commands\nto all hosts in parallel with the command below. If anything fails it's\nusually a good idea to connect to a single host and execute the commands\ninteractively.\n\nFor convenience, the TPU creation commands are inlined below. Please note that\nwe define `GCS_TFDS_BUCKET` to where your data stands in your cloud bucket.\nAlso `YOUR_BUCKET` is the work directory you are experimenting in. You should\nchoose ZONE based on where your TPU and work directory is. [Here](https://cloud.google.com/tpu/docs/types-zones)\nhas some useful information on which zones you can have different types of TPUs.\n\n```shell\nVM_NAME=imagenet\nREPO=https://github.com/google/flax\nBRANCH=main\nWORKDIR=gs://$YOUR_BUCKET/flax/examples/imagenet/$(date +%Y%m%d_%H%M)\n\ngcloud alpha compute tpus tpu-vm create $VM_NAME \\\n    --zone=$ZONE \\\n    --version v2-alpha --accelerator-type v3-32\nFLAGS=\"--config.batch_size=$((32*256))\"\n\ngcloud alpha compute tpus tpu-vm ssh $VM_NAME --zone $ZONE \\\n--worker=all --command \"\npip install 'jax[tpu]>=0.2.21' -f https://storage.googleapis.com/jax-releases/libtpu_releases.html &&\npip install --user git+$REPO.git &&\ngit clone --depth=1 -b $BRANCH $REPO &&\ncd flax/examples/imagenet &&\npip install -r requirements.txt &&\nexport TFDS_DATA_DIR=gs://$GCS_TFDS_BUCKET/datasets &&\npython3 main.py --workdir=$WORKDIR --config=configs/tpu.py $FLAGS\n\"\n```\n\nPlease don't forget to disconnect and delete your vm after you are done:\n\n```\ngcloud alpha compute tpus tpu-vm delete $VM_NAME \\\n  --zone $ZONE\n```\n\n#### Google Cloud GPU\n\nCan be launched with utility script described in\n[../cloud/README.md](../cloud/README.md)\n\nThere are two configurations available:\n\n- `configs/v100_x8.py` : Full precision GPU training\n- `configs/v100_x8_mixed_precision.py` : Mixed precision GPU training. Note that\n  mixed precision handling is implemented manually with\n  [`training.dynamic_scale`](https://github.com/google/flax/blob/main/flax/training/dynamic_scale.py)\n"
  },
  {
    "path": "examples/imagenet/configs/default.py",
    "content": "# Copyright 2021 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Default Hyperparameter configuration.\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = ml_collections.ConfigDict()\n\n  # As defined in the `models` module.\n  config.model = 'ResNet50'\n  # `name` argument of tensorflow_datasets.builder()\n  config.dataset = 'imagenet2012:5.*.*'\n\n  config.learning_rate = 0.1\n  config.warmup_epochs = 5.0\n  config.momentum = 0.9\n  config.batch_size = 128\n  config.shuffle_buffer_size = 16 * 128\n  config.prefetch = 10\n\n  config.num_epochs = 100.0\n  config.log_every_steps = 100\n\n  config.cache = False\n  config.half_precision = False\n\n  # If num_train_steps==-1 then the number of training steps is calculated from\n  # num_epochs using the entire dataset. Similarly for steps_per_eval.\n  config.num_train_steps = -1\n  config.steps_per_eval = -1\n\n  # whether to profile the training loop\n  config.profile = True\n\n  return config\n\n\ndef metrics():\n  return [\n      'train_loss',\n      'eval_loss',\n      'train_accuracy',\n      'eval_accuracy',\n      'steps_per_second',\n      'train_learning_rate',\n  ]\n"
  },
  {
    "path": "examples/imagenet/configs/fake_data_benchmark.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Hyperparameter configuration for Fake data benchmark.\"\"\"\n\nimport jax\n\nfrom configs import default as default_lib\n\n\ndef get_config():\n  \"\"\"Get the hyperparameter configuration for Fake data benchmark.\"\"\"\n  # Override default configuration to avoid duplication of field definition.\n  config = default_lib.get_config()\n  config.batch_size = 256 * jax.device_count()\n  config.half_precision = True\n  config.num_epochs = 5\n\n  # Run for a single step:\n  config.num_train_steps = 1\n  config.steps_per_eval = 1\n\n  return config\n"
  },
  {
    "path": "examples/imagenet/configs/tpu.py",
    "content": "# Copyright 2021 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Hyperparameter configuration to run the example on TPUs.\"\"\"\n\nfrom configs import default as default_lib\n\n\ndef get_config():\n  \"\"\"Get the hyperparameter configuration to train on TPUs.\"\"\"\n  config = default_lib.get_config()\n\n  # Consider setting the batch size to max(tpu_chips * 256, 8 * 1024) if you\n  # train on a larger pod slice.\n  config.batch_size = 1024\n  config.shuffle_buffer_size = 16 * 1024\n  config.cache = True\n  config.half_precision = True\n\n  return config\n\n\nmetrics = default_lib.metrics\n"
  },
  {
    "path": "examples/imagenet/configs/v100_x8.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Hyperparameter configuration to run the example on 8 x Nvidia V100 GPUs.\"\"\"\n\nfrom configs import default as default_lib\n\n\ndef get_config():\n  \"\"\"Get the hyperparameter configuration to train on 8 x Nvidia V100 GPUs.\"\"\"\n  # Override default configuration to avoid duplication of field definition.\n  config = default_lib.get_config()\n\n  config.batch_size = 512\n  config.shuffle_buffer_size = 16 * 512\n  config.cache = True\n\n  return config\n\n\nmetrics = default_lib.metrics\n"
  },
  {
    "path": "examples/imagenet/configs/v100_x8_mixed_precision.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Hyperparameter configuration to run the example on 8 x Nvidia V100 GPUs.\"\"\"\n\nfrom configs import default as default_lib\n\n\ndef get_config():\n  \"\"\"Get the hyperparameter configuration to train on 8 x Nvidia V100 GPUs.\"\"\"\n  # Override default configuration to avoid duplication of field definition.\n  config = default_lib.get_config()\n\n  config.batch_size = 2048\n  config.shuffle_buffer_size = 16 * 2048\n  config.cache = True\n  config.half_precision = True\n\n  return config\n\n\nmetrics = default_lib.metrics\n"
  },
  {
    "path": "examples/imagenet/imagenet.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Flax Imagenet Example\\n\",\n    \"\\n\",\n    \"<a href=\\\"https://colab.research.google.com/github/google/flax/blob/main/examples/imagenet/imagenet.ipynb\\\" ><img src=\\\"https://colab.research.google.com/assets/colab-badge.svg\\\" alt=\\\"Open In Colab\\\"/></a>\\n\",\n    \"\\n\",\n    \"Demonstration notebook for\\n\",\n    \"https://github.com/google/flax/tree/main/examples/imagenet\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The **Flax Notebook Workflow**:\\n\",\n    \"\\n\",\n    \"1. Run the entire notebook end-to-end and check out the outputs.\\n\",\n    \"   - This will open Python files in the right-hand editor!\\n\",\n    \"   - You'll be able to interactively explore metrics in TensorBoard.\\n\",\n    \"2. Change `config` and train for different hyperparameters. Check out the\\n\",\n    \"   updated TensorBoard plots.\\n\",\n    \"3. Update the code in `train.py`. Thanks to `%autoreload`, any changes you\\n\",\n    \"   make in the file will automatically appear in the notebook. Some ideas to\\n\",\n    \"   get you started:\\n\",\n    \"   - Change the model.\\n\",\n    \"   - Log some per-batch metrics during training.\\n\",\n    \"   - Add new hyperparameters to `configs/default.py` and use them in\\n\",\n    \"     `train.py`.\\n\",\n    \"4. At any time, feel free to paste code from `train.py` into the notebook\\n\",\n    \"   and modify it directly there!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Setup\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 28,\n   \"metadata\": {\n    \"outputId\": \"cb862d1a-2f71-444f-9770-9f0d53b11389\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"GPU 0: Tesla T4 (UUID: GPU-d1652fa8-88b9-3d02-7a65-e7ebabeb0372)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Tested with a T4 GPU.\\n\",\n    \"!nvidia-smi -L\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 29,\n   \"metadata\": {\n    \"outputId\": \"80340396-77c2-4654-cc6d-67040f227eb9\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"  Preparing metadata (setup.py) ... \\u001b[?25l\\u001b[?25hdone\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Install ml-collections & latest Flax version from Github.\\n\",\n    \"!pip install -q clu ml-collections git+https://github.com/google/flax\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 30,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"example_directory = 'examples/imagenet'\\n\",\n    \"editor_relpaths = ('configs/default.py', 'input_pipeline.py', 'models.py', 'train.py')\\n\",\n    \"\\n\",\n    \"repo, branch = 'https://github.com/google/flax', 'main'\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 31,\n   \"metadata\": {\n    \"cellView\": \"form\",\n    \"outputId\": \"9449a7b4-8a5d-4446-abe0-7886435ebd1c\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<h1 style=\\\"color:red;\\\" class=\\\"blink\\\">WARNING : Editing in VM - changes lost after reboot!!</h1>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/imagenet/configs/default.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/imagenet/input_pipeline.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/imagenet/models.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/imagenet/train.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# (If you run this code in Jupyter[lab], then you're already in the\\n\",\n    \"#  example directory and nothing needs to be done.)\\n\",\n    \"\\n\",\n    \"#@markdown **Fetch newest Flax, copy example code**\\n\",\n    \"#@markdown\\n\",\n    \"#@markdown **If you select no** below, then the files will be stored on the\\n\",\n    \"#@markdown *ephemeral* Colab VM. **After some time of inactivity, this VM will\\n\",\n    \"#@markdown be restarted and any changes are lost**.\\n\",\n    \"#@markdown\\n\",\n    \"#@markdown **If you select yes** below, then you will be asked for your\\n\",\n    \"#@markdown credentials to mount your personal Google Drive. In this case, all\\n\",\n    \"#@markdown changes you make will be *persisted*, and even if you re-run the\\n\",\n    \"#@markdown Colab later on, the files will still be the same (you can of course\\n\",\n    \"#@markdown remove directories inside your Drive's `flax/` root if you want to\\n\",\n    \"#@markdown manually revert these files).\\n\",\n    \"\\n\",\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  import os\\n\",\n    \"  os.chdir('/content')\\n\",\n    \"  # Download Flax repo from Github.\\n\",\n    \"  if not os.path.isdir('flaxrepo'):\\n\",\n    \"    !git clone --depth=1 -b $branch $repo flaxrepo\\n\",\n    \"  # Copy example files & change directory.\\n\",\n    \"  mount_gdrive = 'no' #@param ['yes', 'no']\\n\",\n    \"  if mount_gdrive == 'yes':\\n\",\n    \"    DISCLAIMER = 'Note : Editing in your Google Drive, changes will persist.'\\n\",\n    \"    from google.colab import drive\\n\",\n    \"    drive.mount('/content/gdrive')\\n\",\n    \"    example_root_path = f'/content/gdrive/My Drive/flax/{example_directory}'\\n\",\n    \"  else:\\n\",\n    \"    DISCLAIMER = 'WARNING : Editing in VM - changes lost after reboot!!'\\n\",\n    \"    example_root_path = f'/content/{example_directory}'\\n\",\n    \"    from IPython import display\\n\",\n    \"    display.display(display.HTML(\\n\",\n    \"        f'<h1 style=\\\"color:red;\\\" class=\\\"blink\\\">{DISCLAIMER}</h1>'))\\n\",\n    \"  if not os.path.isdir(example_root_path):\\n\",\n    \"    os.makedirs(example_root_path)\\n\",\n    \"    !cp -r flaxrepo/$example_directory/* \\\"$example_root_path\\\"\\n\",\n    \"  os.chdir(example_root_path)\\n\",\n    \"  from google.colab import files\\n\",\n    \"  for relpath in editor_relpaths:\\n\",\n    \"    s = open(f'{example_root_path}/{relpath}').read()\\n\",\n    \"    open(f'{example_root_path}/{relpath}', 'w').write(\\n\",\n    \"        f'## {DISCLAIMER}\\\\n' + '#' * (len(DISCLAIMER) + 3) + '\\\\n\\\\n' + s)\\n\",\n    \"    files.view(f'{example_root_path}/{relpath}')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"outputId\": \"acc1f45d-5062-4ff3-e6d4-10b4ffe0f8ef\"\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# Note : In Colab, above cell changed the working directory.\\n\",\n    \"!pwd\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Imports / Helpers\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 33,\n   \"metadata\": {\n    \"cellView\": \"form\",\n    \"outputId\": \"9dc7fb32-331e-44a6-b6e8-830f6a64d845\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"[StreamExecutorGpuDevice(id=0, process_index=0, slice_index=0)]\"\n      ]\n     },\n     \"execution_count\": 33,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# It's possible to run this Colab with TPUs:\\n\",\n    \"# 1. change runtime type to TPU\\n\",\n    \"# 2. install compatible flax version: `!pip install flax==0.6.4 jax==0.3.25 jaxlib==0.3.25`\\n\",\n    \"# 3. uncomment lines below\\n\",\n    \"\\n\",\n    \"# import flax, jax, jax.tools.colab_tpu\\n\",\n    \"# jax.tools.colab_tpu.setup_tpu()\\n\",\n    \"\\n\",\n    \"import jax\\n\",\n    \"jax.devices()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 34,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import json\\n\",\n    \"from absl import logging\\n\",\n    \"import flax\\n\",\n    \"import jax\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"from matplotlib import pyplot as plt\\n\",\n    \"import numpy as np\\n\",\n    \"import tensorflow as tf\\n\",\n    \"import tensorflow_datasets as tfds\\n\",\n    \"\\n\",\n    \"logging.set_verbosity(logging.INFO)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 35,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Helper functions for images.\\n\",\n    \"\\n\",\n    \"def show_img(img, ax=None, title=None):\\n\",\n    \"  \\\"\\\"\\\"Shows a single image.\\\"\\\"\\\"\\n\",\n    \"  if ax is None:\\n\",\n    \"    ax = plt.gca()\\n\",\n    \"  img *= tf.constant(input_pipeline.STDDEV_RGB, shape=[1, 1, 3], dtype=img.dtype)\\n\",\n    \"  img += tf.constant(input_pipeline.MEAN_RGB, shape=[1, 1, 3], dtype=img.dtype)\\n\",\n    \"  img = np.clip(img.numpy().astype(int), 0, 255)\\n\",\n    \"  ax.imshow(img)\\n\",\n    \"  ax.set_xticks([])\\n\",\n    \"  ax.set_yticks([])\\n\",\n    \"  if title:\\n\",\n    \"    ax.set_title(title)\\n\",\n    \"\\n\",\n    \"def show_img_grid(imgs, titles):\\n\",\n    \"  \\\"\\\"\\\"Shows a grid of images.\\\"\\\"\\\"\\n\",\n    \"  n = int(np.ceil(len(imgs)**.5))\\n\",\n    \"  _, axs = plt.subplots(n, n, figsize=(3 * n, 3 * n))\\n\",\n    \"  for i, (img, title) in enumerate(zip(imgs, titles)):\\n\",\n    \"    show_img(img, axs[i // n][i % n], title)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 36,\n   \"metadata\": {\n    \"outputId\": \"f943d165-b953-4a70-9f93-96eb857c3d53\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"The autoreload extension is already loaded. To reload it, use:\\n\",\n      \"  %reload_ext autoreload\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Local imports from current directory - auto reload.\\n\",\n    \"# Any changes you make to train.py will appear automatically.\\n\",\n    \"%load_ext autoreload\\n\",\n    \"%autoreload 2\\n\",\n    \"import input_pipeline\\n\",\n    \"import models\\n\",\n    \"import train\\n\",\n    \"from configs import default as config_lib\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Dataset\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 37,\n   \"metadata\": {\n    \"outputId\": \"a9b6cfe9-cc1c-451a-f8f7-69356cb7bdd2\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:No config specified, defaulting to config: imagenette/full-size-v2\\n\",\n      \"INFO:absl:Load dataset info from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"INFO:absl:Reusing dataset imagenette (/root/tensorflow_datasets/imagenette/full-size-v2/1.0.0)\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset imagenette for split train, from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"tfds.core.DatasetInfo(\\n\",\n       \"    name='imagenette',\\n\",\n       \"    full_name='imagenette/full-size-v2/1.0.0',\\n\",\n       \"    description=\\\"\\\"\\\"\\n\",\n       \"    Imagenette is a subset of 10 easily classified classes from the Imagenet\\n\",\n       \"    dataset. It was originally prepared by Jeremy Howard of FastAI. The objective\\n\",\n       \"    behind putting together a small version of the Imagenet dataset was mainly\\n\",\n       \"    because running new ideas/algorithms/experiments on the whole Imagenet take a\\n\",\n       \"    lot of time.\\n\",\n       \"    \\n\",\n       \"    This version of the dataset allows researchers/practitioners to quickly try out\\n\",\n       \"    ideas and share with others. The dataset comes in three variants:\\n\",\n       \"    \\n\",\n       \"    *   Full size\\n\",\n       \"    *   320 px\\n\",\n       \"    *   160 px\\n\",\n       \"    \\n\",\n       \"    Note: The v2 config correspond to the new 70/30 train/valid split (released in\\n\",\n       \"    Dec 6 2019).\\n\",\n       \"    \\\"\\\"\\\",\\n\",\n       \"    config_description=\\\"\\\"\\\"\\n\",\n       \"    full-size variant.\\n\",\n       \"    \\\"\\\"\\\",\\n\",\n       \"    homepage='https://github.com/fastai/imagenette',\\n\",\n       \"    data_path='/root/tensorflow_datasets/imagenette/full-size-v2/1.0.0',\\n\",\n       \"    file_format=tfrecord,\\n\",\n       \"    download_size=1.45 GiB,\\n\",\n       \"    dataset_size=1.46 GiB,\\n\",\n       \"    features=FeaturesDict({\\n\",\n       \"        'image': Image(shape=(None, None, 3), dtype=uint8),\\n\",\n       \"        'label': ClassLabel(shape=(), dtype=int64, num_classes=10),\\n\",\n       \"    }),\\n\",\n       \"    supervised_keys=('image', 'label'),\\n\",\n       \"    disable_shuffling=False,\\n\",\n       \"    splits={\\n\",\n       \"        'train': <SplitInfo num_examples=9469, num_shards=16>,\\n\",\n       \"        'validation': <SplitInfo num_examples=3925, num_shards=4>,\\n\",\n       \"    },\\n\",\n       \"    citation=\\\"\\\"\\\"@misc{imagenette,\\n\",\n       \"      author    = \\\"Jeremy Howard\\\",\\n\",\n       \"      title     = \\\"imagenette\\\",\\n\",\n       \"      url       = \\\"https://github.com/fastai/imagenette/\\\"\\n\",\n       \"    }\\\"\\\"\\\",\\n\",\n       \")\"\n      ]\n     },\n     \"execution_count\": 37,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# We load \\\"imagenette\\\" that has similar pictures to \\\"imagenet2012\\\" but can be\\n\",\n    \"# downloaded automatically and is much smaller.\\n\",\n    \"dataset_builder = tfds.builder('imagenette')\\n\",\n    \"dataset_builder.download_and_prepare()\\n\",\n    \"ds = dataset_builder.as_dataset('train')\\n\",\n    \"dataset_builder.info\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 38,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Utilities to help with Imagenette labels.\\n\",\n    \"\\n\",\n    \"![ ! -f mapping_imagenet.json ] && wget --no-check-certificate https://raw.githubusercontent.com/ozendelait/wordnet-to-json/master/mapping_imagenet.json\\n\",\n    \"\\n\",\n    \"with open('mapping_imagenet.json') as f:\\n\",\n    \"  mapping_imagenet = json.load(f)\\n\",\n    \"# Mapping imagenette label name to imagenet label index.\\n\",\n    \"imagenette_labels = {\\n\",\n    \"    d['v3p0']: d['label']\\n\",\n    \"    for d in mapping_imagenet\\n\",\n    \"}\\n\",\n    \"# Mapping imagenette label name to human-readable label.\\n\",\n    \"imagenette_idx = {\\n\",\n    \"    d['v3p0']: idx\\n\",\n    \"    for idx, d in enumerate(mapping_imagenet)\\n\",\n    \"}\\n\",\n    \"\\n\",\n    \"def imagenette_label(idx):\\n\",\n    \"  \\\"\\\"\\\"Returns a short human-readable string for provided imagenette index.\\\"\\\"\\\"\\n\",\n    \"  net = dataset_builder.info.features['label'].int2str(idx)\\n\",\n    \"  return imagenette_labels[net].split(',')[0]\\n\",\n    \"\\n\",\n    \"def imagenette_imagenet2012(idx):\\n\",\n    \"  \\\"\\\"\\\"Returns the imagenet2012 index for provided imagenette index.\\\"\\\"\\\"\\n\",\n    \"  net = dataset_builder.info.features['label'].int2str(idx)\\n\",\n    \"  return imagenette_idx[net]\\n\",\n    \"\\n\",\n    \"def imagenet2012_label(idx):\\n\",\n    \"  \\\"\\\"\\\"Returns a short human-readable string for provided imagenet2012 index.\\\"\\\"\\\"\\n\",\n    \"  return mapping_imagenet[idx]['label'].split(',')[0]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 39,\n   \"metadata\": {\n    \"outputId\": \"78142300-cc8b-4a6c-f781-5ab29578d828\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Constructing tf.data.Dataset imagenette for split train[0:9469], from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"WARNING:absl:options.experimental_threading is deprecated. Use options.threading instead.\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset imagenette for split validation[0:3925], from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"WARNING:absl:options.experimental_threading is deprecated. Use options.threading instead.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"train_ds = input_pipeline.create_split(\\n\",\n    \"    dataset_builder, 128, train=True,\\n\",\n    \")\\n\",\n    \"eval_ds = input_pipeline.create_split(\\n\",\n    \"    dataset_builder, 128, train=False,\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 40,\n   \"metadata\": {\n    \"outputId\": \"8b3b9cf2-7649-4953-99bb-32a689fe0a29\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'image': (TensorShape([128, 224, 224, 3]), tf.float32),\\n\",\n       \" 'label': (TensorShape([128]), tf.int64)}\"\n      ]\n     },\n     \"execution_count\": 40,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"train_batch = next(iter(train_ds))\\n\",\n    \"{k: (v.shape, v.dtype) for k, v in train_batch.items()}\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Training from scratch\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Get a live update during training - use the \\\"refresh\\\" button!\\n\",\n    \"# (In Jupyter[lab] start \\\"tensorbaord\\\" in the local directory instead.)\\n\",\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  %load_ext tensorboard\\n\",\n    \"  %tensorboard --logdir=.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 42,\n   \"metadata\": {\n    \"outputId\": \"2d0bc789-213d-4a34-a7b1-e7852b40f375\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"batch_size: 32\\n\",\n       \"cache: false\\n\",\n       \"dataset: imagenette\\n\",\n       \"half_precision: true\\n\",\n       \"learning_rate: 0.1\\n\",\n       \"log_every_steps: 100\\n\",\n       \"model: ResNet18\\n\",\n       \"momentum: 0.9\\n\",\n       \"num_epochs: 5.0\\n\",\n       \"num_train_steps: -1\\n\",\n       \"prefetch: 1\\n\",\n       \"shuffle_buffer_size: 128\\n\",\n       \"steps_per_eval: -1\\n\",\n       \"warmup_epochs: 0.5\"\n      ]\n     },\n     \"execution_count\": 42,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"config = config_lib.get_config()\\n\",\n    \"config.dataset = 'imagenette'\\n\",\n    \"config.model = 'ResNet18'\\n\",\n    \"config.half_precision = True\\n\",\n    \"# Reduce batch size, shuffle buffer and prefetch to avoid Colab runtime OOM.\\n\",\n    \"config.batch_size = 32\\n\",\n    \"config.shuffle_buffer_size = 128\\n\",\n    \"config.prefetch = 1\\n\",\n    \"# Reduce epochs so this Colab doesn't take forever.\\n\",\n    \"config.num_epochs = 5.0\\n\",\n    \"config.warmup_epochs = 0.5\\n\",\n    \"config\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 43,\n   \"metadata\": {\n    \"outputId\": \"de56d320-c336-459b-f258-5d6ae41ce0af\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Constructing tf.data.Dataset imagenette for split train[0:9469], from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"WARNING:absl:options.experimental_threading is deprecated. Use options.threading instead.\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset imagenette for split validation[0:3925], from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"WARNING:absl:options.experimental_threading is deprecated. Use options.threading instead.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Regenerate datasets with updated batch_size.\\n\",\n    \"train_ds = input_pipeline.create_split(\\n\",\n    \"    dataset_builder, config.batch_size, train=True,\\n\",\n    \")\\n\",\n    \"eval_ds = input_pipeline.create_split(\\n\",\n    \"    dataset_builder, config.batch_size, train=False,\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 44,\n   \"metadata\": {\n    \"outputId\": \"018da2c5-c6f0-42ac-843f-7ac855a6bf14\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:No config specified, defaulting to config: imagenette/full-size-v2\\n\",\n      \"INFO:absl:Load dataset info from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset imagenette for split train[0:9469], from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"WARNING:absl:options.experimental_threading is deprecated. Use options.threading instead.\\n\"\n     ]\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\n\",\n      \"\\n\",\n      \"ResNet18_lr=0.03\\n\"\n     ]\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Constructing tf.data.Dataset imagenette for split validation[0:3925], from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"WARNING:absl:options.experimental_threading is deprecated. Use options.threading instead.\\n\",\n      \"INFO:absl:Restoring legacy Flax checkpoint from models/ResNet18_lr=0.03/checkpoint_1475\\n\",\n      \"INFO:absl:Initial compilation, this might take some minutes...\\n\",\n      \"INFO:absl:No config specified, defaulting to config: imagenette/full-size-v2\\n\",\n      \"INFO:absl:Load dataset info from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset imagenette for split train[0:9469], from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"WARNING:absl:options.experimental_threading is deprecated. Use options.threading instead.\\n\"\n     ]\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\n\",\n      \"\\n\",\n      \"ResNet18_lr=0.1\\n\"\n     ]\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Constructing tf.data.Dataset imagenette for split validation[0:3925], from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"WARNING:absl:options.experimental_threading is deprecated. Use options.threading instead.\\n\",\n      \"INFO:absl:Restoring legacy Flax checkpoint from models/ResNet18_lr=0.1/checkpoint_1475\\n\",\n      \"INFO:absl:Initial compilation, this might take some minutes...\\n\",\n      \"INFO:absl:No config specified, defaulting to config: imagenette/full-size-v2\\n\",\n      \"INFO:absl:Load dataset info from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset imagenette for split train[0:9469], from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"WARNING:absl:options.experimental_threading is deprecated. Use options.threading instead.\\n\"\n     ]\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\n\",\n      \"\\n\",\n      \"ResNet18_lr=0.3\\n\"\n     ]\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Constructing tf.data.Dataset imagenette for split validation[0:3925], from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"WARNING:absl:options.experimental_threading is deprecated. Use options.threading instead.\\n\",\n      \"INFO:absl:Restoring legacy Flax checkpoint from models/ResNet18_lr=0.3/checkpoint_1475\\n\",\n      \"INFO:absl:Initial compilation, this might take some minutes...\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Takes ~1.5 min / epoch.\\n\",\n    \"for lr in  (0.03, 0.1, 0.3):\\n\",\n    \"  config.learning_rate = lr\\n\",\n    \"  name = f'{config.model}_lr={config.learning_rate}'\\n\",\n    \"  print(f'\\\\n\\\\n{name}')\\n\",\n    \"  state = train.train_and_evaluate(config, workdir=f'./models/{name}')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 45,\n   \"metadata\": {\n    \"cellView\": \"form\"\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  #@markdown You can upload the training results directly to https://tensorbaord.dev\\n\",\n    \"  #@markdown\\n\",\n    \"  #@markdown Note that everybody with the link will be able to see the data.\\n\",\n    \"  upload_data = 'no' #@param ['yes', 'no']\\n\",\n    \"  if upload_data == 'yes':\\n\",\n    \"    !tensorboard dev upload --one_shot --logdir ./models --name 'Flax examples/mnist'\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Load pre-trained model\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 46,\n   \"metadata\": {\n    \"outputId\": \"b06fa3d8-a950-46d2-e03e-fc6c971bdbd0\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"-rw-r--r-- 1 root root 196M Apr  3 13:38 checkpoint_250200\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Load model checkpoint from cloud.\\n\",\n    \"from flax.training import checkpoints\\n\",\n    \"\\n\",\n    \"config_name = 'v100_x8'\\n\",\n    \"pretrained_path = f'gs://flax_public/examples/imagenet/{config_name}'\\n\",\n    \"latest_checkpoint = checkpoints.natural_sort(\\n\",\n    \"    tf.io.gfile.glob(f'{pretrained_path}/checkpoint_*'))[0]\\n\",\n    \"if not os.path.exists(os.path.basename(latest_checkpoint)):\\n\",\n    \"  tf.io.gfile.copy(latest_checkpoint, os.path.basename(latest_checkpoint))\\n\",\n    \"!ls -lh checkpoint_*\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 47,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Load config that was used to train checkpoint.\\n\",\n    \"import importlib\\n\",\n    \"config = importlib.import_module(f'configs.{config_name}').get_config()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 48,\n   \"metadata\": {\n    \"outputId\": \"57777298-4b4b-4a82-b0f2-4b6ff3b949af\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Restoring legacy Flax checkpoint from ./checkpoint_250200\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Load models & state (takes ~1 min to load the model).\\n\",\n    \"model_cls = getattr(models, config.model)\\n\",\n    \"model = train.create_model(\\n\",\n    \"    model_cls=model_cls, half_precision=config.half_precision)\\n\",\n    \"base_learning_rate = config.learning_rate * config.batch_size / 256.\\n\",\n    \"steps_per_epoch = (\\n\",\n    \"    dataset_builder.info.splits['train'].num_examples // config.batch_size\\n\",\n    \")\\n\",\n    \"learning_rate_fn = train.create_learning_rate_fn(\\n\",\n    \"    config, base_learning_rate, steps_per_epoch)\\n\",\n    \"state = train.create_train_state(\\n\",\n    \"    jax.random.key(0), config, model, image_size=input_pipeline.IMAGE_SIZE,\\n\",\n    \"    learning_rate_fn=learning_rate_fn)\\n\",\n    \"state = train.restore_checkpoint(state, './')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Inference\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 49,\n   \"metadata\": {\n    \"outputId\": \"793a656b-f3ad-4596-ad4f-44c686e5e885\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"{'image': TensorShape([32, 224, 224, 3]), 'label': TensorShape([32])}\"\n      ]\n     },\n     \"execution_count\": 49,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# Load batch from imagenette eval set.\\n\",\n    \"batch = next(iter(eval_ds))\\n\",\n    \"{k: v.shape for k, v in batch.items()}\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 50,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Evaluate using model trained on imagenet.\\n\",\n    \"logits = model.apply({'params': state.params, 'batch_stats': state.batch_stats}, batch['image'][:128], train=False)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 51,\n   \"metadata\": {\n    \"outputId\": \"6ab4bb0b-2c03-4663-d7ac-e51b979d121f\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"[16, 24]\"\n      ]\n     },\n     \"execution_count\": 51,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# Find classification mistakes.\\n\",\n    \"preds_labels = list(zip(logits.argmax(axis=-1), map(imagenette_imagenet2012, batch['label'])))\\n\",\n    \"error_idxs = [idx for idx, (pred, label) in enumerate(preds_labels) if pred != label]\\n\",\n    \"error_idxs\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 52,\n   \"metadata\": {\n    \"outputId\": \"142c1acf-037e-4ab0-9ca3-bdf0829c51c4\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAAAk0AAAJKCAYAAAAx/3HgAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOz9d7wlVZX3j7/3rnzSzfd2387d5IyNiGRBRQQUEyM+Chhx1NHRnzpRUcfw+HMcnXFGMcyAaQIC84g6ijqAARAEJDYNTedwu28+sXLt7x+7zrn30k1wBLGZ83nZck+dOlW70tqfWuuz1hJKKUUXXXTRRRdddNFFF48L+UwPoIsuuuiiiy666GJ/QJc0ddFFF1100UUXXTwJdElTF1100UUXXXTRxZNAlzR10UUXXXTRRRddPAl0SVMXXXTRRRdddNHFk0CXNHXRRRdddNFFF108CXRJUxdddNFFF1100cWTQJc0ddFFF1100UUXXTwJdElTF1100UUXXXTRxZNAlzQ9i3HTTTchhOCmm256pofyB4OPfOQjCCGe6WF00UUXvwf8IdvA008/ndNPP73zecuWLQghuPLKK5+xMXXxxOiSpi4eE//6r//K5z//+Wd6GP8rcfrpp3PJJZc808PooosuuuhiHsxnegBd/OHiX//1X7n//vv50z/902d6KF100UUXz2qsWLEC3/exLOuZHkoXj4Oup+kPEFmWEQTBMz2MLp4BtFqtZ3oIXXTxjOMP2QY+Xc+oEALXdTEM42nZfhdPDbqk6WlCWzuzfv16LrjgAiqVCgMDA7znPe/ZyxgIIXjXu97Ft7/9bQ4//HAcx+FHP/oRADt37uRNb3oTIyMjOI7D4Ycfzr/8y7/stb8dO3Zw/vnnUywWGR4e5r3vfS9hGO61XqvVYv369UxOTj7u+E8//XR+8IMfsHXrVoQQCCFYuXIlAFEU8eEPf5i1a9fS09NDsVjklFNO4cYbb1ywjXaM/m//9m/53Oc+x4oVK/A8j9NOO437779/r32uX7+eV7/61fT39+O6LscddxzXXXfd447zf7KfR+OKK67gjDPOYHh4GMdxOOyww/jSl760YJ2LL76YwcFB4jje6/cvfvGLOfjggxcs+9a3vsXatWvxPI/+/n5e+9rXsn379gXrnH766RxxxBHceeednHrqqRQKBf7yL//yCcfbRRf7A/Z3GwiP/4x+97vf5ZxzzmF0dBTHcVizZg1/8zd/Q5qme23nK1/5CmvWrMHzPI4//nh+8Ytf7LXOY2mabrjhBk455RSKxSK9vb28/OUv58EHH3zCsXfx9KAbnnuaccEFF7By5Uo+9alP8atf/Yp/+Id/YGZmhm984xsL1rvhhhu46qqreNe73sXg4CArV65kz549nHDCCR2DMjQ0xA9/+EPe/OY3U6vVOmEz3/c588wz2bZtG+9+97sZHR3lm9/8JjfccMNe47n99tt5wQtewGWXXcZHPvKRxxz3X/3VX1GtVtmxYwef+9znACiVSgDUajW+9rWvceGFF/LWt76Ver3OP//zP3PWWWdx++23c8wxxyzY1je+8Q3q9TrvfOc7CYKAv//7v+eMM87gvvvuY2RkBIAHHniAk046iSVLlvDnf/7nFItFrrrqKs4//3yuueYaXvGKVzzhuX4y+9kXvvSlL3H44Yfzspe9DNM0+d73vsc73vEOsizjne98JwBveMMb+MY3vsH111/Pueee2/nt7t27ueGGG7jssss6yz7xiU/woQ99iAsuuIC3vOUtTExM8IUvfIFTTz2V3/zmN/T29nbWnZqa4uyzz+a1r30tr3/96x93nF10sT9if7WBbTzWM3rllVdSKpV43/veR6lU4oYbbuDDH/4wtVqNz3zmM53f//M//zOXXnopJ554In/6p3/Kpk2beNnLXkZ/fz/Lli173H3/9Kc/5eyzz2b16tV85CMfwfd9vvCFL3DSSSdx1113dV5ku/g9QnXxtOCyyy5TgHrZy162YPk73vEOBah77rmnswxQUkr1wAMPLFj3zW9+s1q8eLGanJxcsPy1r32t6unpUa1WSyml1Oc//3kFqKuuuqqzTrPZVAcccIAC1I033thZfuONNypAXXbZZU94DOecc45asWLFXsuTJFFhGC5YNjMzo0ZGRtSb3vSmzrLNmzcrQHmep3bs2NFZfttttylAvfe97+0sO/PMM9WRRx6pgiDoLMuyTJ144onqwAMPfNxx/jb7aV+X+Wifx/k466yz1OrVqzuf0zRVS5cuVX/0R3+0YL2/+7u/U0IItWnTJqWUUlu2bFGGYahPfOITC9a77777lGmaC5afdtppClCXX3754x5fF13sj3g22MDHe0b3ZTcuvfRSVSgUOnYsiiI1PDysjjnmmAU28ytf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wO89X+9/CdrptuvWrVOvfvWrVblcVn19fepd73qX8n1/wbqAeuc737nP7ezZs0e9853vVMuWLVOWZalFixapM888U33lK19ZsN7WrVvVy172MlUoFNTg4KB6z3veo370ox/9Tum2jUZDve51r1O9vb0K6JQfyLJMffKTn1QrVqxQjuOoY489Vn3/+99XF1988YISBe0U2s985jPqs5/9rFq2bJlyHEedcsopC9KN29i4caO66KKL1KJFi5RlWWrJkiXq3HPPVVdfffXjjvO32c++Sg5cd9116qijjlKu66qVK1eqT3/60+pf/uVfFKA2b9681/6uuuoqBai3ve1tjzmma665Rp188smqWCyqYrGoDjnkEPXOd75TPfTQQ511TjvtNHX44Yc/7rF10cX+imeDDXy8Z/Tmm29WJ5xwgvI8T42OjqoPfvCD6vrrr99rf0op9cUvflGtWrVKOY6jjjvuOPXzn/9cnXbaaU9YckAppX7605+qk046SXmepyqVijrvvPPUunXrnnDsXTw9EEo9ihp38ZTgIx/5CB/96EeZmJhgcHDwmR7OM4ItW7awatUqPvOZzzyt3pXf137a+O53v8v555/Pz3/+c0455ZSnfX9ddLE/omsDu3g2oqtp6qKL3xJf/epXWb16NSeffPIzPZQuuuiiiy5+j+hqmrro4kni3//937n33nv5wQ9+wN///d8/ZnZhF1100UUXz050SVMXXTxJXHjhhZRKJd785jfzjne845keThdddNFFF79ndDVNXXTRRRdddNFFF08CXU1TF1100UUXXXTRxZNAlzR10UUXXXTRRRddPAl0SdOjcOWVVyKE2KuZ5JPB6aefzhFHHPGUjmflypVccsklT+k2u/jdcNNNNyGE4Kabbnqmh9LF/wJ0bVIXT4SuTfr9oUuauviDxLp16/jIRz6yz4nii1/8IldeeeXvfUz7Gz7ykY88YX+vLrro4smha5N+dzwbbFKXNHXxB4l169bx0Y9+tGuguuiiiz8IdG1SF9AlTV108axDs9l8pofQRRdddNHBs8kmdUnTk8B3v/tdzjnnHEZHR3EchzVr1vA3f/M3pGm6z/XvvPNOTjzxRDzPY9WqVVx++eV7rROGIZdddhkHHHAAjuOwbNkyPvjBDxKG4ROOZ+PGjWzcuPFJjX12dpb3vve9rFy5EsdxWLp0KRdddFGnw3cURXz4wx9m7dq19PT0UCwWOeWUU7jxxhv32ta///u/s3btWsrlMpVKhSOPPJK///u/73wfxzEf/ehHOfDAA3Fdl4GBAU4++WR+8pOfLNjO+vXrefWrX01/fz+u63Lcccdx3XXXdb6/8sorec1rXgPAC17wAoQQnXj9ypUreeCBB/jZz37WWX766acvON4//dM/ZdmyZTiOwwEHHMCnP/1psix7wnO1cuVKzj33XH784x9zzDHH4Louhx12GNdee+0T/vYXv/gFr3nNa1i+fHnner73ve/F9/3OOldccQVCCH7zm9/s9ftPfvKTGIbBzp07O8tuu+02XvKSl9DT00OhUOC0007j5ptvXvC7j3zkIwghWLduHa973evo6+vrVir/X4CuTdLo2qTHRtcmPT3oFrd8ErjyyisplUq8733vo1QqccMNN/DhD3+YWq3GZz7zmQXrzszM8NKXvpQLLriACy+8kKuuuoo//uM/xrZt3vSmNwGQZRkve9nL+OUvf8nb3vY2Dj30UO677z4+97nP8fDDD/P//t//e9zxnHnmmQBPKAxtNBqccsopPPjgg7zpTW/iOc95DpOTk1x33XXs2LGDwcFBarUaX/va17jwwgt561vfSr1e55//+Z8566yzuP322znmmGMA+MlPfsKFF17ImWeeyac//WkAHnzwQW6++Wbe8573APph+dSnPsVb3vIWjj/+eGq1GnfccQd33XUXL3rRiwB44IEHOOmkk1iyZAl//ud/TrFY5KqrruL888/nmmuu4RWveAWnnnoq7373u/mHf/gH/vIv/5JDDz0UgEMPPZTPf/7z/Mmf/AmlUom/+qu/AmBkZASAVqvFaaedxs6dO7n00ktZvnw5t9xyC3/xF3/B2NgYn//855/wWm/YsIE/+qM/4u1vfzsXX3wxV1xxBa95zWv40Y9+1DmGfeE73/kOrVaLP/7jP2ZgYIDbb7+dL3zhC+zYsYPvfOc7ALz61a/mne98J9/+9rc59thjF/z+29/+NqeffjpLliwB4IYbbuDss89m7dq1XHbZZUgpueKKKzjjjDP4xS9+wfHHH7/g9695zWs48MAD+eQnP7lXp/Uunn3o2qSuTerapGcIz2S34D9EXHHFFXt1t2+1Wnutd+mll6pCoaCCIOgsO+200xSgPvvZz3aWhWGojjnmGDU8PKyiKFJK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rrrqKt7+9rdz4403ctJJJ5GmKevXr+eqq67i+uuv57jjjnvMMT3Z9N4PfOADXH311bzmNa/hTW96E2vXrmV6eprrrruOyy+/nKOPPppzzz2Xa6+9lle84hWcc845bN68mcsvv5zDDjtswcPxlre8henpac444wyWLl3K1q1b+cIXvsAxxxzTSb897LDDOP3001m7di39/f3ccccdXH311bzrXe/qbOef/umfOPnkkznyyCN561vfyurVq9mzZw+33norO3bs4J577gHgmGOOwTAMPv3pT1OtVnEchzPOOIPh4WHWrl3Ll770JT7+8Y9zwAEHMDw8zBlnnMEHPvABrrvuOs4991wuueQS1q5dS7PZ5L777uPqq69my5YtDA4OPu45O+igg3jzm9/Mr3/9a0ZGRviXf/kX9uzZwxVXXPGYvznkkENYs2YN73//+9m5cyeVSoVrrrlmLx3BfFx00UW8//3vB1jgBgdt/L/2ta9x9tlnc/jhh/PGN76RJUuWsHPnTm688UYqlQrf+973Hvc4unj2omuTNLo2qWuTnhH8vtP1/tCxr/Tem2++WZ1wwgnK8zw1OjqqPvjBD6rrr79+rxTP0047TR1++OHqjjvuUM9//vOV67pqxYoV6h//8R/32k8URerTn/60Ovzww5XjOKqvr0+tXbtWffSjH1XVarWz3u+S3quUUlNTU+pd73qXWrJkibJtWy1dulRdfPHFanJyUimlU0k/+clPqhUrVijHcdSxxx6rvv/976uLL754wT6uvvpq9eIXv1gNDw8r27bV8uXL1aWXXqrGxsY663z84x9Xxx9/vOrt7VWe56lDDjlEfeITn+ikNbexceNGddFFF6lFixYpy7LUkiVL1LnnnquuvvrqBet99atfVatXr1aGYSw417t371bnnHOOKpfLCliQ6luv19Vf/MVfqAMOOEDZtq0GBwfViSeeqP72b/92r3E8GitWrFDnnHOOuv7669VRRx2lHMdRhxxyiPrOd76zYL19pfeuW7dOvfCFL1SlUkkNDg6qt771reqee+5ZkLY7H2NjY8owDHXQQQc95nh+85vfqFe+8pVqYGBAOY6jVqxYoS644AL13//935112um9ExMTj3tsXey/6Nqkrk3q2qQ/HAilnk1Vp7ro4n+OlStXcsQRR/D973//ad/X5OQkixcv5sMf/vBjZu10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QT8CkE6OQ9OsWbN05MgRLV++XMFgUJMmTVJDQ0PyYsxDhw71eCf38MMPKysrSw8//LC++eYb/eIXv9CMGTP0xBNPnL1XASAj0Y8ApFOWOQ/OSYfDYRUUFKitrU35+fmDXQ6AfmTCsZoJrxG4EKTiWOXZcwAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYITQAAABYGFJrq6upUWloqr9er8vJybd++/bTzjx49qgULFqi4uFgej0djx45VfX39gAoGgB+iHwFIlxynCzZv3qyamhqtX79e5eXlWrt2raqqqrRv3z6NGDGi1/xYLKZf//rXGjFihF5//XWNHDlSX331lYYNG3Y26geQwehHANIpyxhjnCwoLy/Xtddeq3Xr1kmSEomE/H6/7rvvPi1evLjX/PXr1+upp57S3r17lZuba7WPaDSqaDSa/DkcDsvv96utrU35+flOygWQRuFwWAUFBWk7VulHAPqTin7k6OO5WCymHTt2qLKy8vtfkJ2tyspKNTU19bnmrbfeUkVFhRYsWCCfz6err75aK1euVDwe73c/tbW1KigoSG5+v99JmQAyAP0IQLo5Ck2tra2Kx+Py+Xw9xn0+n4LBYJ9rDhw4oNdff13xeFz19fVatmyZ/vrXv+rxxx/vdz9LlixRW1tbcmtpaXFSJoAMQD8CkG6Or2lyKpFIaMSIEXruuefkcrlUVlamb775Rk899ZQCgUCfazwejzweT6pLA5Bh6EcAzoSj0FRYWCiXy6VQKNRjPBQKqaioqM81xcXFys3NlcvlSo5dccUVCgaDisVicrvdAygbQKajHwFIN0cfz7ndbpWVlamxsTE5lkgk1NjYqIqKij7XTJ06Vfv371cikUiOff755youLqZBARgw+hGAdHN8n6aamhpt2LBBL730kvbs2aN77rlHkUhE8+bNkyTNnTtX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kC6O79MEAACQiS6cr7gAAACkEKEJAADAAqEJAADAAqEJAADAwjkTmurq6lRaWiqv16vy8nJt3779tPNfe+01jRs3Tl6vV+PHj1d9fX2aKu3NSe0bNmzQtGnTNHz4cA0fPlyVlZU/+VpTyenf+ymbNm1SVlaWZs6cmdoC++G07qNHj2rBggUqLi6Wx+PR2LFjB+3/jNPa165dq8svv1x5eXny+/1auHChOjs701Tt9z744APNmDFDJSUlysrK0ptvvvmTa7Zt26ZrrrlGHo9Hl112mTZu3JjyOs8G+tHgOF/7kURPypieNNhf3zOm+14rbrfbvPDCC+bf//63ueuuu8ywYcNMKBTqc/5HH31kXC6XefLJJ83u3bvNww8/7PheK2eL09pnz55t6urqzK5du8yePXvM73//e1NQUGC+/vrrNFfuvPZTDh48aEaOHGmmTZtmfvOb36Sn2B9wWnc0GjWTJ082N910k/nwww/NwYMHzbZt20xzc3OaK3de+8svv2w8Ho95+eWXzcGDB80777xjiouLzcKFC9NcuTH19fVm6dKl5o033jCSej0o98cOHDhghgwZYmpqaszu3bvN008/bVwul2loaEhPwQNEP6IfOUVPypyedE6EpilTppgFCxYkf47H46akpMTU1tb2Of/WW281N998c4+x8vJy84c//CGldfbFae0/1tXVZYYOHWpeeumlVJXYr4HU3tXVZa677jrz97//3VRXVw9Kk3Ja97PPPmtGjx5tYrFYukrsl9PaFyxYYP7v//6vx1hNTY2ZOnVqSuv8KTYN6sEHHzRXXXVVj7FZs2aZqqqqFFZ25uhH9COn6EmZ05MG/eO5WCymHTt2qLKyMjmWnZ2tyspKNTU19bmmqampx3xJqqqq6nd+qgyk9h/r6OjQiRMn0vJU+B8aaO2PPfaYRowYoTvuuCMdZfYykLrfeustVVRUaMGCBfL5fLr66qu1cuVKxePxdJUtaWC1X3fdddqxY0fydPmBAwdUX1+vm266KS01n4lz5Th1gn5EP3KKnpRZPSnnbBflVGtrq+LxuHw+X49xn8+nvXv39rkmGAz2OT8YDKaszr4MpPYfW7RokUpKSnr9Q6baQGr/8MMP9fzzz6u5uTkNFfZtIHUfOHBA//rXv3T77bervr5e+/fv17333qsTJ04oEAiko2xJA6t99uzZam1t1fXXXy9jjLq6unT33XfroYceSkfJZ6S/4zQcDuv48ePKy8sbpMr6Rz+iHzlFT8qsnjToZ5oy2apVq7Rp0yZt2bJFXq93sMs5rfb2ds2ZM0cbNmxQYWHhYJfjSCKR0IgRI/Tcc8+prKxMs2bN0tKlS7V+/frBLu0nbdu2TStXrtQzzzyjnTt36o033tDWrVu1YsWKwS4NFxj6UfrQk85fg36mqbCwUC6XS6FQqMd4KBRSUVFRn2uKiooczU+VgdR+yurVq7Vq1Sq99957mjBhQirL7JPT2r/44gt9+eWXmjFjRnIskUhIknJycrRv3z6NGTMmtUVrYH/nxcXFys3NlcvlSo5dccUVCgaDisVicrvdKa35lIHUvmzZMs2ZM0d33nmnJGn8+PGKRCKaP3++li5dquzsc/d9T3/HaX5+/jl5lkmiH9GPnKMnZVZPGvRX53a7VVZWpsbGxuRYIpFQY2OjKioq+lxTUVHRY74kvfvuu/3OT5WB1C5JTz75pFasWKGGhgZNnjw5HaX24rT2cePG6dNPP1Vzc3Nyu+WWW3TDDTeoublZfr//nKxbkqZOnar9+/cnm6okff755youLk5bc5IGVntHR0evJnSq0Zpz/LGR58px6gT9iH6U6toletJgOSvHqtMr1FNh06ZNxuPxmI0bN5rdu3eb+fPnm2HDhplgMGiMMWbOnDlm8eLFyfkfffSRycnJMatXrzZ79uwxgUBgUL/i66T2VatWGbfbbV5//XXz3//+N7m1t7ef87X/2GB9W8Vp3YcOHTJDhw41f/zjH82+ffvM22+/bUaMGGEef/zxc772QCBghg4dav7xj3+YAwcOmH/+859mzJgx5tZbb0177e3t7WbXrl1m165dRpJZs2aN2bVrl/nqq6+MMcYsXrzYzJkzJzn/1Nd7//znP5s9e/aYurq68+aWA/Qj+pET9KTM6UnnRGgyxpinn37ajBo1yrjdbjNlyhTzySefJP9s+vTpprq6usf8V1991YwdO9a43W5z1VVXma1bt6a54u85qf3SSy81knptgUAg/YUb53/vPzSYTcpp3R9//LEpLy83Ho/HjB492jzxxBOmq6srzVV3c1L7iRMnzCOPPGLGjBljvF6v8fv95t577zXfffdd2ut+//33+/y/e6re6upqM3369F5rJk2aZNxutxk9erR58cUX0173QNCPAukv3Jy//cgYelKm9KQsY87x82kAAADngEG/pgkAAOB8QGgCAACwQGgCAACwQGgCAACwQGgCAACwQGgCAACwQGgCAACwQGgCAACwQGgCAACwQGgCAACwQGgCAACw8P+J+/AF07Vw5wAAAABJRU5ErkJggg==\",\n      \"text/plain\": [\n       \"<Figure size 600x600 with 4 Axes>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# The mistakes look all quite reasonable.\\n\",\n    \"show_img_grid(\\n\",\n    \"    [batch['image'][idx] for idx in error_idxs[:9]],\\n\",\n    \"    [f'pred: {imagenet2012_label(preds_labels[idx][0])}\\\\n'\\n\",\n    \"     f'label: {imagenet2012_label(preds_labels[idx][1])}'\\n\",\n    \"    for idx in error_idxs[:9]],\\n\",\n    \")\\n\",\n    \"plt.tight_layout()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 53,\n   \"metadata\": {\n    \"outputId\": \"4fae2533-5598-4f2e-c133-50bfba463311\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Constructing tf.data.Dataset imagenette for split validation[0:3925], from /root/tensorflow_datasets/imagenette/full-size-v2/1.0.0\\n\",\n      \"WARNING:absl:options.experimental_threading is deprecated. Use options.threading instead.\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Define parallelized inference function in separate cell so the cached\\n\",\n    \"# compilation can be used if below cell is executed multiple times.\\n\",\n    \"@jax.pmap\\n\",\n    \"def p_get_logits(images):\\n\",\n    \"  return model.apply({'params': state.params, 'batch_stats': state.batch_stats},\\n\",\n    \"                     images, train=False)\\n\",\n    \"\\n\",\n    \"eval_iter = train.create_input_iter(dataset_builder, config.batch_size,\\n\",\n    \"                                    input_pipeline.IMAGE_SIZE, tf.float32,\\n\",\n    \"                                    train=False, cache=False, shuffle_buffer_size=None,\\n\",\n    \"                                    prefetch=1)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 54,\n   \"metadata\": {\n    \"outputId\": \"d01e1993-28ab-4a4a-ac58-01c83b80e6c9\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Step 1/7...\\n\",\n      \"Step 2/7...\\n\",\n      \"Step 3/7...\\n\",\n      \"Step 4/7...\\n\",\n      \"Step 5/7...\\n\",\n      \"Step 6/7...\\n\",\n      \"Step 7/7...\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Array(0.9118304, dtype=float32)\"\n      ]\n     },\n     \"execution_count\": 54,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# Compute accuracy.\\n\",\n    \"eval_steps = dataset_builder.info.splits['validation'].num_examples // config.batch_size\\n\",\n    \"count = correct = 0\\n\",\n    \"for step, batch in zip(range(eval_steps), eval_iter):\\n\",\n    \"  labels = [imagenette_imagenet2012(label) for label in batch['label'].flatten()]\\n\",\n    \"  logits = p_get_logits(batch['image'])\\n\",\n    \"  logits = logits.reshape([-1, logits.shape[-1]])\\n\",\n    \"  print(f'Step {step+1}/{eval_steps}...')\\n\",\n    \"  count += len(labels)\\n\",\n    \"  correct += (logits.argmax(axis=-1) == jnp.array(labels)).sum()\\n\",\n    \"\\n\",\n    \"correct / count\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"accelerator\": \"GPU\",\n  \"gpuClass\": \"standard\",\n  \"language_info\": {\n   \"name\": \"python\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "examples/imagenet/imagenet_benchmark.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Benchmark for the ImageNet example.\"\"\"\n\nimport time\n\nfrom absl import flags\nfrom absl.testing import absltest\nfrom absl.testing.flagsaver import flagsaver\nfrom flax.testing import Benchmark\nimport jax\nimport numpy as np\n\n# Local imports.\nimport main\nfrom configs import v100_x8_mixed_precision as config_lib\n\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\nFLAGS = flags.FLAGS\n\n\nclass ImagenetBenchmark(Benchmark):\n  \"\"\"Benchmarks for the ImageNet Flax example.\"\"\"\n\n  @flagsaver\n  def _test_8x_v100_half_precision(\n      self, num_epochs: int, min_accuracy, max_accuracy\n  ):\n    \"\"\"Utility to benchmark ImageNet on 8xV100 GPUs. Use in your test func.\"\"\"\n    # Prepare and set flags defined in main.py.\n    config = config_lib.get_config()\n    config.num_epochs = num_epochs\n    workdir = self.get_tmp_model_dir()\n\n    FLAGS.workdir = workdir\n    FLAGS.config = config\n\n    start_time = time.time()\n    main.main([])\n    benchmark_time = time.time() - start_time\n    summaries = self.read_summaries(workdir)\n\n    # Summaries contain all the information necessary for the regression\n    # metrics.\n    wall_time, _, eval_accuracy = zip(*summaries['eval_accuracy'])\n    wall_time = np.array(wall_time)\n    sec_per_epoch = np.mean(wall_time[1:] - wall_time[:-1])\n    end_accuracy = eval_accuracy[-1]\n\n    # Assertions are deferred until the test finishes, so the metrics are\n    # always reported and benchmark success is determined based on *all*\n    # assertions.\n    self.assertBetween(end_accuracy, min_accuracy, max_accuracy)\n\n    # Use the reporting API to report single or multiple metrics/extras.\n    self.report_wall_time(benchmark_time)\n    self.report_metrics(\n        {'sec_per_epoch': sec_per_epoch, 'accuracy': end_accuracy}\n    )\n\n  def test_8x_v100_half_precision_short(self):\n    \"\"\"Run ImageNet on 8x V100 GPUs in half precision for 2 epochs.\"\"\"\n    self._test_8x_v100_half_precision(\n        num_epochs=2, min_accuracy=0.06, max_accuracy=0.09\n    )\n    self.report_extras({\n        'description': 'Short (2 epochs) 8 x V100 test for ImageNet ResNet50.',\n        'model_name': 'resnet50',\n        'parameters': 'hp=true,bs=2048,num_epochs=2',\n        'implementation': 'linen',\n    })\n\n  def test_8x_v100_half_precision_full(self):\n    \"\"\"Run ImageNet on 8x V100 GPUs in half precision for full 90 epochs.\"\"\"\n    self._test_8x_v100_half_precision(\n        num_epochs=90, min_accuracy=0.76, max_accuracy=0.77\n    )\n    self.report_extras({\n        'description': 'Full (90 epochs) 8 x V100 test for ImageNet ResNet50.',\n        'model_name': 'resnet50',\n        'parameters': 'hp=true,bs=2048,num_epochs=90',\n        'implementation': 'linen',\n    })\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/imagenet/imagenet_fake_data_benchmark.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Benchmark for the ImageNet example using fake data for quick perf results.\n\nThis script doesn't need the dataset, but it needs the dataset metadata.\nThat can be fetched with the script `flax/tests/download_dataset_metadata.sh`.\n\"\"\"\n\nimport pathlib\nimport time\n\nfrom absl.testing import absltest\nfrom flax.testing import Benchmark\nimport jax\nimport tensorflow_datasets as tfds\n\n# Local imports.\nfrom configs import fake_data_benchmark as config_lib\nimport train\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass ImagenetBenchmarkFakeData(Benchmark):\n  \"\"\"Runs ImageNet using fake data for quickly measuring performance.\"\"\"\n\n  def test_fake_data(self):\n    workdir = self.get_tmp_model_dir()\n    config = config_lib.get_config()\n    # Go two directories up to the root of the flax directory.\n    flax_root_dir = pathlib.Path(__file__).absolute().parents[2]\n    data_dir = str(flax_root_dir) + '/.tfds/metadata'\n\n    # Warm-up first so that we are not measuring just compilation.\n    with tfds.testing.mock_data(num_examples=1024, data_dir=data_dir):\n      train.train_and_evaluate(config, workdir)\n\n    start_time = time.time()\n    with tfds.testing.mock_data(num_examples=1024, data_dir=data_dir):\n      train.train_and_evaluate(config, workdir)\n    benchmark_time = time.time() - start_time\n\n    self.report_wall_time(benchmark_time)\n    self.report_extras({\n        'description': 'ImageNet ResNet50 with fake data',\n        'model_name': 'resnet50',\n        'parameters': f'hp=true,bs={config.batch_size}',\n    })\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/imagenet/input_pipeline.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"ImageNet input pipeline.\"\"\"\n\nimport jax\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\n\nIMAGE_SIZE = 224\nCROP_PADDING = 32\nMEAN_RGB = [0.485 * 255, 0.456 * 255, 0.406 * 255]\nSTDDEV_RGB = [0.229 * 255, 0.224 * 255, 0.225 * 255]\n\n\ndef distorted_bounding_box_crop(\n    image_bytes,\n    bbox,\n    min_object_covered=0.1,\n    aspect_ratio_range=(0.75, 1.33),\n    area_range=(0.05, 1.0),\n    max_attempts=100,\n):\n  \"\"\"Generates cropped_image using one of the bboxes randomly distorted.\n\n  See `tf.image.sample_distorted_bounding_box` for more documentation.\n\n  Args:\n    image_bytes: `Tensor` of binary image data.\n    bbox: `Tensor` of bounding boxes arranged `[1, num_boxes, coords]`\n        where each coordinate is [0, 1) and the coordinates are arranged\n        as `[ymin, xmin, ymax, xmax]`. If num_boxes is 0 then use the whole\n        image.\n    min_object_covered: An optional `float`. Defaults to `0.1`. The cropped\n        area of the image must contain at least this fraction of any bounding\n        box supplied.\n    aspect_ratio_range: An optional list of `float`s. The cropped area of the\n        image must have an aspect ratio = width / height within this range.\n    area_range: An optional list of `float`s. The cropped area of the image\n        must contain a fraction of the supplied image within this range.\n    max_attempts: An optional `int`. Number of attempts at generating a cropped\n        region of the image of the specified constraints. After `max_attempts`\n        failures, return the entire image.\n  Returns:\n    cropped image `Tensor`\n  \"\"\"\n  shape = tf.io.extract_jpeg_shape(image_bytes)\n  sample_distorted_bounding_box = tf.image.sample_distorted_bounding_box(\n      shape,\n      bounding_boxes=bbox,\n      min_object_covered=min_object_covered,\n      aspect_ratio_range=aspect_ratio_range,\n      area_range=area_range,\n      max_attempts=max_attempts,\n      use_image_if_no_bounding_boxes=True,\n  )\n  bbox_begin, bbox_size, _ = sample_distorted_bounding_box\n\n  # Crop the image to the specified bounding box.\n  offset_y, offset_x, _ = tf.unstack(bbox_begin)\n  target_height, target_width, _ = tf.unstack(bbox_size)\n  crop_window = tf.stack([offset_y, offset_x, target_height, target_width])\n  image = tf.io.decode_and_crop_jpeg(image_bytes, crop_window, channels=3)\n\n  return image\n\n\ndef _resize(image, image_size):\n  return tf.image.resize(\n      [image], [image_size, image_size], method=tf.image.ResizeMethod.BICUBIC\n  )[0]\n\n\ndef _at_least_x_are_equal(a, b, x):\n  \"\"\"At least `x` of `a` and `b` `Tensors` are equal.\"\"\"\n  match = tf.equal(a, b)\n  match = tf.cast(match, tf.int32)\n  return tf.greater_equal(tf.reduce_sum(match), x)\n\n\ndef _decode_and_random_crop(image_bytes, image_size):\n  \"\"\"Make a random crop of image_size.\"\"\"\n  bbox = tf.constant([0.0, 0.0, 1.0, 1.0], dtype=tf.float32, shape=[1, 1, 4])\n  image = distorted_bounding_box_crop(\n      image_bytes,\n      bbox,\n      min_object_covered=0.1,\n      aspect_ratio_range=(3.0 / 4, 4.0 / 3.0),\n      area_range=(0.08, 1.0),\n      max_attempts=10,\n  )\n  original_shape = tf.io.extract_jpeg_shape(image_bytes)\n  bad = _at_least_x_are_equal(original_shape, tf.shape(image), 3)\n\n  image = tf.cond(\n      bad,\n      lambda: _decode_and_center_crop(image_bytes, image_size),\n      lambda: _resize(image, image_size),\n  )\n\n  return image\n\n\ndef _decode_and_center_crop(image_bytes, image_size):\n  \"\"\"Crops to center of image with padding then scales image_size.\"\"\"\n  shape = tf.io.extract_jpeg_shape(image_bytes)\n  image_height = shape[0]\n  image_width = shape[1]\n\n  padded_center_crop_size = tf.cast(\n      (\n          (image_size / (image_size + CROP_PADDING))\n          * tf.cast(tf.minimum(image_height, image_width), tf.float32)\n      ),\n      tf.int32,\n  )\n\n  offset_height = ((image_height - padded_center_crop_size) + 1) // 2\n  offset_width = ((image_width - padded_center_crop_size) + 1) // 2\n  crop_window = tf.stack([\n      offset_height,\n      offset_width,\n      padded_center_crop_size,\n      padded_center_crop_size,\n  ])\n  image = tf.io.decode_and_crop_jpeg(image_bytes, crop_window, channels=3)\n  image = _resize(image, image_size)\n\n  return image\n\n\ndef normalize_image(image):\n  image -= tf.constant(MEAN_RGB, shape=[1, 1, 3], dtype=image.dtype)\n  image /= tf.constant(STDDEV_RGB, shape=[1, 1, 3], dtype=image.dtype)\n  return image\n\n\ndef preprocess_for_train(image_bytes, dtype=tf.float32, image_size=IMAGE_SIZE):\n  \"\"\"Preprocesses the given image for training.\n\n  Args:\n    image_bytes: `Tensor` representing an image binary of arbitrary size.\n    dtype: data type of the image.\n    image_size: image size.\n\n  Returns:\n    A preprocessed image `Tensor`.\n  \"\"\"\n  image = _decode_and_random_crop(image_bytes, image_size)\n  image = tf.reshape(image, [image_size, image_size, 3])\n  image = tf.image.random_flip_left_right(image)\n  image = normalize_image(image)\n  image = tf.image.convert_image_dtype(image, dtype=dtype)\n  return image\n\n\ndef preprocess_for_eval(image_bytes, dtype=tf.float32, image_size=IMAGE_SIZE):\n  \"\"\"Preprocesses the given image for evaluation.\n\n  Args:\n    image_bytes: `Tensor` representing an image binary of arbitrary size.\n    dtype: data type of the image.\n    image_size: image size.\n\n  Returns:\n    A preprocessed image `Tensor`.\n  \"\"\"\n  image = _decode_and_center_crop(image_bytes, image_size)\n  image = tf.reshape(image, [image_size, image_size, 3])\n  image = normalize_image(image)\n  image = tf.image.convert_image_dtype(image, dtype=dtype)\n  return image\n\n\ndef create_split(\n    dataset_builder,\n    batch_size,\n    train,\n    dtype=tf.float32,\n    image_size=IMAGE_SIZE,\n    cache=False,\n    shuffle_buffer_size=2_000,\n    prefetch=10,\n):\n  \"\"\"Creates a split from the ImageNet dataset using TensorFlow Datasets.\n\n  Args:\n    dataset_builder: TFDS dataset builder for ImageNet.\n    batch_size: the batch size returned by the data pipeline.\n    train: Whether to load the train or evaluation split.\n    dtype: data type of the image.\n    image_size: The target size of the images.\n    cache: Whether to cache the dataset.\n    shuffle_buffer_size: Size of the shuffle buffer.\n    prefetch: Number of items to prefetch in the dataset.\n  Returns:\n    A `tf.data.Dataset`.\n  \"\"\"\n  if train:\n    train_examples = dataset_builder.info.splits['train'].num_examples\n    split_size = train_examples // jax.process_count()\n    start = jax.process_index() * split_size\n    split = f'train[{start}:{start + split_size}]'\n  else:\n    validate_examples = dataset_builder.info.splits['validation'].num_examples\n    split_size = validate_examples // jax.process_count()\n    start = jax.process_index() * split_size\n    split = f'validation[{start}:{start + split_size}]'\n\n  def decode_example(example):\n    if train:\n      image = preprocess_for_train(example['image'], dtype, image_size)\n    else:\n      image = preprocess_for_eval(example['image'], dtype, image_size)\n    return {'image': image, 'label': example['label']}\n\n  ds = dataset_builder.as_dataset(\n      split=split,\n      decoders={\n          'image': tfds.decode.SkipDecoding(),\n      },\n  )\n  options = tf.data.Options()\n  options.experimental_threading.private_threadpool_size = 48\n  ds = ds.with_options(options)\n\n  if cache:\n    ds = ds.cache()\n\n  if train:\n    ds = ds.repeat()\n    ds = ds.shuffle(shuffle_buffer_size, seed=0)\n\n  ds = ds.map(decode_example, num_parallel_calls=tf.data.experimental.AUTOTUNE)\n  ds = ds.batch(batch_size, drop_remainder=True)\n\n  if not train:\n    ds = ds.repeat()\n\n  ds = ds.prefetch(prefetch)\n\n  return ds\n"
  },
  {
    "path": "examples/imagenet/main.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Main file for running the ImageNet example.\n\nThis file is intentionally kept short. The majority for logic is in libraries\nthat can be easily tested and imported in Colab.\n\"\"\"\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nfrom clu import platform\nimport jax\nfrom ml_collections import config_flags\nimport tensorflow as tf\n\nimport train\n\n\nFLAGS = flags.FLAGS\n\nflags.DEFINE_string('workdir', None, 'Directory to store model data.')\nconfig_flags.DEFINE_config_file(\n    'config',\n    None,\n    'File path to the training hyperparameter configuration.',\n    lock_config=True,\n)\n\n\ndef main(argv):\n  if len(argv) > 1:\n    raise app.UsageError('Too many command-line arguments.')\n\n  # Hide any GPUs from TensorFlow. Otherwise TF might reserve memory and make\n  # it unavailable to JAX.\n  tf.config.experimental.set_visible_devices([], 'GPU')\n\n  logging.info('JAX process: %d / %d', jax.process_index(), jax.process_count())\n  logging.info('JAX local devices: %r', jax.local_devices())\n\n  # Add a note so that we can tell which task is which JAX host.\n  # (Depending on the platform task 0 is not guaranteed to be host 0)\n  platform.work_unit().set_task_status(\n      f'process_index: {jax.process_index()}, '\n      f'process_count: {jax.process_count()}'\n  )\n  platform.work_unit().create_artifact(\n      platform.ArtifactType.DIRECTORY, FLAGS.workdir, 'workdir'\n  )\n\n  train.train_and_evaluate(FLAGS.config, FLAGS.workdir)\n\n\nif __name__ == '__main__':\n  flags.mark_flags_as_required(['config', 'workdir'])\n  app.run(main)\n"
  },
  {
    "path": "examples/imagenet/models.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Flax implementation of ResNet V1.5.\"\"\"\n\n# See issue #620.\n# pytype: disable=wrong-arg-count\n\nfrom functools import partial\nfrom typing import Any, Tuple\nfrom collections.abc import Callable, Sequence\n\nfrom flax import linen as nn\nimport jax.numpy as jnp\n\nModuleDef = Any\n\n\nclass ResNetBlock(nn.Module):\n  \"\"\"ResNet block.\"\"\"\n\n  filters: int\n  conv: ModuleDef\n  norm: ModuleDef\n  act: Callable\n  strides: tuple[int, int] = (1, 1)\n\n  @nn.compact\n  def __call__(\n      self,\n      x,\n  ):\n    residual = x\n    y = self.conv(self.filters, (3, 3), self.strides)(x)\n    y = self.norm()(y)\n    y = self.act(y)\n    y = self.conv(self.filters, (3, 3))(y)\n    y = self.norm(scale_init=nn.initializers.zeros_init())(y)\n\n    if residual.shape != y.shape:\n      residual = self.conv(\n          self.filters, (1, 1), self.strides, name='conv_proj'\n      )(residual)\n      residual = self.norm(name='norm_proj')(residual)\n\n    return self.act(residual + y)\n\n\nclass BottleneckResNetBlock(nn.Module):\n  \"\"\"Bottleneck ResNet block.\"\"\"\n\n  filters: int\n  conv: ModuleDef\n  norm: ModuleDef\n  act: Callable\n  strides: tuple[int, int] = (1, 1)\n\n  @nn.compact\n  def __call__(self, x):\n    residual = x\n    y = self.conv(self.filters, (1, 1))(x)\n    y = self.norm()(y)\n    y = self.act(y)\n    y = self.conv(self.filters, (3, 3), self.strides)(y)\n    y = self.norm()(y)\n    y = self.act(y)\n    y = self.conv(self.filters * 4, (1, 1))(y)\n    y = self.norm(scale_init=nn.initializers.zeros_init())(y)\n\n    if residual.shape != y.shape:\n      residual = self.conv(\n          self.filters * 4, (1, 1), self.strides, name='conv_proj'\n      )(residual)\n      residual = self.norm(name='norm_proj')(residual)\n\n    return self.act(residual + y)\n\n\nclass ResNet(nn.Module):\n  \"\"\"ResNetV1.5.\"\"\"\n\n  stage_sizes: Sequence[int]\n  block_cls: ModuleDef\n  num_classes: int\n  num_filters: int = 64\n  dtype: Any = jnp.float32\n  act: Callable = nn.relu\n  conv: ModuleDef = nn.Conv\n\n  @nn.compact\n  def __call__(self, x, train: bool = True):\n    conv = partial(self.conv, use_bias=False, dtype=self.dtype)\n    norm = partial(\n        nn.BatchNorm,\n        use_running_average=not train,\n        momentum=0.9,\n        epsilon=1e-5,\n        dtype=self.dtype,\n        axis_name='batch',\n    )\n\n    x = conv(\n        self.num_filters,\n        (7, 7),\n        (2, 2),\n        padding=[(3, 3), (3, 3)],\n        name='conv_init',\n    )(x)\n    x = norm(name='bn_init')(x)\n    x = nn.relu(x)\n    x = nn.max_pool(x, (3, 3), strides=(2, 2), padding='SAME')\n    for i, block_size in enumerate(self.stage_sizes):\n      for j in range(block_size):\n        strides = (2, 2) if i > 0 and j == 0 else (1, 1)\n        x = self.block_cls(\n            self.num_filters * 2**i,\n            strides=strides,\n            conv=conv,\n            norm=norm,\n            act=self.act,\n        )(x)\n    x = jnp.mean(x, axis=(1, 2))\n    x = nn.Dense(self.num_classes, dtype=self.dtype)(x)\n    x = jnp.asarray(x, self.dtype)\n    return x\n\n\nResNet18 = partial(ResNet, stage_sizes=[2, 2, 2, 2], block_cls=ResNetBlock)\nResNet34 = partial(ResNet, stage_sizes=[3, 4, 6, 3], block_cls=ResNetBlock)\nResNet50 = partial(\n    ResNet, stage_sizes=[3, 4, 6, 3], block_cls=BottleneckResNetBlock\n)\nResNet101 = partial(\n    ResNet, stage_sizes=[3, 4, 23, 3], block_cls=BottleneckResNetBlock\n)\nResNet152 = partial(\n    ResNet, stage_sizes=[3, 8, 36, 3], block_cls=BottleneckResNetBlock\n)\nResNet200 = partial(\n    ResNet, stage_sizes=[3, 24, 36, 3], block_cls=BottleneckResNetBlock\n)\n\n\nResNet18Local = partial(\n    ResNet, stage_sizes=[2, 2, 2, 2], block_cls=ResNetBlock, conv=nn.ConvLocal\n)\n\n\n# Used for testing only.\n_ResNet1 = partial(ResNet, stage_sizes=[1], block_cls=ResNetBlock)\n_ResNet1Local = partial(\n    ResNet, stage_sizes=[1], block_cls=ResNetBlock, conv=nn.ConvLocal\n)\n"
  },
  {
    "path": "examples/imagenet/models_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.examples.imagenet.models.\"\"\"\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\n\nimport jax\nfrom jax import numpy as jnp\n\nimport models\n\n\njax.config.update('jax_disable_most_optimizations', True)\n\n\nclass ResNetTest(parameterized.TestCase):\n  \"\"\"Test cases for ResNet v1.5 model definition.\"\"\"\n\n  def test_resnet_model(self):\n    \"\"\"Tests ResNet V1.5 model definition and output (variables).\"\"\"\n    rng = jax.random.key(0)\n    model_def = models.ResNet50(num_classes=10, dtype=jnp.float32)\n    variables = model_def.init(rng, jnp.ones((8, 224, 224, 3), jnp.float32))\n\n    self.assertLen(variables, 2)\n    # Resnet50 model will create parameters for the following layers:\n    #   conv + batch_norm = 2\n    #   BottleneckResNetBlock in stages: [3, 4, 6, 3] = 16\n    #   Followed by a Dense layer = 1\n    self.assertLen(variables['params'], 19)\n\n  @parameterized.product(model=(models.ResNet18, models.ResNet18Local))\n  def test_resnet_18_model(self, model):\n    \"\"\"Tests ResNet18 V1.5 model definition and output (variables).\"\"\"\n    rng = jax.random.key(0)\n    model_def = model(num_classes=2, dtype=jnp.float32)\n    variables = model_def.init(rng, jnp.ones((1, 64, 64, 3), jnp.float32))\n\n    self.assertLen(variables, 2)\n    self.assertLen(variables['params'], 11)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/imagenet/requirements.txt",
    "content": "absl-py==1.0.0\nclu==0.0.6\nflax==0.6.5\n-f https://storage.googleapis.com/jax-releases/libtpu_releases.html\n-f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html\njax[cuda11_cudnn805]>=0.3.16  # change to jax[tpu] if running on tpus\nml-collections==0.1.0\nnumpy==1.22.0\noptax==0.1.3\ntensorflow==2.11.1\ntensorflow-datasets==4.4.0\n"
  },
  {
    "path": "examples/imagenet/train.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"ImageNet example.\n\nThis script trains a ResNet-50 on the ImageNet dataset.\nThe data is loaded using tensorflow_datasets.\n\"\"\"\n\nimport functools\nimport time\nfrom typing import Any\n\nfrom absl import logging\nfrom clu import metric_writers\nfrom clu import periodic_actions\nfrom flax import jax_utils\nfrom flax.training import checkpoints\nfrom flax.training import common_utils\nfrom flax.training import dynamic_scale as dynamic_scale_lib\nfrom flax.training import train_state\nimport jax\nfrom jax import lax\nimport jax.numpy as jnp\nfrom jax import random\nimport ml_collections\nimport optax\nimport orbax.checkpoint as ocp\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\nimport input_pipeline\nimport models\n\n\nNUM_CLASSES = 1000\n\n\ndef create_model(*, model_cls, half_precision, **kwargs):\n  platform = jax.local_devices()[0].platform\n  if half_precision:\n    if platform == 'tpu':\n      model_dtype = jnp.bfloat16\n    else:\n      model_dtype = jnp.float16\n  else:\n    model_dtype = jnp.float32\n  return model_cls(num_classes=NUM_CLASSES, dtype=model_dtype, **kwargs)\n\n\ndef initialized(key, image_size, model):\n  input_shape = (1, image_size, image_size, 3)\n\n  @jax.jit\n  def init(*args):\n    return model.init(*args)\n\n  variables = init({'params': key}, jnp.ones(input_shape, model.dtype))\n  return variables['params'], variables['batch_stats']\n\n\ndef cross_entropy_loss(logits, labels):\n  one_hot_labels = common_utils.onehot(labels, num_classes=NUM_CLASSES)\n  xentropy = optax.softmax_cross_entropy(logits=logits, labels=one_hot_labels)\n  return jnp.mean(xentropy)\n\n\ndef compute_metrics(logits, labels):\n  loss = cross_entropy_loss(logits, labels)\n  accuracy = jnp.mean(jnp.argmax(logits, -1) == labels)\n  metrics = {\n      'loss': loss,\n      'accuracy': accuracy,\n  }\n  metrics = lax.pmean(metrics, axis_name='batch')\n  return metrics\n\n\ndef create_learning_rate_fn(\n    config: ml_collections.ConfigDict,\n    base_learning_rate: float,\n    steps_per_epoch: int,\n):\n  \"\"\"Create learning rate schedule.\"\"\"\n  warmup_fn = optax.linear_schedule(\n      init_value=0.0,\n      end_value=base_learning_rate,\n      transition_steps=config.warmup_epochs * steps_per_epoch,\n  )\n  cosine_epochs = max(config.num_epochs - config.warmup_epochs, 1)\n  cosine_fn = optax.cosine_decay_schedule(\n      init_value=base_learning_rate, decay_steps=cosine_epochs * steps_per_epoch\n  )\n  schedule_fn = optax.join_schedules(\n      schedules=[warmup_fn, cosine_fn],\n      boundaries=[config.warmup_epochs * steps_per_epoch],\n  )\n  return schedule_fn\n\n\ndef train_step(state, batch, learning_rate_fn):\n  \"\"\"Perform a single training step.\"\"\"\n\n  def loss_fn(params):\n    \"\"\"loss function used for training.\"\"\"\n    logits, new_model_state = state.apply_fn(\n        {'params': params, 'batch_stats': state.batch_stats},\n        batch['image'],\n        mutable=['batch_stats'],\n    )\n    loss = cross_entropy_loss(logits, batch['label'])\n    weight_penalty_params = jax.tree_util.tree_leaves(params)\n    weight_decay = 0.0001\n    weight_l2 = sum(\n        jnp.sum(x**2) for x in weight_penalty_params if x.ndim > 1\n    )\n    weight_penalty = weight_decay * 0.5 * weight_l2\n    loss = loss + weight_penalty\n    return loss, (new_model_state, logits)\n\n  step = state.step\n  dynamic_scale = state.dynamic_scale\n  lr = learning_rate_fn(step)\n\n  if dynamic_scale:\n    grad_fn = dynamic_scale.value_and_grad(\n        loss_fn, has_aux=True, axis_name='batch'\n    )\n    dynamic_scale, is_fin, aux, grads = grad_fn(state.params)\n    # dynamic loss takes care of averaging gradients across replicas\n  else:\n    grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n    aux, grads = grad_fn(state.params)\n    # Re-use same axis_name as in the call to `pmap(...train_step...)` below.\n    grads = lax.pmean(grads, axis_name='batch')\n  new_model_state, logits = aux[1]\n  metrics = compute_metrics(logits, batch['label'])\n  metrics['learning_rate'] = lr\n\n  new_state = state.apply_gradients(\n      grads=grads,\n      batch_stats=lax.pmean(new_model_state['batch_stats'], 'batch'),\n  )\n  if dynamic_scale:\n    # if is_fin == False the gradients contain Inf/NaNs and optimizer state and\n    # params should be restored (= skip this step).\n    new_state = new_state.replace(\n        opt_state=jax.tree_util.tree_map(\n            functools.partial(jnp.where, is_fin),\n            new_state.opt_state,\n            state.opt_state,\n        ),\n        params=jax.tree_util.tree_map(\n            functools.partial(jnp.where, is_fin), new_state.params, state.params\n        ),\n        dynamic_scale=dynamic_scale,\n    )\n    metrics['scale'] = dynamic_scale.scale\n\n  return new_state, metrics\n\n\ndef eval_step(state, batch):\n  variables = {'params': state.params, 'batch_stats': state.batch_stats}\n  logits = state.apply_fn(variables, batch['image'], train=False, mutable=False)\n  return compute_metrics(logits, batch['label'])\n\n\ndef prepare_tf_data(xs):\n  \"\"\"Convert a input batch from tf Tensors to numpy arrays.\"\"\"\n  local_device_count = jax.local_device_count()\n\n  def _prepare(x):\n    # Use _numpy() for zero-copy conversion between TF and NumPy.\n    x = x._numpy()  # pylint: disable=protected-access\n\n    # reshape (host_batch_size, height, width, 3) to\n    # (local_devices, device_batch_size, height, width, 3)\n    return x.reshape((local_device_count, -1) + x.shape[1:])\n\n  return jax.tree_util.tree_map(_prepare, xs)\n\n\ndef create_input_iter(\n    dataset_builder,\n    batch_size,\n    image_size,\n    dtype,\n    train,\n    cache,\n    shuffle_buffer_size,\n    prefetch,\n):\n  ds = input_pipeline.create_split(\n      dataset_builder,\n      batch_size,\n      image_size=image_size,\n      dtype=dtype,\n      train=train,\n      cache=cache,\n      shuffle_buffer_size=shuffle_buffer_size,\n      prefetch=prefetch,\n  )\n  it = map(prepare_tf_data, ds)\n  it = jax_utils.prefetch_to_device(it, 2)\n  return it\n\n\nclass TrainState(train_state.TrainState):\n  batch_stats: Any\n  dynamic_scale: dynamic_scale_lib.DynamicScale\n\n\ndef restore_checkpoint(state, workdir):\n  return checkpoints.restore_checkpoint(workdir, state)\n\n\ndef save_checkpoint(state, workdir):\n  step = int(state.step)\n  logging.info('Saving checkpoint step %d.', step)\n\n  # Orbax can not handle host local arrays from pmap. Convert to global arrays.\n  replicated_state = jax.tree_util.tree_map(\n      ocp.utils.fully_replicated_host_local_array_to_global_array,\n      state,\n  )\n  checkpoints.save_checkpoint_multiprocess(\n      workdir, replicated_state, step, keep=3\n  )\n\n\ndef create_train_state(\n    rng, config: ml_collections.ConfigDict, model, image_size, learning_rate_fn\n):\n  \"\"\"Create initial training state.\"\"\"\n  dynamic_scale = None\n  platform = jax.local_devices()[0].platform\n  if config.half_precision and platform == 'gpu':\n    dynamic_scale = dynamic_scale_lib.DynamicScale()\n\n  params, batch_stats = initialized(rng, image_size, model)\n  tx = optax.sgd(\n      learning_rate=learning_rate_fn,\n      momentum=config.momentum,\n      nesterov=True,\n  )\n  state = TrainState.create(\n      apply_fn=model.apply,\n      params=params,\n      tx=tx,\n      batch_stats=batch_stats,\n      dynamic_scale=dynamic_scale,\n  )\n  return state\n\n\ndef train_and_evaluate(\n    config: ml_collections.ConfigDict, workdir: str\n) -> TrainState:\n  \"\"\"Execute model training and evaluation loop.\n\n  Args:\n    config: Hyperparameter configuration for training and evaluation.\n    workdir: Directory where the tensorboard summaries are written to.\n\n  Returns:\n    Final TrainState.\n  \"\"\"\n\n  writer = metric_writers.create_default_writer(\n      logdir=workdir, just_logging=jax.process_index() != 0\n  )\n\n  rng = random.key(0)\n\n  image_size = 224\n\n  if config.batch_size % jax.device_count() > 0:\n    raise ValueError('Batch size must be divisible by the number of devices')\n  local_batch_size = config.batch_size // jax.process_count()\n\n  platform = jax.local_devices()[0].platform\n\n  if config.half_precision:\n    if platform == 'tpu':\n      input_dtype = tf.bfloat16\n    else:\n      input_dtype = tf.float16\n  else:\n    input_dtype = tf.float32\n\n  dataset_builder = tfds.builder(config.dataset)\n  train_iter = create_input_iter(\n      dataset_builder,\n      local_batch_size,\n      image_size,\n      input_dtype,\n      train=True,\n      cache=config.cache,\n      shuffle_buffer_size=config.shuffle_buffer_size,\n      prefetch=config.prefetch,\n  )\n  eval_iter = create_input_iter(\n      dataset_builder,\n      local_batch_size,\n      image_size,\n      input_dtype,\n      train=False,\n      cache=config.cache,\n      shuffle_buffer_size=None,\n      prefetch=config.prefetch,\n  )\n\n  steps_per_epoch = (\n      dataset_builder.info.splits['train'].num_examples // config.batch_size\n  )\n\n  if config.num_train_steps <= 0:\n    num_steps = int(steps_per_epoch * config.num_epochs)\n  else:\n    num_steps = config.num_train_steps\n\n  if config.steps_per_eval == -1:\n    num_validation_examples = dataset_builder.info.splits[\n        'validation'\n    ].num_examples\n    steps_per_eval = num_validation_examples // config.batch_size\n  else:\n    steps_per_eval = config.steps_per_eval\n\n  steps_per_checkpoint = steps_per_epoch * 10\n\n  base_learning_rate = config.learning_rate * config.batch_size / 256.0\n\n  model_cls = getattr(models, config.model)\n  model = create_model(\n      model_cls=model_cls, half_precision=config.half_precision\n  )\n\n  learning_rate_fn = create_learning_rate_fn(\n      config, base_learning_rate, steps_per_epoch\n  )\n\n  state = create_train_state(rng, config, model, image_size, learning_rate_fn)\n  state = restore_checkpoint(state, workdir)\n  # step_offset > 0 if restarting from checkpoint\n  step_offset = int(state.step)\n\n  p_train_step = jax.pmap(\n      functools.partial(train_step, learning_rate_fn=learning_rate_fn),\n      in_axes=(None, 0),\n      out_axes=(None, 0),\n      axis_name='batch',\n  )\n  p_eval_step = jax.pmap(eval_step, in_axes=(None, 0), axis_name='batch')\n\n  train_metrics = []\n  hooks = []\n  if jax.process_index() == 0 and config.profile:\n    hooks += [\n        periodic_actions.Profile(\n            num_profile_steps=3, profile_duration_ms=None, logdir=workdir\n        )\n    ]\n  train_metrics_last_t = time.time()\n  logging.info('Initial compilation, this might take some minutes...')\n  for step, batch in zip(range(step_offset, num_steps), train_iter):\n    state, metrics = p_train_step(state, batch)\n    for h in hooks:\n      h(step)\n    if step == step_offset:\n      logging.info('Initial compilation completed.')\n\n    if config.get('log_every_steps'):\n      train_metrics.append(metrics)\n      if (step + 1) % config.log_every_steps == 0:\n        train_metrics = common_utils.get_metrics(train_metrics)\n        summary = {\n            f'train_{k}': v\n            for k, v in jax.tree_util.tree_map(\n                lambda x: x.mean(), train_metrics\n            ).items()\n        }\n        summary['steps_per_second'] = config.log_every_steps / (\n            time.time() - train_metrics_last_t\n        )\n        writer.write_scalars(step + 1, summary)\n        train_metrics = []\n        train_metrics_last_t = time.time()\n\n    if (step + 1) % steps_per_epoch == 0:\n      epoch = step // steps_per_epoch\n      eval_metrics = []\n\n      for _ in range(steps_per_eval):\n        eval_batch = next(eval_iter)\n        metrics = p_eval_step(state, eval_batch)\n        eval_metrics.append(metrics)\n      eval_metrics = common_utils.get_metrics(eval_metrics)\n      summary = jax.tree_util.tree_map(lambda x: x.mean(), eval_metrics)\n      logging.info(\n          'eval epoch: %d, loss: %.4f, accuracy: %.2f',\n          epoch,\n          summary['loss'],\n          summary['accuracy'] * 100,\n      )\n      writer.write_scalars(\n          step + 1, {f'eval_{key}': val for key, val in summary.items()}\n      )\n      writer.flush()\n    if (step + 1) % steps_per_checkpoint == 0 or step + 1 == num_steps:\n      save_checkpoint(state, workdir)\n\n  # Wait until computations are done before exiting\n  jax.random.normal(jax.random.key(0), ()).block_until_ready()\n\n  return state\n"
  },
  {
    "path": "examples/imagenet/train_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.examples.imagenet.train.\"\"\"\n\nimport pathlib\nimport tempfile\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\n\nimport jax\nfrom jax import random\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\n# Local imports.\nimport models\nimport train\nfrom configs import default as default_lib\n\n\njax.config.update('jax_disable_most_optimizations', True)\n\n\nclass TrainTest(parameterized.TestCase):\n\n  def setUp(self):\n    super().setUp()\n    # Make sure tf does not allocate gpu memory.\n    tf.config.experimental.set_visible_devices([], 'GPU')\n\n  def test_create_model(self):\n    \"\"\"Tests creating model.\"\"\"\n    model = train.create_model(model_cls=models._ResNet1, half_precision=False)  # pylint: disable=protected-access\n    params, batch_stats = train.initialized(random.key(0), 224, model)\n    variables = {'params': params, 'batch_stats': batch_stats}\n    x = random.normal(random.key(1), (8, 224, 224, 3))\n    y = model.apply(variables, x, train=False)\n    self.assertEqual(y.shape, (8, 1000))\n\n  def test_create_model_local(self):\n    \"\"\"Tests creating an unshared convolution model.\n\n    Uses smaller inputs than `test_create_model` to due to higher compute.\n    \"\"\"\n    model = train.create_model(\n        model_cls=models._ResNet1Local, half_precision=False\n    )  # pylint: disable=protected-access\n    params, batch_stats = train.initialized(random.key(0), 64, model)\n    variables = {'params': params, 'batch_stats': batch_stats}\n    x = random.normal(random.key(1), (1, 64, 64, 3))\n    y = model.apply(variables, x, train=False)\n    self.assertEqual(y.shape, (1, 1000))\n\n  @parameterized.product(model=('_ResNet1', '_ResNet1Local'))\n  def test_train_and_evaluate(self, model):\n    \"\"\"Tests training and evaluation loop using mocked data.\"\"\"\n    # Create a temporary directory where tensorboard metrics are written.\n    workdir = tempfile.mkdtemp()\n\n    # Go two directories up to the root of the flax directory.\n    flax_root_dir = pathlib.Path(__file__).parents[2]\n    data_dir = str(flax_root_dir) + '/.tfds/metadata'\n\n    # Define training configuration\n    config = default_lib.get_config()\n    config.model = model\n    config.batch_size = 1\n    config.num_epochs = 1\n    config.num_train_steps = 1\n    config.steps_per_eval = 1\n\n    with tfds.testing.mock_data(num_examples=1, data_dir=data_dir):\n      train.train_and_evaluate(workdir=workdir, config=config)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/linen_design_test/attention_simple.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport functools\nfrom pprint import pprint\nfrom typing import Any, Optional\nfrom collections.abc import Callable, Sequence\nfrom flax.core.frozen_dict import unfreeze\nfrom flax.linen import initializers\nfrom flax.linen import Module, compact, vmap\nfrom flax.linen.linear import PrecisionLike\nimport jax\nfrom jax import lax, numpy as jnp, random\n\n\nclass Dense(Module):\n  features: int\n  use_bias: bool = True\n  kernel_init: Callable = initializers.lecun_normal()\n  bias_init: Callable = initializers.zeros_init()\n  dtype: Any = jnp.float32\n  precision: PrecisionLike = None\n\n  @compact\n  def __call__(self, inputs):\n    inputs = jnp.asarray(inputs, self.dtype)\n    kernel = self.param(\n        'kernel', self.kernel_init, (inputs.shape[-1], self.features)\n    )\n    kernel = jnp.asarray(kernel, self.dtype)\n    y = lax.dot_general(\n        inputs,\n        kernel,\n        (((inputs.ndim - 1,), (0,)), ((), ())),\n        precision=self.precision,\n    )\n    if self.use_bias:\n      bias = self.param('bias', self.bias_init, (self.features,))\n      bias = jnp.asarray(bias, self.dtype)\n      y = y + bias\n    return y\n\n\nclass SoftmaxAttn(Module):\n\n  @compact\n  def __call__(self, weights):\n    norm_dims = tuple(range(weights.ndim // 2, weights.ndim))\n    return jax.nn.softmax(weights, axis=norm_dims)\n\n\nclass Dropout(Module):\n  rate: float\n\n  @compact\n  def __call__(self, x, deterministic=False, rng=None):\n    if self.rate == 0.0:\n      return x\n    keep_prob = 1.0 - self.rate\n\n    if deterministic:\n      return x\n    else:\n      if rng is None:\n        rng = self.scope.make_rng('dropout')\n      mask = random.bernoulli(rng, p=keep_prob, shape=x.shape)\n      return lax.select(mask, x / keep_prob, jnp.zeros_like(x))\n\n\nclass SoftmaxAttnWDropout(Module):\n  rate: float = 0.0\n  deterministic: bool = False\n\n  @compact\n  def __call__(self, x):\n    x = SoftmaxAttn()(x)\n    x = Dropout(self.rate)(x, deterministic=self.deterministic)\n    return x\n\n\nclass RawDotProductAttention(Module):\n  attn_module: Callable = SoftmaxAttn\n\n  @compact\n  def __call__(self, query, key, value, bias=None, dtype=jnp.float32):\n    assert key.ndim == query.ndim\n    assert key.ndim == value.ndim\n\n    n = query.ndim\n    attn_weights = lax.dot_general(query, key, (((n - 1,), (n - 1,)), ((), ())))\n    if bias is not None:\n      attn_weights += bias\n    attn_weights = self.attn_module()(attn_weights)\n    attn_weights = attn_weights.astype(dtype)\n\n    contract_dims = (\n        tuple(range(n - 1, attn_weights.ndim)),\n        tuple(range(0, n - 1)),\n    )\n    y = lax.dot_general(attn_weights, value, (contract_dims, ((), ())))\n    return y\n\n\nclass DotProductAttention(Module):\n  qkv_features: int | None = None\n  out_features: int | None = None\n  attn_module: Callable = SoftmaxAttn\n\n  @compact\n  def __call__(self, inputs_q, inputs_kv, bias=None, dtype=jnp.float32):\n    qkv_features = self.qkv_features or inputs_q.shape[-1]\n    out_features = self.out_features or inputs_q.shape[-1]\n\n    QKVDense = functools.partial(\n        Dense, features=qkv_features, use_bias=False, dtype=dtype\n    )\n    query = QKVDense(name='query')(inputs_q)\n    key = QKVDense(name='key')(inputs_kv)\n    value = QKVDense(name='value')(inputs_kv)\n\n    y = RawDotProductAttention(attn_module=self.attn_module)(\n        query, key, value, bias=bias, dtype=dtype\n    )\n    y = Dense(features=out_features, dtype=dtype, name='out')(y)\n    return y\n\n\n# Trying out a slightly more compact vmap notation:\n\n\ndef concise_vmap(module, in_axes, out_axes, axis_size=None, **var_specs):\n  variable_axes = {\n      k: v[0] for k, v in var_specs.items() if isinstance(v, Sequence)\n  }\n  splits = {k: v[1] for k, v in var_specs.items() if isinstance(v, Sequence)}\n  return vmap(\n      module,\n      in_axes=in_axes,\n      out_axes=out_axes,\n      variable_axes=variable_axes,\n      split_rngs=splits,\n      axis_size=axis_size,\n  )\n\n\nclass MultiHeadDotProductAttention(Module):\n  qkv_features: int | None = None\n  out_features: int | None = None\n  attn_module: Callable = SoftmaxAttn\n  batch_axes: Sequence[int] = (0,)\n  num_heads: int = 1\n  broadcast_dropout: bool = False\n\n  @compact\n  def __call__(self, inputs_q, inputs_kv, bias=None, dtype=jnp.float32):\n    qkv_features = self.qkv_features or inputs_q.shape[-1]\n    out_features = self.out_features or inputs_q.shape[-1]\n\n    # Now, vmap attn.__call__ along heads and spatial dims.\n    Attn = concise_vmap(\n        DotProductAttention,\n        (None, None, None),\n        -2,\n        param=(0, True),\n        dropout=(None, not self.broadcast_dropout),\n        axis_size=self.num_heads,\n    )\n    for axis in reversed(sorted(self.batch_axes)):\n      Attn = concise_vmap(\n          Attn,\n          (axis, axis, axis),\n          axis,\n          param=(None, False),\n          dropout=(None, not self.broadcast_dropout),\n      )\n\n    attn = Attn(\n        attn_module=self.attn_module,\n        qkv_features=qkv_features // self.num_heads,\n        out_features=out_features,\n    )\n\n    # evaluate multi-headed-attention.\n    y = attn(inputs_q, inputs_kv, bias)\n    return y.mean(axis=-2)\n\n\n# run it.\n\n\nif __name__ == '__main__':\n  inputs = jnp.ones((8, 97, 256))\n  rngs = {'params': random.key(0), 'dropout': random.key(1)}\n  model = MultiHeadDotProductAttention(\n      broadcast_dropout=False,\n      qkv_features=256,\n      out_features=256,\n      attn_module=functools.partial(SoftmaxAttnWDropout, rate=0.1),\n      num_heads=8,\n      batch_axes=(0,),\n  )\n\n  y, params = model.init_with_output(rngs, inputs, inputs)\n\n  print('input shape: ', inputs.shape)\n  print('parameter shapes:')\n  pprint(jax.tree_util.tree_map(jnp.shape, unfreeze(params)))\n  print('output shape: ', y.shape)\n"
  },
  {
    "path": "examples/linen_design_test/autoencoder.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom typing import Tuple\nfrom collections.abc import Iterable\n\nimport jax\nfrom jax import numpy as jnp, random\n\nfrom flax import linen as nn\nfrom flax.linen import Module, Dense, compact\n\n\n# A concise MLP defined via lazy submodule initialization\nclass MLP(Module):\n  widths: Iterable\n\n  @compact\n  def __call__(self, x):\n    for width in self.widths[:-1]:\n      x = nn.relu(Dense(width)(x))\n    return Dense(self.widths[-1])(x)\n\n\n# An autoencoder exposes multiple methods, so we define all\n# submodules in setup().\nclass AutoEncoder(Module):\n  encoder_widths: Iterable\n  decoder_widths: Iterable\n  input_shape: tuple = None\n\n  def setup(self):\n    # Submodules attached in `setup` get names via attribute assignment\n    self.encoder = MLP(self.encoder_widths)\n    self.decoder = MLP(self.decoder_widths + (jnp.prod(self.input_shape),))\n\n  def __call__(self, x):\n    return self.decode(self.encode(x))\n\n  def encode(self, x):\n    assert x.shape[-len(self.input_shape) :] == self.input_shape\n    return self.encoder(jnp.reshape(x, (x.shape[0], -1)))\n\n  def decode(self, z):\n    z = self.decoder(z)\n    x = nn.sigmoid(z)\n    x = jnp.reshape(x, (x.shape[0],) + self.input_shape)\n    return x\n\n\n# `ae` is a detached module, which has no variables.\nae = AutoEncoder(\n    encoder_widths=(32, 32, 32),\n    decoder_widths=(32, 32, 32),\n    input_shape=(28, 28, 1),\n)\n\n\n# `ae.initialized` returns a materialized copy of `ae` by\n# running through an input to create submodules defined lazily.\nparams = ae.init({\"params\": random.key(42)}, jnp.ones((1, 28, 28, 1)))\n\n\n# Now you can use `ae` as a normal object, calling any methods defined on AutoEncoder\nprint(\"reconstruct\", jnp.shape(ae.apply(params, jnp.ones((1, 28, 28, 1)))))\nprint(\n    \"encoder\",\n    jnp.shape(ae.apply(params, jnp.ones((1, 28, 28, 1)), method=ae.encode)),\n)\n\n\n# `ae.variables` is a frozen dict that looks like\n# {'params': {\"decoder\": {\"Dense_0\": {\"bias\": ..., \"kernel\": ...}, ...}}\nprint(\"var shapes\", jax.tree_util.tree_map(jnp.shape, params))\n\n\n# TODO(avital, levskaya): resurrect this example once interactive api is restored.\n\n\n# You can access submodules defined in setup(), they are just references on\n# the autoencoder instance\n# encoder = ae.encoder\n# print(\"encoder var shapes\", jax.tree_util.tree_map(jnp.shape, encoder.variables))\n\n\n# # You can also access submodules that were defined in-line.\n# # (We may add syntactic sugar here, e.g. to allow `ae.encoder.Dense_0`)\n# encoder_dense0 = ae.encoder.children['Dense_0']\n# print(\"encoder dense0 var shapes\", jax.tree_util.tree_map(jnp.shape, encoder_dense0.variables))\n"
  },
  {
    "path": "examples/linen_design_test/dense.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom jax import lax\nfrom flax.linen import initializers\nfrom collections.abc import Callable\nfrom flax.linen import Module, compact\n\n\nclass Dense(Module):\n  features: int\n  kernel_init: Callable = initializers.lecun_normal()\n  bias_init: Callable = initializers.zeros_init()\n  use_bias: bool = True\n\n  @compact\n  def __call__(self, inputs):\n    kernel = self.param(\n        'kernel', self.kernel_init, (inputs.shape[-1], self.features)\n    )\n    y = lax.dot_general(\n        inputs,\n        kernel,\n        (((inputs.ndim - 1,), (0,)), ((), ())),\n    )\n    if self.use_bias:\n      bias = self.param('bias', self.bias_init, (self.features,))\n      y = y + bias\n    return y\n"
  },
  {
    "path": "examples/linen_design_test/linear_regression.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax\nfrom jax import numpy as jnp, jit\nfrom dense import Dense\n\n\nX = jnp.ones((1, 10))\nY = jnp.ones((5,))\n\nmodel = Dense(features=5)\n\n\n@jit\ndef predict(params):\n  return model.apply({\"params\": params}, X)\n\n\n@jit\ndef loss_fn(params):\n  return jnp.mean(jnp.abs(Y - predict(params)))\n\n\n@jit\ndef init_params(rng):\n  mlp_variables = model.init({\"params\": rng}, X)\n  return mlp_variables[\"params\"]\n\n\n# Get initial parameters\nparams = init_params(jax.random.key(42))\nprint(\"initial params\", params)\n\n# Run SGD.\nfor i in range(50):\n  loss, grad = jax.value_and_grad(loss_fn)(params)\n  print(i, \"loss = \", loss, \"Yhat = \", predict(params))\n  lr = 0.03\n  params = jax.tree_util.tree_map(lambda x, d: x - lr * d, params, grad)\n"
  },
  {
    "path": "examples/linen_design_test/mlp_explicit.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom pprint import pprint\nfrom typing import Optional\nfrom flax.deprecated import nn\nfrom dense import Dense\nfrom flax.linen import Module\nimport jax\nfrom jax import numpy as jnp\n\n\n# Add `in_features` to the built-in Dense layer that normally works\n# via shape inference.\nclass DenseExplicit(Dense):\n  in_features: int | None = None\n\n  def setup(self):\n    # We feed a fake batch through the module, which initialized parameters.\n    # Assuming we're in a jit, should use no FLOPs -- \"just shape inference\".\n    self.__call__(\n        jnp.zeros((\n            1,\n            self.in_features,\n        ))\n    )\n\n\nclass MLP(Module):\n\n  def setup(self):\n    self.dense1 = DenseExplicit(in_features=3, features=2)\n    self.dense2 = DenseExplicit(in_features=2, features=1)\n\n    # explicit instances are materialized immediately at init\n    pprint(self.dense2.variables)\n    # {'params': {'bias': DeviceArray([0.], dtype=float32),\n    #            'kernel': DeviceArray([[ 0.6704609 ],\n    #              [-0.90477365]], dtype=float32)}}\n\n  def __call__(self, x):\n    return self.dense2(nn.relu(self.dense1(x)))\n\n\n# Return an initialized instance of MLP by only calling `setup`.\nrngkey = jax.random.key(10)\ninit_variables = MLP().init({'params': rngkey}, jnp.ones((1, 3)))\n\npprint(init_variables)\n# {'params': {'dense1': {'bias': DeviceArray([0., 0.], dtype=float32),\n#                       'kernel': DeviceArray([[ 0.18307537, -0.38739476],\n#              [-0.902451  , -0.5190721 ],\n#              [ 0.51552075,  1.1169153 ]], dtype=float32)},\n#            'dense2': {'bias': DeviceArray([0.], dtype=float32),\n#                       'kernel': DeviceArray([[ 0.6704609 ],\n#              [-0.90477365]], dtype=float32)}}}\n"
  },
  {
    "path": "examples/linen_design_test/mlp_inline.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax\nfrom jax import numpy as jnp\nfrom flax import linen as nn\nfrom collections.abc import Iterable\nfrom flax.linen import Module, compact\nfrom dense import Dense\n\n\n# Many NN layers and blocks are best described by a single function with inline variables.\n# In this case, variables are initialized during the first call.\nclass MLP(Module):\n  sizes: Iterable[int]\n\n  @compact\n  def __call__(self, x):\n    for size in self.sizes[:-1]:\n      x = Dense(size)(x)\n      x = nn.relu(x)\n    return Dense(self.sizes[-1])(x)\n\n\n# Return an initialized instance of MLP by calling `__call__` with an input batch,\n# initializing all variables.\n#\n# Variable shapes depend on the input shape passed in.\nrngkey = jax.random.key(10)\nmodel = MLP((2, 1))\nx = jnp.ones((1, 3))\nmlp_variables = model.init(rngkey, x)\nprint(mlp_variables)\n# {'params': {'Dense_0': {'bias': DeviceArray([0.], dtype=float32),\n#                        'kernel': DeviceArray([[-0.04267037],\n#              [-0.51097125]], dtype=float32)},\n#            'Dense_1': {'bias': DeviceArray([0., 0.], dtype=float32),\n#                        'kernel': DeviceArray([[-6.3845289e-01,  6.0373604e-01],\n#              [-5.9814966e-01,  5.1718324e-01],\n#              [-6.2220657e-01,  5.8988278e-04]], dtype=float32)}}}\nprint(model.apply(mlp_variables, x))\n"
  },
  {
    "path": "examples/linen_design_test/mlp_lazy.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax\nfrom jax import numpy as jnp\nfrom flax import linen as nn\nfrom flax.linen import Module\nfrom pprint import pprint\nfrom dense import Dense\n\n\n# Here submodules are explicitly defined during init, but still materialized\n# lazily only once a first input is passed through and shapes are known.\nclass MLP(Module):\n\n  def setup(self):\n    self.dense1 = Dense(features=2)\n    self.dense2 = Dense(features=1)\n\n    # shapes aren't yet known, so variables aren't materialized\n    print(self.dense2.variables)\n    # FrozenDict({})\n\n  def __call__(self, x):\n    return self.dense2(nn.relu(self.dense1(x)))\n\n\n# Return an initialized instance of MLP by calling `__call__` with an input batch,\n# initializing all variables.\n#\n# Variable shapes depend on the input shape passed in.\nrngkey = jax.random.key(10)\nmlp_variables = MLP().init(rngkey, jnp.zeros((1, 3)))\n\npprint(mlp_variables)\n# {'params': {'dense1': {'bias': DeviceArray([0., 0.], dtype=float32),\n#                       'kernel': DeviceArray([[ 0.18307537, -0.38739476],\n#              [-0.902451  , -0.5190721 ],\n#              [ 0.51552075,  1.1169153 ]], dtype=float32)},\n#            'dense2': {'bias': DeviceArray([0.], dtype=float32),\n#                       'kernel': DeviceArray([[ 0.6704609 ],\n#              [-0.90477365]], dtype=float32)}}}\n"
  },
  {
    "path": "examples/lm1b/README.md",
    "content": "\n## Language modeling\nTrains a Transformer-based model (Vaswani *et al.*, 2017) on the One Billion\nWord Benchmark (lm1b; Chelba *et al.*, 2013).\n\nThis example uses linear learning rate warmup and inverse square root learning\nrate schedule. Based off of Machine Translation `wmt` example.\n\n\n### Requirements\n\n*   TensorFlow datasets `lm1b` need to be downloaded and prepared.\n    A sentencepiece tokenizer vocabulary will be automatically generated\n    and saved on each training run.\n*   This example additionally depends on the `sentencepiece` and\n    `tensorflow-text` packages.\n\n\n### How to run on Cloud TPUs\n\nSetup the TPU VM and install the Flax dependencies on it as described\n[here](https://cloud.google.com/tpu/docs/jax-pods) for creating pod slices, or\n[here](https://cloud.google.com/tpu/docs/jax-quickstart-tpu-vm) for a single\nv3-8 TPU.\n\n\nFirst create a single TPUv3-8 VM and connect to it (you can find more detailed\ninstructions [here](https://cloud.google.com/tpu/docs/jax-quickstart-tpu-vm)):\n\n```\nZONE=us-central1-a\nTPU_TYPE=v3-8\nTPU_NAME=$USER-flax-lm1b\ngcloud alpha compute tpus tpu-vm create $TPU_NAME \\\n    --zone $ZONE \\\n    --accelerator-type $TPU_TYPE \\\n    --version v2-alpha\ngcloud alpha compute tpus tpu-vm ssh $TPU_NAME --zone $ZONE -- \\\n    -L 6006:localhost:6006\n```\n\nWhen connected install JAX:\n\n```\npip install \"jax[tpu]>=0.2.16\" \\\n    -f https://storage.googleapis.com/jax-releases/libtpu_releases.html\n```\n\nThen install Flax + the example dependencies:\n\n```\ngit clone --depth=1 --branch=main https://github.com/google/flax\ncd flax\npip install -e .\ncd examples/lm1b\npip install -r requirements.txt\n```\n\nAnd finally start the training:\n\n```\npython3 main.py --workdir=$HOME/logs/lm1b_256 \\\n    --config.per_device_batch_size=32 \\\n    --jax_backend_target=\"grpc://192.168.0.2:8470\"\n```\n\nNote that you might want to set `TFDS_DATA_DIR` as explained below. You probably\nalso want to start the long-running command above in a `tmux` session and start\nsome monitoring in a separate pane (note that we forwarded port 6006 locally\nabove):\n\n```\ntensorboard --logdir=$HOME/logs\n```\n\nYou should expect to get numbers similar to these:\n\n\nHardware | config  | Training time |      Loss      |                             TensorBoard.dev                              |                                                          Workdir\n-------- | ------- | ------------- | -------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------\nTPU v3-8 | default | 13h18m | 3.127 | [2021-08-08](https://tensorboard.dev/experiment/n30WkNOZTJq3RHWD7wNslg/) | [gs://flax_public/examples/lm1b/default](https://console.cloud.google.com/storage/browser/flax_public/examples/lm1b/default)\n\n### Downloading the LM1B Datasets\n\nWe recommend downloading and preparing the TFDS datasets beforehand. For Cloud\nTPUs, we recommend using a cheap standard instance and saving the prepared TFDS\ndata on a storage bucket, from where it can be loaded directly. Set the\n`TFDS_DATA_DIR` to your storage bucket path (`gs://<bucket name>`).\n\nYou can download and prepare LM1B datasets using TFDS directly:\n`python -m tensorflow_datasets.scripts.download_and_prepare\n--datasets=lm1b`\n\n"
  },
  {
    "path": "examples/lm1b/configs/default.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Default Hyperparameter configuration.\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = ml_collections.ConfigDict()\n\n  # Path to load or store sentencepiece vocab file.\n  config.vocab_path = None\n\n  # Vocabulary size if `vocab_path` is not given.\n  config.vocab_size = 30_000\n\n  config.max_corpus_chars = 10**7\n\n  # Name of TFDS translation dataset to use.\n  config.dataset_name = 'lm1b'\n\n  # Optional name of TFDS translation dataset to use for evaluation.\n  config.eval_dataset_name = 'lm1b'\n  config.eval_split = 'test'\n\n  # Per device batch size for training.\n  config.per_device_batch_size = 32\n\n  # Per device batch size for training.\n  config.eval_per_device_batch_size = 32\n\n  # Sampling temperature for language model inference.\n  config.sampling_temperature = 0.6\n\n  # Top k cutoff for logit sampling. If 0 then no top-k cutoff is used.\n  config.sampling_top_k = 20\n\n  config.num_train_steps = 500_000\n\n  # Number of steps to take during evaluation. Large enough to evaluate all.\n  # Large enough to evaluate all samples: 306_688 / (32 * 8) = 1198\n  config.num_eval_steps = 2_000\n  # Number of steps to generate predictions.\n  # -1 will use the whole eval dataset.\n  config.num_predict_steps = -1\n\n  # Base learning rate.\n  config.learning_rate = 0.0016\n\n  # Linear learning rate warmup.\n  config.warmup_steps = 1000\n\n  # Cross entropy loss label smoothing.\n  config.label_smoothing = 0.0\n\n  # Decay factor for AdamW style weight decay.\n  config.weight_decay = 0.1\n\n  # Maximum length cutoff for training examples.\n  config.max_target_length = 128\n  # Maximum length cutoff for eval examples.\n  config.max_eval_target_length = 512\n  # Maximum length cutoff for predicted tokens.\n  config.max_predict_length = 50\n\n  # Final logit transform uses embedding matrix transpose.\n  config.logits_via_embedding = False\n\n  # Number of transformer layers.\n  config.num_layers = 6\n\n  # Size of query/key/value for attention.\n  config.qkv_dim = 512\n  # Size of embeddings.\n  config.emb_dim = 512\n  # Size of the MLP.\n  config.mlp_dim = 2048\n\n  # Number of attention heads.\n  config.num_heads = 8\n\n  # Dropout rate.\n  config.dropout_rate = 0.1\n\n  # Attention dropout rate.\n  config.attention_dropout_rate = 0.1\n\n  # Whether to save model checkpoints.\n  config.save_checkpoints = True\n  # Whether to restore from existing model checkpoints.\n  config.restore_checkpoints = True\n\n  # Save a checkpoint every these number of steps.\n  config.checkpoint_every_steps = 10_000\n  # Frequency of eval during training, e.g. every 1_000 steps.\n  config.eval_every_steps = 1_000\n\n  # Use bfloat16 mixed precision training instead of float32.\n  config.use_bfloat16 = True\n\n  # Integer for PRNG random seed.\n  config.seed = 0\n\n  # Prompt for language model sampling,\n  # taken from MaxText (https://github.com/google/maxtext/blob/main/MaxText/configs/base.yml).\n  config.prompts = 'I love to '\n\n  # Parallelism\n  config.mesh_axes = ['data', 'fsdp', 'tensor']\n  config.logical_axis_rules = [\n      ['mlp', 'tensor'],\n      ['vocab', 'tensor'],\n      ['embed', 'fsdp'],\n      ['heads', 'tensor'],\n  ]\n  config.data_sharding = ['data']\n\n  # One axis for each parallelism type may hold a placeholder (-1)\n  # value to auto-shard based on available slices and devices.\n  # By default, product of the DCN axes should equal number of slices\n  # and product of the ICI axes should equal number of devices per slice.\n  # ICI (Inter-Chip Interconnection): A high-speed connection between\n  # sets of TPU chips, which form the TPU network.\n  # DCN (Data Center Network): A connection between the TPU networks;\n  # not as fast as ICI.\n  # ICI has around 100x the bandwidth of DCN, but it is not a general\n  # purpose connection, which is why DCN is necessary for scaling to\n  # extremely large ML models.\n  config.dcn_data_parallelism = -1  # recommended DCN axis to be auto-sharded\n  config.dcn_fsdp_parallelism = 1\n  config.dcn_tensor_parallelism = 1\n  config.ici_data_parallelism = 1\n  config.ici_fsdp_parallelism = -1  # recommended ICI axis to be auto-sharded\n  config.ici_tensor_parallelism = 1\n\n  return config\n"
  },
  {
    "path": "examples/lm1b/input_pipeline.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Input pipeline for a LM1B dataset.\"\"\"\n\nimport os\nfrom typing import Dict, Optional, List, Union\n\nfrom clu import deterministic_data\nimport ml_collections\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\nimport tokenizer\n\nAUTOTUNE = tf.data.experimental.AUTOTUNE\nFeatures = dict[str, tf.Tensor]\n\n\nclass NormalizeFeatureNamesOp:\n  \"\"\"Normalizes feature names to 'inputs' and 'targets'.\"\"\"\n\n  def __init__(self, ds_info: tfds.core.DatasetInfo):\n    self.ds_info = ds_info\n\n  def __call__(self, features: Features) -> Features:\n    features['inputs'] = features.pop('text')\n    # Unnecessary step used for uniformizing with examples/wmt.\n    features['targets'] = features['inputs']\n    return features\n\n\ndef get_raw_dataset(\n    dataset_builder: tfds.core.DatasetBuilder, split: str\n) -> tf.data.Dataset:\n  \"\"\"Loads a raw text dataset and normalizes feature keys.\n\n  Args:\n    dataset_builder: TFDS dataset builder that can build `split`.\n    split: Split to use. This must be the full split. We shard the split across\n      multiple hosts and currently don't support sharding subsplits.\n\n  Returns:\n    Dataset with source and target language features mapped to 'inputs' and\n    'targets'.\n  \"\"\"\n  num_examples = dataset_builder.info.splits[split].num_examples\n  per_host_split = deterministic_data.get_read_instruction_for_host(\n      split, num_examples, drop_remainder=False\n  )\n  ds = dataset_builder.as_dataset(split=per_host_split, shuffle_files=False)\n  ds = ds.map(\n      NormalizeFeatureNamesOp(dataset_builder.info), num_parallel_calls=AUTOTUNE\n  )\n  return ds\n\n\ndef pack_dataset(\n    dataset: tf.data.Dataset,\n    key2length: int | dict[str, int],\n    keys: list[str] | None = None,\n) -> tf.data.Dataset:\n  \"\"\"Creates a 'packed' version of a dataset on-the-fly.\n\n  Adapted from the mesh-tf implementation.\n\n  This is meant to replace the irritation of having to create a separate\n  \"packed\" version of a dataset to train efficiently on TPU.\n  Each example in the output dataset represents several examples in the\n  input dataset.\n  For each key in the input dataset, two additional keys are created:\n  <key>_segmentation: an int32 tensor identifying the parts\n     representing the original example.\n  <key>_position: an int32 tensor identifying the position within the original\n     example.\n  Example:\n  Two input examples get combined to form an output example.\n  The input examples are:\n  {\"inputs\": [8, 7, 1, 0], \"targets\":[4, 1, 0]}\n  {\"inputs\": [2, 3, 4, 1], \"targets\":[5, 6, 1]}\n  The output example is:\n  {\n                 \"inputs\": [8, 7, 1, 2, 3, 4, 1, 0, 0, 0]\n    \"inputs_segmentation\": [1, 1, 1, 2, 2, 2, 2, 0, 0, 0]\n        \"inputs_position\": [0, 1, 2, 0, 1, 2, 3, 0, 0, 0]\n                \"targets\": [4, 1, 5, 6, 1, 0, 0, 0, 0, 0]\n   \"targets_segmentation\": [1, 1, 2, 2, 2, 0, 0, 0, 0, 0]\n       \"targets_position\": [0, 1, 0, 1, 2, 0, 0, 0, 0, 0]\n  }\n  0 represents padding in both the inputs and the outputs.\n  Sequences in the incoming examples are truncated to length \"length\", and the\n  sequences in the output examples all have fixed (padded) length \"length\".\n\n  Args:\n    dataset: a tf.data.Dataset\n    key2length: an integer, or a dict from feature-key to integer\n    keys: a list of strings (e.g. [\"inputs\", \"targets\"])\n\n  Returns:\n    a tf.data.Dataset\n  \"\"\"\n  shapes = tf.nest.map_structure(lambda spec: spec.shape, dataset.element_spec)\n  if keys is None:\n    keys = list(shapes.keys())\n  for k in keys:\n    if k not in shapes:\n      raise ValueError(\n          'Key %s not found in dataset.  Available keys are %s'\n          % (k, shapes.keys())\n      )\n    if not shapes[k].is_compatible_with(tf.TensorShape([None])):  # type: ignore[wrong-arg-types]\n      raise ValueError('Tensors to be packed must be one-dimensional.')\n  # make sure that the length dictionary contains all keys as well as the\n  # keys suffixed by \"_segmentation\" and \"_position\"\n  if isinstance(key2length, int):\n    key2length = {k: key2length for k in keys}\n  for k in keys:\n    for suffix in ['_segmentation', '_position']:\n      key2length[k + suffix] = key2length[k]\n\n  # trim to length\n  dataset = dataset.map(\n      lambda x: {k: x[k][: key2length[k]] for k in keys},\n      num_parallel_calls=AUTOTUNE,\n  )\n  # Setting batch_size=length ensures that the concatenated sequences (if they\n  # have length >=1) are sufficient to fill at least one packed example.\n  batch_size = max(key2length.values())\n  dataset = dataset.padded_batch(\n      batch_size, padded_shapes={k: [-1] for k in keys}\n  )\n  dataset = _pack_with_tf_ops(dataset, keys, key2length)\n\n  # Set the Tensor shapes correctly since they get lost in the process.\n  def my_fn(x):\n    return {k: tf.reshape(v, [key2length[k]]) for k, v in x.items()}\n\n  return dataset.map(my_fn, num_parallel_calls=AUTOTUNE)\n\n\ndef _pack_with_tf_ops(\n    dataset: tf.data.Dataset, keys: list[str], key2length: dict[str, int]\n) -> tf.data.Dataset:\n  \"\"\"Helper-function for packing a dataset which has already been batched.\n\n  Helper for pack_dataset()  Uses tf.while_loop.\n\n  Args:\n    dataset: a dataset containing padded batches of examples.\n    keys: a list of strings\n    key2length: a dict from feature-key to integer\n\n  Returns:\n    a dataset.\n  \"\"\"\n  empty_example = {}\n  for k in keys:\n    empty_example[k] = tf.zeros([0], dtype=tf.int32)\n    empty_example[k + '_position'] = tf.zeros([0], dtype=tf.int32)\n  keys_etc = empty_example.keys()\n\n  def write_packed_example(partial, outputs):\n    new_partial = empty_example.copy()\n    new_outputs = {}\n    for k in keys_etc:\n      new_outputs[k] = outputs[k].write(\n          outputs[k].size(),\n          tf.pad(partial[k], [[0, key2length[k] - tf.size(partial[k])]]),\n      )\n    return new_partial, new_outputs\n\n  def map_fn(x):\n    \"\"\"Internal function to flat_map over.\n\n    Consumes a batch of input examples and produces a variable number of output\n    examples.\n    Args:\n      x: a single example\n\n    Returns:\n      a tf.data.Dataset\n    \"\"\"\n    partial = empty_example.copy()\n    i = tf.zeros([], dtype=tf.int32)\n    dynamic_batch_size = tf.shape(x[keys[0]])[0]\n    outputs = {}\n    for k in keys:\n      outputs[k] = tf.TensorArray(\n          tf.int32, size=0, dynamic_size=True, element_shape=[key2length[k]]\n      )\n      outputs[k + '_position'] = tf.TensorArray(\n          tf.int32, size=0, dynamic_size=True, element_shape=[key2length[k]]\n      )\n\n    def body_fn(i, partial, outputs):\n      \"\"\"Body function for while_loop.\n\n      Args:\n        i: integer scalar\n        partial: dictionary of Tensor (partially-constructed example)\n        outputs: dictionary of TensorArray\n\n      Returns:\n        A triple containing the new values of the inputs.\n      \"\"\"\n      can_append = True\n      one_example = {}\n      for k in keys:\n        val = tf.cast(x[k][i], tf.int32)\n        val = val[: tf.reduce_sum(tf.cast(tf.not_equal(val, 0), tf.int32))]\n        one_example[k] = val\n      for k in keys:\n        can_append = tf.logical_and(\n            can_append,\n            tf.less_equal(\n                tf.size(partial[k]) + tf.size(one_example[k]), key2length[k]\n            ),\n        )\n\n      def false_fn():\n        return write_packed_example(partial, outputs)\n\n      def true_fn():\n        return partial, outputs\n\n      partial, outputs = tf.cond(can_append, true_fn, false_fn)\n      new_partial = {}\n      for k in keys:\n        new_seq = one_example[k][: key2length[k]]\n        new_seq_len = tf.size(new_seq)\n        new_partial[k] = tf.concat([partial[k], new_seq], 0)\n        new_partial[k + '_position'] = tf.concat(\n            [partial[k + '_position'], tf.range(new_seq_len)], 0\n        )\n      partial = new_partial\n      return i + 1, partial, outputs\n\n    # For loop over all examples in the batch.\n    i, partial, outputs = tf.while_loop(\n        cond=lambda *_: True,\n        body=body_fn,\n        loop_vars=(i, partial, outputs),\n        shape_invariants=(\n            tf.TensorShape([]),\n            {k: tf.TensorShape([None]) for k in keys_etc},  # type: ignore[wrong-arg-types]\n            {k: tf.TensorShape(None) for k in keys_etc},  # type: ignore[wrong-arg-types]\n        ),\n        maximum_iterations=dynamic_batch_size,\n    )\n    _, outputs = write_packed_example(partial, outputs)\n    packed = {k: outputs[k].stack() for k in keys_etc}\n    for k in keys:\n      packed[k + '_segmentation'] = tf.cumsum(\n          tf.cast(tf.equal(packed[k + '_position'], 0), tf.int32), axis=1\n      ) * tf.cast(tf.not_equal(packed[k], 0), tf.int32)\n    return packed\n\n  dataset = dataset.map(map_fn, num_parallel_calls=AUTOTUNE)\n  return dataset.unbatch()\n\n\n# -----------------------------------------------------------------------------\n# Main dataset prep routines.\n# -----------------------------------------------------------------------------\ndef preprocess_data(\n    dataset,\n    shuffle: bool,\n    num_epochs: int | None = 1,\n    pack_examples: bool = True,\n    shuffle_buffer_size: int = 1024,\n    max_length: int = 512,\n    batch_size: int = 256,\n    drop_remainder: bool = True,\n    prefetch_size: int = AUTOTUNE,\n):\n  \"\"\"Shuffle and batch/pack the given dataset.\"\"\"\n\n  def length_filter(max_len):\n    def filter_fn(x):\n      source, target = x['inputs'], x['targets']\n      l = tf.maximum(tf.shape(source)[0], tf.shape(target)[0])\n      return tf.less(l, max_len + 1)\n\n    return filter_fn\n\n  if max_length > 0:\n    dataset = dataset.filter(length_filter(max_length))\n\n  if shuffle:\n    dataset = dataset.shuffle(shuffle_buffer_size)\n  dataset = dataset.repeat(num_epochs)\n\n  if pack_examples:\n    dataset = pack_dataset(dataset, max_length)\n    dataset = dataset.batch(batch_size, drop_remainder=drop_remainder)\n  else:  # simple (static-shape) padded batching\n    dataset = dataset.padded_batch(\n        batch_size,\n        padded_shapes={'inputs': max_length, 'targets': max_length},\n        padding_values={'inputs': 0, 'targets': 0},\n        drop_remainder=drop_remainder,\n    )\n\n  if prefetch_size:\n    dataset = dataset.prefetch(prefetch_size)\n\n  return dataset\n\n\ndef get_datasets(\n    config: ml_collections.ConfigDict,\n    *,\n    n_devices: int,\n    vocab_path: str | None = None,\n):\n  \"\"\"Load and return dataset of batched examples for use during training.\"\"\"\n  if vocab_path is None:\n    vocab_path = os.path.expanduser('~/lm1b_sentencepiece_model')\n\n  train_ds_builder = tfds.builder(config.dataset_name)\n  train_data = get_raw_dataset(train_ds_builder, 'train')\n\n  if config.eval_dataset_name:\n    eval_ds_builder = tfds.builder(config.eval_dataset_name)\n  else:\n    eval_ds_builder = train_ds_builder\n  eval_data = get_raw_dataset(eval_ds_builder, config.eval_split)\n\n  # Tokenize data.\n  sp_tokenizer = tokenizer.load_or_train_tokenizer(\n      train_data,\n      vocab_path=vocab_path,\n      vocab_size=config.vocab_size,\n      max_corpus_chars=config.max_corpus_chars,\n  )\n  train_data = train_data.map(\n      tokenizer.TokenizeOp(sp_tokenizer), num_parallel_calls=AUTOTUNE\n  )\n  eval_data = eval_data.map(\n      tokenizer.TokenizeOp(sp_tokenizer), num_parallel_calls=AUTOTUNE\n  )\n\n  batch_size = config.per_device_batch_size * n_devices\n  if config.eval_per_device_batch_size > 0:\n    eval_batch_size = config.eval_per_device_batch_size * n_devices\n  else:\n    eval_batch_size = batch_size\n\n  train_ds = preprocess_data(\n      train_data,\n      shuffle=True,\n      num_epochs=None,\n      pack_examples=True,\n      batch_size=batch_size,\n      max_length=config.max_target_length,\n  )\n\n  eval_ds = preprocess_data(\n      eval_data,\n      shuffle=False,\n      pack_examples=False,\n      batch_size=eval_batch_size,\n      max_length=config.max_eval_target_length,\n  )\n\n  predict_ds = preprocess_data(\n      eval_data,\n      shuffle=False,\n      pack_examples=False,\n      batch_size=eval_batch_size,\n      max_length=config.max_predict_length,\n      drop_remainder=False,\n  )\n\n  return train_ds, eval_ds, predict_ds, sp_tokenizer\n"
  },
  {
    "path": "examples/lm1b/input_pipeline_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport pathlib\nimport sys\nimport tempfile\n\nfrom absl.testing import absltest\nimport tensorflow_datasets as tfds\n\nfrom configs import default\nimport input_pipeline\n\n# We just use different values here to verify that the input pipeline uses the\n# the correct value for the 3 different datasets.\n_TARGET_LENGTH = 32\n_EVAL_TARGET_LENGTH = 48\n_PREDICT_TARGET_LENGTH = 64\n\n\nclass InputPipelineTest(absltest.TestCase):\n\n  def setUp(self):\n    super().setUp()\n    if sys.version_info >= (3, 13):\n      self.skipTest('Test (and tensorflow-text) does not suport Python 3.13+')\n    self.train_ds, self.eval_ds, self.predict_ds = self._get_datasets()\n\n  def _get_datasets(self):\n    config = default.get_config()\n    config.per_device_batch_size = 1\n    config.eval_per_device_batch_size = 2\n    config.vocab_size = 32\n    config.max_corpus_chars = 1000\n    config.max_target_length = _TARGET_LENGTH\n    config.max_eval_target_length = _EVAL_TARGET_LENGTH\n    config.max_predict_length = _PREDICT_TARGET_LENGTH\n\n    vocab_path = os.path.join(tempfile.mkdtemp(), 'sentencepiece_model')\n\n    # Go two directories up to the root of the flax directory.\n    flax_root_dir = pathlib.Path(__file__).parents[2]\n    data_dir = str(flax_root_dir) + '/.tfds/metadata'  # pylint: disable=unused-variable\n\n    with tfds.testing.mock_data(num_examples=128, data_dir=data_dir):\n      train_ds, eval_ds, predict_ds, _ = input_pipeline.get_datasets(\n          n_devices=2, config=config, vocab_path=vocab_path\n      )\n    return train_ds, eval_ds, predict_ds\n\n  def test_train_ds(self):\n    expected_shape = [2, _TARGET_LENGTH]  # 2 devices.\n    # For training we pack multiple short examples in one example.\n    # *_position and *_segmentation indicate the boundaries.\n    for batch in self.train_ds.take(3):\n      self.assertEqual(\n          {k: v.shape.as_list() for k, v in batch.items()},\n          {\n              'inputs': expected_shape,\n              'inputs_position': expected_shape,\n              'inputs_segmentation': expected_shape,\n              'targets': expected_shape,\n              'targets_position': expected_shape,\n              'targets_segmentation': expected_shape,\n          },\n      )\n\n  def test_eval_ds(self):\n    expected_shape = [4, _EVAL_TARGET_LENGTH]  # 2 devices.\n    for batch in self.eval_ds.take(3):\n      self.assertEqual(\n          {k: v.shape.as_list() for k, v in batch.items()},\n          {\n              'inputs': expected_shape,\n              'targets': expected_shape,\n          },\n      )\n\n  def test_predict_ds(self):\n    expected_shape = [4, _PREDICT_TARGET_LENGTH]  # 2 devices.\n    for batch in self.predict_ds.take(3):\n      self.assertEqual(\n          {k: v.shape.as_list() for k, v in batch.items()},\n          {\n              'inputs': expected_shape,\n              'targets': expected_shape,\n          },\n      )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/lm1b/main.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Main file for running the Language Modelling example with LM1B.\n\nThis file is intentionally kept short. The majority for logic is in libraries\nthat can be easily tested and imported in Colab.\n\"\"\"\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nfrom clu import platform\nimport jax\nfrom ml_collections import config_flags\nimport tensorflow as tf\n\nimport train\n\nFLAGS = flags.FLAGS\n\nflags.DEFINE_string('workdir', None, 'Directory to store model data.')\nconfig_flags.DEFINE_config_file(\n    'config',\n    'configs/default.py',\n    'File path to the training hyperparameter configuration.',\n    lock_config=True,\n)\nflags.mark_flags_as_required(['config', 'workdir'])\n\n\ndef main(argv):\n  if len(argv) > 1:\n    raise app.UsageError('Too many command-line arguments.')\n\n  # Hide any GPUs from TensorFlow. Otherwise TF might reserve memory and make\n  # it unavailable to JAX.\n  tf.config.experimental.set_visible_devices([], 'GPU')\n\n  logging.info('JAX process: %d / %d', jax.process_index(), jax.process_count())\n  logging.info('JAX local devices: %r', jax.local_devices())\n\n  # Add a note so that we can tell which task is which JAX host.\n  # (Depending on the platform task 0 is not guaranteed to be host 0)\n  platform.work_unit().set_task_status(\n      f'process_index: {jax.process_index()}, '\n      f'process_count: {jax.process_count()}'\n  )\n  platform.work_unit().create_artifact(\n      platform.ArtifactType.DIRECTORY, FLAGS.workdir, 'workdir'\n  )\n\n  train.train_and_evaluate(FLAGS.config, FLAGS.workdir)\n\n\nif __name__ == '__main__':\n  jax.config.config_with_absl()\n  app.run(main)\n"
  },
  {
    "path": "examples/lm1b/models.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Transformer-based language model.\n\nReusing decoder only model from examples/wmt.\n\"\"\"\n\n# pylint: disable=attribute-defined-outside-init\n# See issue #620.\n# pytype: disable=wrong-arg-count\n# pytype: disable=wrong-keyword-args\n# pytype: disable=attribute-error\n\nfrom typing import Any, Optional\nfrom collections.abc import Callable\n\nfrom flax import linen as nn\nfrom flax import struct\nfrom jax import lax\nimport jax.numpy as jnp\nimport numpy as np\n\n\n@struct.dataclass\nclass TransformerConfig:\n  \"\"\"Global hyperparameters used to minimize obnoxious kwarg plumbing.\"\"\"\n\n  vocab_size: int\n  output_vocab_size: int\n  share_embeddings: bool = False\n  logits_via_embedding: bool = False\n  dtype: Any = jnp.float32\n  emb_dim: int = 512\n  num_heads: int = 8\n  num_layers: int = 6\n  qkv_dim: int = 512\n  mlp_dim: int = 2048\n  max_len: int = 2048\n  dropout_rate: float = 0.1\n  attention_dropout_rate: float = 0.1\n  deterministic: bool = False\n  decode: bool = False\n  kernel_init: Callable = nn.initializers.xavier_uniform()\n  bias_init: Callable = nn.initializers.normal(stddev=1e-6)\n  posemb_init: Callable | None = None\n\n\ndef shift_right(x, axis=1):\n  \"\"\"Shift the input to the right by padding and slicing on axis.\"\"\"\n  pad_widths = [(0, 0)] * len(x.shape)\n  pad_widths[axis] = (1, 0)\n  padded = jnp.pad(\n      x, pad_widths, mode='constant', constant_values=x.dtype.type(0)\n  )\n  return lax.dynamic_slice_in_dim(padded, 0, padded.shape[axis] - 1, axis)\n\n\ndef shift_inputs(x, segment_ids=None, axis=1):\n  \"\"\"Shift inputs and replace EOS by 0 for packed inputs.\"\"\"\n  shifted = shift_right(x, axis=axis)\n  # For packed targets, the first shifted token of a new sequence is made\n  # 0, rather than being the EOS token for the last sequence.\n  if segment_ids is not None:\n    shifted *= segment_ids == shift_right(segment_ids, axis=axis)\n  return shifted\n\n\ndef sinusoidal_init(max_len=2048, min_scale=1.0, max_scale=10000.0):\n  \"\"\"1D Sinusoidal Position Embedding Initializer.\n\n  Args:\n      max_len: maximum possible length for the input.\n      min_scale: float: minimum frequency-scale in sine grating.\n      max_scale: float: maximum frequency-scale in sine grating.\n\n  Returns:\n      output: init function returning `(1, max_len, d_feature)`\n  \"\"\"\n\n  def init(key, shape, dtype=np.float32):\n    \"\"\"Sinusoidal init.\"\"\"\n    del key, dtype\n    d_feature = shape[-1]\n    pe = np.zeros((max_len, d_feature), dtype=np.float32)\n    position = np.arange(0, max_len)[:, np.newaxis]\n    scale_factor = -np.log(max_scale / min_scale) / (d_feature // 2 - 1)\n    div_term = min_scale * np.exp(np.arange(0, d_feature // 2) * scale_factor)\n    pe[:, : d_feature // 2] = np.sin(position * div_term)\n    pe[:, d_feature // 2 : 2 * (d_feature // 2)] = np.cos(position * div_term)\n    pe = pe[np.newaxis, :, :]  # [1, max_len, d_feature]\n    return jnp.array(pe)\n\n  return init\n\n\nclass AddPositionEmbs(nn.Module):\n  \"\"\"Adds (optionally learned) positional embeddings to the inputs.\n\n  Args:\n    config: TransformerConfig dataclass containing hyperparameters.\n    decode: whether to run in single-position autoregressive mode.\n  \"\"\"\n\n  config: TransformerConfig\n  decode: bool = False\n\n  @nn.compact\n  def __call__(self, inputs, inputs_positions=None):\n    \"\"\"Applies AddPositionEmbs module.\n\n    By default this layer uses a fixed sinusoidal embedding table. If a\n    learned position embedding is desired, pass an initializer to\n    posemb_init in the configuration.\n\n    Args:\n      inputs: input data.\n      inputs_positions: input position indices for packed sequences.\n\n    Returns:\n      output: `(bs, timesteps, in_dim)`\n    \"\"\"\n    config = self.config\n    # inputs.shape is (batch_size, seq_len, emb_dim)\n    assert inputs.ndim == 3, (\n        'Number of dimensions should be 3, but it is: %d' % inputs.ndim\n    )\n    length = inputs.shape[1]\n    pos_emb_shape = (1, config.max_len, inputs.shape[-1])\n    if config.posemb_init is None:\n      # Use a fixed (non-learned) sinusoidal position embedding.\n      pos_embedding = sinusoidal_init(max_len=config.max_len)(\n          None, pos_emb_shape, None\n      )\n    else:\n      pos_embedding = self.param(\n          'pos_embedding', config.posemb_init, pos_emb_shape\n      )\n    pe = pos_embedding[:, :length, :]\n\n    # We use a cache position index for tracking decoding position.\n    if self.decode:\n      is_initialized = self.has_variable('cache', 'cache_index')\n      cache_index = self.variable(\n          'cache', 'cache_index', lambda: jnp.array(0, dtype=jnp.uint32)\n      )\n      if is_initialized:\n        i = cache_index.value\n        cache_index.value = i + 1\n        _, _, df = pos_embedding.shape\n        pe = lax.dynamic_slice(pos_embedding, jnp.array((0, i, 0)), (1, 1, df))\n    if inputs_positions is None:\n      # normal unpacked case:\n      return inputs + pe\n    else:\n      # for packed data we need to use known position indices:\n      return inputs + jnp.take(pe[0], inputs_positions, axis=0)\n\n\nclass MlpBlock(nn.Module):\n  \"\"\"Transformer MLP / feed-forward block.\n\n  Args:\n    config: TransformerConfig dataclass containing hyperparameters.\n    out_dim: optionally specify out dimension.\n  \"\"\"\n\n  config: TransformerConfig\n  out_dim: int | None = None\n\n  @nn.compact\n  def __call__(self, inputs):\n    \"\"\"Applies Transformer MlpBlock module.\"\"\"\n    config = self.config\n    actual_out_dim = inputs.shape[-1] if self.out_dim is None else self.out_dim\n    x = nn.Dense(\n        config.mlp_dim,\n        dtype=config.dtype,\n        kernel_init=nn.with_logical_partitioning(\n            config.kernel_init, ('embed', 'mlp')\n        ),\n        bias_init=nn.with_logical_partitioning(config.bias_init, ('mlp',)),\n    )(inputs)\n    x = nn.relu(x)\n    x = nn.Dropout(rate=config.dropout_rate)(\n        x, deterministic=config.deterministic\n    )\n    output = nn.Dense(\n        actual_out_dim,\n        dtype=config.dtype,\n        kernel_init=nn.with_logical_partitioning(\n            config.kernel_init, ('mlp', 'embed')\n        ),\n        bias_init=nn.with_logical_partitioning(config.bias_init, ('embed',)),\n    )(x)\n    output = nn.Dropout(rate=config.dropout_rate)(\n        output, deterministic=config.deterministic\n    )\n    return output\n\n\nclass EncoderDecoder1DBlock(nn.Module):\n  \"\"\"Transformer encoder-decoder layer.\n\n  Args:\n    config: TransformerConfig dataclass containing hyperparameters.\n  \"\"\"\n\n  config: TransformerConfig\n\n  @nn.compact\n  def __call__(self, inputs, decoder_mask=None, encoder_decoder_mask=None):\n    \"\"\"Applies EncoderDecoder1DBlock module.\n\n    Args:\n      inputs: input data for decoder\n      decoder_mask: decoder self-attention mask.\n      encoder_decoder_mask: encoder-decoder attention mask.\n\n    Returns:\n      output after transformer encoder-decoder block.\n    \"\"\"\n    config = self.config\n\n    # Decoder block.\n    assert inputs.ndim == 3\n    x = nn.LayerNorm(\n        dtype=config.dtype,\n        bias_init=nn.with_logical_partitioning(\n            nn.initializers.zeros, ('embed',)\n        ),\n        scale_init=nn.with_logical_partitioning(\n            nn.initializers.ones, ('embed',)\n        ),\n    )(inputs)\n    x = nn.MultiHeadDotProductAttention(\n        num_heads=config.num_heads,\n        dtype=config.dtype,\n        qkv_features=config.qkv_dim,\n        kernel_init=nn.with_logical_partitioning(\n            config.kernel_init, ('embed', 'kv')\n        ),\n        bias_init=nn.with_logical_partitioning(config.bias_init, ('embed',)),\n        use_bias=False,\n        broadcast_dropout=False,\n        dropout_rate=config.attention_dropout_rate,\n        deterministic=config.deterministic,\n        decode=config.decode,\n    )(x, mask=decoder_mask)\n    x = nn.Dropout(rate=config.dropout_rate)(\n        x, deterministic=config.deterministic\n    )\n    x = x + inputs\n\n    # MLP block.\n    z = nn.LayerNorm(\n        dtype=config.dtype,\n        bias_init=nn.with_logical_partitioning(\n            nn.initializers.zeros, ('embed',)\n        ),\n        scale_init=nn.with_logical_partitioning(\n            nn.initializers.ones, ('embed',)\n        ),\n    )(x)\n    z = MlpBlock(config=config)(z)\n\n    return x + z\n\n\nclass Decoder(nn.Module):\n  \"\"\"Transformer Model Decoder for sequence to sequence translation.\n\n  Args:\n    config: TransformerConfig dataclass containing hyperparameters.\n    shared_embedding: a shared embedding layer to use.\n  \"\"\"\n\n  config: TransformerConfig\n  shared_embedding: Any = None\n\n  @nn.compact\n  def __call__(\n      self,\n      inputs,\n      inputs_positions=None,\n      inputs_segmentation=None,\n      decoder_mask=None,\n      encoder_decoder_mask=None,\n  ):\n    \"\"\"Applies Transformer model on the inputs.\n\n    Args:\n      encoded: encoded input data from encoder.\n      inputs: input data.\n      inputs_positions: input subsequence positions for packed examples.\n      inputs_segmentation: input segmentation info for packed examples.\n      decoder_mask: decoder self-attention mask.\n      encoder_decoder_mask: encoder-decoder attention mask.\n\n    Returns:\n      output of a transformer decoder.\n    \"\"\"\n    config = self.config\n    assert inputs.ndim == 2  # (batch, len)\n\n    # Target Embedding\n    if self.shared_embedding is None:\n      output_embed = nn.Embed(\n          num_embeddings=config.output_vocab_size,\n          features=config.emb_dim,\n          embedding_init=nn.with_logical_partitioning(\n              nn.initializers.normal(stddev=1.0), ('vocab', 'embed')\n          ),\n      )\n    else:\n      output_embed = self.shared_embedding\n\n    y = inputs.astype('int32')\n    if not config.decode:\n      y = shift_inputs(y, segment_ids=inputs_segmentation)\n    y = output_embed(y)\n    y = AddPositionEmbs(\n        config=config, decode=config.decode, name='posembed_output'\n    )(y, inputs_positions=inputs_positions)\n    y = nn.Dropout(rate=config.dropout_rate)(\n        y, deterministic=config.deterministic\n    )\n\n    y = y.astype(config.dtype)\n\n    # Target-Input Decoder\n    for lyr in range(config.num_layers):\n      y = EncoderDecoder1DBlock(\n          config=config, name=f'encoderdecoderblock_{lyr}'\n      )(y, decoder_mask=decoder_mask, encoder_decoder_mask=encoder_decoder_mask)\n    y = nn.LayerNorm(\n        dtype=config.dtype,\n        name='encoderdecoder_norm',\n        bias_init=nn.with_logical_partitioning(\n            nn.initializers.zeros, ('embed',)\n        ),\n        scale_init=nn.with_logical_partitioning(\n            nn.initializers.ones, ('embed',)\n        ),\n    )(y)\n\n    # Decoded Logits\n    if config.logits_via_embedding:\n      # Use the transpose of embedding matrix for logit transform.\n      logits = output_embed.attend(y.astype(jnp.float32))\n      # Correctly normalize pre-softmax logits for this shared case.\n      logits = logits / jnp.sqrt(y.shape[-1])\n    else:\n      logits = nn.Dense(\n          config.output_vocab_size,\n          dtype=config.dtype,\n          kernel_init=nn.with_logical_partitioning(\n              config.kernel_init, ('embed', 'vocab')\n          ),\n          bias_init=nn.with_logical_partitioning(config.bias_init, ('vocab',)),\n          name='logitdense',\n      )(y)\n    return logits\n\n\nclass TransformerLM(nn.Module):\n  \"\"\"Transformer pure decoder stack for language modelling.\n\n  Args:\n    config: TransformerConfig dataclass containing hyperparameters.\n  \"\"\"\n\n  config: TransformerConfig\n\n  @nn.compact\n  def __call__(self, inputs, inputs_positions=None, inputs_segmentation=None):\n    \"\"\"Applies TransformerLM on the inputs.\n\n    Args:\n      inputs: target data.\n      inputs_positions: input subsequence positions for packed examples.\n      inputs_segmentation: input segmentation info for packed examples.\n\n    Returns:\n      logits array from transformer decoder.\n    \"\"\"\n    config = self.config\n\n    # Make padding attention masks.\n    if config.decode:\n      # for fast autoregressive decoding we use no decoder mask\n      decoder_mask = None\n    else:\n      decoder_mask = nn.combine_masks(\n          nn.make_attention_mask(inputs > 0, inputs > 0, dtype=config.dtype),\n          nn.make_causal_mask(inputs, dtype=config.dtype),\n      )\n\n    # Add segmentation block-diagonal attention masks if using segmented data.\n    if inputs_segmentation is not None:\n      decoder_mask = nn.combine_masks(\n          decoder_mask,\n          nn.make_attention_mask(\n              inputs_segmentation,\n              inputs_segmentation,\n              jnp.equal,\n              dtype=config.dtype,\n          ),\n      )\n\n    logits = Decoder(config=config, shared_embedding=None, name='decoder')(\n        inputs,\n        inputs_positions=inputs_positions,\n        inputs_segmentation=inputs_segmentation,\n        decoder_mask=decoder_mask,\n        encoder_decoder_mask=None,\n    )\n    return logits.astype(self.config.dtype)\n"
  },
  {
    "path": "examples/lm1b/requirements.txt",
    "content": "absl-py==1.4.0\nclu==0.0.9\nflax==0.6.11\njax==0.4.13\n--find-links https://storage.googleapis.com/jax-releases/jax_releases.html\njaxlib==0.4.13+cuda11.cudnn82  # Make sure CUDA version matches the base image.\nml-collections==0.1.1\nnumpy==1.24.3\noptax==0.1.5\nsentencepiece==0.1.99\ntensorflow==2.13.0\ntensorflow-datasets==4.9.2\ntensorflow-text==2.13.0\n"
  },
  {
    "path": "examples/lm1b/temperature_sampler.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Fast decoding routines for inference from a trained language model.\"\"\"\n\nfrom jax import lax\nfrom jax import random\nimport jax.numpy as jnp\n\n\n# Constants\n# The default End-of-Sentence token id is 2 (SentencePiece).\nEOS_ID = 2\n\n\ndef temperature_sample(\n    prompt_inputs,\n    init_cache,\n    tokens_to_logits,\n    prng_key,\n    temperature=1.0,\n    topk=20,\n    eos_token=EOS_ID,\n):\n  \"\"\"Temperature sampling for language model generation.\n\n  Args:\n    prompt_inputs: array: [batch_size, max_decode_len] int32 sequence of tokens.\n    init_cache: flax attention cache.\n    tokens_to_logits: fast autoregressive decoder function taking single token\n      slices and cache and returning next-token logits and updated cache.\n    prng_key: JAX PRNGKey.\n    temperature: float: sampling temperature factor. As it approaches\n      zero this becomes equivalent to greedy sampling.\n    topk: integer: if nonzero only use the top-k logits to sample next token,\n      if zero don't use any cutoff and sample from full logits over vocabulary.\n    eos_token: int: end-of-sentence token for target vocabulary.\n\n  Returns:\n    Array of sampled sequences: [batch_size, max_decode_len]\n  \"\"\"\n  batch_size = prompt_inputs.shape[0]\n  max_decode_len = prompt_inputs.shape[1]\n  end_marker = jnp.array(eos_token)\n  temperature = jnp.array(temperature)\n\n  # Initialize sampling loop state.\n  # initial loop PRNGKey\n  rng0 = prng_key\n  # loop position counter.\n  i0 = jnp.array(-1)\n  # per batch-item holding current token in loop.\n  token0 = jnp.zeros((batch_size, 1), dtype=jnp.int32)\n  # per batch-item state bit indicating if sentence has finished.\n  ended0 = jnp.zeros((batch_size, 1), dtype=jnp.bool_)\n  # (batch, length) array containing prefix prompt tokens for sampling loop\n  # as well as the generated output of newly sampled tokens.\n  sequences0 = prompt_inputs\n  # Sampling loop state is stored in a simple tuple.\n  sampling_loop_init_state = (i0, sequences0, init_cache, token0, ended0, rng0)\n\n  def sampling_loop_cond_fn(state):\n    \"\"\"Sampling loop termination condition.\"\"\"\n    (i, _, _, _, ended, _) = state\n    # Have we reached max decoding length?\n    not_at_end = i < max_decode_len - 1\n    # Have all sampled sequences reached an end marker?\n    all_sequences_ended = jnp.all(ended)\n    return not_at_end & (~all_sequences_ended)\n\n  def sampling_loop_body_fn(state):\n    \"\"\"Sampling loop state update.\"\"\"\n    i, sequences, cache, cur_token, ended, rng = state\n    # Split RNG for sampling.\n    rng1, rng2 = random.split(rng)\n    # Call fast-decoder model on current tokens to get next-position logits.\n    logits, new_cache = tokens_to_logits(cur_token, cache)\n    # Sample next token from logits.\n    # TODO(levskaya): add top-p \"nucleus\" sampling option.\n    if topk:\n      # Get top-k logits and their indices, sample within these top-k tokens.\n      topk_logits, topk_idxs = lax.top_k(logits, topk)\n      topk_token = jnp.expand_dims(\n          random.categorical(rng1, topk_logits / temperature).astype(jnp.int32),\n          axis=-1,\n      )\n      # Return the original indices corresponding to the sampled top-k tokens.\n      next_token = jnp.squeeze(\n          jnp.take_along_axis(topk_idxs, topk_token, axis=-1), axis=-1\n      )\n    else:\n      next_token = random.categorical(rng1, logits / temperature).astype(\n          jnp.int32\n      )\n    # Only use sampled tokens if we're past provided prefix tokens.\n    out_of_prompt = sequences[:, i + 1] == 0\n    next_token = (\n        next_token * out_of_prompt + sequences[:, i + 1] * ~out_of_prompt\n    )\n    # If end-marker reached for batch item, only emit padding tokens.\n    next_token_or_endpad = next_token[None] * ~ended\n    ended |= next_token_or_endpad == end_marker\n    # Add current sampled tokens to recorded sequences.\n    new_sequences = lax.dynamic_update_slice(\n        sequences, next_token_or_endpad, (0, i + 1)\n    )\n    return (i + 1, new_sequences, new_cache, next_token_or_endpad, ended, rng2)\n\n  # Run sampling loop and collect final state.\n  final_state = lax.while_loop(\n      sampling_loop_cond_fn, sampling_loop_body_fn, sampling_loop_init_state\n  )\n\n  # Pick part of the state corresponding to the sampled sequences.\n  final_sequences = final_state[1]\n  return final_sequences\n"
  },
  {
    "path": "examples/lm1b/temperature_sampler_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom absl.testing import absltest\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\n\nfrom temperature_sampler import temperature_sample\n\n\njax.config.update('jax_disable_most_optimizations', True)\n\n\nclass TestTemperatureSampler(absltest.TestCase):\n\n  def test_temperature_sampler(self):\n    tokens = jnp.array([[5, 0, 0, 0]], dtype=jnp.int32)\n    cache = None\n    key = jax.random.PRNGKey(0)\n\n    def tokens_to_logits(tokens, cache):\n      jax.debug.print('tokens: {}', tokens)\n      logits = jax.nn.one_hot(tokens[..., -1:] + 1, 10)\n      logits = jnp.where(logits < 0.5, float('-inf'), logits)\n      logits = logits.squeeze(axis=1)\n      return logits, cache\n\n    new_tokens = temperature_sample(\n        tokens, cache, tokens_to_logits, key, topk=5\n    )\n\n    np.testing.assert_array_equal(new_tokens, [[5, 6, 7, 8]])\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/lm1b/tokenizer.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Provides op for tokenizing a dataset.\"\"\"\n\nimport os\nimport sys\nimport tempfile\nimport time\nfrom typing import Any, Dict, Tuple\nfrom collections.abc import Iterable\n\nfrom absl import logging\nimport dataclasses\nimport jax\nfrom sentencepiece import SentencePieceTrainer\nimport tensorflow as tf\nif sys.version_info < (3, 13):\n  import tensorflow_text as tftxt\n\nFeatures = dict[str, tf.Tensor]\n\n\ndef _dump_chars_to_textfile(\n    dataset: tf.data.Dataset,\n    maxchars: int = int(1e7),\n    data_keys=('inputs', 'targets'),\n) -> tuple[str, int]:\n  \"\"\"Write part of a TFDS sentence dataset to lines in a text file.\n\n  Args:\n    dataset: tf.dataset containing string-data.\n    maxchars: int: approximate number of characters to save from dataset.\n    data_keys: Tuple[str]: what keys in dataset to dump from.\n\n  Returns:\n    name of temp file with dataset bytes, exact number of characters dumped.\n  \"\"\"\n  char_count = 0\n  ds_iter = dataset.as_numpy_iterator()\n  with tempfile.NamedTemporaryFile(\n      delete=False, prefix='/tmp/ds_chars'\n  ) as outfp:\n    while char_count < maxchars:\n      example = next(ds_iter)\n      for k in data_keys:\n        line = example[k] + b'\\n'\n        char_count += len(line)\n        outfp.write(line)\n  return outfp.name, char_count\n\n\ndef _train_sentencepiece(\n    dataset: tf.data.Dataset,\n    *,\n    vocab_size: int,\n    maxchars: int = int(1e7),\n    model_path: str,\n    model_type: str = 'unigram',\n    character_coverage: float = 1.0,\n    data_keys=('inputs', 'targets'),\n):\n  \"\"\"Train SentencePiece tokenizer from subset of tf dataset.\n\n  Args:\n    dataset: tf.dataset\n    vocab_size: int: size of vocab tokens to train.\n    maxchars: int: number of characters to use for sentencepiece training.\n    model_path: str: path of model file to save vocab model to.\n    model_type: str: type of sentencepiece vocab to train.\n    character_coverage: amount of characters covered by the model, good defaults\n      are 0.9995 for languages with rich character set like Japanese or Chinese\n      and 1.0 for other languages with small character set.\n    data_keys: Tuple[str]: keys of dataset to use for training.\n\n  Returns:\n    path to the trained sentencepiece vocabulary model.\n  \"\"\"\n  if model_path.startswith('gs://'):\n    abs_model_path = model_path\n  else:\n    abs_model_path = os.path.abspath(os.path.expanduser(model_path))\n  fname, _ = _dump_chars_to_textfile(\n      dataset, maxchars=maxchars, data_keys=data_keys\n  )\n  with tempfile.NamedTemporaryFile(\n      delete=False, prefix='/tmp/sp_tmp'\n  ) as model_fp:\n    pass  # we just want a prefix'd tmp-filename\n  argstr = ' '.join([\n      f'--input={fname}',\n      f'--vocab_size={vocab_size}',\n      f'--character_coverage={character_coverage}',\n      f'--model_prefix={model_fp.name}',\n      f'--model_type={model_type}',\n  ])\n  SentencePieceTrainer.Train(argstr)\n  if jax.process_index() == 0:\n    # Use an intermediate filename that is renamed to the target name to address\n    # create and fill delays.\n    copy_rename_path = abs_model_path + '.rntmp'\n    tf.io.gfile.copy(model_fp.name + '.model', copy_rename_path, overwrite=True)\n    tf.io.gfile.rename(copy_rename_path, abs_model_path, overwrite=True)\n    logging.info('copied %s to %s', model_fp.name + '.model', abs_model_path)\n  else:\n    while not tf.io.gfile.exists(abs_model_path):\n      time.sleep(1)\n    time.sleep(1)\n  return abs_model_path\n\n\ndef _load_sentencepiece_tokenizer(\n    model_path: str,\n    add_bos: bool = False,\n    add_eos: bool = True,\n    reverse: bool = False,\n):\n  \"\"\"Load a tf-text SentencePiece tokenizer from given model filepath.\"\"\"\n  with tf.io.gfile.GFile(model_path, 'rb') as model_fp:\n    sp_model = model_fp.read()\n  sp_tokenizer = tftxt.SentencepieceTokenizer(\n      model=sp_model, add_bos=add_bos, add_eos=add_eos, reverse=reverse\n  )\n  return sp_tokenizer\n\n\ndef load_or_train_tokenizer(\n    dataset: tf.data.Dataset,\n    *,\n    vocab_path: str,\n    vocab_size: int,\n    max_corpus_chars: int,\n    data_keys: tuple[str, str] = ('inputs', 'targets'),\n):\n  \"\"\"Loads the tokenizer at `vocab_path` or trains a one from `dataset`.\"\"\"\n  try:\n    return _load_sentencepiece_tokenizer(vocab_path)\n  except tf.errors.NotFoundError:\n    logging.info('SentencePiece vocab not found, building one from data.')\n    vocab_path = _train_sentencepiece(\n        dataset,\n        vocab_size=vocab_size,\n        maxchars=max_corpus_chars,\n        model_path=vocab_path,\n        data_keys=data_keys,\n    )\n    return _load_sentencepiece_tokenizer(vocab_path)\n\n\n@dataclasses.dataclass\nclass TokenizeOp:\n  sp_tokenizer: Any\n  data_keys: Iterable[str] = ('inputs', 'targets')\n\n  def __call__(self, features: Features) -> Features:\n    for k in self.data_keys:\n      features[k] = self.sp_tokenizer.tokenize(features[k])\n    return features\n"
  },
  {
    "path": "examples/lm1b/train.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Language Modeling example.\n\nThis script trains a Transformer on a LM1B dataset.\n\"\"\"\n\n# pytype: disable=wrong-arg-count\n# pytype: disable=attribute-error\n\nimport collections\nimport os\n\nfrom absl import logging\nfrom clu import metric_writers\nfrom clu import periodic_actions\nfrom flax import linen as nn\nfrom flax.training import checkpoints\nfrom flax.training import common_utils\nimport jax\nfrom jax import random\nimport jax.numpy as jnp\nfrom jax.sharding import PartitionSpec as P, Mesh, NamedSharding\nimport ml_collections\nimport numpy as np\nimport optax\nimport tensorflow as tf\n\nimport input_pipeline\nimport models\nimport temperature_sampler\nimport utils\n\n\ndef rsqrt_schedule(\n    init_value: float,\n    shift: int = 0,\n):\n  \"\"\"Applies a reverse square-root schedule.\n\n  The reverse square root schedule is simply `lr = init_value / sqrt(step)`.\n\n  Args:\n    init_value: Base learning rate (before applying the rsqrt schedule).\n    shift: How many steps the rsqrt should be shifted. Shifting the rsqrt\n      schedule makes it less steep in the beginning (close to 0).\n\n  Returns:\n    A schedule that applies the reverse square root.\n  \"\"\"\n\n  def schedule(count):\n    return init_value * (count + shift) ** -0.5 * shift**0.5\n\n  return schedule\n\n\ndef create_learning_rate_schedule(learning_rate: float, warmup_steps: int):\n  \"\"\"Creates a rsqrt schedule with linear warmup.\"\"\"\n  return optax.join_schedules(\n      [\n          optax.linear_schedule(\n              init_value=0,\n              end_value=learning_rate,\n              transition_steps=warmup_steps,\n          ),\n          rsqrt_schedule(init_value=learning_rate, shift=warmup_steps),\n      ],\n      boundaries=[warmup_steps],\n  )\n\n\ndef compute_weighted_cross_entropy(\n    logits, targets, weights=None, label_smoothing=0.0\n):\n  \"\"\"Compute weighted cross entropy and entropy for log probs and targets.\n\n  Args:\n   logits: [batch, length, num_classes] float array.\n   targets: categorical targets [batch, length] int array.\n   weights: None or array of shape [batch, length].\n   label_smoothing: label smoothing constant, used to determine the on and off\n     values.\n\n  Returns:\n    Tuple of scalar loss and batch normalizing factor.\n  \"\"\"\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        \"Incorrect shapes. Got shape %s logits and %s targets\"\n        % (str(logits.shape), str(targets.shape))\n    )\n  vocab_size = logits.shape[-1]\n  confidence = 1.0 - label_smoothing\n  low_confidence = (1.0 - confidence) / (vocab_size - 1)\n  normalizing_constant = -(\n      confidence * jnp.log(confidence)\n      + (vocab_size - 1) * low_confidence * jnp.log(low_confidence + 1e-20)\n  )\n  soft_targets = common_utils.onehot(\n      targets, vocab_size, on_value=confidence, off_value=low_confidence\n  )\n\n  loss = -jnp.sum(soft_targets * nn.log_softmax(logits), axis=-1)\n  loss = loss - normalizing_constant\n\n  normalizing_factor = np.prod(targets.shape)\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_weighted_accuracy(logits, targets, weights=None):\n  \"\"\"Compute weighted accuracy for log probs and targets.\n\n  Args:\n   logits: [batch, length, num_classes] float array.\n   targets: categorical targets [batch, length] int array.\n   weights: None or array of shape [batch, length]\n\n  Returns:\n    Tuple of scalar loss and batch normalizing factor.\n  \"\"\"\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        \"Incorrect shapes. Got shape %s logits and %s targets\"\n        % (str(logits.shape), str(targets.shape))\n    )\n  loss = jnp.equal(jnp.argmax(logits, axis=-1), targets)\n  normalizing_factor = np.prod(logits.shape[:-1])\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_metrics(logits, labels, weights, label_smoothing=0.0):\n  \"\"\"Compute summary metrics.\"\"\"\n  loss, weight_sum = compute_weighted_cross_entropy(\n      logits, labels, weights, label_smoothing\n  )\n  acc, _ = compute_weighted_accuracy(logits, labels, weights)\n  metrics = {\n      \"loss\": loss,\n      \"accuracy\": acc,\n      \"denominator\": weight_sum,\n  }\n  return metrics\n\n\n# Primary training / eval / decode step functions.\n# -----------------------------------------------------------------------------\n\n\ndef train_step(\n    state,\n    batch,\n    config,\n    learning_rate_fn,\n    label_smoothing=0.0,\n    dropout_rng=None,\n):\n  \"\"\"Perform a single training step.\"\"\"\n  # X_position and X_segmentation are needed only when using \"packed examples\"\n  # where multiple sequences are packed into the same example with this\n  # metadata.\n  # if such features are not present they are ignored and the example is treated\n  # like a normal, unpacked sequence example.\n  train_keys = [\"inputs\", \"inputs_position\", \"inputs_segmentation\"]\n  (inputs, inputs_positions, inputs_segmentation) = (\n      batch.get(k, None) for k in train_keys\n  )\n\n  weights = jnp.where(inputs > 0, 1, 0).astype(jnp.float32)\n\n  dropout_rng = jax.random.fold_in(dropout_rng, state.step)\n\n  def loss_fn(params):\n    \"\"\"loss function used for training.\"\"\"\n    logits = models.TransformerLM(config).apply(\n        {\"params\": params},\n        inputs,\n        inputs_positions=inputs_positions,\n        inputs_segmentation=inputs_segmentation,\n        rngs={\"dropout\": dropout_rng},\n    )\n\n    loss, weight_sum = compute_weighted_cross_entropy(\n        logits, inputs, weights, label_smoothing\n    )\n    mean_loss = loss / weight_sum\n    return mean_loss, logits\n\n  step = state.step\n  lr = learning_rate_fn(step)\n  grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n  (_, logits), grads = grad_fn(state.params)\n  new_state = state.apply_gradients(grads=grads)\n  metrics = compute_metrics(logits, inputs, weights)\n  metrics[\"learning_rate\"] = lr\n\n  return new_state, metrics\n\n\ndef eval_step(params, batch, config, label_smoothing=0.0):\n  \"\"\"Calculate evaluation metrics on a batch.\"\"\"\n  inputs = batch[\"inputs\"]\n  weights = jnp.where(inputs > 0, 1.0, 0.0)\n  logits = models.TransformerLM(config).apply({\"params\": params}, inputs)\n\n  return compute_metrics(logits, inputs, weights, label_smoothing)\n\n\ndef predict_step(\n    inputs, params, rngkey, eos_id, max_decode_len, config, temperature, top_k\n):\n  \"\"\"Predict language model on a batch.\"\"\"\n  target_shape = (inputs.shape[0], max_decode_len) + inputs.shape[2:]\n  initial_variables = models.TransformerLM(config).init(\n      jax.random.PRNGKey(0), jnp.ones(target_shape, config.dtype)\n  )\n  cache = initial_variables[\"cache\"]\n\n  def tokens_ids_to_logits(flat_ids, flat_cache):\n    \"\"\"Token slice to logits from decoder model.\"\"\"\n    # --> [batch * beam, 1, vocab]\n    flat_logits, new_vars = models.TransformerLM(config).apply(\n        {\"params\": params, \"cache\": flat_cache}, flat_ids, mutable=[\"cache\"]\n    )\n    new_flat_cache = new_vars[\"cache\"]\n    # Remove singleton sequence-length dimension:\n    # [batch, 1, vocab] --> [batch, vocab]\n    flat_logits = flat_logits.squeeze(axis=1)\n    return flat_logits, new_flat_cache\n\n  # Using the above-defined single-step decoder function, run a\n  # beam search over possible sequences given input encoding.\n  seqs = temperature_sampler.temperature_sample(\n      inputs,\n      cache,\n      tokens_ids_to_logits,\n      rngkey,\n      temperature=temperature,\n      topk=top_k,\n      eos_token=eos_id,\n  )\n\n  return seqs\n\n\n# Utils for prediction\n# -----------------------------------------------------------------------------\n\n\ndef pad_examples(x, desired_batch_size):\n  \"\"\"Expand batch to desired size by repeating last slice.\"\"\"\n  batch_pad = desired_batch_size - x.shape[0]\n  return np.concatenate([x, np.tile(x[-1], (batch_pad, 1))], axis=0)\n\n\ndef tohost(x):\n  \"\"\"Collect batches from all devices to host and flatten batch dimensions.\"\"\"\n  n_device, n_batch, *remaining_dims = x.shape\n  return np.array(x).reshape((n_device * n_batch,) + tuple(remaining_dims))\n\n\ndef evaluate(\n    *,\n    jit_eval_step,\n    params,\n    eval_ds: tf.data.Dataset,\n    num_eval_steps: int,\n    config,\n):\n  \"\"\"Evaluate the target an return a dictionary with the metrics.\"\"\"\n  logging.info(\"Gathering evaluation metrics.\")\n  eval_metrics = []\n  eval_iter = iter(eval_ds)  # pytype: disable=wrong-arg-types\n  for _, eval_batch in zip(range(num_eval_steps), eval_iter):\n    eval_batch = jax.tree_util.tree_map(lambda x: x._numpy(), eval_batch)  # pylint: disable=protected-access\n    metrics = jit_eval_step(params, eval_batch, config)\n    eval_metrics.append(metrics)\n  eval_metrics = common_utils.stack_forest(eval_metrics)\n  eval_metrics_sums = jax.tree_util.tree_map(jnp.sum, eval_metrics)\n  eval_denominator = eval_metrics_sums.pop(\"denominator\")\n  eval_summary = jax.tree_util.tree_map(\n      lambda x: x / eval_denominator,  # pylint: disable=cell-var-from-loop\n      eval_metrics_sums,\n  )\n  return eval_summary\n\n\ndef generate_prediction(\n    *,\n    jit_pred_step,\n    params,\n    tokenized_prompts,\n    eos_id,\n    inference_rng,\n    decode_tokens,\n    config,\n    predict_config,\n):\n  \"\"\"Generate text from the prompt.\"\"\"\n  n_devices = jax.local_device_count()\n\n  logging.info(\"Generating text.\")\n  predictions = []\n  # Use batch of prompts provided by user.\n  for pred_batch in jnp.array_split(\n      tokenized_prompts, int(np.ceil(len(tokenized_prompts) / n_devices))\n  ):\n    cur_pred_batch_size = pred_batch.shape[0]\n    if cur_pred_batch_size % n_devices:\n      padded_size = int(np.ceil(cur_pred_batch_size / n_devices) * n_devices)\n      pred_batch = jax.tree_util.tree_map(\n          lambda x: pad_examples(x, padded_size), pred_batch\n      )  # pylint: disable=cell-var-from-loop\n    pred_batch = common_utils.shard(pred_batch)\n    inference_rng, sub_rng = random.split(inference_rng)\n    inference_rngs = random.split(sub_rng, n_devices)\n\n    predicted = jit_pred_step(\n        pred_batch,\n        params,\n        inference_rngs,\n        eos_id,\n        config.max_predict_length,\n        predict_config,\n        config.sampling_temperature,\n        config.sampling_top_k,\n    )\n    predicted = tohost(predicted)\n    # Iterate through non-padding examples of batch.\n    for s in predicted[:cur_pred_batch_size]:\n      prediction = decode_tokens(s)\n      logging.info(\"Sample: %s\", str(prediction))\n      predictions.append(prediction)\n\n    # Save generated texts for tensorboard.\n    exemplars = \"\"\n    for prediction in predictions:\n      exemplars += f\"{prediction}\\n\\n\"\n  return exemplars\n\n\ndef train_and_evaluate(config: ml_collections.ConfigDict, workdir: str):\n  \"\"\"Runs a training and evaluation loop.\n\n  Args:\n    config: Configuration to use.\n    workdir: Working directory for checkpoints and TF summaries. If this\n      contains checkpoint training will be resumed from the latest checkpoint.\n  \"\"\"\n  tf.io.gfile.makedirs(workdir)\n\n  vocab_path = config.vocab_path\n  if vocab_path is None:\n    vocab_path = os.path.join(workdir, \"sentencepiece_model\")\n    config.vocab_path = vocab_path\n  tf.io.gfile.makedirs(os.path.split(vocab_path)[0])\n\n  # Load Dataset\n  # ---------------------------------------------------------------------------\n  logging.info(\"Initializing dataset.\")\n  train_ds, eval_ds, _, encoder = input_pipeline.get_datasets(\n      n_devices=jax.local_device_count(), config=config, vocab_path=vocab_path\n  )\n\n  train_iter = iter(train_ds)\n  vocab_size = int(encoder.vocab_size())\n  eos_id = temperature_sampler.EOS_ID  # Default Sentencepiece EOS token.\n\n  def decode_tokens(toks):\n    valid_toks = toks[: np.argmax(toks == eos_id) + 1].astype(np.int32)\n    return encoder.detokenize(valid_toks).numpy().decode(\"utf-8\")\n\n  def encode_strings(strs, max_len):\n    tokenized_batch = np.zeros((len(strs), max_len), np.int32)\n    for i, s in enumerate(strs):\n      toks = encoder.tokenize(s).numpy()\n      # Remove EOS token in prompt.\n      tokenized_batch[i, : toks.shape[0] - 1] = toks[:-1]\n    return tokenized_batch\n\n  tokenized_prompts = encode_strings(\n      [config.prompts], config.max_predict_length\n  )\n\n  logging.info(\"Initializing model, optimizer, and step functions.\")\n  # Build Model and Optimizer\n  # ---------------------------------------------------------------------------\n  train_config = models.TransformerConfig(\n      vocab_size=vocab_size,\n      output_vocab_size=vocab_size,\n      logits_via_embedding=config.logits_via_embedding,\n      dtype=jnp.bfloat16 if config.use_bfloat16 else jnp.float32,\n      emb_dim=config.emb_dim,\n      num_heads=config.num_heads,\n      num_layers=config.num_layers,\n      qkv_dim=config.qkv_dim,\n      mlp_dim=config.mlp_dim,\n      max_len=max(config.max_target_length, config.max_eval_target_length),\n      dropout_rate=config.dropout_rate,\n      attention_dropout_rate=config.attention_dropout_rate,\n      deterministic=False,\n      decode=False,\n      kernel_init=nn.initializers.xavier_uniform(),\n      bias_init=nn.initializers.normal(stddev=1e-6),\n  )\n  eval_config = train_config.replace(deterministic=True)\n  predict_config = train_config.replace(deterministic=True, decode=True)\n\n  # Mesh definition\n  devices_array = utils.create_device_mesh(config)\n  mesh = Mesh(devices_array, config.mesh_axes)\n\n  start_step = 0\n  rng = jax.random.PRNGKey(config.seed)\n  rng, init_rng = jax.random.split(rng)\n  rng, inference_rng = random.split(rng)\n\n  m = models.TransformerLM(eval_config)\n\n  learning_rate_fn = create_learning_rate_schedule(\n      learning_rate=config.learning_rate, warmup_steps=config.warmup_steps\n  )\n\n  optimizer = optax.adamw(\n      learning_rate_fn,\n      b1=0.9,\n      b2=0.98,\n      eps=1e-9,\n      weight_decay=config.weight_decay,\n  )\n\n  state, state_mesh_annotations = utils.setup_initial_state(\n      m, optimizer, config, init_rng, mesh\n  )\n  data_sharding = NamedSharding(mesh, P(config.data_sharding))\n\n  if config.restore_checkpoints:\n    # Restore unreplicated optimizer + model state from last checkpoint.\n    state = checkpoints.restore_checkpoint(workdir, state)\n    # Grab last step.\n    start_step = int(state.step)\n\n  writer = metric_writers.create_default_writer(\n      workdir, just_logging=jax.process_index() > 0\n  )\n  if start_step == 0:\n    writer.write_hparams(dict(config))\n\n  # compile multidevice versions of train/eval/predict step fn.\n  jit_train_step = jax.jit(\n      train_step,\n      in_shardings=(\n          state_mesh_annotations,\n          data_sharding,\n          None,\n      ),  # type: ignore\n      out_shardings=(state_mesh_annotations, None),  # type: ignore\n      static_argnums=(2, 3, 4),\n      donate_argnums=0,\n  )\n\n  jit_eval_step = jax.jit(\n      eval_step,\n      in_shardings=(\n          state_mesh_annotations.params,\n          data_sharding,\n      ),  # type: ignore\n      out_shardings=None,  # type: ignore\n      static_argnums=(2, 3),\n  )\n\n  # Since the inputs and rngkey args for predict_step will be batched,\n  # we must vmap them, otherwise the global arrays will be seen in each device\n  jit_pred_step = jax.jit(\n      jax.vmap(\n          predict_step,\n          in_axes=(\n              0,\n              jax.tree_util.tree_map(lambda x: None, state.params),\n              0,\n              None,\n              None,\n              jax.tree_util.tree_map(lambda x: None, predict_config),\n              None,\n              None,\n          ),\n      ),\n      in_shardings=(\n          data_sharding,\n          state_mesh_annotations.params,\n          data_sharding,\n      ),  # type: ignore\n      out_shardings=data_sharding,  # type: ignore\n      static_argnums=(3, 4, 5, 6, 7),\n  )\n\n  # Main Train Loop\n  # ---------------------------------------------------------------------------\n\n  # We init the first set of dropout PRNG keys, but update it afterwards inside\n  # the main pmap\"d training update for performance.\n  dropout_rngs = rng\n\n  logging.info(\"Starting training loop.\")\n  hooks = []\n  report_progress = periodic_actions.ReportProgress(\n      num_train_steps=config.num_train_steps, writer=writer\n  )\n  if jax.process_index() == 0:\n    hooks += [\n        report_progress,\n        periodic_actions.Profile(logdir=workdir, num_profile_steps=5),\n    ]\n  train_metrics = []\n  with metric_writers.ensure_flushes(writer):\n    for step in range(start_step, config.num_train_steps):\n      is_last_step = step == config.num_train_steps - 1\n\n      # Shard data to devices and do a training step.\n      with jax.profiler.StepTraceAnnotation(\"train\", step_num=step):\n        batch = next(train_iter)\n        batch = jax.tree_util.tree_map(lambda x: jnp.array(x), batch)\n        state, metrics = jit_train_step(\n            state, batch, train_config, learning_rate_fn, 0.0, dropout_rngs\n        )\n        train_metrics.append(metrics)\n\n      # Quick indication that training is happening.\n      logging.log_first_n(logging.INFO, \"Finished training step %d.\", 5, step)\n      for h in hooks:\n        h(step)\n\n      # Periodic metric handling.\n      if step % config.eval_every_steps == 0 or is_last_step:\n        with report_progress.timed(\"training_metrics\"):\n          logging.info(\"Gathering training metrics.\")\n          train_metrics = common_utils.stack_forest(train_metrics)\n          lr = train_metrics.pop(\"learning_rate\").mean()\n          metrics_sums = jax.tree_util.tree_map(jnp.sum, train_metrics)\n          denominator = metrics_sums.pop(\"denominator\")\n          summary = jax.tree_util.tree_map(\n              lambda x: x / denominator, metrics_sums\n          )  # pylint: disable=cell-var-from-loop\n          summary[\"learning_rate\"] = lr\n          summary[\"perplexity\"] = jnp.clip(\n              jnp.exp(summary[\"loss\"]), max=1.0e4\n          )\n          summary = {\"train_\" + k: v for k, v in summary.items()}\n          writer.write_scalars(step, summary)\n          train_metrics = []\n\n        with report_progress.timed(\"eval\"):\n          eval_results = evaluate(\n              jit_eval_step=jit_eval_step,\n              params=state.params,\n              eval_ds=eval_ds,\n              num_eval_steps=config.num_eval_steps,\n              config=eval_config,\n          )\n          # (clipped) perplexity after averaging log-perplexitie\n          eval_results[\"perplexity\"] = jnp.clip(\n              jnp.exp(eval_results[\"loss\"]), max=1.0e4\n          )\n          writer.write_scalars(\n              step, {\"eval_\" + k: v for k, v in eval_results.items()}\n          )\n\n        with report_progress.timed(\"generate_text\"):\n          exemplars = generate_prediction(\n              jit_pred_step=jit_pred_step,\n              params=state.params,\n              tokenized_prompts=tokenized_prompts,\n              eos_id=eos_id,\n              inference_rng=inference_rng,\n              decode_tokens=decode_tokens,\n              config=config,\n              predict_config=predict_config,\n          )\n          writer.write_texts(step, {\"samples\": exemplars})\n\n      # Save a checkpoint on one host after every checkpoint_freq steps.\n      save_checkpoint = (\n          step % config.checkpoint_every_steps == 0 or is_last_step\n      )\n      if config.save_checkpoints and save_checkpoint:\n        logging.info(\"Saving checkpoint step %d.\", step)\n        with report_progress.timed(\"checkpoint\"):\n          checkpoints.save_checkpoint_multiprocess(workdir, state, step)\n"
  },
  {
    "path": "examples/lm1b/train_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport pathlib\nimport sys\nimport tempfile\n\nfrom absl import logging\nfrom absl.testing import absltest\nimport jax\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\nfrom configs import default\nimport train\n\n\njax.config.update('jax_disable_most_optimizations', True)\n\n\nclass TrainTest(absltest.TestCase):\n  \"\"\"Test cases for LM library.\"\"\"\n\n  def setUp(self):\n    super().setUp()\n    if sys.version_info >= (3, 13):\n      self.skipTest('Test (and tensorflow-text) does not suport Python 3.13+')\n    tf.config.experimental.set_visible_devices([], 'GPU')\n\n  def test_train_and_evaluate(self):\n    config = default.get_config()\n    config.max_corpus_chars = 1000\n    config.vocab_size = 32\n    config.per_device_batch_size = 2\n    config.num_train_steps = 1\n    config.num_eval_steps = 1\n    config.num_predict_steps = 1\n\n    config.num_layers = 1\n    config.qkv_dim = 128\n    config.emb_dim = 128\n    config.mlp_dim = 512\n    config.num_heads = 2\n\n    config.max_target_length = 32\n    config.max_eval_target_length = 32\n    config.max_predict_length = 32\n\n    workdir = tempfile.mkdtemp()\n\n    # Go two directories up to the root of the flax directory.\n    flax_root_dir = pathlib.Path(__file__).parents[2]\n    data_dir = str(flax_root_dir) + '/.tfds/metadata'  # pylint: disable=unused-variable\n\n    with tfds.testing.mock_data(num_examples=128, data_dir=data_dir):\n      train.train_and_evaluate(config, workdir)\n    logging.info('workdir content: %s', tf.io.gfile.listdir(workdir))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/lm1b/utils.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Copied over from MaxText (https://github.com/google/maxtext/blob/main/MaxText/max_utils.py).\n\nimport functools\nimport logging\n\nimport numpy as np\nimport flax.linen as nn\nfrom flax.linen import partitioning as nn_partitioning\nfrom flax.training import train_state\nimport jax\nimport jax.numpy as jnp\nfrom jax.experimental import mesh_utils\n\n\n# Mesh utils.\n# -----------------------------------------------------------------------------\n\n\ndef create_device_mesh(config):\n  \"\"\"Creates a device mesh with each slice in its own data parallel group. If there is only one slice, uses two replicas.\"\"\"\n  devices = jax.devices()\n  num_devices = len(devices)\n  try:\n    num_slices = 1 + max([d.slice_index for d in devices])\n  except:\n    num_slices = 1\n  num_devices_per_slice = num_devices // num_slices\n  logging.info(f\"Devices: {devices}\")\n  logging.info(f\"Number of devices: {num_devices}\")\n\n  multi_slice_env = hasattr(jax.devices()[0], \"slice_index\")\n\n  dcn_parallelism = [\n      config.dcn_data_parallelism,\n      config.dcn_fsdp_parallelism,\n      config.dcn_tensor_parallelism,\n  ]\n  ici_parallelism = [\n      config.ici_data_parallelism,\n      config.ici_fsdp_parallelism,\n      config.ici_tensor_parallelism,\n  ]\n\n  # Find possible unspecified parallelisms\n  dcn_parallelism = fill_unspecified_mesh_axes(\n      dcn_parallelism, num_slices, \"DCN\"\n  )\n  ici_parallelism = fill_unspecified_mesh_axes(\n      ici_parallelism, num_devices_per_slice, \"ICI\"\n  )\n\n  if multi_slice_env:\n    mesh = mesh_utils.create_hybrid_device_mesh(\n        ici_parallelism, dcn_parallelism\n    )\n  else:\n    mesh = mesh_utils.create_device_mesh(ici_parallelism)\n\n  logging.info(f\"Decided on mesh: {mesh}\")\n  logging.info(f\"Mesh shape: {mesh.shape}\")\n\n  return mesh\n\n\ndef fill_unspecified_mesh_axes(\n    parallelism_vals, target_product, parallelism_type\n):\n  \"\"\"Evaluates unspecified DCN/ICI parallelism values\"\"\"\n  if -1 in parallelism_vals:\n    assert parallelism_vals.count(-1) == 1, (\n        f\"Found unspecified values (-1) for more than one {parallelism_type}   \"\n        \"   parallelism axis. At most one axis can be unspecified.\"\n    )\n\n    determined_val = target_product / np.prod(parallelism_vals) * -1\n\n    assert determined_val >= 1 and determined_val.is_integer, (\n        \"Unspecified value unable to be determined with the given     \"\n        f\" {parallelism_type} parallelism values\"\n    )\n\n    parallelism_vals[parallelism_vals.index(-1)] = int(determined_val)\n\n  target_type = \"slices\" if parallelism_type == \"DCN\" else \"devices per slice\"\n\n  assert np.prod(parallelism_vals) == target_product, (\n      f\"Number of {target_type} {target_product} does not match    the product\"\n      f\" of the {parallelism_type} parallelism {np.prod(parallelism_vals)}\"\n  )\n\n  return parallelism_vals\n\n\n# State initialization utils.\n# -----------------------------------------------------------------------------\n\n\ndef unbox_logicallypartioned_trainstate(\n    boxed_train_state: train_state.TrainState,\n):\n  \"\"\"Unboxes the flax.LogicallyPartitioned pieces in a train state.\n\n  Args:\n    boxed_train_state: a train state that includes LogicallyPartitioned\n      leaves.\n  Returns:\n    a TrainState where all all LogicallyPartitioned leaves have been unboxed.\n  \"\"\"\n  return jax.tree_util.tree_map(\n      lambda x: x.unbox() if isinstance(x, nn.spmd.LogicallyPartitioned) else x,\n      boxed_train_state,\n      is_leaf=lambda k: isinstance(k, nn.spmd.LogicallyPartitioned),\n  )\n\n\ndef init_train_state(model, tx, config, key):\n  \"\"\"\n  We pass in \"static\" objects like model, tx, config as JAX compares them by\n  object hash, and instantiating them inside causes pjit top-level annotations\n  to fail to match as pytree prefixes if we re-instantiate.\n\n  Args: model, tx, config, key\n  \"\"\"\n  input_shape = (config.per_device_batch_size, config.max_target_length)\n  initial_variables = jax.jit(model.init)(\n      key, jnp.ones(input_shape, jnp.float32)\n  )\n\n  state = train_state.TrainState.create(\n      apply_fn=model.apply, params=initial_variables[\"params\"], tx=tx\n  )\n  return state\n\n\ndef setup_initial_state(model, tx, config, rng, mesh):\n  \"\"\"We initialize the model and optimizer state, and optionally load from a\n  checkpoint as necessary.\n\n  Args:\n    model: the flax model to initialize\n    tx: the optax.GradientTransformation\n    config: config object\n    rng: jax.prng key\n    mesh: jax.devices() mesh\n\n  Returns:\n    state: the initialized train state\n    state_mesh_annotations: the mesh annotations for the train state\n  \"\"\"\n  init_train_state_partial = functools.partial(\n      init_train_state, model, tx, config\n  )\n  abstract_state = jax.eval_shape(init_train_state_partial, rng)\n  state_logical_annotations = nn.get_partition_spec(abstract_state)\n\n  # Initialization\n  with jax.set_mesh(mesh), nn_partitioning.axis_rules(config.logical_axis_rules):\n    state_mesh_annotations = nn.logical_to_mesh_sharding(\n        state_logical_annotations, mesh, config.logical_axis_rules\n    )\n    state = jax.jit(\n        init_train_state_partial,\n        in_shardings=None,  # type: ignore\n        out_shardings=state_mesh_annotations,\n    )(rng)\n\n  state = unbox_logicallypartioned_trainstate(state)\n  return state, state_mesh_annotations\n"
  },
  {
    "path": "examples/mnist/README.md",
    "content": "## MNIST classification\n\nTrains a simple convolutional network on the MNIST dataset.\n\nYou can run this code and even modify it directly in Google Colab, no\ninstallation required:\n\nhttps://colab.research.google.com/github/google/flax/blob/main/examples/mnist/mnist.ipynb\n\n### Requirements\n* TensorFlow dataset `mnist` will be downloaded and prepared automatically, if necessary\n\n### Example output\n\n|  Name   | Epochs | Walltime | Top-1 accuracy |   Metrics   |                  Workdir                  |\n| :------ | -----: | :------- | :------------- | :---------- | :---------------------------------------- |\n| default |     10 | 7.7m     | 99.17%         | [tfhub.dev] | [gs://flax_public/examples/mnist/default] |\n\n[tfhub.dev]: https://tensorboard.dev/experiment/1G9SvrW5RQyojRtMKNmMuQ/#scalars&_smoothingWeight=0&regexInput=default\n[gs://flax_public/examples/mnist/default]: https://console.cloud.google.com/storage/browser/flax_public/examples/mnist/default\n\n```\nI1009 17:56:42.674334 3280981 train.py:175] epoch: 10, train_loss: 0.0073, train_accuracy: 99.75, test_loss: 0.0294, test_accuracy: 99.25\n```\n\n### How to run\n\n`python main.py --workdir=/tmp/mnist --config=configs/default.py`\n\n#### Overriding Hyperparameter configurations\n\nMNIST example allows specifying a hyperparameter configuration by the means of\nsetting `--config` flag. Configuration flag is defined using\n[config_flags](https://github.com/google/ml_collections/tree/master#config-flags).\n`config_flags` allows overriding configuration fields. This can be done as\nfollows:\n\n```shell\npython main.py \\\n--workdir=/tmp/mnist --config=configs/default.py \\\n--config.learning_rate=0.05 --config.num_epochs=5\n```\n"
  },
  {
    "path": "examples/mnist/configs/default.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Default Hyperparameter configuration.\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = ml_collections.ConfigDict()\n\n  config.learning_rate = 0.1\n  config.momentum = 0.9\n  config.batch_size = 128\n  config.num_epochs = 10\n  return config\n\n\ndef metrics():\n  return []\n"
  },
  {
    "path": "examples/mnist/main.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Main file for running the MNIST example.\n\nThis file is intentionally kept short. The majority of logic is in libraries\nthan can be easily tested and imported in Colab.\n\"\"\"\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nfrom clu import platform\nimport jax\nfrom ml_collections import config_flags\nimport tensorflow as tf\n\nimport train  # pylint: disable=g-bad-import-order\n\n\nFLAGS = flags.FLAGS\n\nflags.DEFINE_string('workdir', None, 'Directory to store model data.')\nconfig_flags.DEFINE_config_file(\n    'config',\n    None,\n    'File path to the training hyperparameter configuration.',\n    lock_config=True,\n)\n\n\ndef main(argv):\n  if len(argv) > 1:\n    raise app.UsageError('Too many command-line arguments.')\n\n  # Hide any GPUs from TensorFlow. Otherwise TF might reserve memory and make\n  # it unavailable to JAX.\n  tf.config.experimental.set_visible_devices([], 'GPU')\n\n  logging.info('JAX process: %d / %d', jax.process_index(), jax.process_count())\n  logging.info('JAX local devices: %r', jax.local_devices())\n\n  # Add a note so that we can tell which task is which JAX host.\n  # (Depending on the platform task 0 is not guaranteed to be host 0)\n  platform.work_unit().set_task_status(\n      f'process_index: {jax.process_index()}, '\n      f'process_count: {jax.process_count()}'\n  )\n  platform.work_unit().create_artifact(\n      platform.ArtifactType.DIRECTORY, FLAGS.workdir, 'workdir'\n  )\n\n  train.train_and_evaluate(FLAGS.config, FLAGS.workdir)\n\n\nif __name__ == '__main__':\n  flags.mark_flags_as_required(['config', 'workdir'])\n  app.run(main)\n"
  },
  {
    "path": "examples/mnist/mnist.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Flax MNIST Example\\n\",\n    \"\\n\",\n    \"<a href=\\\"https://colab.research.google.com/github/google/flax/blob/main/examples/mnist/mnist.ipynb\\\" ><img src=\\\"https://colab.research.google.com/assets/colab-badge.svg\\\" alt=\\\"Open In Colab\\\"/></a>\\n\",\n    \"\\n\",\n    \"Demonstration notebook for\\n\",\n    \"https://github.com/google/flax/tree/main/examples/mnist\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The **Flax Notebook Workflow**:\\n\",\n    \"\\n\",\n    \"1. Run the entire notebook end-to-end and check out the outputs.\\n\",\n    \"   - This will open Python files in the right-hand editor!\\n\",\n    \"   - You'll be able to interactively explore metrics in TensorBoard.\\n\",\n    \"2. Change `config` and train for different hyperparameters. Check out the\\n\",\n    \"   updated TensorBoard plots.\\n\",\n    \"3. Update the code in `train.py`. Thanks to `%autoreload`, any changes you\\n\",\n    \"   make in the file will automatically appear in the notebook. Some ideas to\\n\",\n    \"   get you started:\\n\",\n    \"   - Change the model.\\n\",\n    \"   - Log some per-batch metrics during training.\\n\",\n    \"   - Add new hyperparameters to `configs/default.py` and use them in\\n\",\n    \"     `train.py`.\\n\",\n    \"4. At any time, feel free to paste code from `train.py` into the notebook\\n\",\n    \"   and modify it directly there!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Setup\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"outputId\": \"8520b2f8-2b9d-4216-ba1f-d96175455bbc\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[?25l\\r\\n\",\n      \"\\u001b[K     |███▊                            | 10kB 21.5MB/s eta 0:00:01\\r\\n\",\n      \"\\u001b[K     |███████▍                        | 20kB 12.7MB/s eta 0:00:01\\r\\n\",\n      \"\\u001b[K     |███████████                     | 30kB 9.4MB/s eta 0:00:01\\r\\n\",\n      \"\\u001b[K     |██████████████▉                 | 40kB 8.4MB/s eta 0:00:01\\r\\n\",\n      \"\\u001b[K     |██████████████████▌             | 51kB 5.2MB/s eta 0:00:01\\r\\n\",\n      \"\\u001b[K     |██████████████████████▏         | 61kB 5.2MB/s eta 0:00:01\\r\\n\",\n      \"\\u001b[K     |█████████████████████████▉      | 71kB 5.7MB/s eta 0:00:01\\r\\n\",\n      \"\\u001b[K     |█████████████████████████████▋  | 81kB 6.3MB/s eta 0:00:01\\r\\n\",\n      \"\\u001b[K     |████████████████████████████████| 92kB 4.4MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 634kB 8.5MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 102kB 6.0MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 61kB 6.3MB/s \\n\",\n      \"\\u001b[?25h  Building wheel for flax (setup.py) ... \\u001b[?25l\\u001b[?25hdone\\n\",\n      \"  Building wheel for jax (setup.py) ... \\u001b[?25l\\u001b[?25hdone\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Install ml-collections & latest Flax version from Github.\\n\",\n    \"!pip install -q ml-collections git+https://github.com/google/flax\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {\n    \"tags\": []\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"example_directory = 'examples/mnist'\\n\",\n    \"editor_relpaths = ('configs/default.py', 'train.py')\\n\",\n    \"\\n\",\n    \"repo, branch = 'https://github.com/google/flax', 'main'\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {\n    \"cellView\": \"form\",\n    \"outputId\": \"2dfbdfa6-d213-4b5b-dc82-ee1765705255\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Cloning into 'flaxrepo'...\\n\",\n      \"remote: Enumerating objects: 330, done.\\u001b[K\\n\",\n      \"remote: Counting objects: 100% (330/330), done.\\u001b[K\\n\",\n      \"remote: Compressing objects: 100% (298/298), done.\\u001b[K\\n\",\n      \"remote: Total 330 (delta 58), reused 126 (delta 14), pack-reused 0\\u001b[K\\n\",\n      \"Receiving objects: 100% (330/330), 1.81 MiB | 6.59 MiB/s, done.\\n\",\n      \"Resolving deltas: 100% (58/58), done.\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<h1 style=\\\"color:red;\\\" class=\\\"blink\\\">WARNING : Editing in VM - changes lost after reboot!!</h1>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {\n      \"tags\": []\n     },\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/mnist/configs/default.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {\n      \"tags\": []\n     },\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/mnist/train.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {\n      \"tags\": []\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# (If you run this code in Jupyter[lab], then you're already in the\\n\",\n    \"#  example directory and nothing needs to be done.)\\n\",\n    \"\\n\",\n    \"#@markdown **Fetch newest Flax, copy example code**\\n\",\n    \"#@markdown\\n\",\n    \"#@markdown **If you select no** below, then the files will be stored on the\\n\",\n    \"#@markdown *ephemeral* Colab VM. **After some time of inactivity, this VM will\\n\",\n    \"#@markdown be restarted an any changes are lost**.\\n\",\n    \"#@markdown\\n\",\n    \"#@markdown **If you select yes** below, then you will be asked for your\\n\",\n    \"#@markdown credentials to mount your personal Google Drive. In this case, all\\n\",\n    \"#@markdown changes you make will be *persisted*, and even if you re-run the\\n\",\n    \"#@markdown Colab later on, the files will still be the same (you can of course\\n\",\n    \"#@markdown remove directories inside your Drive's `flax/` root if you want to\\n\",\n    \"#@markdown manually revert these files).\\n\",\n    \"\\n\",\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  import os\\n\",\n    \"  os.chdir('/content')\\n\",\n    \"  # Download Flax repo from Github.\\n\",\n    \"  if not os.path.isdir('flaxrepo'):\\n\",\n    \"    !git clone --depth=1 -b $branch $repo flaxrepo\\n\",\n    \"  # Copy example files & change directory.\\n\",\n    \"  mount_gdrive = 'no' #@param ['yes', 'no']\\n\",\n    \"  if mount_gdrive == 'yes':\\n\",\n    \"    DISCLAIMER = 'Note : Editing in your Google Drive, changes will persist.'\\n\",\n    \"    from google.colab import drive\\n\",\n    \"    drive.mount('/content/gdrive')\\n\",\n    \"    example_root_path = f'/content/gdrive/My Drive/flax/{example_directory}'\\n\",\n    \"  else:\\n\",\n    \"    DISCLAIMER = 'WARNING : Editing in VM - changes lost after reboot!!'\\n\",\n    \"    example_root_path = f'/content/{example_directory}'\\n\",\n    \"    from IPython import display\\n\",\n    \"    display.display(display.HTML(\\n\",\n    \"        f'<h1 style=\\\"color:red;\\\" class=\\\"blink\\\">{DISCLAIMER}</h1>'))\\n\",\n    \"  if not os.path.isdir(example_root_path):\\n\",\n    \"    os.makedirs(example_root_path)\\n\",\n    \"    !cp -r flaxrepo/$example_directory/* \\\"$example_root_path\\\"\\n\",\n    \"  os.chdir(example_root_path)\\n\",\n    \"  from google.colab import files\\n\",\n    \"  for relpath in editor_relpaths:\\n\",\n    \"    s = open(f'{example_root_path}/{relpath}').read()\\n\",\n    \"    open(f'{example_root_path}/{relpath}', 'w').write(\\n\",\n    \"        f'## {DISCLAIMER}\\\\n' + '#' * (len(DISCLAIMER) + 3) + '\\\\n\\\\n' + s)\\n\",\n    \"    files.view(f'{example_root_path}/{relpath}')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"outputId\": \"e9061488-ac3e-4d23-f24f-06e1988e7541\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"/content/examples/mnist\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Note : In Colab, above cell changed the working directory.\\n\",\n    \"!pwd\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Imports / Helpers\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from absl import logging\\n\",\n    \"import flax\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"from matplotlib import pyplot as plt\\n\",\n    \"import numpy as np\\n\",\n    \"import tensorflow_datasets as tfds\\n\",\n    \"\\n\",\n    \"logging.set_verbosity(logging.INFO)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Helper functions for images.\\n\",\n    \"\\n\",\n    \"def show_img(img, ax=None, title=None):\\n\",\n    \"  \\\"\\\"\\\"Shows a single image.\\\"\\\"\\\"\\n\",\n    \"  if ax is None:\\n\",\n    \"    ax = plt.gca()\\n\",\n    \"  ax.imshow(img[..., 0], cmap='gray')\\n\",\n    \"  ax.set_xticks([])\\n\",\n    \"  ax.set_yticks([])\\n\",\n    \"  if title:\\n\",\n    \"    ax.set_title(title)\\n\",\n    \"\\n\",\n    \"def show_img_grid(imgs, titles):\\n\",\n    \"  \\\"\\\"\\\"Shows a grid of images.\\\"\\\"\\\"\\n\",\n    \"  n = int(np.ceil(len(imgs)**.5))\\n\",\n    \"  _, axs = plt.subplots(n, n, figsize=(3 * n, 3 * n))\\n\",\n    \"  for i, (img, title) in enumerate(zip(imgs, titles)):\\n\",\n    \"    show_img(img, axs[i // n][i % n], title)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {\n    \"tags\": []\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# Local imports from current directory - auto reload.\\n\",\n    \"# Any changes you make to train.py will appear automatically.\\n\",\n    \"%load_ext autoreload\\n\",\n    \"%autoreload 2\\n\",\n    \"import train\\n\",\n    \"from configs import default as config_lib\\n\",\n    \"config = config_lib.get_config()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Dataset\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {\n    \"outputId\": \"bb4525f4-8ca4-4e9d-d1cc-48a3e0533645\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Load pre-computed DatasetInfo (eg: splits, num examples,...) from GCS: mnist/3.0.1\\n\",\n      \"INFO:absl:Load dataset info from /tmp/tmpqyu1t56xtfds\\n\",\n      \"INFO:absl:Field info.citation from disk and from code do not match. Keeping the one from code.\\n\",\n      \"INFO:absl:Generating dataset mnist (/root/tensorflow_datasets/mnist/3.0.1)\\n\"\n     ]\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[1mDownloading and preparing dataset mnist/3.0.1 (download: 11.06 MiB, generated: 21.00 MiB, total: 32.06 MiB) to /root/tensorflow_datasets/mnist/3.0.1...\\u001b[0m\\n\"\n     ]\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"WARNING:absl:Dataset mnist is hosted on GCS. It will automatically be downloaded to your\\n\",\n      \"local data directory. If you'd instead prefer to read directly from our public\\n\",\n      \"GCS bucket (recommended if you're running on GCP), you can instead pass\\n\",\n      \"`try_gcs=True` to `tfds.load` or set `data_dir=gs://tfds-data/datasets`.\\n\",\n      \"\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"1c176027dbbf459b8ed946ccc58de845\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"HBox(children=(FloatProgress(value=0.0, description='Dl Completed...', max=4.0, style=ProgressStyle(descriptio…\"\n      ]\n     },\n     \"metadata\": {\n      \"tags\": []\n     },\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Load dataset info from /root/tensorflow_datasets/mnist/3.0.1.incompleteJ6TJES\\n\",\n      \"INFO:absl:Field info.citation from disk and from code do not match. Keeping the one from code.\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset for split train, from /root/tensorflow_datasets/mnist/3.0.1\\n\"\n     ]\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\n\",\n      \"\\n\",\n      \"\\u001b[1mDataset mnist downloaded and prepared to /root/tensorflow_datasets/mnist/3.0.1. Subsequent calls will reuse this data.\\u001b[0m\\n\"\n     ]\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Constructing tf.data.Dataset for split test, from /root/tensorflow_datasets/mnist/3.0.1\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Get datasets as dict of JAX arrays.\\n\",\n    \"train_ds, test_ds = train.get_datasets()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {\n    \"outputId\": \"89de05b0-aede-414f-cf43-5e7c71871140\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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3z+HDx9uslWrVpnshBNOMNnmzZsDq0sc7kwBAAAAQACaKQAAAAAIQDMFAAAAAAFopgAAAAAgAAMoUkDnzp1N9txzz5msV69eJnv00UcTUhNwQIUKFUz24IMPmuywww4zWZcuXUy2f//++BSGrHLeeed587/97W9xvU7Pnj1NlpeXZ7KxY8fG9boAEA/HHnusyXyDIS655BKTlS9fPvKavmzhwoUmu+qqq0zmGwh09NFHR6qHARQAAAAAkCFopgAAAAAgAM0UAAAAAASgmQIAAACAAAygSAFdu3Y1me/hvBo1apREOchiqmqyhx9+2GTdu3ePtN6IESNMtmDBguIXhqziG2YydOhQ77G+B6vjbciQISZjAEV6mzt3rsnatWtnspEjR5rslltuSURJwP9Xrlw5kx1zzDEmmzBhgsl+9atfmaxKlSom873PLA7f+4WTTz45pjXTFXemAAAAACAAzRQAAAAABKCZAgAAAIAANFMAAAAAEIABFCWsfv36JuvYsaPJ5s+fb7Knn346ITUBB5xwwgkmizpsYsuWLSZbv359zDUh+7zwwgsma9GihffYqA9R+wafNGvWLNK5ZcrwR2W68D0Uf/zxx5vM95D+/v37TXbTTTeZbN++fSabPn26yYram19//bU3P9hZZ51lsmOPPdZkS5YsMdnMmTNNtmfPnkjXRcny/T997rnnTObbs1G9++67Jvv2229NNmPGDO/5mzZtMtns2bOD6/FZsWKFyXbt2hXXayQKd6YAAAAAIADNFAAAAAAEoJkCAAAAgAA0UwAAAAAQIOOfqvU9jOoT60+CjuqPf/yjyXJyckz23XffmWzZsmUJqQk44NJLLw0+94cffjAZexY/59prrzVZu3btYlrT9/2zbdu2JvMNuujQoYPJfAMojjvuOJP5HuhGyWrSpInJPv300+D1fH8+33LLLZGyZHr77bdN1qlTJ5Nt3LixJMpBAd/AsaKGPhxs69atJps7d67JRo8ebTLfAIriuPLKKyMdt23btkjHVa5c2WRvvvmmyTZv3hxpvWTjzhQAAAAABKCZAgAAAIAANFMAAAAAEIBmCgAAAAACZPwACt+DzGPHjjXZH/7wB5N98MEHca/H93Csz4IFC+J+beDn3HTTTZGO27t3r8lGjBgR73KQYa666iqTjRs3zmRly5aNvObixYtNdu6555rM92D0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uSa78847vcdeeeWVJitqKNDBvv76a5P9/ve/N9nevXsjrYf0VrZsWZO99NJLJuvYsaPJfHuuqO/Rffv2NdnmzZujlOjVvHnzSMd9/vnnJsvLywu+bqJwZwoAAAAAAtBMAQAAAEAAmikAAAAACEAzBQAAAAABMmoARUno2rWryYp6cPSjjz5KdDnAf2nXrl1M50+ePDkudSD9+H5KfVEP0Efx7LPPxlJOZN27dzfZHXfcYbLSpUsHX+P222832bfffhu8HtLbSSedZLKRI0ea7LzzzvOeH3XYxIsvvmgy35Ch5cuXR1oP6eOoo44ymW8A2uWXXx7p3J9++slkvj3ry0REdu7c6c2j8A3B6t27d6RzR4wYYbJUHK7CnSkAAAAACEAzBQAAAAABaKYAAAAAIADNFAAAAAAEYABFMbVt29ZkRT1M+tZbbyW6HGSxk08+2WSNGjWKdK7vJ6Qju61evTqu6+Xk5JjsmmuuMdkpp5xismXLlpmsqOEqbdq0iXRtn/3795vMN4hjzJgxkdZD5qlTp47JJk2aZLIWLVrEdJ2+ffuabMKECTGtidRToUIFkz344IMm69Gjh8miDi554403TDZo0CCTTZs2LdJ6sWrSpInJfO9VVqxYYbKZM2cmpKZ4484UAAAAAASgmQIAAACAADRTAAAAABCAZgoAAAAAAjCA4hCaN29usjJl7Jfstdde857/wQcfxL0m4IBx48aZrGzZspHOHTp0aLzLAf7LgAED4rpeqVL+v/vzDZHwWbNmjcn++te/muzee+8tXmHIaDfddJPJWrZsaTLfcIBt27Z517zllltMNnHixIDqkMp+/etfm8z357ZvCI+qmsz3/WrYsGEm27hxY9QS465evXommzFjhsl8n99dd91lss2bN8ensATjzhQAAAAABKCZAgAAAIAANFMAAAAAEIBmCgAAAAACMIDiEEaOHGmyypUrm6x9+/be83v37m0yfqI5QlSqVMlkxx57bKRzfQ+j5uXlxVwTMovvJ8379skJJ5xQEuUYvgf8RUTWrVtnskceecRkkyZNMtmSJUtirguZw/cAvG8AhW8v+h6UHzRokPc6Dz/8cEB1SDeXXHKJyXyDzYr63nawL7/80mS+96S+IRCJcNppp5nMt+erVatmsm+//dZkvu/b6YI7UwAAAAAQgGYKAAAAAALQTAEAAABAAJop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7OlHhf526qEY10RmSbU9KyJyseT/e/65cVgLmSfV9izvDfBzUmrPOufmSP7dsRki8qPkj0bvGsua6UCdy4o7cAAAAAAQV9l6ZwoAAAAAYkIzBQAAAAABaKYAAAAAIADNFAAAAAAEoJkCAAAAgABlinOwqjL6D8XinDv4h8eVKPYsios9izS0zjlXM1kXZ88iAHsW6abIPcudKQAA0tvSZBcAFBN7FummyD1LMwUAAAAAAWimAAAAACAAzRQAAAAABKCZAgAAAIAANFMAAAAAEIBmCgAAAAAC0EwBAAAAQIBi/dBeAAAAANmjfPnyJjvnnHNM1r9/f5P99a9/NdlHH31kslWrVgVWl3zcmQIAAACAADRTAAAAABCAZgoAAAAAAtBMAQAAAEAABlAAAAAA8Bo/frzJevToEenc1q1bm2zChAkmu/HGG4tfWIrgzhQAAAAABKCZAgAAAIAANFMAAAAAEIBmCgAAAAACMIAigcqVK2eyd99912THHnusyTp06GCyTz75JD6FIWU88MADJjvllFMinfvqq6+abOnSpSZbvXq1yWbPnh3pGgCA1NG4cWOTLViwwGQfffSRyc4444yE1IT0lJOTYzLfexIRkauvvtpkzrlI19mzZ4/JPvjgg0jnpgvuTAEAAABAAJopAAAAAAhAMwUAAAAAAWimAAAAACAAAygSqHr16iZr3rx5pHMnT55sspYtW5rsp59+KnZdSDzf8BHfTxDv2bNn8DVatWplMt8Dofv37zfZxx9/7F3ztttuM9lrr70WUB0AIN5at25tstKlS5vspJNOMtlxxx1nsm+//TY+hSHtXH/99Sa75pprYlrTNwTr7rvvNtlTTz0V03VSDXemAAAAACAAzRQAAAAABKCZAgAAAIAANFMAAAAAEIABFAl0xx13BJ9bpUoVk9WsWdNky5cvD74GEufPf/6zyWIZNuET9aePlypl/87k1FNP9R7rG5LRpUsXkxU1wAJIlDZt2njz+++/32THH3+8yfr372+yCRMmxF4YkCAdO3Y0mW9IUJky9q3cjh07TLZr1674FIa04/v+2a9fv5jW9A0v6dChg8mWLVsW03XSAXemAAAAACAAzRQAAAAABKCZAgAAAIAANFMAAAAAEIABFHHQqVMnb96rVy+TRR0akJeXZzKGTaSP2rVrRzpu+vTpJlu4cKHJtm3bZrInn3zSZOXKlTOZ7yeNn3baad56jjvuOJM98sgjJmvZsqXJ9u3b510Tma9SpUom27t3r8l8++ukk04ymW9/FjWAokmTJlFKlFatWpmMARRIFaVLlzZZnz59TFa3bl2T+b73vvnmmyZbsWJFYHVIJ1WrVjXZ0KFDTZabmxt5zTVr1pisW7duJsuGYRM+3JkCAAAAgAA0UwAAAAAQgGYKAAAAAALQTAEAAABAAAZQxEHjxo1jOt/3UGjPnj1jWhPJ5Xuw/YcffjDZqFGjTBbvQQ7t2rUz2auvvuo99pxzzjFZs2bNTPaHP/zBZOPHjy9+cc5TUHEAACAASURBVEgZhx12mMlmzpwZ6dzdu3ebrEGDBiarVauWycqXL28yVTVZ1OE9Rdm6dWtM5wOJ5BsQ8Jvf/CbSuR999JHJrrrqqphrQnoaN26cyVq3bm2y4nxP9Q2dYqDJf3BnCgAAAAAC0EwBAAAAQACaKQAAAAAIQDMFAAAAAAEYQBEHsT7o+cgjj5hs9erVMa2J5Prss88iZcly9913e3PfsIqcnByTDRkyxGSvvPKKyXxDN5CaKlSoYLIzzjjDZLEMh9i1a5fJtm3bZrLHH3/cZIcffrh3zcsvv9xkpUuXNplvSAaQDL6hVTfffHOkc30DinzDK5KlRYsWJvv444+TUEl2uO6660zWuXPnSOfu2bPHZH379vUey7CJQ+POFAAAAAAEoJkCAAAAgAA0UwAAAAAQgGYKAAAAAAIwgKKYunTpYrKGDRvGtOayZctiOh8ornfeecebjx492mT/+7//a7IjjzzSZLm5uSZjAEX62Lp1q8kuuOCCuF5jyZIlJtuyZYvJVq5cGXnNU0891WQNGjSIdB0gkQ477DBvfvvtt0c+9mBTp0412axZs4pXWALt2LEj2SVklYceeshkUQcCPf/88yabNGlSzDVlI+5MAQAAAEAAmikAAAAACEAzBQAAAAABaKYAAAAAIAADKIppyJAhJitVKnpPunbtWpNNnz49ppqAeHn55ZdN5htA4dOkSROTzZs3L+aaUDJ2795tsldffTUJlfhVq1bNm/se3FdVk/mGXwCJdOGFF3rzK664ItL5GzZsMNnDDz8cU02JlpeXl+wSMpZvAFpUvj+L+/btG0s5KIQ7UwAAAAAQgGYKAAAAAALQTAEAAABAAJopAAAAAAjAAIpiql69ekznjx071mRbtmyJaU0gFfgetvb9dPZ9+/aVRDnIMMcff7w3r127tsmccyY788wzTfb444/HXhggIu3atTPZlClTIp/v27P9+/c32TvvvFOsupA5Bg8ebDLfsB2ft956y2S894wf7kwBAAAAQACaKQAAAAAIQDMFAAAAAAFopgAAAAAgAAMoDuHKK6802ZFHHhn5/G3btplszJgxMdUEJNLatWtNtm7dOpMdccQRJmvQoIHJcnJyTLZz587A6pDNmjRpEtP5ixYtilMlgHXbbbeZrFy5cpHPHzdunMmKM8ACmaVZs2Ymq1+/vsl8g0t82UsvvRRTPVWrVjXZOeecY7Lf//73JitfvrzJnnnmGZM98sgjgdUlH3emAAAAACAAzRQAAAAABKCZAgAAAIAANFMAAAAAEIABFIdw9tlnm6xUqej95969e022Z8+emGpC+qpWrZrJateuHelc31765ptvYq7pYDVr1jSZb9iEz9ixY03GsAnES6wDKBLx+wXZqXfv3iZr3bp15POXLl1qsltvvTWmmpBZfO8XDjvssEjn5uXlmWzx4sUm8w2IOv30071rTps2zWS+oRRRtWnTxmS+oRt9+vQJvkZJ4s4UAAAAAASgmQIAAACAADRTAAAAABCAZgoAAAAAAtBMAQAAAEAApvkV8E0RufDCC03mnIu85qhRo2KqCemrY8eOJvNNu2vUqFGk9Xbv3m2yO++802QzZ8402cKFCyNdQ0TkoosuinzswRYtWhR8LjKPby/5JvJ9//33JuvWrZvJGjduHFM948aNM9kpp5xisttuuy2m6yCz1KpVy2R/+ctfTFa2bFmT+aawioiMHj3aZFu2bAmoDrB80/x8k6R9k/Luvfde75qqarLivB+OokuXLibzfd/2fX7Jxp0pAAAAAAhAMwUAAAAAAWimAAAAACAAzRQAAAAABGAARYGGDRuarGrVqjGtOWPGjJjOR/p6+eWXTVamTPhvt5ycHJMNGzbMZLfffrvJXnnlFZMVtTf//Oc/R6rH9zDrTz/9FOlcZJ6JEyea7PLLLzdZxYoVI60X68POvoEtvt9DQGG+79FTpkwxWf369SOtV9RQnvHjxxevMCBGDzzwgMmuueaamNb0vc95++23TVbUUIuDValSxWQ1a9YsfmFJwJ0pAAAAAAhAMwUAAAAAAWimAAAAACAAzRQAAAAABGAA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zW9+E9frvvvuu3FdL1G4MwUAAAAAAWimAAAAACAAzRQAAAAABKCZAgAAAIAAWTmAYuLEiSbzDXfwPcTs+6npRfE9ZNqpUyeTDRgwwGQ5OTkmc86ZbNu2bSbzPQA4b968IutE8hx++OEma9WqVaRzS2oYSlQNGjQwWaVKlUy2du1ak61atSohNSG6Z599NlKWDurUqRN87r59++JYCUqa7/tLz549TeYbuFKjRg2TDRs2zGTjx4832e7du6OWKB06dDBZhQoVTOYbarF+/frI1wEOpXv37iYbMWKE91jfsKyKFSvGtZ4ePXqY7IUXXojrNRKFO1MAAAAAEIBmCgAAAAAC0EwBAAAAQACaKQAAAAAIkPEDKNq3b2+yc88912S+B/Hy8vJMVrp0aZPdcccd3msPGjTIZBs2bDCZ7yHTb7/91mS+wQTXXXedyS688EKTMYAi+XwPcBb1sGcUW7ZsiaWcuPM9VF2zZk2T+fbiggULElITMp9vaEDfvn2D13vwwQdjKQdJtn//fpM98cQTkbJ4833/ExHp1atXpPPHjBkTz3KQxXzDhDp27Ggy39CoWPkGt/neu7711lsm27FjR9zrSQTuTAEAAABAAJopAAAAAAhAMwUAAAAAAWimAAAAACBARg2gqFy5sskeffRRk3355ZcmmzNnjslq165tsieffNJkZ555preeN99802SdO3c22ebNm73nR+F7iG/dunXB6yFxGjdubLJLL700CZUULScnx2QnnHCCyc4++2yT9ezZMyE1AYfyu9/9zmTly5ePdO7cuXNNtmnTpphrAkREevTo4c3r1q1bwpUg2/kGSyRi2ITP22+/bbJXXnmlRK5dUrgzBQAAAAABaKYAAAAAIADNFAAAAAAEoJkCAAAAgAA0UwAAAAAQIKOm+XXp0sVkubm5Jjv11FNNVq9ePZM9/fTTJmvQoIHJHnvsMW89N954o8l27tzpPTYKX92+yX1TpkwJvgYSp2vXrsHnzp4922RHHXWUyfbv32+yfv36mcw3qVJEpEKFCia76KKLopQIpB3f76vdu3cnoRJkogsuuCDysc8995zJdu3aFc9ykMWuueYak3Xr1s1kN998s/f8qlWrmqykpgGmA+5MAQAAAEAAmikAAAAACEAzBQAAAAABaKYAAAAAIEBGDaDo37+/ydasWWOyrVu3mmzOnDkmO/roo012++23m+yvf/2rt55Yhk10797dZL4HCEeMGGGy1atXB18X8XHSSSeZ7JJLLgle79xzzzXZ0qVLI53rnDPZnj17vMdu3rzZZDfddJPJOnToYLILL7wwUj1APLVt29Zkqhrp3Hnz5sW7HGSp3/72tyZr166d99ivv/7aZL6BVb6BQkAI3/vCMWPGRMpERO677z6T+fasT7NmzUxWv359k0V9T5OKuDMFAAAAAAFopgAAAAAgAM0UAAAAAASgmQIAAACAABk1gKJixYomW7t2rcn27dtnMt+widtuu81kf/vb30y2ffv2qCV6dezY0WTDhw832cyZM012zz33xHRtJMYxxxxjstq1a8f1Gr494ntgefHixSabMmVKTNe+//77TeYb7MJPSEe81KhRw5ufdtppJvMNXfnmm28iZUAI3yCqogZIvPvuuyZbv3593GtCYvi+5yxYsMBkO3bsKIlyUt5xxx1nslq1apmMARQAAAAAkGVopgAAAAAgAM0UAAAAAASgmQIAAACAABk1gCKqTZs2maxXr14me+aZZ0y2bdu2mK7dqVMnk/l+srRvaMDAgQNNtmvXrpjqQWLMmjXLZO+//77JWrVqZbJ58+aZbOzYsSZ7+eWXTeZ78B7IBPfee683z83NjXT+xo0bI2XAz6lfv77JKleubDLfUB4R/wAfpI+5c+eazDdUZPTo0SbzvTdIB0OGDDHZjTfemIRKUhN3pgAAAAAgAM0UAAAAAASgmQIAAACAADRTAAAAABAgowZQ7Nmzx2THHHOMyX7xi1+Y7PHHHzfZ3r17TaaqJqtevbq3Ht9gic6dO5ts2rRpJhs0aJDJli9f7r0OUo9v7/j+35cvX95k69atM9mWLVviUxiQBmrUqGGy008/PfL527dvN1nv3r1jqgnZqUwZ+zbJ9+D94YcfbrKnn37au+bChQtjLwxJ880335isXbt2JmvZsqXJ7rzzTpNNnTrVZCtWrAgrLkH69euX7BJSGnemAAAAACAAzRQAAAAABKCZAgAAAIAANFMAAAAAECCjBlB07drVZLNnzzbZJ598YrIPPvjAZJ9//rnJfAMt2rdv763n+++/N9mVV15pMt8ACmSelStXJrsEIC3cfPPNJmvQoEHk830DKHjoHyEaN25sMt/+9JkxY0a8y0EKGD58uMkmT55ssooVK5ps1KhRJrv++utN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gWYKAAAAADzQTAEAAACAh7gbQOEaNrFs2TKVVaxY0XuNCxcuqOzixYve18tI7ty5VZYrl+5fy5Urp7K5c+eqrHr16tEpDHGjYMGCKqtcubLKNm3apLIiRYqorE2bNiobNGiQylx/zkREjhw5orJ3331XZa6H9N98802VHT58WGU//vijyp566imV3X333SobPny4ylzvD4i9kiVLqsz1/6pGjRoRreN6765bt6539tBDD6ns+PHjntUhTFwDUlxfdx944AGV9enTR2VXXXWVc51//vOfKrvhhhtU1qxZM5WtXr1aZefOnXOuA2Ql13CISIZNhBl3pgAAAADAA80UAAAAAHigmQIAAAAADzRTAAAAAOAh7gZQVKhQQWVlypRRWUpKisq2bt2qsqlTp6rso48+Utl3330XtMTAkpKSVBb0YXnX5+waGrBt27bMF4aYuOKKK1TmGtrgerj5888/V1mJEiVUFnRIibXWmRctWlRlvXv3DnTNJ598UmV79uxR2d69e1XmGgTgMmvWLJXVqVNHZcnJyYGuh+goVaqUyhYvXqyySIZNfPPNN8785MmTKrvppptUlieP/nLnesD/3nvvVdmMGTOClIg4lTdvXpVdffXVKps3b57KbrnlFu91M3qfve222wId63rff+utt1T26KOPqsz1PRLgyzVYYsiQIdlfSJzizhQAAAAAeKCZAgAAAAAPNFMAAAAA4IFmCgAAAAA8xN0AihUrVqhs0aJFKnMNY6hZs2ZWlOStWrVq3udu2LBBZQybCLd+/fqpzDVswqVRo0Yqcz0U/+tf/zrzhaUTdNiE66Fs18P8ZcuWVVm5cuVU5nr42pX16dNHZbt27cqwTkSfazjO/PnzVXbjjTcGup5rgIRrbz/zzDPO848ePaqy+vXrq2z58uUqy507d5ASESLXX3+9ykaPHq2y+++/33uNHTt2qOzcuXOBzx81apTK7rrrLpXdfvvtKuvSpYvKli5dqrL33nsvcD3Az3ENm3ANpYi2oUOHZvka0cCdKQAAAADwQDMFAAAAAB5opgAAAADAA80UAAAAAHgwGf2EbufBxgQ/OIpcP5H+tddeU1njxo1Vlh0Pp+fJ457j4XpI1fXwtkupUqVUduDAgcwVFgestSaW68dqz7qsWrVKZbfeemugc1evXq2yli1bqiy79kjt2rVVtm7dukDnnjp1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\",\n      \"text/plain\": [\n       \"<Figure size 1080x1080 with 25 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"tags\": []\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"show_img_grid(\\n\",\n    \"    [train_ds['image'][idx] for idx in range(25)],\\n\",\n    \"    [f'label={train_ds[\\\"label\\\"][idx]}' for idx in range(25)],\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Training\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Get a live update during training - use the \\\"refresh\\\" button!\\n\",\n    \"# (In Jupyter[lab] start \\\"tensorboard\\\" in the local directory instead.)\\n\",\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  %load_ext tensorboard\\n\",\n    \"  %tensorboard --logdir=.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {\n    \"outputId\": \"a0eb78b5-ee73-4f4f-8400-41b521f42b75\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Load dataset info from /root/tensorflow_datasets/mnist/3.0.1\\n\",\n      \"INFO:absl:Field info.citation from disk and from code do not match. Keeping the one from code.\\n\",\n      \"INFO:absl:Reusing dataset mnist (/root/tensorflow_datasets/mnist/3.0.1)\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset for split train, from /root/tensorflow_datasets/mnist/3.0.1\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset for split test, from /root/tensorflow_datasets/mnist/3.0.1\\n\",\n      \"INFO:absl:train epoch: 1, loss: 0.2352, accuracy: 92.94\\n\",\n      \"INFO:absl:eval epoch: 1, loss: 0.0592, accuracy: 98.00\\n\",\n      \"INFO:absl:train epoch: 2, loss: 0.0584, accuracy: 98.15\\n\",\n      \"INFO:absl:eval epoch: 2, loss: 0.0575, accuracy: 98.14\\n\",\n      \"INFO:absl:train epoch: 3, loss: 0.0423, accuracy: 98.66\\n\",\n      \"INFO:absl:eval epoch: 3, loss: 0.0357, accuracy: 98.78\\n\",\n      \"INFO:absl:Load dataset info from /root/tensorflow_datasets/mnist/3.0.1\\n\",\n      \"INFO:absl:Field info.citation from disk and from code do not match. Keeping the one from code.\\n\",\n      \"INFO:absl:Reusing dataset mnist (/root/tensorflow_datasets/mnist/3.0.1)\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset for split train, from /root/tensorflow_datasets/mnist/3.0.1\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset for split test, from /root/tensorflow_datasets/mnist/3.0.1\\n\",\n      \"INFO:absl:train epoch: 1, loss: 0.2745, accuracy: 91.83\\n\",\n      \"INFO:absl:eval epoch: 1, loss: 0.0478, accuracy: 98.36\\n\",\n      \"INFO:absl:train epoch: 2, loss: 0.0508, accuracy: 98.42\\n\",\n      \"INFO:absl:eval epoch: 2, loss: 0.0382, accuracy: 98.81\\n\",\n      \"INFO:absl:train epoch: 3, loss: 0.0374, accuracy: 98.85\\n\",\n      \"INFO:absl:eval epoch: 3, loss: 0.0264, accuracy: 99.09\\n\",\n      \"INFO:absl:Load dataset info from /root/tensorflow_datasets/mnist/3.0.1\\n\",\n      \"INFO:absl:Field info.citation from disk and from code do not match. Keeping the one from code.\\n\",\n      \"INFO:absl:Reusing dataset mnist (/root/tensorflow_datasets/mnist/3.0.1)\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset for split train, from /root/tensorflow_datasets/mnist/3.0.1\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset for split test, from /root/tensorflow_datasets/mnist/3.0.1\\n\",\n      \"INFO:absl:train epoch: 1, loss: 0.2676, accuracy: 91.85\\n\",\n      \"INFO:absl:eval epoch: 1, loss: 0.0485, accuracy: 98.57\\n\",\n      \"INFO:absl:train epoch: 2, loss: 0.0483, accuracy: 98.54\\n\",\n      \"INFO:absl:eval epoch: 2, loss: 0.0461, accuracy: 98.64\\n\",\n      \"INFO:absl:train epoch: 3, loss: 0.0341, accuracy: 98.91\\n\",\n      \"INFO:absl:eval epoch: 3, loss: 0.0396, accuracy: 98.74\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# 3x 3 epochs trains in ~1 minute in the GPU Colab...\\n\",\n    \"\\n\",\n    \"# We don't use TPUs in this Colab because we do not distribute our\\n\",\n    \"# training using pmap() - if you're looking for an example using TPUs\\n\",\n    \"# checkout below Colab:\\n\",\n    \"# https://colab.research.google.com/github/google/flax/blob/main/examples/imagenet/imagenet.ipynb\\n\",\n    \"\\n\",\n    \"config.num_epochs = 3\\n\",\n    \"models = {}\\n\",\n    \"for momentum in (0.8, 0.9, 0.95):\\n\",\n    \"  name = f'momentum={momentum}'\\n\",\n    \"  config.momentum = momentum\\n\",\n    \"  state = train.train_and_evaluate(config, workdir=f'./models/{name}')\\n\",\n    \"  models[name] = state.params\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {\n    \"cellView\": \"form\",\n    \"tags\": []\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  #@markdown You can upload the training results directly to https://tensorboard.dev\\n\",\n    \"  #@markdown\\n\",\n    \"  #@markdown Note that everbody with the link will be able to see the data.\\n\",\n    \"  upload_data = 'no' #@param ['yes', 'no']\\n\",\n    \"  if upload_data == 'yes':\\n\",\n    \"    !tensorboard dev upload --one_shot --logdir ./models --name 'Flax examples/mnist'\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Inference\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {\n    \"outputId\": \"3af424f7-4433-475d-817c-5c0bbc4599ae\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"0.0126\"\n      ]\n     },\n     \"execution_count\": 13,\n     \"metadata\": {\n      \"tags\": []\n     },\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# Find all mistakes in testset.\\n\",\n    \"logits = train.CNN().apply({'params': state.params}, test_ds['image'])\\n\",\n    \"error_idxs, = jnp.where(test_ds['label'] != logits.argmax(axis=1))\\n\",\n    \"len(error_idxs) / len(logits)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {\n    \"outputId\": \"949487f5-8aa2-45c8-9b54-efbf34ab58f1\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"iVBORw0KGgoAAAANSUhEUgAAA1MAAANRCAYAAAAGcOaXAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nOzde7zVc9r/8etKqelEaiuFUigR6TDMOFQynYZu/RJjKodRQjRKdJIGHSZTFCoGk+PcIjWYSEqoUJSkExWddNBOEZUO+/P7Y+/u2XfXZ3d/12evvdfp9Xw81uNR7/09fNZ2WXtdfff3WuqcEwAAAABAbEokegEAAAAAkIpopgAAAAAgAM0UAAAAAASgmQIAAACAADRTAAAAABCAZgoAAAAAAtBMxZGqrlXVSxO9DiAqahaphppFqqFmkWqo2djQTCUBVe2tqltU9UdV/Yeqlk70moCCqOpZqvq2qmarKh9Uh6RHzSKVqeosVXWqWjLRawEKksmvszRTBSiuFy1VbS0i/UWkpYjUFJHaInJfcZwb6aUYf9DuF5GXReTGYjof0hQ1i1RT3A2NqnYWkVLFeU6kF15ni17GNVN5ly4HqOpyVd2hqhNVtYyqNlfVjaraT1W3iMhEVS2hqv1VdY2qblfVl1X1uHzH6qqq6/K+NihwSdeJyNPOuWXOuR0i8oCIXF/4Z4p0kWw165z70jn3tIgsi9dzRHqhZpFqkq1m845zjIgMEZG74/AUkWaSrWYz+XU245qpPJ1FpLWI1BGR00Xknry8mogcJ7lXiG4SkdtF5AoRaSYi1UVkh4iMExFR1foiMkFEuuZ9rbKInHjoBKr6R1XdeYTHyXmbnikin+db2+ciUlVVKxfB80bqSqaaBaKgZpFqkq1mh+cda0tRPWGkvGSr2czknMuoh4isFZGb8/29nYisEZHmIrJPRMrk+9oKEWmZ7+8nSO5lzJIicq+IvJTva+Xy9r80xvWsEZE2+f5eSkSciNRK9PeKR3I8kq1m8+1/au5LSOK/RzyS60HN8ki1R7LVrIg0EZHFeceslfe+oGSiv088kueRbDWbb/+Me53N1JsZN+T78zrJ7cRFRLY55/bm+1pNEZmqqjn5soMiUjVvn/85jnPuZ1XdHrCWn0SkYr6/H/rzroBjIX0lU80CUVCzSDVJUbOqWkJExovIn51zB1Q1lt2RWZKiZjNdpv6a30n5/nyyiGzK+/Ph00c2iEhb59yx+R5lnHPfisjm/MdR1bKSe2n00N87q+pPR3gcuiy6TETOyXfOc0Rkq3OOQkZ+yVSzQBTULFJNstRsRcm9MjUp756XT/J236iqF8X1GSPVJUvNZrRMbaZ6quqJeTffDRKRSQVs97iIDFPVmiIiqpqlqv+V97XJInKZql6oqkeLyP2S7/vpnHvROVf+CI/1eZs+JyI3qmp9VT1Wcn/f9Zm4P2OkuqSpWc1VRkSOzvt7GWWcPyxqFqkmWWr2B8m9WtAw79Eub/fGIjI/zs8ZqS1ZajajX2cztZn6p4jMEJGvJff3S4cWsN1YEXldRGao6i4R+VhEzhMRcc4tE5GeecfaLLk3822MdSHOueki8qCIzBaR9ZJ7mXZIrMdB2kuampXcXxfYI/+Z2LNHRL4MOA7SGzWLVJMUNetybTn0EJFteV/a6pzbF9tTQppLiprNk7Gvs+pcRn2ulqjqWhHp5pybmei1AFFQs0g11CxSDTWLVEPNJo9MvTIFAAAAAIVCMwUAAAAAATLu1/wAAAAAIB64MgUAAAAAAWimAAAAACBAyVg2VlV+JxAxcc4l9KPbqVnEippFCsp2zmUl6uTULAJQs0g1BdYsV6YAAEht6xK9ACBG1CxSTYE1SzMFAAAAAAFopgAAAAAgAM0UAAAAAASgmQIAAACAADRTAAAAABCAZgoAAAAAAtBMAQAAAEAAmikAAAAACEAzBQAAAAABaKYAAAAAIADNFAAAAAAEoJkCAAAAgAA0UwAAAAAQgGYKAAAAAALQTAEAAABAAJopAAAAAAhAMwUAAAAAAUomegEAAABHkpWVZbKbb77ZZB07djRZhQoVTDZ79myTdevWLXB1ADIZV6YAAAAAIADNFAAAAAAEoJkCAAAAgAA0UwAAAAAQgAEUAIBCqVKlislmzJhhsjPOOMNkH3/8caR9R48e7T33vn37oiwRKW7nzp0m69Kli8lOPfXUSMe74YYbTLZ69WqTvfHGGyZbtmxZpHMARa13794mu+KKK0yWnZ0daTtV9Z7HOWeyuXPnmqxPnz4mW7hwofeY6YQrUwAAAAAQgGYKAAAAAALQTAEAAABAAJopAAAAAAigvpvKCtxYNfrG8N7IV758eZNVqlTJZD169DDZ1VdfbbLatWub7O677zbZY489ZrK9e/eaLN6cc/67GYsJNetXr149b/7ZZ5+Z7IsvvjDZ+eefb7KcnJzCLywJULOx89VTYW7S9712FjSA4q677go+TxpZ6JxrkqiTJ6pmfXV34403muzWW281WZkyZUzmq7s9e/ZEOt6kSZNMVhw/Y1NYRtZsvPleZ+vWrWsyX2373v/HMoDCt+22bdtMdsstt5hs6tSp3vMkuQJrlitTAAAAABCAZgoAAAAAAtBMAQAAAEAAmikAAAAACMAAijioVq2aNx8yZIjJbrrpJpP98MMPJvPd9Oq7wfWbb74xme9mv4MHD5rsggsuMNmPP/5ossLgZv7EO+GEE0w2ffp077a1atUyme+G7smTJxd6XcmKmo1dqVKlTFanTh2T9evXL9LxrrvuOpPNnDnTu+0VV1xhst27d0c6TxrhZv4jaNiwocmeffZZkzVo0MBkUd8jTZkyxWR9+/b1brtu3bpIx0xz1GwcdOjQwWQPPfSQyapUqWKylStXFurcjRs3Npnv/5dFixaZrGnTpoU6d4IwgAIAAAAA4olmCgAAAAAC0EwBAAAAQACaKQAAAAAIwACKI6hYsaLJhg4darIePXp49y9ZsqTJFi9ebLKrr77aZKtXr46yRK+rrrrKZP/93/9tsho1aphsy5Ytwef14Wb+4tWsWTOT9e/f32StW7f27t+pUyeTvfrqq4VfWAqhZhMvJyfHZAX9rGrTpo3J3nnnnbivKclxM38cPPXUUya74YYbgo9X0KAe38/8DETNFhHfsImiGEDhq2/fQCDfQLVrr73WZFOnTi3UeooBAygAAAAAIJ5opgAAAAAgAM0UAAAAAASgmQIAAACAAHZCQoY6//zzTfbII4+YzPeJz1u3bvUe84UXXjDZ3XffHbC6wvv+++9Ntn///gSsBPkde+yxJtu5c6fJVO1MhF69eplsyJAhJvPVcd26db3rqV69ujcHitO4ceNMduutt3q3/eMf/2iyDBxAgTjo1q2byY4//niTXXbZZZGO5xvoI+J/Pe/cubPJ+BmNENnZ2ZGyWGRlZZns//2//2cy36CgcuXKmaxVq1YmS4EBFAXiyhQAAAAABKCZAgAAAIAANFMAAAAAEIBmCgAAAAACZOQAinPOOcdkvhv3fcMmfJ5++mlvPnjw4NgWFidXXXWVyV577TWTbd++vTiWgzy+TyAfNWqUyXyDS3y1dMopp5isa9euJps2bZrJWrZsWeA6gUQbPXq0ya6//nrvtr4bmU866SSTbdiwodDrQua57bbbTFanTh2T1atXL/Ixr7zySpPt2LHDZD169Ih8TKAoPffccybzDZvwZT6pPGzChytTAAAAABCAZgoAAAAAAtBMAQAAAEAAmikAAAAACJBWAygqVqxosj/84Q8mGzlypMl2794dabubbropcHVF4/zzzzeZ74bs9u3bF8dycAQ9e/Y0WZcuXUx23XXXmezf//63yS655BKTrV69OtJaPvjgg0jbAYmwdu1ak/lugBYRufnmm01WtmzZeC8JGWr9+vUm69+/v8mefPJJk2VlZUU+T4MGDWJbGFAEChq81qhRI5OpaqRjLlq0yGQzZsyIbWFJjitTAAAAABCAZgoAAAAAAtBMAQAAAEAAmikAAAAACJBWAyhq165tsgkTJphs9uzZJhs0aJDJtm7dajLfzc6JdOedd5rM90nqK1euLI7l4Ah8n/j9008/mWzatGkmW7VqlckOHjwYn4XlU6IE/76C5LRixYpELwEQEZE33njDZMOGDTPZmDFjIh/zzDPPNFnbtm1N9tZbb0U+JhCrhx56yJtXrlzZZM65SFkm4J0TAAAAAASgmQIAAACAADRTAAAAABCAZgoAAAAAAqTsAIqKFSuabOTIkZH2/ctf/mKy4447zmSvvfaayb799luTjRs3LtJ5C+vGG280Wbt27Uzmuzl2y5YtRbImRLdkyZJIWXGYP3++N+/bt6/JHnnkEZNl6k2mAODz+uuvm6xLly7ebZs2bWqy8uXLm+zhhx82GQMoEC9ZWVkmu+iii7zb+n7mq2qk83Tt2jW2haUgrkwBAAAAQACaKQAAAAAIQDMFAAAAAAFopgAAAAAgQMoOoBg6dKjJLr30UpP5hkP4hjE8+eSTJtu2bZvJWrVqFel4heW7MfCOO+4wWZkyZUz26KOPxn09SC/vvfeeN3/qqadMdswxx5hs586d8V4SAKSEU0891WSnnXaaydatW+fd/9e//nWk85QrV85kxx57rMl4Pcb/xfee8s033zRZQcOlog6dmjJlislWrlwZad9UxpUpAAAAAAhAMwUAAAAAAWimAAAAACAAzRQAAAAABKCZAgAAAIAAKTHNr1q1aibr0aOHyZYsWWKy+fPnm2zatGkmO3DggMl+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AADxU7p0aZP99re/Ndm9997r3b958+Ymy8nJMdmoUaNMds8995hs//793vMAAACkA65MAQAAAEAAmikAAAAACEAzBQAAAAABaKYAAAAAIIA656JvrBp9Y0BEnHOayPOnS81Wr17dZNdee63JLrnkkkhZQVTtf66orxENGzY02dKlSyOfO1lQs0WnQoUKJrvtttsi7duqVStvfv7555vsoYceipRt37490rlTwELnXJNEnTydazaqJk3st3/kyJHebVu0aGGyefPmmezyyy832c6dOwNWl5SoWaSaAmuWK1MAAAAAEIBmCgAAAAAC0EwBAAAAQACaKQAAAAAIkNABFJUrVzZZlSpVTPbzzz+bbNOmTSbLycmJz8IQN9zMH7vmzZub7B//+IfJTj755LifuzADKIYNG2ayIUOGFHpNxY2ajY+6deuabMGCBSYrV65cpOP5alMken3u3r3bZAMGDDDZuHHjIh0vyXAzfzHyvfa+9957JqtVq5Z3/6g16xvOMmHChEj7pgBqFv9L7dq1TdaxY0eTnXvuuSbz/WwZM2ZMfBb2HwygAAAAAIB4opkCAAAAgAA0UwAAAAAQgGYKAAAAAAKUTOTJJ0+ebLJKlSqZbM6cOSZbtGiRySZOnBifhQFFoHr16ibz3WDcp08fk5UqVcpksQyPOZzv/z0RkTPPPNNkZ5xxRqRj1qxZM3g9SG2+wUHjx483WdRhE0WhbNmyJnvwwQdN1rp1a5O1b9++SNaE5HfWWWeZbNKkSSaL5fVv165dJqtQoYLJ9u3bF/mYQLKqU6eOyfr27Wuy7t27m6xECXvNZ+3atSabOnVq2OLihCtTAAAAABCAZgoAAAAAAtBMAQAAAEAAmikAAAAACJDQARRlypQxWYMGDUy2ZMkSk/k+Bdx3E/Tf/va3wNUB8fXYY4+ZLN43to8aNcpkDz30kMmys7O9+2dlZZls5syZJvMNpfB9Uvldd91lsm3btnnPjdRw/PHHm+zFF180WbNmzYpjOYVSunRpk/l+jiAznHrqqSabNWuWyXw18ssvv5jshhtu8J7n008/NVmTJk0infvKK680mW+40TPPPGOyH3/80bseIMQ555xjsieeeMJkjRo1MlnJkrb92L9/v8l87/VHjx5tMt9QiuLElSkAAAAACEAzBQAAAAABaKYAAAAAIADNFAAAAAAESOgAiokT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JuvZs6fJ9uzZY7Lhw4ebbPr06XFZVzpyztnpBcUoXWo23g4ePOjNfa8HvgEUvu06depksqlTpwasLrGoWaSghc65Jok6ebrUbNWqVU12yy23mKx58+Ym27Rpk8kKen+1ZcsWk915550RVphWqNk4KFeunMnq1atnsu7du0c6XlZWljf31XJ2drbJfD/z33777UjnTgEF1ixXpgAAAAAgAM0UAAAAAASgmQIAAACAADRTAAAAABCgZKIXUNQmTJhgsqOOOspkv//970123HHHmWzKlCkmO/HEE73nfuqpp6IsESh2vpuqCzJ06FCTVa5c2WQDBw40WSoOoEDsfK+LHTp0iLTv3LlzTdauXTuT7dq1K/aFASJSq1Ytk914440m69Gjh8l87wOiDuWJxbHHHmuyu+++22Tbt28v1HmQGnyDIHyvqX/+859NVrduXZNFrVnfdrFs261bN5Oly3CqI+HKFAAAAAAEoJkCAAAAgAA0UwAAAAAQgGYKAAAAAAKk1QAK341vxx9/vMn+8Ic/mGz//v0m++6770zWpIn98ONTTz016hKBpPD3v/898raNGjUyme//NWQu383JUW/Iv+CCC0y2du1ak40aNcq7/7PPPmuyTZs2RTo3MsO8efNMVrVq1eDj7d6922S+wRAFDafyuf766022evVqk40YMSLyMZG6evXqZTLfkKfCDpYI3S6WbZ944gmTrV+/3mQLFy6MfO5kw5UpAAAAAAhAMwUAAAAAAWimAAAAACAAzRQAAAAABEirARR/+tOfTHbppZeazDdsIqozzjjDZPXr1/duu2TJkuDzAMnMd+PpnDlzErASJIO//e1vJuvQoUPw8SpVqmSyYcOGebdt3769yQYPHmyymTNnBq8HqaNx48Ymq1atmsl8N+l/9dVXJnvggQdM9t5775ls3759Jjv77LO9a5w6darJKlSoYDJfHZ900kkmu/XWW73nQeryDZuIOtQn3tsV9piVK1c2mW+IFQMoAAAAACDD0EwBAAAAQACaKQAAAAAIQDMFAAAAAAFSdgBFixYtTPbZZ5+ZzPdJ5YXhu/mzb9++3m2ff/75uJ4bSATfIAHfTaZTpkwpjuUgCX3++ecme+ONN0x2+eWXRzpex44dTTZy5Ejvtuedd57J/v3vf5usc+fOJnv11VcjrQepwze0oUQJ++/GOTk5Jtu7d6/JfLW0a9euSGuZPXu2Nz/22GNNdt9995nsnnvuMVmPHj1MdtRRR0XaDqkjas36hkFlZ2ebbP369YVaT7169UxWrly5SPv61phuuDIFAAAAAAFopgAAAAAgAM0UAAAAAASgmQIAAACAACk7gOJ3v/udydauXVvk5/XdeHrKKad4t61fv77Jli9fHvc1AUVp6tSpJuvevbvJ5s6dWxzLQRLas2ePyTZv3hx8vAULFpjslltu8W47evRok5199tkmGzBggMl8AwK+//77KEtEkjr33HNN5rtx3/ez/IEHHoi0XVHwDVi55JJLTPab3/zGZDfeeKPJfANgfMM0kHi+IU++mvUNfpozZ47J+vTpY7JFixYFri7Xc889ZzLfUB/fGjMBV6YAAAAAIADNFAAAAAAEoJkCAAAAgAA0UwAAAAAQIGUHUDRt2tRky5Yti+s5srKyTDZ8+HCTTZs2zbu/75PYr7nmmsIvDCgkX22L+G/S990cyyAVFLdZs2Z588cff9xk48ePN1mjRo1Mdscdd5js3nvvDVgdEqFly5YmK+i17XBffPGFyXzDdorL7t27TfbQQw+Z7JVXXol0vNNOO63Qa0LxWLFihclmzJhhsq5du5osOzs7+Ly+/1d69erl3dY3bEJVI53Ht11h1p2MuDIFAAAAAAFopgAAAAAgAM0UAAAAAASgmQIAAACAACk7gMJ3Y+a4ceNMFvUTzX2fmv7oo4+a7MUXXzTZvHnzvGv03TB91llnmWzp0qXe/YFY1axZ02Tbtm0zWZcuXbz7//nPfzaZ78boTp06BawOiKZVq1Ymmzhxonfbjz/+uKiXgyR19913m+zoo49OwEqKxubNm4P37datm8kefvjhwiwHRWTlypUma9u2bVzPcdNNN5mse/fuJvMN6hERcc5FOo9vO99glxEjRkQ6XqrgyhQAAAAABKCZAgAAAIAANFMAAAAAEIBmCgAAAAACpOwAirfeestkb7/9tsm++eYbk23dutVkO3bsMFnv3r1NVtCwCZ8JEyaYbMiQISa76qqrTBb1Zj8gvwULFpisT58+Juvfv793f1/dDR8+3GS+G2aBeHnyySdNVqZMGe+2H330UVEvBylEVSNlY8eOLY7lxJ3vufhUqFChiFeCZNC4cWOTvfnmmybLysoyme/nfdT6imXbW265xWS+wVapjCtTAAAAABCAZgoA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uV7rYjFy5EiV3X777YGysF4/Y7DaWqtfJJIk1dfstGnTVDZ48GCVffbZZyrLyspKSE1gzYatSZMmKnviiSec+3bu3FllGzduVNnOnTtV9umnn6rsrrvuClJiqil2zXJnCgAAAAA80EwBAAAAgAeaKQAAAADwQDMFAAAAAB4ywi6gtDlw4IDKTpw44X0+16dVV6hQwft8SK46deqozPXw/Z133qky17CJffv2qWzixIkqe/fdd531uB7AbtiwoXNf4CTXJ9y7hk24Hlh+4IEHVFbSYROua48bN05lZ555pspatGihsggMoMAPcP1/1+X8889XWevWrVW2cuXKQOcbMGCAM7/ssstUtnr16kDnfOONN1TmGjKE0qt69eoq69Onj8pc7w1c7wFERC655BKV5eXlqez0009XWU5OjvOc6YQ7UwAAAADggWYKAAAAADzQTAEAAACAB5opAAAAAPDAAAogRNddd53Kxo8frzJr9Ye1f/zxxyobM2aMypYuXaoy1yefF6dRo0aB90X6cz1g3KxZs0DHuh543r59e4lrOpXrYWvXsAmXLVu2xLschGzPnj2B9nOt7T/96U8qW7Nmjcp2796tsr59+zqv43o9D2rKlCkqu/baa1W2atUq72sgOlxr7LbbblOZa+jJHXfcobI333zTeZ3jx4+r7IwzzlBZuXLlnMenO+5MAQAAAIAHmikAAAAA8EAzBQAAAAAeaKYAAAAAwEPaD6CoUqWKyurUqaOy7OzsQNk777yjsrVr1zqvvX//fpVlZWWpzPUQH9JPZmamykaNGqUyY4zK8vPzVda9e3eV7d2717O64q+9cOFC7/NVq1ZNZd988433+RA+1+vVRRddpLIdO3ao7LXXXktITaeqV69eoP2+/vprlS1btize5SBkU6dOVZnr9Xjw4MEqq169uso6dOgQ6LoHDhxw5q73Bm+88YbKXINUatWqpbL27durjAEU6ce1Rpo3b66ybt26qWzz5s1xr+eSSy5RWd26dVX285//XGUjRoxQ2aZNm+JTWAi4MwUAAAAAHmimAAAAAMADzRQAAAAAeKCZAgAAAAAPkR1A4XrguXbt2ipzPXjaokUL7+u6HqRzPbQvIpKbm6sy10OvFSpUCHRt13W2bt2qsk8//TTQ+ZBcP/3pT1V23nnnqezbb79VmetTzksybMI10EJEZN68eSqb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\",\n      \"text/plain\": [\n       \"<Figure size 1080x1080 with 25 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"tags\": []\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Show some of them.\\n\",\n    \"show_img_grid(\\n\",\n    \"    [test_ds['image'][idx] for idx in error_idxs[:25]],\\n\",\n    \"    [f'pred={logits[idx].argmax()}' for idx in error_idxs[:25]],\\n\",\n    \")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"accelerator\": \"GPU\",\n  \"language_info\": {\n   \"name\": \"python\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "examples/mnist/mnist_benchmark.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Benchmark for the MNIST example.\"\"\"\nimport time\n\nfrom absl import flags\nfrom absl.testing import absltest\nfrom absl.testing.flagsaver import flagsaver\nfrom flax.testing import Benchmark\nimport jax\nimport numpy as np\n\nimport main\nfrom configs import default\n\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\nFLAGS = flags.FLAGS\n\n\nclass MnistBenchmark(Benchmark):\n  \"\"\"Benchmarks for the MNIST Flax example.\"\"\"\n\n  @flagsaver\n  def test_cpu(self):\n    \"\"\"Run full training for MNIST CPU training.\"\"\"\n    # Prepare and set flags defined in main.py.\n    workdir = self.get_tmp_model_dir()\n    config = default.get_config()\n\n    FLAGS.workdir = workdir\n    FLAGS.config = config\n\n    start_time = time.time()\n    main.main([])\n    benchmark_time = time.time() - start_time\n\n    summaries = self.read_summaries(workdir)\n\n    # Summaries contain all the information necessary for the regression\n    # metrics.\n    wall_time, _, eval_accuracy = zip(*summaries['eval_accuracy'])\n    wall_time = np.array(wall_time)\n    sec_per_epoch = np.mean(wall_time[1:] - wall_time[:-1])\n    end_eval_accuracy = eval_accuracy[-1]\n\n    # Assertions are deferred until the test finishes, so the metrics are\n    # always reported and benchmark success is determined based on *all*\n    # assertions.\n    self.assertBetween(end_eval_accuracy, 0.98, 1.0)\n\n    # Use the reporting API to report single or multiple metrics/extras.\n    self.report_wall_time(benchmark_time)\n    self.report_metrics({\n        'sec_per_epoch': sec_per_epoch,\n        'accuracy': end_eval_accuracy,\n    })\n    self.report_extras({\n        'model_name': 'MNIST',\n        'description': 'CPU test for MNIST.',\n        'implementation': 'linen',\n    })\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/mnist/requirements.txt",
    "content": "absl-py==1.0.0\nclu==0.0.6\nflax==0.4.1\njax==0.3.4\n--find-links https://storage.googleapis.com/jax-releases/jax_releases.html\njaxlib==0.3.2+cuda11.cudnn82  # Make sure CUDA version matches the base image.\nml-collections==0.1.0\nnumpy==1.22.0\noptax==0.1.0\ntensorflow==2.11.1\ntensorflow-datasets==4.4.0\n"
  },
  {
    "path": "examples/mnist/train.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"MNIST example.\n\nLibrary file which executes the training and evaluation loop for MNIST.\nThe data is loaded using tensorflow_datasets.\n\"\"\"\n\n# See issue #620.\n# pytype: disable=wrong-keyword-args\nfrom functools import partial\nfrom typing import Any\nfrom pathlib import Path\n\nfrom absl import logging\nfrom flax import nnx\nfrom flax.metrics import tensorboard\nimport jax\nimport ml_collections\nimport optax\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\ntf.random.set_seed(0)  # Set the random seed for reproducibility.\n\n\nclass CNN(nnx.Module):\n  \"\"\"A simple CNN model.\"\"\"\n\n  def __init__(self, rngs: nnx.Rngs):\n    self.conv1 = nnx.Conv(1, 32, kernel_size=(3, 3), rngs=rngs)\n    self.batch_norm1 = nnx.BatchNorm(32, rngs=rngs)\n    self.dropout1 = nnx.Dropout(rate=0.025)\n    self.conv2 = nnx.Conv(32, 64, kernel_size=(3, 3), rngs=rngs)\n    self.batch_norm2 = nnx.BatchNorm(64, rngs=rngs)\n    self.avg_pool = partial(nnx.avg_pool, window_shape=(2, 2), strides=(2, 2))\n    self.linear1 = nnx.Linear(3136, 256, rngs=rngs)\n    self.dropout2 = nnx.Dropout(rate=0.025)\n    self.linear2 = nnx.Linear(256, 10, rngs=rngs)\n\n  def __call__(self, x, rngs: nnx.Rngs):\n    x = self.avg_pool(nnx.relu(self.batch_norm1(self.dropout1(self.conv1(x), rngs=rngs))))\n    x = self.avg_pool(nnx.relu(self.batch_norm2(self.conv2(x))))\n    x = x.reshape(x.shape[0], -1)  # flatten\n    x = nnx.relu(self.dropout2(self.linear1(x), rngs=rngs))\n    x = self.linear2(x)\n    return x\n\n\n\ndef loss_fn(model: CNN, batch, rngs):\n  logits = model(batch['image'], rngs)\n  loss = optax.softmax_cross_entropy_with_integer_labels(\n    logits=logits, labels=batch['label']\n  ).mean()\n  return loss, logits\n\n\n@nnx.jit\ndef train_step(model: CNN, optimizer: nnx.Optimizer, metrics: nnx.MultiMetric, batch, rngs):\n  \"\"\"Train for a single step.\"\"\"\n  grad_fn = nnx.value_and_grad(loss_fn, has_aux=True)\n  (loss, logits), grads = grad_fn(model, batch, rngs)\n  metrics.update(loss=loss, logits=logits, labels=batch['label'])  # In-place updates.\n  optimizer.update(model, grads)  # In-place updates.\n\n\n\n\n@nnx.jit\ndef eval_step(model: CNN, metrics: nnx.MultiMetric, batch):\n  loss, logits = loss_fn(model, batch, None)\n  metrics.update(loss=loss, logits=logits, labels=batch['label'])  # In-place updates.\n\n\ndef get_datasets(\n    config: ml_collections.ConfigDict,\n) -> tuple[tf.data.Dataset, tf.data.Dataset]:\n  \"\"\"Load MNIST train and test datasets into memory.\"\"\"\n  batch_size = config.batch_size\n  train_ds: tf.data.Dataset = tfds.load('mnist', split='train')\n  test_ds: tf.data.Dataset = tfds.load('mnist', split='test')\n\n  train_ds = train_ds.map(\n      lambda sample: {\n          'image': tf.cast(sample['image'], tf.float32) / 255,\n          'label': sample['label'],\n      }\n  )  # normalize train set\n  test_ds = test_ds.map(\n      lambda sample: {\n          'image': tf.cast(sample['image'], tf.float32) / 255,\n          'label': sample['label'],\n      }\n  )  # normalize the test set.\n\n  # Create a shuffled dataset by allocating a buffer size of 1024 to randomly\n  # draw elements from.\n  train_ds = train_ds.shuffle(1024)\n  # Group into batches of `batch_size` and skip incomplete batches, prefetch the\n  # next sample to improve latency.\n  train_ds = train_ds.batch(batch_size, drop_remainder=True).prefetch(1)\n  # Group into batches of `batch_size` and skip incomplete batches, prefetch the\n  # next sample to improve latency.\n  test_ds = test_ds.batch(batch_size, drop_remainder=True).prefetch(1)\n\n  return train_ds, test_ds\n\n\ndef train_and_evaluate(config: ml_collections.ConfigDict, workdir: str) -> None:\n  \"\"\"Execute model training and evaluation loop.\n\n  Args:\n    config: Hyperparameter configuration for training and evaluation.\n    workdir: Directory path to store metrics.\n  \"\"\"\n  train_ds, test_ds = get_datasets(config)\n\n  # Instantiate the model.\n  model = CNN(rngs=nnx.Rngs(0))\n\n  learning_rate = config.learning_rate\n  momentum = config.momentum\n\n  summary_writer = tensorboard.SummaryWriter(workdir)\n  summary_writer.hparams(dict(config))\n\n  optimizer = nnx.Optimizer(\n      model, optax.sgd(learning_rate, momentum), wrt=nnx.Param\n  )\n  metrics = nnx.MultiMetric(\n      accuracy=nnx.metrics.Accuracy(),\n      loss=nnx.metrics.Average('loss'),\n  )\n  rngs = nnx.Rngs(0)\n\n  for epoch in range(1, config.num_epochs + 1):\n    # Run the optimization for one step and make a stateful update to the\n    # following:\n    # - The train state's model parameters\n    # - The optimizer state\n    # - The training loss and accuracy batch metrics\n    model.train()  # Switch to train mode\n\n    for batch in train_ds.as_numpy_iterator():\n      train_step(model, optimizer, metrics, batch, rngs)\n    #  Compute the training metrics.\n    train_metrics = metrics.compute()\n    metrics.reset()  # Reset the metrics for the test set.\n\n    # Compute the metrics on the test set after each training epoch.\n    model.eval()  # Switch to eval mode\n    for batch in test_ds.as_numpy_iterator():\n      eval_step(model, metrics, batch)\n\n    # Compute the eval metrics.\n    eval_metrics = metrics.compute()\n    metrics.reset()  # Reset the metrics for the next training epoch.\n\n    logging.info(  # pylint: disable=logging-not-lazy\n        'epoch:% 3d, train_loss: %.4f, train_accuracy: %.2f, test_loss: %.4f,'\n        ' test_accuracy: %.2f'\n        % (\n            epoch,\n            train_metrics['loss'],\n            train_metrics['accuracy'] * 100,\n            eval_metrics['loss'],\n            eval_metrics['accuracy'] * 100,\n        )\n    )\n\n    # Write the metrics to TensorBoard.\n    summary_writer.scalar('train_loss', train_metrics['loss'], epoch)\n    summary_writer.scalar('train_accuracy', train_metrics['accuracy'], epoch)\n    summary_writer.scalar('test_loss', eval_metrics['loss'], epoch)\n    summary_writer.scalar('test_accuracy', eval_metrics['accuracy'], epoch)\n\n  summary_writer.flush()\n\n  # Export the model to a SavedModel directory.\n  from orbax.export import JaxModule, ExportManager, ServingConfig\n\n  def exported_predict(model, y):\n      return model(y, None)\n\n  model.eval()\n  jax_module = JaxModule(model, exported_predict)\n  sig = [tf.TensorSpec(shape=(1, 28, 28, 1), dtype=tf.float32)]\n  export_mgr = ExportManager(jax_module, [\n      ServingConfig('mnist_server', input_signature=sig)\n  ])\n\n  output_dir= Path(workdir) / 'mnist_export'\n  export_mgr.save(str(output_dir))\n"
  },
  {
    "path": "examples/mnist/train_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.examples.mnist.mnist_lib.\"\"\"\n\nimport pathlib\nimport tempfile\nimport sys\n\nfrom absl.testing import absltest\nimport jax\nimport flax.nnx as nnx\nfrom jax import numpy as jnp\nimport numpy as np\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\nfrom configs import default\nimport train\n\n\nCNN_PARAMS = 825_034\n\n\nclass TrainTest(absltest.TestCase):\n  \"\"\"Test cases for train.\"\"\"\n\n  def setUp(self):\n    super().setUp()\n    if sys.version_info < (3, 13):\n      self.skipTest('Tensorflow 2.20 required for this test, which conflicts with tensorflow_text.')\n    # Make sure tf does not allocate gpu memory.\n    tf.config.experimental.set_visible_devices([], \"GPU\")\n\n  def test_cnn(self):\n    \"\"\"Tests CNN module used as the trainable model.\"\"\"\n    inputs = jnp.ones((1, 28, 28, 1), jnp.float32)\n    cnn = train.CNN(nnx.Rngs(0))\n    cnn.eval()\n    output = cnn(inputs, None)\n\n    self.assertEqual((1, 10), output.shape)\n\n  def test_train_and_evaluate(self):\n    \"\"\"Tests training and evaluation code by running a single step.\"\"\"\n    # Create a temporary directory where tensorboard metrics are written.\n    workdir = tempfile.mkdtemp()\n\n    # Go two directories up to the root of the flax directory.\n    flax_root_dir = pathlib.Path(__file__).parents[2]\n    data_dir = str(flax_root_dir) + \"/.tfds/metadata\"  # pylint: disable=unused-variable\n\n    # Define training configuration.\n    config = default.get_config()\n    config.num_epochs = 1\n    config.batch_size = 8\n\n    with tfds.testing.mock_data(num_examples=8, data_dir=data_dir):\n      train.train_and_evaluate(config=config, workdir=workdir)\n\n\nif __name__ == \"__main__\":\n  absltest.main()\n"
  },
  {
    "path": "examples/nlp_seq/README.md",
    "content": "## Part-of-Speech Tagging\nTrains a simple sequence-based part-of-speech tagger. The following sentence\nshows an example.\n\n```\nFrom|ADP the|DT AP|PROPN comes|VBZ this|DT story|NN :|:\n```\n\n### Requirements\n* Universal Dependency data sets:  https://universaldependencies.org/#download.\n\n    Download via command line:\n\n    ```\n    curl -# -o ud-treebanks-v2.0.tgz https://lindat.mff.cuni.cz/repository/xmlui/bitstream/handle/11234/1-1976/ud-treebanks-v2.0.tgz\n    tar xzf ud-treebanks-v2.0.tgz\n    ```\n\n### Supported setups\nThe model should run with other configurations and hardware, but explicitly tested on the following.\n\n| Hardware |  Batch size  | Learning rate | Training time | Accuracy  | TensorBoard.dev |\n|:---:|:---:|:---:|:---:|:---:|:---:|\n| Nvidia Titan V (12GB) | 64  |  0.05 | 5:58h | 68.6% | [2022-05-01](https://tensorboard.dev/experiment/F5ULHlyzQlieVJn5PG8mRQ/) |\n\n### Running\n```\npython train.py --batch_size=64 --model_dir=./ancient_greek \\\n    --dev=ud-treebanks-v2.0/UD_Ancient_Greek/grc-ud-dev.conllu \\\n    --train=ud-treebanks-v2.0/UD_Ancient_Greek/grc-ud-train.conllu\n```\n"
  },
  {
    "path": "examples/nlp_seq/configs/default.py",
    "content": "# Copyright 2025 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Default hyperparameters for NLP sequence tagging.\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = ml_collections.ConfigDict()\n\n  # Model directory for checkpoints and logs\n  config.model_dir = ''\n\n  # Experiment name\n  config.experiment = 'xpos'\n\n  # Training hyperparameters\n  config.batch_size = 64\n  config.num_train_steps = 75000\n  config.eval_frequency = 100\n\n  # Optimizer hyperparameters\n  config.learning_rate = 0.05\n  config.weight_decay = 1e-1\n\n  # Model hyperparameters\n  config.max_length = 256\n\n  # Random seed\n  config.random_seed = 0\n\n  # Data paths\n  config.train = ''\n  config.dev = ''\n\n  return config\n"
  },
  {
    "path": "examples/nlp_seq/input_pipeline.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Input pipeline for the sequence tagging dataset.\"\"\"\n\nimport codecs\nimport collections\nimport enum\n\nimport tensorflow as tf  # pytype: disable=import-error\n\n\n# Values for padding, unknown words and a root.\nPAD = '<p>'\nPAD_ID = 0\n\nUNKNOWN = '<u>'\nUNKNOWN_ID = 1\n\nROOT = '<r>'\nROOT_ID = 2\n\n\nclass CoNLLAttributes(enum.Enum):\n  \"\"\"CoNLL attributre names and indices.\n\n  A UD CoNLL file looks like:\n  1    They     they    PRON    PRP    Case=Nom|Number=Plur       2    nsubj\n  2    buy      buy     VERB    VBP    Number=Plur|PTense=Pres    0    root\n  3    books    book    NOUN    NNS    Number=Plur                2    obj\n  4    .        .       PUNCT   .      _                          2    punct\n\n  For details, please see: http://universaldependencies.org/format.html.\n  \"\"\"\n\n  ID = 0\n  FORM = 1\n  LEMMA = 2\n  UPOS = 3\n  XPOS = 4\n  FEATS = 5\n  HEAD = 6\n  DEPREL = 7\n\n\ndef create_vocabs(filename, max_num_forms=100000):\n  \"\"\"Loads corpus and create vocabulary lists.\n\n  Args:\n    filename: file name of a corpus.\n    max_num_forms: maximum number of tokens included.\n\n  Returns:\n    Dictionary containing named vocab dictionaries.\n\n  \"\"\"\n  form_counter = collections.Counter()\n  xpos_counter = collections.Counter()\n  with tf.io.gfile.GFile(filename, 'rb') as f:\n    for line in codecs.getreader('utf-8')(f):\n      line = line.strip()\n      split = line.split('\\t')\n      if not line.startswith('#') and split[0]:\n        form_counter[split[CoNLLAttributes.FORM.value]] += 1\n        xpos_counter[split[CoNLLAttributes.XPOS.value]] += 1\n\n  special_tokens = {PAD: PAD_ID, UNKNOWN: UNKNOWN_ID, ROOT: ROOT_ID}\n\n  # create word form vocab\n  vocabs = {'forms': {}, 'xpos': {}}\n  vocabs['forms'].update(special_tokens)\n  vocabs['forms'].update(\n      {\n          form[0]: id\n          for id, form in enumerate(\n              form_counter.most_common(max_num_forms), start=ROOT_ID + 1\n          )\n      }\n  )\n\n  # create xpos vocab\n  vocabs['xpos'].update(special_tokens)\n  vocabs['xpos'].update(\n      {\n          tag[0]: id\n          for id, tag in enumerate(\n              xpos_counter.most_common(), start=ROOT_ID + 1\n          )\n      }\n  )\n\n  return vocabs\n\n\ndef create_token(token, attributes, vocabs):\n  \"\"\"Map for a token a selected subset of attributes to indices.\n\n  Input example: CoNLL 09 representation for a token.\n    ['Ms.', 'ms.', 'ms.', 'NNP', '_', '2', 'TITLE]\n  Output example: Indices as defined in self._attributes, e.g., [word form,\n    part-of-speech tag, and head].\n    [1025, 3, 1]\n\n  Args:\n    token: CoNLL token attributes.\n    attributes: selected attributes.\n    vocabs: dictionary of vocabs.\n\n  Returns:\n    List of attribute ids for a token, e.g. [1025, 3] with word id and pos id.\n\n  Raises:\n    ValueError: CoNLL attribute requested but not covered by mapping.\n  \"\"\"\n  selected_attributes = []\n  for attribute in attributes:\n    index = attribute.value\n    if attribute == CoNLLAttributes.FORM:\n      selected_attributes.append(vocabs['forms'].get(token[index], UNKNOWN_ID))\n    elif attribute == CoNLLAttributes.XPOS:\n      selected_attributes.append(vocabs['xpos'].get(token[index], UNKNOWN_ID))\n    elif attribute == CoNLLAttributes.HEAD:\n      selected_attributes.append(int(token[index]))\n    else:\n      raise ValueError(\n          'CoNLL index %s not covered by mapping.' % str(attribute.name)\n      )\n  return selected_attributes\n\n\ndef create_sentence_with_root(attributes, vocabs):\n  \"\"\"Create a sentence containing a root.\n\n  Args:\n    attributes: attributes extracted from token.\n    vocabs: dictionary of vocabs.\n\n  Returns:\n    A list representing a sentence containing the root only,\n    e.g., [[2, 1, 0]] for root word, unknown xpos, and head 0.\n  \"\"\"\n  # Create the token properties of an artificial root node.\n  token_properties = [ROOT for _ in range(12)]  # CoNLL 09 has 12 columns.\n  token_properties[CoNLLAttributes.ID.value] = '0'\n  token_properties[CoNLLAttributes.HEAD.value] = '0'\n  token = create_token(token_properties, attributes, vocabs)\n  if len(token) == 1:\n    token = token[0]\n  return [token]\n\n\ndef sentences_from_conll_data(\n    corpus_filename, vocabs, attributes, max_sentence_length=1000\n):\n  \"\"\"Load and returns conll data in list format.\n\n  Args:\n    corpus_filename: filename of corpus.\n    vocabs: dictionary of vocabs\n    attributes: list of conll attributes to include into the batch\n    max_sentence_length: cut off sentences longer as max tokens\n\n  Yields:\n      A sentence as a list of tokens while tokens are lists of attributes.\n  \"\"\"\n  with tf.io.gfile.GFile(corpus_filename, 'rb') as f:\n    sentence = create_sentence_with_root(attributes, vocabs)\n    for line in codecs.getreader('utf-8')(f):\n      line = line.strip()\n      if line.startswith('#'):\n        continue\n      split = line.split('\\t')\n      if split[0]:  # Not an empty line, process next token:\n        if len(sentence) < max_sentence_length:\n          if len(attributes) == 1:\n            sentence.append(create_token(split, attributes, vocabs)[0])\n          else:\n            sentence.append(create_token(split, attributes, vocabs))\n      else:  # Sentences start with an empty line, yield sentence:\n        yield sentence\n\n        # Reset sentence.\n        sentence = create_sentence_with_root(attributes, vocabs)\n    if len(sentence) > 1:  # sentences does not only contain a root.\n      yield sentence\n\n\ndef sentence_dataset_dict(\n    filename,\n    vocabs,\n    attributes_input,\n    attributes_target,\n    batch_size,\n    bucket_size,\n    repeat=None,\n    prefetch_size=tf.data.experimental.AUTOTUNE,\n):\n  \"\"\"Combines sentences into a dataset of padded batches.\n\n  Args:\n    filename: file name of a corpus.\n    vocabs: dictionary of dictionaries to map from strings to ids.\n    attributes_input: attributes for the input.\n    attributes_target: target attributes empty targets is not included.\n    batch_size: the size of a batch.\n    bucket_size: the size of a bucket.\n    repeat: number of times the dataset is repeated.\n    prefetch_size: prefetch size of the data.\n\n  Returns:\n    Returns dataset as dictionary containing the data as key value pairs.\n  \"\"\"\n  data_keys = ['inputs']\n  if attributes_target:\n    data_keys.append('targets')\n\n  def generator():\n    \"\"\"Generator to create the data.\"\"\"\n    input_generator = sentences_from_conll_data(\n        filename, vocabs, attributes_input, max_sentence_length=bucket_size\n    )\n\n    if attributes_target:\n      target_generator = sentences_from_conll_data(\n          filename, vocabs, attributes_target, max_sentence_length=bucket_size\n      )\n\n    for inputs in input_generator:\n      data = {'inputs': inputs}\n      if attributes_target:\n        data['targets'] = next(target_generator)\n      yield data\n\n  output_types = {k: tf.float32 for k in data_keys}\n  output_shapes = {k: (None,) for k in data_keys}\n  dataset = tf.data.Dataset.from_generator(\n      generator, output_types=output_types, output_shapes=output_shapes\n  )\n\n  # cache the dataset in memory and repeat.\n  dataset = dataset.cache()\n  dataset = dataset.repeat(repeat)\n\n  # static padding up to bucket size.\n  padded_shapes = {k: [bucket_size] for k in data_keys}\n  dataset = dataset.padded_batch(\n      batch_size=batch_size, padded_shapes=(padded_shapes)\n  )\n\n  dataset = dataset.prefetch(prefetch_size)\n  return dataset\n"
  },
  {
    "path": "examples/nlp_seq/input_pipeline_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.examples.nlp.input_pipeline.\"\"\"\n\nimport os\n\nfrom absl.testing import absltest\nimport jax\nimport tensorflow as tf\n\nimport input_pipeline\n\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nCONLL_DATA = \"\"\"1\\tThey\\tthey\\tPRON\\tPRP\\tCase=Nom|Number=Plur\\t2\\tnsubj\n2\\tbuy\\tbuy\\t VERB\\tVBP\\tNumber=Plur|PTense=Pres\\t0\\troot\n3\\tbooks\\tbook\\tNOUN\\tNNS\\tNumber=Plur\\t2\\tobj\n4\\t.\\t.\\tPUNCT\\t.\\t_\\t2\\tpunct\n\n1\\tThey\\tthey\\tPRON\\tPRP\\tCase=Nom|Number=Plur\\t2\\tnsubj\n2\\tbuy\\tbuy\\t VERB\\tVBP\\tNumber=Plur|PTense=Pres\\t0\\troot\n3\\tbooks\\tbook\\tNOUN\\tNNS\\tNumber=Plur\\t2\\tobj\n4\\t.\\t.\\tPUNCT\\t.\\t_\\t2\\tpunct\n\n1\\tNY\\tNY\\tNOUN\\tNNS\\tNumber=Singular\\t0\\troot\n\"\"\"\n\n\nclass InputPipelineTest(absltest.TestCase):\n\n  def setUp(self):\n    super().setUp()\n    self.test_tmpdir = self.create_tempdir()\n\n    # Write a sample corpus.\n    self._filename = os.path.join(self.test_tmpdir.full_path, 'data.conll')\n    with tf.io.gfile.GFile(self._filename, 'w') as f:\n      # The CoNLL data has to end with an empty line.\n      f.write(CONLL_DATA)\n      f.write('\\n')\n\n  def test_vocab_creation(self):\n    \"\"\"Tests the creation of the vocab.\"\"\"\n    vocabs = input_pipeline.create_vocabs(self._filename)\n    self.assertEqual(\n        vocabs['forms'],\n        {\n            '<p>': 0,\n            '<u>': 1,\n            '<r>': 2,\n            'They': 3,\n            'buy': 4,\n            'books': 5,\n            '.': 6,\n            'NY': 7,\n        },\n    )\n\n  def testInputBatch(self):\n    \"\"\"Test the batching of the dataset.\"\"\"\n    vocabs = input_pipeline.create_vocabs(self._filename)\n\n    attributes_input = [input_pipeline.CoNLLAttributes.FORM]\n    attributes_target = []  # empty target for tagging of unlabeled data.\n    sentence_dataset = input_pipeline.sentence_dataset_dict(\n        self._filename,\n        vocabs,\n        attributes_input,\n        attributes_target,\n        batch_size=2,\n        bucket_size=10,\n        repeat=1,\n    )\n\n    sentence_dataset_iter = iter(sentence_dataset)\n\n    batch = next(sentence_dataset_iter)\n    inputs = batch['inputs'].numpy().tolist()\n    self.assertSameStructure(\n        inputs,\n        [\n            [2.0, 3.0, 4.0, 5.0, 6.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n            [2.0, 3.0, 4.0, 5.0, 6.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n        ],\n    )\n    self.assertLen(batch, 1)  # make sure target is not included.\n\n  def testInputTargetBatch(self):\n    \"\"\"Test the batching of the dataset.\"\"\"\n    vocabs = input_pipeline.create_vocabs(self._filename)\n\n    attributes_input = [input_pipeline.CoNLLAttributes.FORM]\n    attributes_target = [input_pipeline.CoNLLAttributes.XPOS]\n    sentence_dataset = input_pipeline.sentence_dataset_dict(\n        self._filename,\n        vocabs,\n        attributes_input,\n        attributes_target,\n        batch_size=2,\n        bucket_size=10,\n        repeat=1,\n    )\n\n    sentence_dataset_iter = iter(sentence_dataset)\n\n    batch = next(sentence_dataset_iter)\n    inputs = batch['inputs'].numpy().tolist()\n    self.assertSameStructure(\n        inputs,\n        [\n            [2.0, 3.0, 4.0, 5.0, 6.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n            [2.0, 3.0, 4.0, 5.0, 6.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n        ],\n    )\n    targets = batch['targets'].numpy().tolist()\n    self.assertSameStructure(\n        targets,\n        [\n            [2.0, 4.0, 5.0, 3.0, 6.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n            [2.0, 4.0, 5.0, 3.0, 6.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n        ],\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/nlp_seq/main.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Main file for running the NLP sequence tagging example.\n\nThis file is intentionally kept short to allow config-based execution.\n\"\"\"\n\nfrom absl import app\nfrom absl import flags\nimport train\nfrom ml_collections import config_flags\n\n\nFLAGS = flags.FLAGS\n\nconfig_flags.DEFINE_config_file(\n    'config',\n    'configs/default.py',\n    'File path to the training hyperparameter configuration.',\n    lock_config=True,\n)\n\n\ndef main(argv):\n  if len(argv) > 1:\n    raise app.UsageError('Too many command-line arguments.')\n\n  # Convert config to FLAGS for train.py compatibility\n  config = FLAGS.config\n\n  # Override FLAGS with config values\n  FLAGS.model_dir = config.model_dir\n  FLAGS.experiment = config.experiment\n  FLAGS.batch_size = config.batch_size\n  FLAGS.eval_frequency = config.eval_frequency\n  FLAGS.num_train_steps = config.num_train_steps\n  FLAGS.learning_rate = config.learning_rate\n  FLAGS.weight_decay = config.weight_decay\n  FLAGS.max_length = config.max_length\n  FLAGS.random_seed = config.random_seed\n  FLAGS.train = config.train\n  FLAGS.dev = config.dev\n\n  # Run the training\n  train.main(argv)\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": "examples/nlp_seq/models.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Transformer-based language models.\"\"\"\n\nfrom typing import Any, Optional\nfrom collections.abc import Callable\n\nfrom flax import linen as nn\nfrom flax import struct\nimport jax.numpy as jnp\nimport numpy as np\n\n\n@struct.dataclass\nclass TransformerConfig:\n  \"\"\"Global hyperparameters used to minimize obnoxious kwarg plumbing.\"\"\"\n\n  vocab_size: int\n  output_vocab_size: int\n  dtype: Any = jnp.float32\n  emb_dim: int = 512\n  num_heads: int = 8\n  num_layers: int = 6\n  qkv_dim: int = 512\n  mlp_dim: int = 2048\n  max_len: int = 2048\n  dropout_rate: float = 0.3\n  attention_dropout_rate: float = 0.3\n  kernel_init: Callable = nn.initializers.xavier_uniform()\n  bias_init: Callable = nn.initializers.normal(stddev=1e-6)\n  posemb_init: Callable | None = None\n\n\ndef sinusoidal_init(max_len=2048):\n  \"\"\"1D Sinusoidal Position Embedding Initializer.\n\n  Args:\n      max_len: maximum possible length for the input\n\n  Returns:\n      output: init function returning `(1, max_len, d_feature)`\n  \"\"\"\n\n  def init(key, shape, dtype=np.float32):\n    \"\"\"Sinusoidal init.\"\"\"\n    del key, dtype\n    d_feature = shape[-1]\n    pe = np.zeros((max_len, d_feature), dtype=np.float32)\n    position = np.arange(0, max_len)[:, np.newaxis]\n    div_term = np.exp(\n        np.arange(0, d_feature, 2) * -(np.log(10000.0) / d_feature)\n    )\n    pe[:, 0::2] = np.sin(position * div_term)\n    pe[:, 1::2] = np.cos(position * div_term)\n    pe = pe[np.newaxis, :, :]  # [1, max_len, d_feature]\n    return jnp.array(pe)\n\n  return init\n\n\nclass AddPositionEmbs(nn.Module):\n  \"\"\"Adds (optionally learned) positional embeddings to the inputs.\n\n  Attributes:\n    config: TransformerConfig dataclass containing hyperparameters.\n  \"\"\"\n\n  config: TransformerConfig\n\n  @nn.compact\n  def __call__(self, inputs):\n    \"\"\"Applies AddPositionEmbs module.\n\n    By default this layer uses a fixed sinusoidal embedding table. If a\n    learned position embedding is desired, pass an initializer to\n    posemb_init in the configuration.\n\n    Args:\n      inputs: input data.\n\n    Returns:\n      output: `(bs, timesteps, in_dim)`\n    \"\"\"\n    config = self.config\n    # inputs.shape is (batch_size, seq_len, emb_dim)\n    assert inputs.ndim == 3, (\n        'Number of dimensions should be 3, but it is: %d' % inputs.ndim\n    )\n    length = inputs.shape[1]\n    pos_emb_shape = (1, config.max_len, inputs.shape[-1])\n    if config.posemb_init is None:\n      # Use a fixed (non-learned) sinusoidal position embedding.\n      pos_embedding = sinusoidal_init(max_len=config.max_len)(\n          None, pos_emb_shape, None\n      )\n    else:\n      pos_embedding = self.param(\n          'pos_embedding', config.posemb_init, pos_emb_shape\n      )\n    pe = pos_embedding[:, :length, :]\n    return inputs + pe\n\n\nclass MlpBlock(nn.Module):\n  \"\"\"Transformer MLP / feed-forward block.\n\n  Attributes:\n    config: TransformerConfig dataclass containing hyperparameters.\n    out_dim: optionally specify out dimension.\n  \"\"\"\n\n  config: TransformerConfig\n  out_dim: int | None = None\n\n  @nn.compact\n  def __call__(self, inputs, deterministic=True):\n    \"\"\"Applies Transformer MlpBlock module.\"\"\"\n    config = self.config\n    actual_out_dim = inputs.shape[-1] if self.out_dim is None else self.out_dim\n    x = nn.Dense(\n        config.mlp_dim,\n        dtype=config.dtype,\n        kernel_init=config.kernel_init,\n        bias_init=config.bias_init,\n    )(inputs)\n    x = nn.elu(x)\n    x = nn.Dropout(rate=config.dropout_rate)(x, deterministic=deterministic)\n    output = nn.Dense(\n        actual_out_dim,\n        dtype=config.dtype,\n        kernel_init=config.kernel_init,\n        bias_init=config.bias_init,\n    )(x)\n    output = nn.Dropout(rate=config.dropout_rate)(\n        output, deterministic=deterministic\n    )\n    return output\n\n\nclass Encoder1DBlock(nn.Module):\n  \"\"\"Transformer encoder layer.\n\n  Attributes:\n    config: TransformerConfig dataclass containing hyperparameters.\n  \"\"\"\n\n  config: TransformerConfig\n\n  @nn.compact\n  def __call__(self, inputs, deterministic):\n    \"\"\"Applies Encoder1DBlock module.\n\n    Args:\n      inputs: input data.\n      deterministic: if true dropout is applied otherwise not.\n\n    Returns:\n      output after transformer encoder block.\n    \"\"\"\n    config = self.config\n\n    # Attention block.\n    assert inputs.ndim == 3\n    x = nn.LayerNorm(dtype=config.dtype)(inputs)\n    x = nn.MultiHeadDotProductAttention(\n        num_heads=config.num_heads,\n        dtype=config.dtype,\n        qkv_features=config.qkv_dim,\n        kernel_init=config.kernel_init,\n        bias_init=config.bias_init,\n        use_bias=False,\n        broadcast_dropout=False,\n        dropout_rate=config.attention_dropout_rate,\n        deterministic=deterministic,\n    )(x)\n\n    x = nn.Dropout(rate=config.dropout_rate)(x, deterministic=deterministic)\n    x = x + inputs\n\n    # MLP block.\n    y = nn.LayerNorm(dtype=config.dtype)(x)\n    y = MlpBlock(config=config)(y, deterministic=deterministic)\n    return x + y\n\n\nclass Transformer(nn.Module):\n  \"\"\"Transformer Model for sequence tagging.\"\"\"\n\n  config: TransformerConfig\n\n  @nn.compact\n  def __call__(self, *, inputs, train):\n    \"\"\"Applies Transformer model on the inputs.\n\n    Args:\n      inputs: input data\n      train: if it is training.\n\n    Returns:\n      output of a transformer encoder.\n\n    \"\"\"\n    assert inputs.ndim == 2  # (batch, len)\n\n    config = self.config\n\n    x = inputs.astype('int32')\n    x = nn.Embed(\n        num_embeddings=config.vocab_size, features=config.emb_dim, name='embed'\n    )(x)\n    x = nn.Dropout(rate=config.dropout_rate)(x, deterministic=not train)\n    x = AddPositionEmbs(config)(x)\n\n    for _ in range(config.num_layers):\n      x = Encoder1DBlock(config)(x, deterministic=not train)\n\n    x = nn.LayerNorm(dtype=config.dtype)(x)\n    logits = nn.Dense(\n        config.output_vocab_size,\n        kernel_init=config.kernel_init,\n        bias_init=config.bias_init,\n    )(x)\n    return logits\n"
  },
  {
    "path": "examples/nlp_seq/requirements.txt",
    "content": "absl-py==1.0.0\nflax==0.3.6\nnumpy==1.22.0\ntensorflow==2.11.1\n"
  },
  {
    "path": "examples/nlp_seq/train.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Sequence Tagging example.\n\nThis script trains a Transformer on the Universal dependency dataset.\n\"\"\"\n\nimport functools\nimport os\nimport time\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nfrom flax import jax_utils\nfrom flax import linen as nn\nfrom flax.metrics import tensorboard\nfrom flax.training import common_utils\nfrom flax.training import train_state\nimport jax\nimport jax.numpy as jnp\nfrom jax import random\nimport numpy as np\nimport optax\nimport tensorflow as tf\n\nimport input_pipeline\nimport models\n\n\nFLAGS = flags.FLAGS\n\nflags.DEFINE_string('model_dir', default='', help='Directory for model data.')\n\nflags.DEFINE_string('experiment', default='xpos', help='Experiment name.')\n\nflags.DEFINE_integer('batch_size', default=64, help='Batch size for training.')\n\nflags.DEFINE_integer(\n    'eval_frequency',\n    default=100,\n    help='Frequency of eval during training, e.g. every 1000 steps.',\n)\n\nflags.DEFINE_integer(\n    'num_train_steps', default=75000, help='Number of train steps.'\n)\n\nflags.DEFINE_float('learning_rate', default=0.05, help='Learning rate.')\n\nflags.DEFINE_float(\n    'weight_decay',\n    default=1e-1,\n    help='Decay factor for AdamW style weight decay.',\n)\n\nflags.DEFINE_integer(\n    'max_length', default=256, help='Maximum length of examples.'\n)\n\nflags.DEFINE_integer(\n    'random_seed', default=0, help='Integer for PRNG random seed.'\n)\n\nflags.DEFINE_string('train', default='', help='Path to training data.')\n\nflags.DEFINE_string('dev', default='', help='Path to development data.')\n\n\ndef create_learning_rate_scheduler(\n    factors='constant * linear_warmup * rsqrt_decay',\n    base_learning_rate=0.5,\n    warmup_steps=8000,\n    decay_factor=0.5,\n    steps_per_decay=20000,\n    steps_per_cycle=100000,\n):\n  \"\"\"creates learning rate schedule.\n\n  Interprets factors in the factors string which can consist of:\n  * constant: interpreted as the constant value,\n  * linear_warmup: interpreted as linear warmup until warmup_steps,\n  * rsqrt_decay: divide by square root of max(step, warmup_steps)\n  * decay_every: Every k steps decay the learning rate by decay_factor.\n  * cosine_decay: Cyclic cosine decay, uses steps_per_cycle parameter.\n\n  Args:\n    factors: a string with factors separated by '*' that defines the schedule.\n    base_learning_rate: float, the starting constant for the lr schedule.\n    warmup_steps: how many steps to warm up for in the warmup schedule.\n    decay_factor: The amount to decay the learning rate by.\n    steps_per_decay: How often to decay the learning rate.\n    steps_per_cycle: Steps per cycle when using cosine decay.\n\n  Returns:\n    a function learning_rate(step): float -> {'learning_rate': float}, the\n    step-dependent lr.\n  \"\"\"\n  factors = [n.strip() for n in factors.split('*')]\n\n  def step_fn(step):\n    \"\"\"Step to learning rate function.\"\"\"\n    ret = 1.0\n    for name in factors:\n      if name == 'constant':\n        ret *= base_learning_rate\n      elif name == 'linear_warmup':\n        ret *= jnp.minimum(1.0, step / warmup_steps)\n      elif name == 'rsqrt_decay':\n        ret /= jnp.sqrt(jnp.maximum(step, warmup_steps))\n      elif name == 'rsqrt_normalized_decay':\n        ret *= jnp.sqrt(warmup_steps)\n        ret /= jnp.sqrt(jnp.maximum(step, warmup_steps))\n      elif name == 'decay_every':\n        ret *= decay_factor ** (step // steps_per_decay)\n      elif name == 'cosine_decay':\n        progress = jnp.maximum(\n            0.0, (step - warmup_steps) / float(steps_per_cycle)\n        )\n        ret *= jnp.maximum(\n            0.0, 0.5 * (1.0 + jnp.cos(jnp.pi * (progress % 1.0)))\n        )\n      else:\n        raise ValueError('Unknown factor %s.' % name)\n    return jnp.asarray(ret, dtype=jnp.float32)\n\n  return step_fn\n\n\ndef compute_weighted_cross_entropy(logits, targets, weights=None):\n  \"\"\"Compute weighted cross entropy and entropy for log probs and targets.\n\n  Args:\n   logits: [batch, length, num_classes] float array.\n   targets: categorical targets [batch, length] int array.\n   weights: None or array of shape [batch x length]\n\n  Returns:\n    Tuple of scalar loss and batch normalizing factor.\n  \"\"\"\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        'Incorrect shapes. Got shape %s logits and %s targets'\n        % (str(logits.shape), str(targets.shape))\n    )\n  onehot_targets = common_utils.onehot(targets, logits.shape[-1])\n  loss = -jnp.sum(onehot_targets * nn.log_softmax(logits), axis=-1)\n  normalizing_factor = onehot_targets.sum()\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_weighted_accuracy(logits, targets, weights=None):\n  \"\"\"Compute weighted accuracy for log probs and targets.\n\n  Args:\n   logits: [batch, length, num_classes] float array.\n   targets: categorical targets [batch, length] int array.\n   weights: None or array of shape [batch x length]\n\n  Returns:\n    Tuple of scalar accuracy and batch normalizing factor.\n  \"\"\"\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        'Incorrect shapes. Got shape %s logits and %s targets'\n        % (str(logits.shape), str(targets.shape))\n    )\n  loss = jnp.equal(jnp.argmax(logits, axis=-1), targets)\n  normalizing_factor = np.prod(logits.shape[:-1])\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_metrics(logits, labels, weights):\n  \"\"\"Compute summary metrics.\"\"\"\n  loss, weight_sum = compute_weighted_cross_entropy(logits, labels, weights)\n  acc, _ = compute_weighted_accuracy(logits, labels, weights)\n  metrics = {\n      'loss': loss,\n      'accuracy': acc,\n      'denominator': weight_sum,\n  }\n  metrics = np.sum(metrics, -1)\n  return metrics\n\n\ndef train_step(state, batch, model, learning_rate_fn, dropout_rng=None):\n  \"\"\"Perform a single training step.\"\"\"\n  train_keys = ['inputs', 'targets']\n  (inputs, targets) = (batch.get(k, None) for k in train_keys)\n\n  weights = jnp.where(targets > 0, 1, 0).astype(jnp.float32)\n\n  dropout_rng = jax.random.fold_in(dropout_rng, state.step)\n\n  def loss_fn(params):\n    \"\"\"loss function used for training.\"\"\"\n    logits = model.apply(\n        {'params': params},\n        inputs=inputs,\n        train=True,\n        rngs={'dropout': dropout_rng},\n    )\n    loss, weight_sum = compute_weighted_cross_entropy(logits, targets, weights)\n\n    mean_loss = loss / weight_sum\n    return mean_loss, logits\n\n  lr = learning_rate_fn(state.step)\n  grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n  (_, logits), grads = grad_fn(state.params)\n  grads = jax.lax.pmean(grads, 'batch')\n  new_state = state.apply_gradients(grads=grads)\n  metrics = compute_metrics(logits, targets, weights)\n  metrics['learning_rate'] = lr\n\n  return new_state, metrics\n\n\ndef pad_examples(x, desired_batch_size):\n  \"\"\"Expand batch to desired size by zeros with the shape of last slice.\"\"\"\n  batch_pad = desired_batch_size - x.shape[0]\n  # Padding with zeros to avoid that they get counted in compute_metrics.\n  return np.concatenate([x, np.tile(np.zeros_like(x[-1]), (batch_pad, 1))])\n\n\ndef main(argv):\n  if len(argv) > 1:\n    raise app.UsageError('Too many command-line arguments.')\n\n  # Make sure tf does not allocate gpu memory.\n  tf.config.experimental.set_visible_devices([], 'GPU')\n\n  batch_size = FLAGS.batch_size\n  learning_rate = FLAGS.learning_rate\n  num_train_steps = FLAGS.num_train_steps\n  eval_freq = FLAGS.eval_frequency\n  random_seed = FLAGS.random_seed\n\n  if not FLAGS.dev:\n    raise app.UsageError('Please provide path to dev set.')\n  if not FLAGS.train:\n    raise app.UsageError('Please provide path to training set.')\n  if batch_size % jax.device_count() > 0:\n    raise ValueError('Batch size must be divisible by the number of devices')\n  device_batch_size = batch_size // jax.device_count()\n\n  if jax.process_index() == 0:\n    train_summary_writer = tensorboard.SummaryWriter(\n        os.path.join(FLAGS.model_dir, FLAGS.experiment + '_train')\n    )\n    eval_summary_writer = tensorboard.SummaryWriter(\n        os.path.join(FLAGS.model_dir, FLAGS.experiment + '_eval')\n    )\n\n  # create the training and development dataset\n  vocabs = input_pipeline.create_vocabs(FLAGS.train)\n  config = models.TransformerConfig(\n      vocab_size=len(vocabs['forms']),\n      output_vocab_size=len(vocabs['xpos']),\n      max_len=FLAGS.max_length,\n  )\n\n  attributes_input = [input_pipeline.CoNLLAttributes.FORM]\n  attributes_target = [input_pipeline.CoNLLAttributes.XPOS]\n  train_ds = input_pipeline.sentence_dataset_dict(\n      FLAGS.train,\n      vocabs,\n      attributes_input,\n      attributes_target,\n      batch_size=batch_size,\n      bucket_size=config.max_len,\n  )\n  train_iter = iter(train_ds)\n\n  eval_ds = input_pipeline.sentence_dataset_dict(\n      FLAGS.dev,\n      vocabs,\n      attributes_input,\n      attributes_target,\n      batch_size=batch_size,\n      bucket_size=config.max_len,\n      repeat=1,\n  )\n\n  model = models.Transformer(config)\n\n  rng = random.key(random_seed)\n  rng, init_rng = random.split(rng)\n\n  # call a jitted initialization function to get the initial parameter tree\n  @jax.jit\n  def initialize_variables(init_rng):\n    init_batch = jnp.ones((config.max_len, 1), jnp.float32)\n    init_variables = model.init(init_rng, inputs=init_batch, train=False)\n    return init_variables\n\n  init_variables = initialize_variables(init_rng)\n\n  learning_rate_fn = create_learning_rate_scheduler(\n      base_learning_rate=learning_rate\n  )\n\n  optimizer = optax.adamw(\n      learning_rate_fn, b1=0.9, b2=0.98, eps=1e-9, weight_decay=1e-1\n  )\n  state = train_state.TrainState.create(\n      apply_fn=model.apply, params=init_variables['params'], tx=optimizer\n  )\n\n  # Replicate optimizer.\n  state = jax_utils.replicate(state)\n\n  p_train_step = jax.pmap(\n      functools.partial(\n          train_step, model=model, learning_rate_fn=learning_rate_fn\n      ),\n      axis_name='batch',\n      donate_argnums=(0,),\n  )  # pytype: disable=wrong-arg-types\n\n  def eval_step(params, batch):\n    \"\"\"Calculate evaluation metrics on a batch.\"\"\"\n    inputs, targets = batch['inputs'], batch['targets']\n    weights = jnp.where(targets > 0, 1.0, 0.0)\n    logits = model.apply({'params': params}, inputs=inputs, train=False)\n    return compute_metrics(logits, targets, weights)\n\n  p_eval_step = jax.pmap(eval_step, axis_name='batch')\n\n  # We init the first set of dropout PRNG keys, but update it afterwards inside\n  # the main pmap'd training update for performance.\n  dropout_rngs = random.split(rng, jax.local_device_count())\n  metrics_all = []\n  tick = time.time()\n  best_dev_score = 0\n  for step, batch in zip(range(num_train_steps), train_iter):\n    batch = common_utils.shard(\n        jax.tree_util.tree_map(lambda x: x._numpy(), batch)\n    )  # pylint: disable=protected-access\n\n    state, metrics = p_train_step(state, batch, dropout_rng=dropout_rngs)\n    metrics_all.append(metrics)\n    if (step + 1) % eval_freq == 0:\n      metrics_all = common_utils.get_metrics(metrics_all)\n      lr = metrics_all.pop('learning_rate').mean()\n      metrics_sums = jax.tree_util.tree_map(jnp.sum, metrics_all)\n      denominator = metrics_sums.pop('denominator')\n      summary = jax.tree_util.tree_map(lambda x: x / denominator, metrics_sums)  # pylint: disable=cell-var-from-loop\n      summary['learning_rate'] = lr\n      logging.info('train in step: %d, loss: %.4f', step, summary['loss'])\n      if jax.process_index() == 0:\n        tock = time.time()\n        steps_per_sec = eval_freq / (tock - tick)\n        tick = tock\n        train_summary_writer.scalar('steps per second', steps_per_sec, step)\n        for key, val in summary.items():\n          train_summary_writer.scalar(key, val, step)\n        train_summary_writer.flush()\n\n      metrics_all = []  # reset metric accumulation for next evaluation cycle.\n\n      eval_metrics = []\n      eval_iter = iter(eval_ds)\n\n      for eval_batch in eval_iter:\n        eval_batch = jax.tree_util.tree_map(lambda x: x._numpy(), eval_batch)  # pylint: disable=protected-access\n        # Handle final odd-sized batch by padding instead of dropping it.\n        cur_pred_batch_size = eval_batch['inputs'].shape[0]\n        if cur_pred_batch_size != batch_size:\n          # pad up to batch size\n          eval_batch = jax.tree_util.tree_map(\n              lambda x: pad_examples(x, batch_size), eval_batch\n          )\n        eval_batch = common_utils.shard(eval_batch)\n\n        metrics = p_eval_step(state.params, eval_batch)\n\n        eval_metrics.append(metrics)\n      eval_metrics = common_utils.get_metrics(eval_metrics)\n      eval_metrics_sums = jax.tree_util.tree_map(jnp.sum, eval_metrics)\n      eval_denominator = eval_metrics_sums.pop('denominator')\n      eval_summary = jax.tree_util.tree_map(\n          lambda x: x / eval_denominator,  # pylint: disable=cell-var-from-loop\n          eval_metrics_sums,\n      )\n\n      logging.info(\n          'eval in step: %d, loss: %.4f, accuracy: %.4f',\n          step,\n          eval_summary['loss'],\n          eval_summary['accuracy'],\n      )\n\n      if best_dev_score < eval_summary['accuracy']:\n        best_dev_score = eval_summary['accuracy']\n        # TODO: save model.\n      eval_summary['best_dev_score'] = best_dev_score\n      logging.info('best development model score %.4f', best_dev_score)\n      if jax.process_index() == 0:\n        for key, val in eval_summary.items():\n          eval_summary_writer.scalar(key, val, step)\n        eval_summary_writer.flush()\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": "examples/nnx_toy_examples/01_functional_api.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# %%\nimport jax\nimport jax.numpy as jnp\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nfrom flax import nnx\n\nX = np.linspace(-jnp.pi, jnp.pi, 100)[:, None]\nY = 0.8 * jnp.sin(X) + 0.1 + np.random.normal(0, 0.1, size=X.shape)\n\n\ndef dataset(batch_size):\n  while True:\n    idx = np.random.choice(len(X), size=batch_size)\n    yield X[idx], Y[idx]\n\n\nclass Linear(nnx.Module):\n  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n    self.w = nnx.Param(jax.random.uniform(rngs.params(), (din, dout)))\n    self.b = nnx.Param(jnp.zeros((dout,)))\n\n  def __call__(self, x):\n    return x @ self.w.value + self.b.value\n\n\nclass Count(nnx.Variable[nnx.A]):\n  pass\n\n\nclass MLP(nnx.Module):\n  def __init__(self, din, dhidden, dout, *, rngs: nnx.Rngs):\n    self.count = Count(jnp.array(0))\n    self.linear1 = Linear(din, dhidden, rngs=rngs)\n    self.linear2 = Linear(dhidden, dout, rngs=rngs)\n\n  def __call__(self, x):\n    self.count[...] += 1\n    return self.linear2(jax.nn.relu(self.linear1(x) * 0.5))\n\n\ngraphdef, params, counts = nnx.split(\n  MLP(din=1, dhidden=32, dout=1, rngs=nnx.Rngs(0)), nnx.Param, Count\n)\n\n\n@jax.jit\ndef train_step(params, counts, batch):\n  x, y = batch\n\n  def loss_fn(params):\n    model = nnx.merge(graphdef, params, counts)\n    y_pred = model(x)\n    new_counts = nnx.state(model, Count)\n    loss = jnp.mean((y - y_pred) ** 2)\n    return loss, new_counts\n\n  grad, counts = jax.grad(loss_fn, has_aux=True)(params)\n  #                          |-------- sgd ---------|\n  params = jax.tree.map(lambda w, g: w - 0.1 * g, params, grad)\n\n  return params, counts\n\n\n@jax.jit\ndef test_step(params: nnx.State, counts: nnx.State, batch):\n  x, y = batch\n  model = nnx.merge(graphdef, params, counts)\n  y_pred = model(x)\n  loss = jnp.mean((y - y_pred) ** 2)\n  return {'loss': loss}\n\n\ntotal_steps = 10_000\nfor step, batch in enumerate(dataset(32)):\n  params, counts = train_step(params, counts, batch)\n\n  if step % 1000 == 0:\n    logs = test_step(params, counts, (X, Y))\n    print(f\"step: {step}, loss: {logs['loss']}\")\n\n  if step >= total_steps - 1:\n    break\n\nmodel = nnx.merge(graphdef, params, counts)\nprint('times called:', model.count.value)\n\ny_pred = model(X)\n\nplt.scatter(X, Y, color='blue')\nplt.plot(X, y_pred, color='black')\nplt.show()\n"
  },
  {
    "path": "examples/nnx_toy_examples/02_lifted_transforms.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# %%\nimport jax\nimport jax.numpy as jnp\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport optax\n\nfrom flax import nnx\n\nX = np.linspace(0, 1, 100)[:, None]\nY = 0.8 * X**2 + 0.1 + np.random.normal(0, 0.1, size=X.shape)\n\n\ndef dataset(batch_size):\n  while True:\n    idx = np.random.choice(len(X), size=batch_size)\n    yield X[idx], Y[idx]\n\n\nclass Linear(nnx.Module):\n  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n    self.w = nnx.Param(jax.random.uniform(rngs.params(), (din, dout)))\n    self.b = nnx.Param(jnp.zeros((dout,)))\n\n  def __call__(self, x):\n    return x @ self.w.value + self.b.value\n\n\nclass Count(nnx.Variable):\n  pass\n\n\nclass MLP(nnx.Module):\n  def __init__(self, din, dhidden, dout, *, rngs: nnx.Rngs):\n    self.count = Count(jnp.array(0))\n    self.linear1 = Linear(din, dhidden, rngs=rngs)\n    self.linear2 = Linear(dhidden, dout, rngs=rngs)\n\n  def __call__(self, x):\n    self.count.value += 1\n    x = self.linear1(x)\n    x = jax.nn.relu(x)\n    x = self.linear2(x)\n    return x\n\n\nmodel = MLP(din=1, dhidden=32, dout=1, rngs=nnx.Rngs(0))\ntx = optax.sgd(1e-3)\noptimizer = nnx.Optimizer(model, tx, wrt=nnx.Param)\n\n\n@nnx.jit\ndef train_step(model: MLP, optimizer: nnx.Optimizer, batch):\n  x, y = batch\n\n  def loss_fn(model: MLP):\n    y_pred = model(x)\n    return jnp.mean((y - y_pred) ** 2)\n\n  grads: nnx.State = nnx.grad(loss_fn)(model)\n  optimizer.update(model, grads)\n\n\n@nnx.jit\ndef test_step(model: MLP, batch):\n  x, y = batch\n  y_pred = model(x)\n  loss = jnp.mean((y - y_pred) ** 2)\n  return {'loss': loss}\n\ncached_train_step = nnx.cached_partial(train_step, model, optimizer)\ncached_test_step = nnx.cached_partial(test_step, model)\n\ntotal_steps = 10_000\nfor step, batch in enumerate(dataset(32)):\n  cached_train_step(batch)\n\n  if step % 1000 == 0:\n    logs = cached_test_step((X, Y))\n    print(f\"step: {step}, loss: {logs['loss']}\")\n\n  if step >= total_steps - 1:\n    break\n\nprint('times called:', model.count.value)\n\ny_pred = model(X)\n\nplt.scatter(X, Y, color='blue')\nplt.plot(X, y_pred, color='black')\nplt.show()\n"
  },
  {
    "path": "examples/nnx_toy_examples/03_train_state.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# %%\nimport jax\nimport jax.numpy as jnp\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport optax\n\nfrom flax import nnx\nfrom flax.training import train_state\n\nX = np.linspace(0, 1, 100)[:, None]\nY = 0.8 * X**2 + 0.1 + np.random.normal(0, 0.1, size=X.shape)\n\n\ndef dataset(batch_size):\n  while True:\n    idx = np.random.choice(len(X), size=batch_size)\n    yield X[idx], Y[idx]\n\n\nclass Linear(nnx.Module):\n  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n    self.w = nnx.Param(jax.random.uniform(rngs.params(), (din, dout)))\n    self.b = nnx.Param(jnp.zeros((dout,)))\n\n  def __call__(self, x):\n    return x @ self.w.value + self.b.value\n\n\nclass Count(nnx.Variable[nnx.A]):\n  pass\n\n\nclass MLP(nnx.Module):\n  def __init__(self, din, dhidden, dout, *, rngs: nnx.Rngs):\n    self.count = Count(jnp.array(0))\n    self.linear1 = Linear(din, dhidden, rngs=rngs)\n    self.linear2 = Linear(dhidden, dout, rngs=rngs)\n\n  def __call__(self, x):\n    self.count.value += 1\n    x = self.linear1(x)\n    x = jax.nn.relu(x)\n    x = self.linear2(x)\n    return x\n\nclass TrainState(train_state.TrainState):\n  counts: nnx.State\n  graphdef: nnx.GraphDef\n\nmodel = MLP(din=1, dhidden=32, dout=1, rngs=nnx.Rngs(0))\ngraphdef, params, counts = nnx.split(model, nnx.Param, Count)\n\nstate = TrainState.create(\n  apply_fn=None,\n  graphdef=graphdef,\n  params=params,\n  tx=optax.sgd(0.1),\n  counts=counts,\n)\ndel params, counts\n\n\n@jax.jit\ndef train_step(state: TrainState, batch):\n  x, y = batch\n\n  def loss_fn(params):\n    model = nnx.merge(state.graphdef, params, state.counts, copy=True)\n    y_pred = model(x)\n    loss = jnp.mean((y - y_pred) ** 2)\n    counts = nnx.state(model, Count)\n    return loss, counts\n\n  grads, counts = jax.grad(loss_fn, has_aux=True)(state.params)\n  # sdg update\n  state = state.apply_gradients(grads=grads, counts=counts)\n\n  return state\n\n\n@jax.jit\ndef test_step(state: nnx.TrainState[MLP], batch):\n  x, y = batch\n  model = nnx.merge(state.graphdef, state.params, state.counts)\n  y_pred = model(x)\n  loss = jnp.mean((y - y_pred) ** 2)\n  return {'loss': loss}\n\n\ntotal_steps = 10_000\nfor step, batch in enumerate(dataset(32)):\n  state = train_step(state, batch)\n\n  if step % 1000 == 0:\n    logs = test_step(state, (X, Y))\n    print(f\"step: {step}, loss: {logs['loss']}\")\n\n  if step >= total_steps - 1:\n    break\n\nmodel = nnx.merge(state.graphdef, state.params, state.counts)\nprint('times called:', model.count.value)\n\ny_pred = model(X)\n\nplt.scatter(X, Y, color='blue')\nplt.plot(X, y_pred, color='black')\nplt.show()\n"
  },
  {
    "path": "examples/nnx_toy_examples/04_data_parallel_with_jit.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nos.environ['XLA_FLAGS'] = '--xla_force_host_platform_device_count=8'\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nimport optax\nfrom flax import nnx\nfrom jax.experimental import mesh_utils\nimport matplotlib.pyplot as plt\n\n# create a mesh + shardings\nnum_devices = jax.local_device_count()\nmesh = jax.sharding.Mesh(\n  mesh_utils.create_device_mesh((num_devices,)), ('data',)\n)\nmodel_sharding = jax.NamedSharding(mesh, jax.sharding.PartitionSpec())\ndata_sharding = jax.NamedSharding(mesh, jax.sharding.PartitionSpec('data'))\n\n\n# create model\nclass MLP(nnx.Module):\n  def __init__(self, din, dmid, dout, *, rngs: nnx.Rngs):\n    self.linear1 = nnx.Linear(din, dmid, rngs=rngs)\n    self.linear2 = nnx.Linear(dmid, dout, rngs=rngs)\n\n  def __call__(self, x):\n    return self.linear2(nnx.relu(self.linear1(x)))\n\n\nmodel = MLP(1, 64, 1, rngs=nnx.Rngs(0))\noptimizer = nnx.Optimizer(model, optax.adamw(1e-2), wrt=nnx.Param)\n\n# replicate state\nstate = nnx.state((model, optimizer))\nstate = jax.device_put(state, model_sharding)\nnnx.update((model, optimizer), state)\n\n# visualize model sharding\nprint('model sharding')\njax.debug.visualize_array_sharding(model.linear1.kernel.value)\n\n\n@nnx.jit\ndef train_step(model: MLP, optimizer: nnx.Optimizer, x, y):\n  def loss_fn(model: MLP):\n    y_pred = model(x)\n    return jnp.mean((y - y_pred) ** 2)\n\n  loss, grads = nnx.value_and_grad(loss_fn)(model)\n  optimizer.update(model, grads)\n  return loss\n\n\ndef dataset(steps, batch_size):\n  for _ in range(steps):\n    x = np.random.uniform(-2, 2, size=(batch_size, 1))\n    y = 0.8 * x**2 + 0.1 + np.random.normal(0, 0.1, size=x.shape)\n    yield x, y\n\n\nfor step, (x, y) in enumerate(dataset(1000, 16)):\n  # shard data\n  x, y = jax.device_put((x, y), data_sharding)\n  # train\n  loss = train_step(model, optimizer, x, y)\n\n  if step == 0:\n    print('data sharding')\n    jax.debug.visualize_array_sharding(x)\n\n  if step % 100 == 0:\n    print(f'step={step}, loss={loss}')\n\n# dereplicate state\nstate = nnx.state((model, optimizer))\nstate = jax.device_get(state)\nnnx.update((model, optimizer), state)\n\nX, Y = next(dataset(1, 1000))\nx_range = np.linspace(X.min(), X.max(), 100)[:, None]\ny_pred = model(x_range)\n\n# plot\nplt.scatter(X, Y, label='data')\nplt.plot(x_range, y_pred, color='black', label='model')\nplt.legend()\nplt.show()"
  },
  {
    "path": "examples/nnx_toy_examples/05_vae.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# %%\nimport typing as tp\n\nimport jax\nimport jax.numpy as jnp\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport optax\nfrom datasets import load_dataset\n\nfrom flax import nnx\n\nnp.random.seed(42)\nlatent_size = 32\nimage_shape: tp.Sequence[int] = (28, 28)\nsteps_per_epoch: int = 200\nbatch_size: int = 64\nepochs: int = 20\n\n\ndataset = load_dataset('mnist')\nX_train = np.array(np.stack(dataset['train']['image']), dtype=np.uint8)\nX_test = np.array(np.stack(dataset['test']['image']), dtype=np.uint8)\n# Now binarize data\nX_train = (X_train > 0).astype(jnp.float32)\nX_test = (X_test > 0).astype(jnp.float32)\n\nprint('X_train:', X_train.shape, X_train.dtype)\nprint('X_test:', X_test.shape, X_test.dtype)\n\n\nclass Loss(nnx.Variable):\n  pass\n\n\n# %%\nclass Encoder(nnx.Module):\n  def __init__(self, din: int, dmid: int, dout: int, *, rngs: nnx.Rngs):\n    self.linear1 = nnx.Linear(din, dmid, rngs=rngs)\n    self.linear_mean = nnx.Linear(dmid, dout, rngs=rngs)\n    self.linear_std = nnx.Linear(dmid, dout, rngs=rngs)\n    self.rngs = rngs\n\n  def __call__(self, x: jax.Array) -> jax.Array:\n    x = x.reshape((x.shape[0], -1))  # flatten\n    x = self.linear1(x)\n    x = jax.nn.relu(x)\n\n    mean = self.linear_mean(x)\n    std = jnp.exp(self.linear_std(x))\n\n    self.kl_loss = Loss(\n      jnp.mean(\n        0.5 * jnp.mean(-jnp.log(std**2) - 1.0 + std**2 + mean**2, axis=-1)\n      )\n    )\n    key = self.rngs.noise()\n    z = mean + std * jax.random.normal(key, mean.shape)\n    return z\n\n\nclass Decoder(nnx.Module):\n  def __init__(self, din: int, dmid: int, dout: int, *, rngs: nnx.Rngs):\n    self.linear1 = nnx.Linear(din, dmid, rngs=rngs)\n    self.linear2 = nnx.Linear(dmid, dout, rngs=rngs)\n\n  def __call__(self, z: jax.Array) -> jax.Array:\n    z = self.linear1(z)\n    z = jax.nn.relu(z)\n    logits = self.linear2(z)\n    return logits\n\n\nclass VAE(nnx.Module):\n  def __init__(\n    self,\n    din: int,\n    hidden_size: int,\n    latent_size: int,\n    output_shape: tp.Sequence[int],\n    *,\n    rngs: nnx.Rngs,\n  ):\n    self.output_shape = output_shape\n    self.encoder = Encoder(din, hidden_size, latent_size, rngs=rngs)\n    self.decoder = Decoder(\n      latent_size, hidden_size, int(np.prod(output_shape)), rngs=rngs\n    )\n\n  def __call__(self, x: jax.Array) -> jax.Array:\n    z = self.encoder(x)\n    logits = self.decoder(z)\n    logits = jnp.reshape(logits, (-1, *self.output_shape))\n    return logits\n\n  def generate(self, z):\n    logits = self.decoder(z)\n    logits = jnp.reshape(logits, (-1, *self.output_shape))\n    return nnx.sigmoid(logits)\n\n\nmodel = VAE(\n  din=int(np.prod(image_shape)),\n  hidden_size=256,\n  latent_size=latent_size,\n  output_shape=image_shape,\n  rngs=nnx.Rngs(0, noise=1),\n)\n\noptimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\n\n\n# %%\n@nnx.jit\ndef train_step(model: VAE, optimizer: nnx.Optimizer, x: jax.Array):\n  def loss_fn(model: VAE):\n    logits = model(x)\n    losses = nnx.pop(model, Loss)\n    kl_loss = sum(jax.tree_util.tree_leaves(losses), 0.0)\n    reconstruction_loss = jnp.mean(\n      optax.sigmoid_binary_cross_entropy(logits, x)\n    )\n    loss = reconstruction_loss + 0.1 * kl_loss\n    return loss\n\n  loss, grads = nnx.value_and_grad(loss_fn)(model)\n  optimizer.update(model, grads)\n\n  return loss\n\n\n@nnx.jit\ndef forward(model: VAE, x: jax.Array) -> jax.Array:\n  y_pred = model(x)\n  return jax.nn.sigmoid(y_pred)\n\n\n@nnx.jit\ndef sample(model: VAE, z: jax.Array) -> jax.Array:\n  return model.generate(z)\n\n\n# %%\n\nfor epoch in range(epochs):\n  losses = []\n  for step in range(steps_per_epoch):\n    idxs = np.random.randint(0, len(X_train), size=(batch_size,))\n    x_batch = X_train[idxs]\n\n    loss = train_step(model, optimizer, x_batch)\n    losses.append(np.asarray(loss))\n\n  print(f'Epoch {epoch} loss: {np.mean(losses)}')\n\n# exit()\n# %%\n# get random samples\nidxs = np.random.randint(0, len(X_test), size=(5,))\nx_sample = X_test[idxs]\n\n# get predictions\ny_pred = forward(model, x_sample)\n\n# plot reconstruction\nfigure = plt.figure(figsize=(3 * 5, 3 * 2))\nplt.title('Reconstruction Samples')\nfor i in range(5):\n  plt.subplot(2, 5, i + 1)\n  plt.imshow(x_sample[i], cmap='gray')\n  plt.subplot(2, 5, 5 + i + 1)\n  plt.imshow(y_pred[i], cmap='gray')\n  # # tbwriter.add_figure(\"VAE Example\", figure, epochs)\n\nplt.show()\n\n# %%\n# plot generative samples\nz_samples = np.random.normal(scale=1.5, size=(12, latent_size))\nsamples = sample(model, z_samples)\n\nfigure = plt.figure(figsize=(3 * 5, 3 * 2))\nplt.title('Generative Samples')\nfor i in range(5):\n  plt.subplot(2, 5, 2 * i + 1)\n  plt.imshow(samples[i], cmap='gray')\n  plt.subplot(2, 5, 2 * i + 2)\n  plt.imshow(samples[i + 1], cmap='gray')\n\nplt.show()\n\n# %%\n"
  },
  {
    "path": "examples/nnx_toy_examples/06_scan_over_layers.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nimport jax\nimport jax.numpy as jnp\n\nfrom flax import nnx\n\n\nclass Block(nnx.Module):\n  def __init__(self, dim: int, *, rngs: nnx.Rngs):\n    self.linear = nnx.Linear(dim, dim, rngs=rngs)\n    self.bn = nnx.BatchNorm(dim, rngs=rngs)\n    self.dropout = nnx.Dropout(0.5, rngs=rngs)\n\n  def __call__(self, x: jax.Array):\n    return jax.nn.gelu(self.dropout(self.bn(self.linear(x))))\n\n\nclass ScanMLP(nnx.Module):\n  \"\"\"\n  An MLP that uses `vmap` during `__init__` to create a Block instance\n  with an additional `layer` axis, and `scan` during `__call__` to apply\n  the sequence of layers iteratively over the input / output `x`.\n  \"\"\"\n\n  def __init__(self, dim: int, *, n_layers: int, rngs: nnx.Rngs):\n    self.n_layers = n_layers\n\n    @nnx.split_rngs(splits=n_layers)\n    @nnx.vmap(axis_size=n_layers)\n    def create_block(rngs: nnx.Rngs):\n      return Block(dim, rngs=rngs)\n\n    self.layers = create_block(rngs)\n\n  def __call__(self, x: jax.Array) -> jax.Array:\n    @nnx.scan\n    def scan_fn(x: jax.Array, block: Block):\n      x = block(x)\n      return x, None\n\n    x, _ = scan_fn(x, self.layers)\n\n    return x\n\n\nmodel = ScanMLP(10, n_layers=5, rngs=nnx.Rngs(0))\n\nx = jnp.ones((3, 10))\ny = model(x)\n\nprint(jax.tree.map(jnp.shape, nnx.state(model)))\nprint(y.shape)\n"
  },
  {
    "path": "examples/nnx_toy_examples/07_array_leaves.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# %%\nimport jax\nimport jax.numpy as jnp\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport optax\n\nfrom flax import nnx, struct\n\nX = np.linspace(0, 1, 100)[:, None]\nY = 0.8 * X**2 + 0.1 + np.random.normal(0, 0.1, size=X.shape)\n\n\ndef dataset(batch_size):\n  while True:\n    idx = np.random.choice(len(X), size=batch_size)\n    yield X[idx], Y[idx]\n\nclass Linear(nnx.Module):\n  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n    self.w = jax.random.normal(rngs.params(), (din, dout))\n    self.b = jnp.zeros((dout,))\n\n  def __call__(self, x):\n    return x @ self.w + self.b\n\n\nclass MLP(nnx.Module):\n  def __init__(self, din, dhidden, dout, *, rngs: nnx.Rngs):\n    self.count = jnp.array(0)\n    self.linear1 = Linear(din, dhidden, rngs=rngs)\n    self.linear2 = Linear(dhidden, dout, rngs=rngs)\n\n  def __call__(self, x):\n    self.count += 1\n    return self.linear2(nnx.relu(self.linear1(x)))\n\ndef is_param(path, value):\n  key = path[-1]\n  return key == 'w' or key == 'b'\n\nmodel = MLP(din=1, dhidden=32, dout=1, rngs=nnx.Rngs(0))\ntx = optax.sgd(1e-3)\noptimizer = nnx.Optimizer(model, tx, wrt=is_param)\n\n\n@nnx.jit\ndef train_step(model: MLP, optimizer: nnx.Optimizer, batch):\n  x, y = batch\n\n  def loss_fn(model: MLP):\n    y_pred = model(x)\n    return jnp.mean((y - y_pred) ** 2)\n\n  diff_state = nnx.DiffState(0, is_param)\n  grads: nnx.State = nnx.grad(loss_fn, argnums=diff_state)(model)\n  optimizer.update(model, grads)\n\n\n@nnx.jit\ndef test_step(model: MLP, batch):\n  x, y = batch\n  y_pred = model(x)\n  loss = jnp.mean((y - y_pred) ** 2)\n  return {'loss': loss}\n\n\ntotal_steps = 10_000\nfor step, batch in enumerate(dataset(32)):\n  train_step(model, optimizer, batch)\n\n  if step % 1000 == 0:\n    logs = test_step(model, (X, Y))\n    print(f\"step: {step}, loss: {logs['loss']}\")\n\n  if step >= total_steps - 1:\n    break\n\nprint('times called:', model.count)\n\ny_pred = model(X)\n\nplt.scatter(X, Y, color='blue')\nplt.plot(X, y_pred, color='black')\nplt.show()\n"
  },
  {
    "path": "examples/nnx_toy_examples/08_save_load_checkpoints.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom tempfile import TemporaryDirectory\n\nimport jax\nimport jax.numpy as jnp\nimport orbax.checkpoint as orbax\n\nfrom flax import nnx\n\n\nclass MLP(nnx.Module):\n  def __init__(self, din: int, dmid: int, dout: int, *, rngs: nnx.Rngs):\n    self.dense1 = nnx.Linear(din, dmid, rngs=rngs)\n    self.dense2 = nnx.Linear(dmid, dout, rngs=rngs)\n\n  def __call__(self, x: jax.Array) -> jax.Array:\n    x = self.dense1(x)\n    x = jax.nn.relu(x)\n    x = self.dense2(x)\n    return x\n\n\ndef create_model(seed: int):\n  return MLP(10, 20, 30, rngs=nnx.Rngs(seed))\n\n\ndef create_and_save(seed: int, path: str):\n  model = create_model(seed)\n  state = nnx.state(model)\n  # Save the parameters\n  checkpointer = orbax.PyTreeCheckpointer()\n  checkpointer.save(f'{path}/state', state)\n\n\ndef load_model(path: str) -> MLP:\n  # create that model with abstract shapes\n  model = nnx.eval_shape(lambda: create_model(0))\n  state = nnx.state(model)\n  # Load the parameters\n  checkpointer = orbax.PyTreeCheckpointer()\n  state = checkpointer.restore(f'{path}/state', item=state)\n  # update the model with the loaded state\n  nnx.update(model, state)\n  return model\n\n\nwith TemporaryDirectory() as tmpdir:\n  # create a checkpoint\n  create_and_save(42, tmpdir)\n  # load model from checkpoint\n  model = load_model(tmpdir)\n  # run the model\n  y = model(jnp.ones((1, 10)))\n  print(model)\n  print(y)\n"
  },
  {
    "path": "examples/nnx_toy_examples/09_parameter_surgery.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nimport jax\n\nfrom flax import nnx\n\n\n# lets pretend this function loads a pretrained model from a checkpoint\ndef load_pretrained():\n  return nnx.Linear(784, 128, rngs=nnx.Rngs(0))\n\n\n# create a simple linear classifier using a pretrained backbone\nclass Classifier(nnx.Module):\n  def __init__(self, *, rngs: nnx.Rngs):\n    self.backbone = nnx.Linear(784, 128, rngs=nnx.Rngs(0))\n    self.head = nnx.Linear(128, 10, rngs=rngs)\n\n  def __call__(self, x):\n    x = self.backbone(x)\n    x = nnx.relu(x)\n    x = self.head(x)\n    return x\n\n\n# create the classifier using the pretrained backbone, here we are technically\n# doing \"parameter surgery\", however, compared to Haiku/Flax where you must manually\n# construct the parameter structure, in NNX this is done automatically\nmodel = Classifier(rngs=nnx.Rngs(42))\nmodel.backbone = load_pretrained()\n\n\n# create a filter to select all the parameters that are not part of the\n# backbone, i.e. the classifier parameters\nis_trainable = lambda path, node: (\n  'backbone' in path and isinstance(node, nnx.Param)\n)\n\n# split the parameters into trainable and non-trainable parameters\ngraphdef, trainable_params, non_trainable = nnx.split(model, is_trainable, ...)\n\nprint(\n  'trainable_params =',\n  jax.tree.map(jax.numpy.shape, trainable_params),\n)\nprint('non_trainable = ', jax.tree.map(jax.numpy.shape, non_trainable))\n"
  },
  {
    "path": "examples/nnx_toy_examples/10_fsdp_and_optimizer.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport dataclasses\nimport os\nos.environ['XLA_FLAGS'] = '--xla_force_host_platform_device_count=8'\n\nfrom matplotlib import pyplot as plt\nfrom jax.experimental import mesh_utils\nfrom jax.sharding import Mesh, PartitionSpec as P, NamedSharding\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom flax import nnx\nimport typing as tp\n\nmesh = jax.sharding.Mesh(\n  mesh_utils.create_device_mesh((2, 4)),\n  ('data', 'model'),\n)\n\n\ndef named_sharding(*names: str | None) -> NamedSharding:\n  return NamedSharding(mesh, P(*names))\n\n\n@dataclasses.dataclass(unsafe_hash=True)\nclass MeshRules:\n  embed: str | None = None\n  mlp: str | None = None\n  data: str | None = None\n\n  def __call__(self, *keys: str) -> tuple[str, ...]:\n    return tuple(getattr(self, key) for key in keys)\n\n\nmesh_rules = MeshRules(\n  embed=None,\n  mlp='model',\n  data='data',\n)\n\n\nclass MLP(nnx.Module):\n  def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n    self.w1 = nnx.Param(\n      nnx.initializers.lecun_normal()(rngs.params(), (din, dmid)),\n      out_sharding=mesh_rules('embed', 'mlp'),\n    )\n    self.b1 = nnx.Param(\n      jnp.zeros((dmid,)),\n      out_sharding=mesh_rules('mlp'),\n    )\n    self.w2 = nnx.Param(\n      nnx.initializers.lecun_normal()(rngs.params(), (dmid, dout)),\n      out_sharding=mesh_rules('embed', 'mlp'),\n    )\n\n  def __call__(self, x: jax.Array):\n    return nnx.relu(x @ self.w1 + self.b1) @ self.w2\n\n\nclass SGDState(nnx.Variable):\n  pass\n\n\nclass SGD(nnx.Pytree):\n  def __init__(self, params: nnx.State, lr, decay=0.9):\n    def init_optimizer_state(variable: nnx.Variable):\n      return SGDState(\n        jnp.zeros_like(variable.value), **variable.get_metadata()\n      )\n\n    self.lr = lr\n    self.params = nnx.data(params)\n    self.momentum: nnx.State = nnx.data(jax.tree.map(\n      init_optimizer_state,\n      self.params,\n      is_leaf=lambda x: isinstance(x, nnx.Variable),\n    ))\n    self.decay = decay\n\n  def update(self, grads: nnx.State):\n    def update_fn(\n      params: nnx.Variable, momentum: SGDState, grad: nnx.Variable\n    ):\n      # v_t = β * v_{t-1} + (1 - β) * ∇J(θ_t)\n      momentum[...] = self.decay * momentum[...] + (1 - self.decay) * grad[...]\n      # θ_{t+1} = θ_t - α * v_t\n      params[...] -= self.lr * momentum[...]\n\n    jax.tree.map(\n      update_fn,\n      self.params,\n      self.momentum,\n      grads,\n      is_leaf=lambda x: isinstance(x, nnx.Variable),\n    )\n\n\n@nnx.jit\ndef create_model():\n  model = MLP(1, 32, 1, rngs=nnx.Rngs(0))\n  optimizer = SGD(nnx.variables(model, nnx.Param), 0.01, decay=0.9)\n  return model, optimizer\n\nwith jax.set_mesh(mesh):\n  model, optimizer = create_model()\n\nprint('Model parameters sharding:')\njax.debug.visualize_array_sharding(model.w1.value)\nprint('Optimizer momentum sharding:')\njax.debug.visualize_array_sharding(optimizer.momentum['w1'].value)\n\n\n@nnx.jit\ndef train_step(model: MLP, optimizer: SGD, x, y):\n  def loss_fn(model):\n    y_pred = model(x)\n    loss = jnp.mean((y - y_pred) ** 2)\n    return loss\n\n  loss, grad = nnx.value_and_grad(loss_fn)(model)\n  optimizer.update(grad)\n  return loss\n\n\nX = np.linspace(-2, 2, 100)[:, None]\nY = 0.8 * X**2 + 0.1 + np.random.normal(0, 0.1, size=X.shape)\n\n\ndef dataset(batch_size, num_steps):\n  for _ in range(num_steps):\n    idx = np.random.choice(len(X), size=batch_size)\n    yield X[idx], Y[idx]\n\n\nlosses = []\nfor step, (x_batch, y_batch) in enumerate(\n  dataset(batch_size=32, num_steps=10_000)\n):\n  x_batch, y_batch = jax.device_put((x_batch, y_batch), named_sharding('data'))\n  loss = train_step(model, optimizer, x_batch, y_batch)\n  losses.append(float(loss))\n  if step % 1000 == 0:\n    print(f'Step {step}: Loss = {loss}')\n\nplt.figure()\nplt.plot(losses[20:])\n\ny_pred = model(X)\nplt.figure()\nplt.scatter(X, Y, color='blue')\nplt.plot(X, y_pred, color='black')\nplt.show()\n"
  },
  {
    "path": "examples/nnx_toy_examples/hijax_basic.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# %%\nimport jax\nimport jax.numpy as jnp\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport optax\n\nfrom flax import nnx\n\n\nX = np.linspace(-jnp.pi, jnp.pi, 100)[:, None]\nY = 0.8 * jnp.sin(X) + 0.1 + np.random.normal(0, 0.1, size=X.shape)\n\ndef dataset(batch_size):\n  while True:\n    idx = np.random.choice(len(X), size=batch_size)\n    yield X[idx], Y[idx]\n\n\nclass Linear(nnx.Module):\n  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n    self.w = nnx.Param(rngs.params.uniform((din, dout)))\n    self.b = nnx.Param(jnp.zeros((dout,)))\n\n  def __call__(self, x):\n    return x @ self.w + self.b[None]\n\n\nclass Count(nnx.Variable[nnx.A]):\n  pass\n\n\nclass MLP(nnx.Module):\n  def __init__(self, din, dhidden, dout, *, rngs: nnx.Rngs):\n    self.count = Count(jnp.array(0))\n    self.linear1 = Linear(din, dhidden, rngs=rngs)\n    self.linear2 = Linear(dhidden, dout, rngs=rngs)\n\n  def __call__(self, x):\n    self.count[...] += 1\n    return self.linear2(jax.nn.relu(self.linear1(x)) * 0.5)\n\nnnx.var_defaults(hijax=True)\n\nmodel = MLP(din=1, dhidden=32, dout=1, rngs=nnx.Rngs(0))\noptimizer = nnx.Optimizer(model, optax.sgd(learning_rate=0.1), wrt=nnx.Param)\n\n\n@jax.jit\ndef train_step(model, optimizer, x, y):\n  graphdef, params, nondiff = nnx.split(model, nnx.Param, ...)\n\n  def loss_fn(params):\n    model = nnx.merge(graphdef, params, nondiff)\n    return jnp.mean((y - model(x)) ** 2)\n\n  grads = jax.grad(loss_fn)(nnx.vars_as(params, is_mutable=False))\n  optimizer.update(model, grads)\n\n@jax.jit\ndef test_step(model: MLP, x, y):\n  return {'loss': jnp.mean((y - model(x)) ** 2)}\n\n\ntotal_steps = 10_000\nfor step, (x, y) in enumerate(dataset(32)):\n  train_step(model, optimizer, x, y)\n\n  if step % 1000 == 0:\n    logs = test_step(model, X, Y)\n    print(f\"step: {step}, loss: {logs['loss']}\")\n\n  if step >= total_steps - 1:\n    break\n\nprint('times called:', model.count[...])\n\ny_pred = model(X)\n\nplt.scatter(X, Y, color='blue')\nplt.plot(X, y_pred, color='black')\nplt.show()\n"
  },
  {
    "path": "examples/nnx_toy_examples/hijax_demo.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# %%\nimport jax\nimport jax.numpy as jnp\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nfrom flax import nnx\n\n# ## Data\n# We create a simple dataset of points sampled from a parabola with some noise.\nX = np.linspace(-jnp.pi, jnp.pi, 100)[:, None]\nY = 0.8 * jnp.sin(X) + 0.1 + np.random.normal(0, 0.1, size=X.shape)\n\n\ndef dataset(batch_size):\n  while True:\n    idx = np.random.choice(len(X), size=batch_size)\n    yield X[idx], Y[idx]\n\n\n# ## Model\n# Here we define a MLP made of a stack of blocks. Each block contains a linear layer,\n# batch normalization, and a dropout layer.\n#\n# In this version we want the Modules to be pytrees so they can be used with JAX transforms\n# so we use a new Pytree type as the base. The main difference with current NNX is that\n# attributes that contain arrays or other pytrees now need to be explicitly marked as\n# using `nnx.data` to be included in the pytree.\nclass Linear(nnx.Module):\n  def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n    self.din, self.dout = din, dout\n    initializer = jax.nn.initializers.lecun_normal()\n    # nnx.data is used mark attributes as pytree data\n    # Param, BatchState, and Cache are built-in Variable subtypes\n    self.w = nnx.Param(initializer(rngs.params(), (din, dout)))\n    self.b = nnx.Param(jnp.zeros((dout,)))\n\n  def __call__(self, x: jax.Array):\n    return x @ self.w + self.b[None]\n\n\n# Block implements linear, batch norm, and dropout. Its behavior\n# is controlled by the 'use_stats' and 'deterministic' flags.\nclass Block(nnx.Module):\n  def __init__(\n    self,\n    din: int,\n    dout: int,\n    *,\n    dropout_rate: float = 0.05,\n    moumentum: float = 0.95,\n    use_stats: bool = False,\n    deterministic: bool = False,\n    rngs: nnx.Rngs,\n  ):\n    # ----------- linear -------------------\n    self.din, self.dout = din, dout\n    initializer = jax.nn.initializers.lecun_normal()\n    self.w = nnx.Param(initializer(rngs.params(), (din, dout)))\n    self.b = nnx.Param(jnp.zeros((dout,)))\n    # ----------- batch norm ---------------\n    self.mu = moumentum  # momentum\n    self.use_stats = use_stats\n    self.mean = nnx.BatchStat(jnp.zeros((dout,)))\n    self.var = nnx.BatchStat(jnp.ones((dout,)))\n    self.scale = nnx.Param(jnp.ones((dout,)))\n    self.bias = nnx.Param(jnp.zeros((dout,)))\n    # ----------- dropout ------------------\n    self.dropout_rate = dropout_rate\n    self.deterministic = deterministic\n\n  def __call__(\n    self, x: jax.Array, *, rngs: nnx.Rngs | None = None\n  ) -> jax.Array:\n    # ----------- linear --------------------\n    x = x @ self.w + self.b[None]\n    # ----------- batch norm ----------------\n    if self.use_stats:\n      mean = self.mean\n      var = self.var\n    else:\n      mean = jnp.mean(x, axis=0)\n      var = jnp.var(x, axis=0)\n      # ema updates\n      # stop gradient is used until a Hijax supports updates from grad tracers\n      sg = jax.lax.stop_gradient\n      self.mean[...] = sg(self.mu * self.mean + (1 - self.mu) * mean)\n      self.var[...] = sg(self.mu * self.var + (1 - self.mu) * var)\n    x = (x - mean[None]) / jnp.sqrt(var[None] + 1e-5)\n    x = x * self.scale + self.bias\n    # ----------- dropout -------------------\n    if not self.deterministic and self.dropout_rate > 0.0:\n      assert rngs is not None\n      keep_prob = 1.0 - self.dropout_rate\n      mask = jax.random.bernoulli(rngs.dropout(), keep_prob, x.shape)\n      x = jnp.where(mask, x / keep_prob, jnp.zeros_like(x))\n    # ----------- activation ---------------\n    x = jax.nn.gelu(x)\n    return x\n\n\nclass Model(nnx.Module):\n  def __init__(\n    self,\n    num_blocks: int,\n    din: int,\n    dhidden: int,\n    dout: int,\n    *,\n    use_scan: bool = True,\n    rngs: nnx.Rngs,\n  ):\n    self.count = nnx.Variable(jnp.array(0))\n    self.block_in = Block(din, dhidden, rngs=rngs)\n    self.linear_out = Linear(dhidden, dout, rngs=rngs)\n\n    # 'blocks' is either a list of blocks or single block\n    # whose parameters contain an additional 'layer' dimension,\n    # here created using jax.vmap\n    if use_scan:\n\n      @jax.vmap\n      def create_block(rngs, /):\n        # return nnx.stateless(Block(dhidden, dhidden, rngs=rngs))\n        return Block(dhidden, dhidden, rngs=rngs)\n\n      # self.blocks = nnx.stateful(create_block(rngs.fork(split=num_blocks)))\n      self.blocks = create_block(rngs.fork(split=num_blocks))\n    else:\n      self.blocks = nnx.List(\n        [Block(dhidden, dhidden, rngs=rngs) for i in range(num_blocks)]\n      )\n\n  def __call__(self, x: jax.Array, *, rngs: nnx.Rngs | None = None):\n    self.count[...] += 1\n    x = self.block_in(x, rngs=rngs)\n\n    # on the forward pass we either iterate over the block\n    # list or use jax.lax.scan to apply the blocks, if we\n    # had shared state we would use split and merge to\n    # pass the shared state as a capture\n    if isinstance(self.blocks, nnx.List):\n      for block in self.blocks:\n        x = block(x, rngs=rngs)\n    else:\n\n      def block_fw(x, block: Block):\n        x = block(x, rngs=rngs)\n        return x, None\n\n      x, _ = jax.lax.scan(block_fw, x, self.blocks)\n    x = self.linear_out(x)\n    return x\n\n\n# ## Optimizer\nclass OptState(nnx.Variable): ...\n\n\n# Optimizer are an interesting case as they are inherently stateful and\n# pose a good use case for MutableHijax. Here we implement SGD with\n# momentum. The optimizer receives the params as constructor arguments but doesn't\n# hold a reference to them, it only uses the params to initialize its state\n# by creating new OptState Variables that reuse the param's metadata.\nclass SGD(nnx.Pytree):\n  def __init__(self, params, lr: float, decay: float = 0.9):\n    self.lr = lr\n    self.decay = decay\n\n    def make_opt_state(x):\n      if isinstance(x, nnx.Variable):\n        return OptState(jnp.zeros_like(x[...]), **x.get_metadata())\n      else:\n        return OptState(jnp.zeros_like(x))\n\n    self.momentum = nnx.data(jax.tree.map(make_opt_state, params))\n\n  # during the update we simply map over (params, momentum, grads),\n  # for each triplet we implement the SGD update rule which updates\n  # both the optimizer's state (momentum) and the params in place.\n  def update(self, params, grads):\n    def update_fn(\n      param: nnx.Variable[jax.Array],\n      momentum: nnx.Variable[jax.Array],\n      grad: nnx.Variable[jax.Array],\n    ):\n      momentum[...] = self.decay * momentum + (1 - self.decay) * grad\n      param[...] -= self.lr * momentum\n\n    # is_leaf might not be necesarry as MutableHijaxVariable are not pytreees\n    jax.tree.map(update_fn, params, self.momentum, grads)\n\n\n# ## Training\nnnx.var_defaults(hijax=True)\n\nrngs = nnx.Rngs(params=0, dropout=1)\nmodel = Model(\n  num_blocks=3, din=1, dhidden=256, dout=1, use_scan=False, rngs=rngs\n)\noptimizer = SGD(params=nnx.state(model, nnx.Param), lr=3e-3, decay=0.99)\n\n# Create a copy of the model structure and set its attributes to eval model.\n# This works because they share the underlying ArrayRefs so both models\n# will always be in sync.\neval_model = nnx.merge(*nnx.split(model))\neval_model.set_attributes(use_stats=True, deterministic=True)\n\n\n# The training step uses 'jax.jit' and receives the model and optimizer as arguments,\n# this is supported as they are now pytrees. The first thing we do is group the model\n# state into the params and the non-differentiable state using 'split'. We differentiate\n# the loss function using 'jax.grad' with respect to the params-only. Inside the loss\n# function we merge the params and non-diff state back into a single model and then\n# compute the loss by calling the model with the inputs.\n@jax.jit\ndef train_step(model: Model, optimizer: SGD, rngs: nnx.Rngs, x, y):\n  graphdef, params, nondiff = nnx.split(model, nnx.Param, ...)\n\n  def loss_fn(params):\n    model = nnx.merge(graphdef, params, nondiff)\n    loss = jnp.mean((model(x, rngs=rngs) - y) ** 2)\n    return loss\n\n  # For the time being we have to use 'immutable'\n  # as 'jax.grad' doesn't support QDD types yet.\n  grads = jax.grad(loss_fn)(nnx.vars_as(params, is_mutable=False))\n  # 'update' mutates the optimizer's state and the params in place\n  # so we don't need to return anything 🚀\n  optimizer.update(params, grads)\n\n\n# simple test step that computes the loss\n@jax.jit\ndef test_step(model: Model, x, y):\n  return {'loss': jnp.mean((model(x) - y) ** 2)}\n\n\n# minimalistic training loop\ntotal_steps = 2_000\nfor step, (x, y) in enumerate(dataset(32)):\n  train_step(model, optimizer, rngs, x, y)\n\n  if step % 200 == 0:\n    logs = test_step(eval_model, X, Y)\n    print(f'step: {step}, loss: {logs[\"loss\"]}')\n\n  if step >= total_steps - 1:\n    break\n\n# ## Sample\n# Sampling is trivial, just use 'model_eval'\nprint('times called:', eval_model.count[...])\n\ny_pred = eval_model(X)\n\nplt.scatter(X, Y, color='blue')\nplt.plot(X, y_pred, color='black')\nplt.show()\n"
  },
  {
    "path": "examples/nnx_toy_examples/requirements.txt",
    "content": "matplotlib>=3.7.1\ndatasets>=2.12.0"
  },
  {
    "path": "examples/ogbg_molpcba/README.md",
    "content": "## Predicting Biological Activities of Molecules with Graph Neural Networks\n[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google/flax/blob/main/examples/ogbg_molpcba/ogbg_molpcba.ipynb)\n\nThis example trains a Graph Neural Network to classify molecules\non the basis of their biological activities.\n\n![Prediction on a caramboxin molecule](https://www.gstatic.com/flax_examples/ogbg_molpcba.png \"Prediction on a caramboxin molecule\")\n\nWe use [Jraph](https://github.com/deepmind/jraph/),\na JAX library for Graph Neural Networks, to define models\nwhich are trained on the\n[ogbg-molpcba](https://ogb.stanford.edu/docs/graphprop/)\ndataset, part of the [Open Graph Benchmark](https://ogb.stanford.edu/).\n\nYou can run this code and even modify it directly in Google Colab,\nno installation required!\nThe [Colab notebook](https://colab.research.google.com/github/google/flax/blob/main/examples/ogbg_molpcba/ogbg_molpcba.ipynb)\ncan even create visualizations of model predictions:\n\n![Visualizing predictions of a trained model](https://www.gstatic.com/flax_examples/ogbg_molpcba_predictions.svg? \"Visualizing predictions of a trained model\")\n\n\n### Requirements\n\nWe depend on\n[TensorFlow Datasets](https://www.tensorflow.org/datasets/catalog/ogbg_molpcba)\nfor ogbg-molpcba.\n\n### How to Run\n\nTo run with the default configuration:\n\n```shell\npython main.py --workdir=./ogbg_molpcba --config=configs/default.py\n```\n\nSince the configuration is defined using\n[config_flags](https://github.com/google/ml_collections/tree/master#config-flags),\nyou can override hyperparameters. For example, to change the number of epochs\nand the batch size:\n\n```shell\npython main.py --workdir=./ogbg_molpcba --config=configs/default.py \\\n--config.num_training_epochs=10 --config.batch_size=50\n```\n\nFor more extensive changes, you can directly edit the default\nconfiguration file or even add your own.\n\n### Supported Setups\n\nThis example supports only single device training.\nThe model should run with other configurations and hardware, but was explicitly\ntested on the following.\n\nHardware | Batch size | Training time | Test mean AP  | Validation mean AP | Metrics\n-------- | ---------- | ------------- | ------- | ------- | ---------------\n1x V100  | 256        |   3h20m       | 0.244   | 0.252   |[2021-08-03](https://tensorboard.dev/experiment/AAJqfvgSRJaA1MBkc0jMWQ/)\n\nThese metrics reported above are obtained at the end of training.\nWe observed that slightly higher metrics can be obtained with\nearly-stopping based on the validation mean AP:\n\nHardware | Batch size | Training time | Test mean AP  | Validation mean AP | Metrics\n-------- | ---------- | ------------- | ------- | ------- | ---------------\n1x V100  | 256        |   2h55m       | 0.249   | 0.257   |[2021-08-03](https://tensorboard.dev/experiment/AAJqfvgSRJaA1MBkc0jMWQ/)\n\n\n### Model Description\n\nThe default configuration corresponds to a\n[Graph Convolutional Network](https://arxiv.org/abs/1609.02907)\nmodel with 695,936 parameters.\n\nWe noticed diminishing gains when training for longer.\nFurther, the addition of self-loops and undirected edges significantly\nhelped performance.\nMinor improvements were seen with skip-connections across message-passing\nsteps, together with [LayerNorm](https://arxiv.org/abs/1607.06450).\nOn the contrary, we found that the addition of\n[virtual nodes](https://arxiv.org/abs/1709.03741),\nwhich are connected to all nodes in each graph,\ndid not improve performance.\n\n### References\n\n- Weihua Hu, Matthias Fey, Marinka Zitnik, Yuxiao Dong, Hongyu Ren,\n  Bowen Liu, Michele Catasta and Jure Leskovec (2020).\n  *Open Graph Benchmark: Datasets for Machine Learning on Graphs.*\n  In Advances in Neural Information Processing Systems 33: Annual\n  Conference on Neural Information Processing Systems 2020,\n  NeurIPS 2020, December 6-12,\n  2020, virtual.\n\n- Thomas N. Kipf and Max Welling (2016). *Semi-supervised classification\n  with graph convolutional networks.* arXiv preprint arXiv:1609.02907.\n\n- Jimmy Lei Ba, Jamie Ryan Kiros, and Geoffrey E. Hinton (2016). *Layer\n  normalization.* arXiv preprint arXiv:1607.06450.\n\n- Junying Li, Deng Cai and Xiaofei He (2017). *Learning graph-level\n  representation for drug discovery.* arXiv preprint arXiv:1709.03741.\n\nThe caramboxin molecule diagram depicted above was obtained and modified from\n[Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Caramboxin.svg),\navailable in the public domain.\n"
  },
  {
    "path": "examples/ogbg_molpcba/configs/default.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Defines the default hyperparameters and training configuration.\n\nUses a Graph Convolutional Network model (https://arxiv.org/abs/1609.02907).\n\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = ml_collections.ConfigDict()\n\n  # Optimizer.\n  config.optimizer = 'adam'\n  config.learning_rate = 1e-3\n\n  # Training hyperparameters.\n  config.batch_size = 256\n  config.num_train_steps = 100_000\n  config.log_every_steps = 100\n  config.eval_every_steps = 1_000\n  config.checkpoint_every_steps = 10_000\n  config.add_virtual_node = False\n  config.add_undirected_edges = True\n  config.add_self_loops = True\n\n  # GNN hyperparameters.\n  config.model = 'GraphConvNet'\n  config.message_passing_steps = 5\n  config.latent_size = 256\n  config.dropout_rate = 0.1\n  config.num_mlp_layers = 2\n  config.num_classes = 128\n  config.skip_connections = True\n  config.layer_norm = True\n  return config\n"
  },
  {
    "path": "examples/ogbg_molpcba/configs/default_graph_net.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Defines the default hyperparameters and training configuration.\n\nUses a GraphNetwork model (https://arxiv.org/abs/1806.01261).\n\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the hyperparameter configuration for the GraphNetwork model.\"\"\"\n  config = ml_collections.ConfigDict()\n\n  # Optimizer.\n  config.optimizer = 'adam'\n  config.learning_rate = 1e-3\n\n  # Training hyperparameters.\n  config.batch_size = 256\n  config.num_train_steps = 100_000\n  config.log_every_steps = 100\n  config.eval_every_steps = 10_000\n  config.checkpoint_every_steps = 10_000\n  config.add_virtual_node = True\n  config.add_undirected_edges = True\n  config.add_self_loops = True\n\n  # GNN hyperparameters.\n  config.model = 'GraphNet'\n  config.message_passing_steps = 5\n  config.latent_size = 256\n  config.dropout_rate = 0.1\n  config.num_mlp_layers = 1\n  config.num_classes = 128\n  config.use_edge_model = True\n  config.skip_connections = True\n  config.layer_norm = True\n  return config\n"
  },
  {
    "path": "examples/ogbg_molpcba/configs/hparam_sweep.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Defines a sweep for the hyperparameters for the GNN.\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = ml_collections.ConfigDict()\n\n  # Optimizer.\n  config.optimizer = 'adam'\n  config.learning_rate = 1e-3\n\n  # Training hyperparameters.\n  config.batch_size = 256\n  config.num_train_steps = 500_000\n  config.log_every_steps = 50\n  config.eval_every_steps = 1_000\n  config.checkpoint_every_steps = 10_000\n  config.add_virtual_node = True\n  config.add_undirected_edges = True\n  config.add_self_loops = True\n\n  # GNN hyperparameters.\n  config.model = 'GraphConvNet'\n  config.message_passing_steps = 5\n  config.latent_size = 256\n  config.dropout_rate = 0.1\n  config.num_mlp_layers = 2\n  config.num_classes = 128\n  config.skip_connections = True\n  config.layer_norm = True\n\n  return config\n\n\ndef sweep(add):\n  for add_virtual_node in (True, False):\n    for add_undirected_edges in (True, False):\n      for add_self_loops in (True, False):\n        for layer_norm in (True, False):\n          for skip_connections in (True, False):\n            add(\n                add_virtual_node=add_virtual_node,\n                add_undirected_edges=add_undirected_edges,\n                add_self_loops=add_self_loops,\n                layer_norm=layer_norm,\n                skip_connections=skip_connections,\n            )\n"
  },
  {
    "path": "examples/ogbg_molpcba/configs/test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Defines a CPU-friendly test configuration.\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = ml_collections.ConfigDict()\n\n  # Optimizer.\n  config.optimizer = 'adam'\n  config.learning_rate = 1e-3\n\n  # Training hyperparameters.\n  config.batch_size = 32\n  config.num_train_steps = 10\n  config.log_every_steps = 5\n  config.eval_every_steps = 5\n  config.checkpoint_every_steps = 5\n  config.add_virtual_node = True\n  config.add_undirected_edges = True\n  config.add_self_loops = True\n\n  # GNN hyperparameters.\n  config.model = 'GraphConvNet'\n  config.message_passing_steps = 5\n  config.latent_size = 256\n  config.dropout_rate = 0.1\n  config.num_mlp_layers = 1\n  config.num_classes = 128\n  config.skip_connections = False\n  config.layer_norm = False\n\n  return config\n"
  },
  {
    "path": "examples/ogbg_molpcba/input_pipeline.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Exposes the ogbg-molpcba dataset in a convenient format.\"\"\"\n\nimport functools\nfrom typing import Dict, NamedTuple\n\nimport jraph\nimport numpy as np\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\n\nclass GraphsTupleSize(NamedTuple):\n  \"\"\"Helper class to represent padding and graph sizes.\"\"\"\n\n  n_node: int\n  n_edge: int\n  n_graph: int\n\n\ndef get_raw_datasets() -> dict[str, tf.data.Dataset]:\n  \"\"\"Returns datasets as tf.data.Dataset, organized by split.\"\"\"\n  ds_builder = tfds.builder('ogbg_molpcba')\n  ds_builder.download_and_prepare()\n  ds_splits = ['train', 'validation', 'test']\n  datasets = {split: ds_builder.as_dataset(split=split) for split in ds_splits}\n  return datasets\n\n\ndef get_datasets(\n    batch_size: int,\n    add_virtual_node: bool = True,\n    add_undirected_edges: bool = True,\n    add_self_loops: bool = True,\n) -> dict[str, tf.data.Dataset]:\n  \"\"\"Returns datasets of batched GraphsTuples, organized by split.\"\"\"\n  if batch_size <= 1:\n    raise ValueError('Batch size must be > 1 to account for padding graphs.')\n\n  # Obtain the original datasets.\n  datasets = get_raw_datasets()\n\n  # Construct the GraphsTuple converter function.\n  convert_to_graphs_tuple_fn = functools.partial(\n      convert_to_graphs_tuple,\n      add_virtual_node=add_virtual_node,\n      add_undirected_edges=add_undirected_edges,\n      add_self_loops=add_self_loops,\n  )\n\n  # Process each split separately.\n  for split_name in datasets:\n    # Convert to GraphsTuple.\n    datasets[split_name] = datasets[split_name].map(\n        convert_to_graphs_tuple_fn,\n        num_parallel_calls=tf.data.AUTOTUNE,\n        deterministic=True,\n    )\n\n  # Compute the padding budget for the requested batch size.\n  budget = estimate_padding_budget_for_batch_size(\n      datasets['train'], batch_size, num_estimation_graphs=100\n  )\n\n  # Pad an example graph to see what the output shapes will be.\n  # We will use this shape information when creating the tf.data.Dataset.\n  example_graph = next(datasets['train'].as_numpy_iterator())\n  example_padded_graph = jraph.pad_with_graphs(example_graph, *budget)\n  padded_graphs_spec = specs_from_graphs_tuple(example_padded_graph)\n\n  # Process each split separately.\n  for split_name, dataset_split in datasets.items():\n    # Repeat and shuffle the training split.\n    if split_name == 'train':\n      dataset_split = dataset_split.shuffle(100, reshuffle_each_iteration=True)\n      dataset_split = dataset_split.repeat()\n\n    # Batch and pad each split.\n    batching_fn = functools.partial(\n        jraph.dynamically_batch,\n        graphs_tuple_iterator=iter(dataset_split),\n        n_node=budget.n_node,\n        n_edge=budget.n_edge,\n        n_graph=budget.n_graph,\n    )\n    dataset_split = tf.data.Dataset.from_generator(\n        batching_fn, output_signature=padded_graphs_spec\n    )\n\n    # We cache the validation and test sets, since these are small.\n    if split_name in ['validation', 'test']:\n      dataset_split = dataset_split.cache()\n\n    datasets[split_name] = dataset_split\n  return datasets\n\n\ndef convert_to_graphs_tuple(\n    graph: dict[str, tf.Tensor],\n    add_virtual_node: bool,\n    add_undirected_edges: bool,\n    add_self_loops: bool,\n) -> jraph.GraphsTuple:\n  \"\"\"Converts a dictionary of tf.Tensors to a GraphsTuple.\"\"\"\n  num_nodes = tf.squeeze(graph['num_nodes'])\n  num_edges = tf.squeeze(graph['num_edges'])\n  nodes = graph['node_feat']\n  edges = graph['edge_feat']\n  edge_feature_dim = edges.shape[-1]\n  labels = graph['labels']\n  senders = graph['edge_index'][:, 0]\n  receivers = graph['edge_index'][:, 1]\n\n  # Add a virtual node connected to all other nodes.\n  # The feature vectors for the virtual node\n  # and the new edges are set to all zeros.\n  if add_virtual_node:\n    nodes = tf.concat([nodes, tf.zeros_like(nodes[0, None])], axis=0)\n    senders = tf.concat([senders, tf.range(num_nodes)], axis=0)\n    receivers = tf.concat(\n        [receivers, tf.fill((num_nodes,), num_nodes + 1)], axis=0\n    )\n    edges = tf.concat([edges, tf.zeros((num_nodes, edge_feature_dim))], axis=0)\n    num_edges += num_nodes\n    num_nodes += 1\n\n  # Make edges undirected, by adding edges with senders and receivers flipped.\n  # The feature vector for the flipped edge is the same as the original edge.\n  if add_undirected_edges:\n    new_senders = tf.concat([senders, receivers], axis=0)\n    new_receivers = tf.concat([receivers, senders], axis=0)\n    edges = tf.concat([edges, edges], axis=0)\n    senders, receivers = new_senders, new_receivers\n    num_edges *= 2\n\n  # Add self-loops for each node.\n  # The feature vectors for the self-loops are set to all zeros.\n  if add_self_loops:\n    senders = tf.concat([senders, tf.range(num_nodes)], axis=0)\n    receivers = tf.concat([receivers, tf.range(num_nodes)], axis=0)\n    edges = tf.concat([edges, tf.zeros((num_nodes, edge_feature_dim))], axis=0)\n    num_edges += num_nodes\n\n  return jraph.GraphsTuple(\n      n_node=tf.expand_dims(num_nodes, 0),\n      n_edge=tf.expand_dims(num_edges, 0),\n      nodes=nodes,\n      edges=edges,\n      senders=senders,\n      receivers=receivers,\n      globals=tf.expand_dims(labels, axis=0),\n  )\n\n\ndef estimate_padding_budget_for_batch_size(\n    dataset: tf.data.Dataset, batch_size: int, num_estimation_graphs: int\n) -> GraphsTupleSize:\n  \"\"\"Estimates the padding budget for a dataset of unbatched GraphsTuples.\n\n  Args:\n    dataset: A dataset of unbatched GraphsTuples.\n    batch_size: The intended batch size. Note that no batching is performed by\n      this function.\n    num_estimation_graphs: How many graphs to take from the dataset to estimate\n      the distribution of number of nodes and edges per graph.\n\n  Returns:\n    padding_budget: The padding budget for batching and padding the graphs\n    in this dataset to the given batch size.\n  \"\"\"\n\n  def next_multiple_of_64(val: float):\n    \"\"\"Returns the next multiple of 64 after val.\"\"\"\n    return 64 * (1 + int(val // 64))\n\n  if batch_size <= 1:\n    raise ValueError('Batch size must be > 1 to account for padding graphs.')\n\n  total_num_nodes = 0\n  total_num_edges = 0\n  for graph in dataset.take(num_estimation_graphs).as_numpy_iterator():\n    graph_size = get_graphs_tuple_size(graph)\n    if graph_size.n_graph != 1:\n      raise ValueError('Dataset contains batched GraphTuples.')\n\n    total_num_nodes += graph_size.n_node\n    total_num_edges += graph_size.n_edge\n\n  num_nodes_per_graph_estimate = total_num_nodes / num_estimation_graphs\n  num_edges_per_graph_estimate = total_num_edges / num_estimation_graphs\n\n  padding_budget = GraphsTupleSize(\n      n_node=next_multiple_of_64(num_nodes_per_graph_estimate * batch_size),\n      n_edge=next_multiple_of_64(num_edges_per_graph_estimate * batch_size),\n      n_graph=batch_size,\n  )\n  return padding_budget\n\n\ndef specs_from_graphs_tuple(graph: jraph.GraphsTuple):\n  \"\"\"Returns a tf.TensorSpec corresponding to this graph.\"\"\"\n\n  def get_tensor_spec(array: np.ndarray):\n    shape = list(array.shape)\n    dtype = array.dtype\n    return tf.TensorSpec(shape=shape, dtype=dtype)\n\n  specs = {}\n  for field in [\n      'nodes',\n      'edges',\n      'senders',\n      'receivers',\n      'globals',\n      'n_node',\n      'n_edge',\n  ]:\n    field_sample = getattr(graph, field)\n    specs[field] = get_tensor_spec(field_sample)\n  return jraph.GraphsTuple(**specs)\n\n\ndef get_graphs_tuple_size(graph: jraph.GraphsTuple):\n  \"\"\"Returns the number of nodes, edges and graphs in a GraphsTuple.\"\"\"\n  return GraphsTupleSize(\n      n_node=np.sum(graph.n_node),\n      n_edge=np.sum(graph.n_edge),\n      n_graph=np.shape(graph.n_node)[0],\n  )\n"
  },
  {
    "path": "examples/ogbg_molpcba/input_pipeline_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.examples.ogbg_molpcba.input_pipeline.\"\"\"\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nimport input_pipeline\nimport jraph\nimport tensorflow as tf\n\n\ndef get_dummy_datasets(dataset_length: int):\n  \"\"\"Returns a set of datasets of unbatched GraphsTuples.\"\"\"\n  # The dummy graph.\n  num_nodes = 3\n  num_edges = 4\n  dummy_graph = jraph.GraphsTuple(\n      n_node=tf.expand_dims(num_nodes, 0),\n      n_edge=tf.expand_dims(num_edges, 0),\n      senders=tf.zeros(num_edges, dtype=tf.int32),\n      receivers=tf.ones(num_edges, dtype=tf.int32),\n      nodes=tf.zeros((num_nodes, 9)),\n      edges=tf.ones((num_edges, 3)),\n      globals=tf.ones((1, 128), dtype=tf.int64),\n  )\n  graphs_spec = input_pipeline.specs_from_graphs_tuple(dummy_graph)\n\n  # Yields a set of graphs for the current split.\n  def get_dummy_graphs():\n    for _ in range(dataset_length):\n      yield dummy_graph\n\n  datasets = {}\n  for split in ['train', 'validation', 'test']:\n    datasets[split] = tf.data.Dataset.from_generator(\n        get_dummy_graphs, output_signature=graphs_spec\n    )\n  return datasets\n\n\nclass InputPipelineTest(parameterized.TestCase):\n\n  def setUp(self):\n    super().setUp()\n    dataset_length = 20\n    self.datasets = get_dummy_datasets(dataset_length)\n\n  @parameterized.product(\n      valid_batch_size=[2, 5, 12, 15],\n  )\n  def test_estimate_padding_budget_valid(self, valid_batch_size):\n    budget = input_pipeline.estimate_padding_budget_for_batch_size(\n        self.datasets['train'], valid_batch_size, num_estimation_graphs=1\n    )\n    self.assertEqual(budget.n_graph, valid_batch_size)\n\n  @parameterized.product(\n      invalid_batch_size=[-1, 0, 1],\n  )\n  def test_estimate_padding_budget_invalid(self, invalid_batch_size):\n    with self.assertRaises(ValueError):\n      input_pipeline.estimate_padding_budget_for_batch_size(\n          self.datasets['train'], invalid_batch_size, num_estimation_graphs=1\n      )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/ogbg_molpcba/main.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Main file for running the ogbg-molpcba example.\n\nThis file is intentionally kept short. The majority for logic is in libraries\nthat can be easily tested and imported in Colab.\n\"\"\"\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nfrom clu import platform\nimport jax\nfrom ml_collections import config_flags\nimport tensorflow as tf\n\nimport train\n\n\nFLAGS = flags.FLAGS\n\nflags.DEFINE_string('workdir', None, 'Directory to store model data.')\nconfig_flags.DEFINE_config_file(\n    'config',\n    None,\n    'File path to the training hyperparameter configuration.',\n    lock_config=True,\n)\n\n\ndef main(argv):\n  if len(argv) > 1:\n    raise app.UsageError('Too many command-line arguments.')\n\n  # Hide any GPUs from TensorFlow. Otherwise TF might reserve memory and make\n  # it unavailable to JAX.\n  tf.config.experimental.set_visible_devices([], 'GPU')\n\n  # This example only supports single-host training on a single device.\n  logging.info('JAX host: %d / %d', jax.process_index(), jax.process_count())\n  logging.info('JAX local devices: %r', jax.local_devices())\n\n  # Add a note so that we can tell which task is which JAX host.\n  # (Depending on the platform task 0 is not guaranteed to be host 0)\n  platform.work_unit().set_task_status(\n      f'process_index: {jax.process_index()}, '\n      f'process_count: {jax.process_count()}'\n  )\n  platform.work_unit().create_artifact(\n      platform.ArtifactType.DIRECTORY, FLAGS.workdir, 'workdir'\n  )\n\n  train.train_and_evaluate(FLAGS.config, FLAGS.workdir)\n\n\nif __name__ == '__main__':\n  flags.mark_flags_as_required(['config', 'workdir'])\n  app.run(main)\n"
  },
  {
    "path": "examples/ogbg_molpcba/models.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Definition of the GNN model.\"\"\"\n\nfrom collections.abc import Callable, Sequence\n\nfrom flax import linen as nn\nimport jax.numpy as jnp\nimport jraph\n\n\ndef add_graphs_tuples(\n    graphs: jraph.GraphsTuple, other_graphs: jraph.GraphsTuple\n) -> jraph.GraphsTuple:\n  \"\"\"Adds the nodes, edges and global features from other_graphs to graphs.\"\"\"\n  return graphs._replace(\n      nodes=graphs.nodes + other_graphs.nodes,\n      edges=graphs.edges + other_graphs.edges,\n      globals=graphs.globals + other_graphs.globals,\n  )\n\n\nclass MLP(nn.Module):\n  \"\"\"A multi-layer perceptron.\"\"\"\n\n  feature_sizes: Sequence[int]\n  dropout_rate: float = 0\n  deterministic: bool = True\n  activation: Callable[[jnp.ndarray], jnp.ndarray] = nn.relu\n\n  @nn.compact\n  def __call__(self, inputs):\n    x = inputs\n    for size in self.feature_sizes:\n      x = nn.Dense(features=size)(x)\n      x = self.activation(x)\n      x = nn.Dropout(rate=self.dropout_rate, deterministic=self.deterministic)(\n          x\n      )\n    return x\n\n\nclass GraphNet(nn.Module):\n  \"\"\"A complete Graph Network model defined with Jraph.\"\"\"\n\n  latent_size: int\n  num_mlp_layers: int\n  message_passing_steps: int\n  output_globals_size: int\n  dropout_rate: float = 0\n  skip_connections: bool = True\n  use_edge_model: bool = True\n  layer_norm: bool = True\n  deterministic: bool = True\n\n  @nn.compact\n  def __call__(self, graphs: jraph.GraphsTuple) -> jraph.GraphsTuple:\n    # We will first linearly project the original features as 'embeddings'.\n    embedder = jraph.GraphMapFeatures(\n        embed_node_fn=nn.Dense(self.latent_size),\n        embed_edge_fn=nn.Dense(self.latent_size),\n        embed_global_fn=nn.Dense(self.latent_size),\n    )\n    processed_graphs = embedder(graphs)\n\n    # Now, we will apply a Graph Network once for each message-passing round.\n    mlp_feature_sizes = [self.latent_size] * self.num_mlp_layers\n    for _ in range(self.message_passing_steps):\n      if self.use_edge_model:\n        update_edge_fn = jraph.concatenated_args(\n            MLP(\n                mlp_feature_sizes,\n                dropout_rate=self.dropout_rate,\n                deterministic=self.deterministic,\n            )\n        )\n      else:\n        update_edge_fn = None\n\n      update_node_fn = jraph.concatenated_args(\n          MLP(\n              mlp_feature_sizes,\n              dropout_rate=self.dropout_rate,\n              deterministic=self.deterministic,\n          )\n      )\n      update_global_fn = jraph.concatenated_args(\n          MLP(\n              mlp_feature_sizes,\n              dropout_rate=self.dropout_rate,\n              deterministic=self.deterministic,\n          )\n      )\n\n      graph_net = jraph.GraphNetwork(\n          update_node_fn=update_node_fn,\n          update_edge_fn=update_edge_fn,\n          update_global_fn=update_global_fn,\n      )\n\n      if self.skip_connections:\n        processed_graphs = add_graphs_tuples(\n            graph_net(processed_graphs), processed_graphs\n        )\n      else:\n        processed_graphs = graph_net(processed_graphs)\n\n      if self.layer_norm:\n        processed_graphs = processed_graphs._replace(\n            nodes=nn.LayerNorm()(processed_graphs.nodes),\n            edges=nn.LayerNorm()(processed_graphs.edges),\n            globals=nn.LayerNorm()(processed_graphs.globals),\n        )\n\n    # Since our graph-level predictions will be at globals, we will\n    # decode to get the required output logits.\n    decoder = jraph.GraphMapFeatures(\n        embed_global_fn=nn.Dense(self.output_globals_size)\n    )\n    processed_graphs = decoder(processed_graphs)\n\n    return processed_graphs\n\n\nclass GraphConvNet(nn.Module):\n  \"\"\"A Graph Convolution Network + Pooling model defined with Jraph.\"\"\"\n\n  latent_size: int\n  num_mlp_layers: int\n  message_passing_steps: int\n  output_globals_size: int\n  dropout_rate: float = 0\n  skip_connections: bool = True\n  layer_norm: bool = True\n  deterministic: bool = True\n  pooling_fn: Callable[\n      [jnp.ndarray, jnp.ndarray, jnp.ndarray],  # pytype: disable=annotation-type-mismatch  # jax-ndarray\n      jnp.ndarray,\n  ] = jraph.segment_mean\n\n  def pool(self, graphs: jraph.GraphsTuple) -> jraph.GraphsTuple:\n    \"\"\"Pooling operation, taken from Jraph.\"\"\"\n\n    # Equivalent to jnp.sum(n_node), but JIT-able.\n    sum_n_node = graphs.nodes.shape[0]  # pytype: disable=attribute-error  # jax-ndarray\n    # To aggregate nodes from each graph to global features,\n    # we first construct tensors that map the node to the corresponding graph.\n    # Example: if you have `n_node=[1,2]`, we construct the tensor [0, 1, 1].\n    n_graph = graphs.n_node.shape[0]\n    node_graph_indices = jnp.repeat(\n        jnp.arange(n_graph),\n        graphs.n_node,\n        axis=0,\n        total_repeat_length=sum_n_node,\n    )\n    # We use the aggregation function to pool the nodes per graph.\n    pooled = self.pooling_fn(graphs.nodes, node_graph_indices, n_graph)  # pytype: disable=wrong-arg-types  # jax-ndarray\n    return graphs._replace(globals=pooled)\n\n  @nn.compact\n  def __call__(self, graphs: jraph.GraphsTuple) -> jraph.GraphsTuple:\n    # We will first linearly project the original node features as 'embeddings'.\n    embedder = jraph.GraphMapFeatures(embed_node_fn=nn.Dense(self.latent_size))\n    processed_graphs = embedder(graphs)\n\n    # Now, we will apply the GCN once for each message-passing round.\n    for _ in range(self.message_passing_steps):\n      mlp_feature_sizes = [self.latent_size] * self.num_mlp_layers\n      update_node_fn = jraph.concatenated_args(\n          MLP(\n              mlp_feature_sizes,\n              dropout_rate=self.dropout_rate,\n              deterministic=self.deterministic,\n          )\n      )\n      graph_conv = jraph.GraphConvolution(\n          update_node_fn=update_node_fn, add_self_edges=True\n      )\n\n      if self.skip_connections:\n        processed_graphs = add_graphs_tuples(\n            graph_conv(processed_graphs), processed_graphs\n        )\n      else:\n        processed_graphs = graph_conv(processed_graphs)\n\n      if self.layer_norm:\n        processed_graphs = processed_graphs._replace(\n            nodes=nn.LayerNorm()(processed_graphs.nodes),\n        )\n\n    # We apply the pooling operation to get a 'global' embedding.\n    processed_graphs = self.pool(processed_graphs)\n\n    # Now, we decode this to get the required output logits.\n    decoder = jraph.GraphMapFeatures(\n        embed_global_fn=nn.Dense(self.output_globals_size)\n    )\n    processed_graphs = decoder(processed_graphs)\n\n    return processed_graphs\n"
  },
  {
    "path": "examples/ogbg_molpcba/models_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.examples.ogbg_molpcba.models.\"\"\"\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nimport jax\nimport jax.numpy as jnp\nimport jraph\n\nimport models\n\n\nclass ModelsTest(parameterized.TestCase):\n\n  def setUp(self):\n    super().setUp()\n    self.rngs = {\n        'params': jax.random.key(0),\n        'dropout': jax.random.key(1),\n    }\n    n_node = jnp.arange(3, 11)\n    n_edge = jnp.arange(4, 12)\n    total_n_node = jnp.sum(n_node)\n    total_n_edge = jnp.sum(n_edge)\n    n_graph = n_node.shape[0]\n    feature_dim = 10\n    self.graphs = jraph.GraphsTuple(\n        n_node=n_node,\n        n_edge=n_edge,\n        senders=jnp.zeros(total_n_edge, dtype=jnp.int32),\n        receivers=jnp.ones(total_n_edge, dtype=jnp.int32),\n        nodes=jnp.ones((total_n_node, feature_dim)),\n        edges=jnp.zeros((total_n_edge, feature_dim)),\n        globals=jnp.zeros((n_graph, feature_dim)),\n    )\n\n  @parameterized.product(\n      dropout_rate=[0.0, 0.5, 1.0], output_size=[50, 100], num_layers=[2]\n  )\n  def test_mlp(self, dropout_rate, output_size, num_layers):\n    # Input definition.\n    nodes = self.graphs.nodes\n\n    # Model definition.\n    mlp = models.MLP(\n        feature_sizes=[output_size] * num_layers,\n        dropout_rate=dropout_rate,\n        activation=lambda x: x,\n        deterministic=False,\n    )\n    nodes_after_mlp, _ = mlp.init_with_output(self.rngs, nodes)\n\n    # Test that dropout actually worked.\n    num_masked_entries = jnp.sum(nodes_after_mlp == 0)\n    num_total_entries = jnp.size(nodes_after_mlp)\n    self.assertLessEqual(\n        num_masked_entries, (dropout_rate + 0.05) * num_total_entries\n    )\n    self.assertLessEqual(\n        (dropout_rate - 0.05) * num_total_entries, num_masked_entries\n    )\n\n    # Test the shape of the output.\n    self.assertEqual(nodes_after_mlp.shape[-1], output_size)\n\n  @parameterized.parameters(\n      {\n          'latent_size': 5,\n          'output_globals_size': 15,\n          'use_edge_model': True,\n      },\n      {\n          'latent_size': 5,\n          'output_globals_size': 15,\n          'use_edge_model': False,\n      },\n  )\n  def test_graph_net(\n      self, latent_size: int, output_globals_size: int, use_edge_model: bool\n  ):\n    # Input definition.\n    graphs = self.graphs\n    num_nodes = jnp.sum(graphs.n_node)\n    num_edges = jnp.sum(graphs.n_edge)\n    num_graphs = graphs.n_node.shape[0]\n\n    # Model definition.\n    net = models.GraphNet(\n        latent_size=latent_size,\n        num_mlp_layers=2,\n        message_passing_steps=2,\n        output_globals_size=output_globals_size,\n        use_edge_model=use_edge_model,\n    )\n    output, _ = net.init_with_output(self.rngs, graphs)\n\n    # Output should be graph with the same topology, but a\n    # different number of features.\n    self.assertIsInstance(output, jraph.GraphsTuple)\n    self.assertSequenceAlmostEqual(output.n_node, graphs.n_node)\n    self.assertSequenceAlmostEqual(output.n_edge, graphs.n_edge)\n    self.assertSequenceAlmostEqual(output.senders, graphs.senders)\n    self.assertSequenceAlmostEqual(output.receivers, graphs.receivers)\n    self.assertEqual(output.nodes.shape, (num_nodes, latent_size))\n    self.assertEqual(output.edges.shape, (num_edges, latent_size))\n    self.assertEqual(output.globals.shape, (num_graphs, output_globals_size))\n\n  @parameterized.parameters(\n      {'latent_size': 15, 'output_globals_size': 15},\n      {'latent_size': 5, 'output_globals_size': 5},\n  )\n  def test_graph_conv_net(self, latent_size: int, output_globals_size: int):\n    graphs = self.graphs\n    num_nodes = jnp.sum(graphs.n_node)\n    num_graphs = graphs.n_node.shape[0]\n\n    # Model definition.\n    net = models.GraphConvNet(\n        latent_size=latent_size,\n        num_mlp_layers=2,\n        message_passing_steps=2,\n        output_globals_size=output_globals_size,\n    )\n    output, _ = net.init_with_output(self.rngs, graphs)\n\n    # Output should be graph with the same topology, but a\n    # different number of features.\n    self.assertIsInstance(output, jraph.GraphsTuple)\n    self.assertSequenceAlmostEqual(output.n_node, graphs.n_node)\n    self.assertSequenceAlmostEqual(output.n_edge, graphs.n_edge)\n    self.assertSequenceAlmostEqual(\n        output.edges.flatten(), graphs.edges.flatten()\n    )\n    self.assertSequenceAlmostEqual(output.senders, graphs.senders)\n    self.assertSequenceAlmostEqual(output.receivers, graphs.receivers)\n    self.assertEqual(output.nodes.shape, (num_nodes, latent_size))\n    self.assertEqual(output.globals.shape, (num_graphs, output_globals_size))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/ogbg_molpcba/ogbg_molpcba.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Flax ogbg-molpcba Example\\n\",\n    \"\\n\",\n    \"<a href=\\\"https://colab.research.google.com/github/google/flax/blob/main/examples/ogbg_molpcba/ogbg_molpcba.ipynb\\\" ><img src=\\\"https://colab.research.google.com/assets/colab-badge.svg\\\" alt=\\\"Open In Colab\\\"/></a>\\n\",\n    \"\\n\",\n    \"Demonstration notebook for\\n\",\n    \"https://github.com/google/flax/tree/main/examples/ogbg_molpcba.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Setup\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"outputId\": \"6508ab2f-b0e5-4693-f6a0-7bc495ec1344\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[?25l\\r\",\n      \"\\u001b[K     |████▏                           | 10 kB 20.9 MB/s eta 0:00:01\\r\",\n      \"\\u001b[K     |████████▍                       | 20 kB 10.6 MB/s eta 0:00:01\\r\",\n      \"\\u001b[K     |████████████▋                   | 30 kB 8.8 MB/s eta 0:00:01\\r\",\n      \"\\u001b[K     |████████████████▉               | 40 kB 8.0 MB/s eta 0:00:01\\r\",\n      \"\\u001b[K     |█████████████████████           | 51 kB 5.1 MB/s eta 0:00:01\\r\",\n      \"\\u001b[K     |█████████████████████████▎      | 61 kB 5.3 MB/s eta 0:00:01\\r\",\n      \"\\u001b[K     |█████████████████████████████▌  | 71 kB 5.5 MB/s eta 0:00:01\\r\",\n      \"\\u001b[K     |████████████████████████████████| 77 kB 2.7 MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 88 kB 5.5 MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 4.0 MB 38.2 MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 73 kB 1.3 MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 118 kB 38.3 MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 57 kB 3.6 MB/s \\n\",\n      \"\\u001b[?25h  Building wheel for flax (setup.py) ... \\u001b[?25l\\u001b[?25hdone\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Install clu, ml-collections, latest Flax version, and tensorflow_datasets.\\n\",\n    \"!pip install -U -q clu ml-collections git+https://github.com/google/flax tfds_nightly jraph\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"example_directory = 'examples/ogbg_molpcba'\\n\",\n    \"editor_relpaths = ('configs/default.py', 'input_pipeline.py', 'models.py', 'train.py')\\n\",\n    \"\\n\",\n    \"repo, branch = 'https://github.com/google/flax', 'main'\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {\n    \"cellView\": \"form\",\n    \"outputId\": \"8261a349-b41e-4e1b-a2ca-8d23412155be\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Cloning into 'flaxrepo'...\\n\",\n      \"remote: Enumerating objects: 350, done.\\u001b[K\\n\",\n      \"remote: Counting objects: 100% (350/350), done.\\u001b[K\\n\",\n      \"remote: Compressing objects: 100% (312/312), done.\\u001b[K\\n\",\n      \"remote: Total 350 (delta 65), reused 140 (delta 20), pack-reused 0\\u001b[K\\n\",\n      \"Receiving objects: 100% (350/350), 2.10 MiB | 20.10 MiB/s, done.\\n\",\n      \"Resolving deltas: 100% (65/65), done.\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<h1 style=\\\"color:red;\\\" class=\\\"blink\\\">WARNING : Editing in VM - changes lost after reboot!!</h1>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/ogbg_molpcba/configs/default.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/ogbg_molpcba/input_pipeline.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/ogbg_molpcba/models.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/ogbg_molpcba/train.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# (If you run this code in Jupyter[lab], then you're already in the\\n\",\n    \"#  example directory and nothing needs to be done.)\\n\",\n    \"\\n\",\n    \"#@markdown **Fetch newest Flax version and copy of example code.**\\n\",\n    \"#@markdown\\n\",\n    \"#@markdown **If you select no** below, then the files will be stored on the\\n\",\n    \"#@markdown *ephemeral* Colab VM. **After some time of inactivity, this VM will\\n\",\n    \"#@markdown be restarted and any changes will be lost**.\\n\",\n    \"#@markdown\\n\",\n    \"#@markdown **If you select yes** below, then you will be asked for your\\n\",\n    \"#@markdown credentials to mount your personal Google Drive. In this case, all\\n\",\n    \"#@markdown changes you make will *persist*. Even if you re-run this\\n\",\n    \"#@markdown Colab notebook later on, the files will still exist. You can\\n\",\n    \"#@markdown remove directories inside your Drive's `flax/` root if you want to\\n\",\n    \"#@markdown manually revert these files.\\n\",\n    \"\\n\",\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  import os\\n\",\n    \"  os.chdir('/content')\\n\",\n    \"  # Download Flax repo from Github.\\n\",\n    \"  if not os.path.isdir('flaxrepo'):\\n\",\n    \"    !git clone --depth=1 -b $branch $repo flaxrepo\\n\",\n    \"  # Copy example files & change directory.\\n\",\n    \"  mount_gdrive = 'no' #@param ['yes', 'no']\\n\",\n    \"  if mount_gdrive == 'yes':\\n\",\n    \"    DISCLAIMER = 'Note : Editing in your Google Drive, changes will persist.'\\n\",\n    \"    from google.colab import drive\\n\",\n    \"    drive.mount('/content/gdrive')\\n\",\n    \"    example_root_path = f'/content/gdrive/My Drive/flax/{example_directory}'\\n\",\n    \"  else:\\n\",\n    \"    DISCLAIMER = 'WARNING : Editing in VM - changes lost after reboot!!'\\n\",\n    \"    example_root_path = f'/content/{example_directory}'\\n\",\n    \"    from IPython import display\\n\",\n    \"    display.display(display.HTML(\\n\",\n    \"        f'<h1 style=\\\"color:red;\\\" class=\\\"blink\\\">{DISCLAIMER}</h1>'))\\n\",\n    \"  if not os.path.isdir(example_root_path):\\n\",\n    \"    os.makedirs(example_root_path)\\n\",\n    \"    !cp -r flaxrepo/$example_directory/* \\\"$example_root_path\\\"\\n\",\n    \"  os.chdir(example_root_path)\\n\",\n    \"  from google.colab import files\\n\",\n    \"  for relpath in editor_relpaths:\\n\",\n    \"    s = open(f'{example_root_path}/{relpath}').read()\\n\",\n    \"    open(f'{example_root_path}/{relpath}', 'w').write(\\n\",\n    \"        f'## {DISCLAIMER}\\\\n' + '#' * (len(DISCLAIMER) + 3) + '\\\\n\\\\n' + s)\\n\",\n    \"    files.view(f'{example_root_path}/{relpath}')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"outputId\": \"14b17380-5077-4354-e651-027f3d933cfe\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"/content/examples/ogbg_molpcba\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Display current working directory.\\n\",\n    \"# Note: In Colab, running the above cell changes the working directory.\\n\",\n    \"!pwd\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Imports\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Base imports\\n\",\n    \"from absl import logging\\n\",\n    \"import flax\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"import jraph\\n\",\n    \"from matplotlib import pyplot as plt\\n\",\n    \"import numpy as np\\n\",\n    \"import pprint\\n\",\n    \"import tensorflow_datasets as tfds\\n\",\n    \"logging.set_verbosity(logging.INFO)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Local imports from current directory - auto reload.\\n\",\n    \"# Any changes you make to train.py and other modules below will appear automatically.\\n\",\n    \"%load_ext autoreload\\n\",\n    \"%autoreload 2\\n\",\n    \"import train\\n\",\n    \"import input_pipeline\\n\",\n    \"from configs import default as config_lib\\n\",\n    \"config = config_lib.get_config()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Dataset\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"TensorFlow Datasets supports customizable visualization of the ogbg_molpcba dataset.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Visualization helpers\\n\",\n    \"# Dictionaries used to map nodes and edges to colors.\\n\",\n    \"atomic_numbers_to_elements = {\\n\",\n    \"    6: 'C', 7: 'N', 8: 'O', 9: 'F', 14: 'Si',\\n\",\n    \"    15: 'P', 16: 'S', 17: 'Cl', 35: 'Br',\\n\",\n    \"}\\n\",\n    \"elements_to_colors = {\\n\",\n    \"    element: f'C{index}'\\n\",\n    \"    for index, element in enumerate(atomic_numbers_to_elements.values())\\n\",\n    \"}\\n\",\n    \"bond_types_to_colors = {num: f'C{num}' for num in range(4)}\\n\",\n    \"\\n\",\n    \"# Node colors are atomic numbers.\\n\",\n    \"def node_color_fn(graph):\\n\",\n    \"  atomic_numbers = 1 + graph['node_feat'][:, 0].numpy()\\n\",\n    \"  return {\\n\",\n    \"      index: elements_to_colors[atomic_numbers_to_elements[atomic_number]]\\n\",\n    \"      for index, atomic_number in enumerate(atomic_numbers)\\n\",\n    \"  }\\n\",\n    \"\\n\",\n    \"# Node labels are element names.\\n\",\n    \"def node_label_fn(graph):\\n\",\n    \"  atomic_numbers = 1 + graph['node_feat'][:, 0].numpy()\\n\",\n    \"  return {\\n\",\n    \"      index: atomic_numbers_to_elements[atomic_number]\\n\",\n    \"      for index, atomic_number in enumerate(atomic_numbers)\\n\",\n    \"  }\\n\",\n    \"\\n\",\n    \"# Edge colors are bond types.\\n\",\n    \"def edge_color_fn(graph):\\n\",\n    \"  bonds = graph['edge_index'].numpy()\\n\",\n    \"  bond_types = graph['edge_feat'][:, 0].numpy()\\n\",\n    \"  return {\\n\",\n    \"      tuple(bond): bond_types_to_colors[bond_type]\\n\",\n    \"      for bond, bond_type in zip(bonds, bond_types)\\n\",\n    \"  }\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {\n    \"outputId\": \"d9336190-e685-43e8-e3f1-73e3f1ce1cd2\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Load pre-computed DatasetInfo (eg: splits, num examples,...) from GCS: ogbg_molpcba/0.1.2\\n\",\n      \"INFO:absl:Load dataset info from /tmp/tmpmdoxgxq7tfds\\n\",\n      \"INFO:absl:Generating dataset ogbg_molpcba (/root/tensorflow_datasets/ogbg_molpcba/0.1.2)\\n\"\n     ]\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[1mDownloading and preparing dataset 37.70 MiB (download: 37.70 MiB, generated: 822.53 MiB, total: 860.23 MiB) to /root/tensorflow_datasets/ogbg_molpcba/0.1.2...\\u001b[0m\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"78bb0e82e40b4c3c937ae1f248ba1d54\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Dl Completed...: 0 url [00:00, ? url/s]\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"f933ef3ecafb4973b5c54cc4ff039ca1\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Dl Size...: 0 MiB [00:00, ? MiB/s]\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"0607a3a123334ae882e26c39c543c9e7\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Extraction completed...: 0 file [00:00, ? file/s]\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Downloading https://snap.stanford.edu/ogb/data/graphproppred/csv_mol_download/pcba.zip into /root/tensorflow_datasets/downloads/snap.stan.edu_ogb_grap_csv_mol_down_pcbapc4I82Cv1THcU-IggPHK8IHZ8qM-BJ3VDk-q_rtqrf4.zip.tmp.495be609cf3840d3a942e7f9ba5d705e...\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"83b57edf962a4faa9e875b4fecbb1eb1\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Generating splits...:   0%|          | 0/3 [00:00<?, ? splits/s]\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"3ae4475b89b74b16adce75b0861d0c0d\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Generating train examples...:   0%|          | 0/350343 [00:00<?, ? examples/s]\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"605be0ec188d4d5da5e0226e89852dbd\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Shuffling ogbg_molpcba-train.tfrecord...:   0%|          | 0/350343 [00:00<?, ? examples/s]\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Done writing ogbg_molpcba-train.tfrecord. Number of examples: 350343 (shards: [43793, 43793, 43793, 43793, 43792, 43793, 43793, 43793])\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"a090f8269d7242cf8462550443539dad\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Generating validation examples...:   0%|          | 0/43793 [00:00<?, ? examples/s]\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"269f565e9d1b4ebc806bf4b643e16567\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Shuffling ogbg_molpcba-validation.tfrecord...:   0%|          | 0/43793 [00:00<?, ? examples/s]\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Done writing ogbg_molpcba-validation.tfrecord. Number of examples: 43793 (shards: [43793])\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"2b5554f45233471ab5981bb06f664bea\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Generating test examples...:   0%|          | 0/43793 [00:00<?, ? examples/s]\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"044e1f545d014917b7edd7d3e7222d0d\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Shuffling ogbg_molpcba-test.tfrecord...:   0%|          | 0/43793 [00:00<?, ? examples/s]\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Done writing ogbg_molpcba-test.tfrecord. Number of examples: 43793 (shards: [43793])\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset ogbg_molpcba for split train, from /root/tensorflow_datasets/ogbg_molpcba/0.1.2\\n\"\n     ]\n    },\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[1mDataset ogbg_molpcba downloaded and prepared to /root/tensorflow_datasets/ogbg_molpcba/0.1.2. Subsequent calls will reuse this data.\\u001b[0m\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\",\n      \"text/plain\": [\n       \"<Figure size 1080x1080 with 9 Axes>\"\n      ]\n     },\n     \"execution_count\": 8,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\",\n      \"text/plain\": [\n       \"<Figure size 1080x1080 with 9 Axes>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Visualize examples from the training set.\\n\",\n    \"# It takes a few minutes to download & prepare the dataset.\\n\",\n    \"ds, ds_info = tfds.load('ogbg_molpcba', split='train', with_info=True)\\n\",\n    \"tfds.visualization.show_examples(ds, ds_info,\\n\",\n    \"                                 node_color_fn=node_color_fn,\\n\",\n    \"                                 node_label_fn=node_label_fn,\\n\",\n    \"                                 edge_color_fn=edge_color_fn)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Training\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Start TensorBoard\\n\",\n    \"# Get a live update during training - use the \\\"refresh\\\" button!\\n\",\n    \"# (In Jupyter[lab] start \\\"tensorboard\\\" in the local directory instead.)\\n\",\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  %load_ext tensorboard\\n\",\n    \"  %tensorboard --logdir=.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {\n    \"outputId\": \"7696aae4-beb5-4df2-b72a-7f391ac30c2e\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Unable to initialize backend 'tpu_driver': NOT_FOUND: Unable to find driver in registry given worker: \\n\",\n      \"INFO:absl:Unable to initialize backend 'tpu': INVALID_ARGUMENT: TpuPlatform is not available.\\n\",\n      \"INFO:absl:Hyperparameters: {'add_self_loops': True, 'add_undirected_edges': True, 'add_virtual_node': False, 'batch_size': 256, 'checkpoint_every_steps': 10000, 'dropout_rate': 0.1, 'eval_every_steps': 500, 'latent_size': 256, 'layer_norm': True, 'learning_rate': 0.001, 'log_every_steps': 500, 'message_passing_steps': 5, 'model': 'GraphConvNet', 'num_classes': 128, 'num_mlp_layers': 2, 'num_train_steps': 1000, 'optimizer': 'adam', 'skip_connections': True}\\n\",\n      \"INFO:absl:Obtaining datasets.\\n\",\n      \"INFO:absl:Load dataset info from /root/tensorflow_datasets/ogbg_molpcba/0.1.2\\n\",\n      \"INFO:absl:Reusing dataset ogbg_molpcba (/root/tensorflow_datasets/ogbg_molpcba/0.1.2)\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset ogbg_molpcba for split train, from /root/tensorflow_datasets/ogbg_molpcba/0.1.2\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset ogbg_molpcba for split validation, from /root/tensorflow_datasets/ogbg_molpcba/0.1.2\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset ogbg_molpcba for split test, from /root/tensorflow_datasets/ogbg_molpcba/0.1.2\\n\",\n      \"INFO:absl:Initializing network.\\n\",\n      \"INFO:absl:\\n\",\n      \"+-----------------------------+------------+--------+-----------+--------+\\n\",\n      \"| Name                        | Shape      | Size   | Mean      | Std    |\\n\",\n      \"+-----------------------------+------------+--------+-----------+--------+\\n\",\n      \"| params/Dense_0/bias         | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/Dense_0/kernel       | (9, 256)   | 2,304  | -0.000941 | 0.335  |\\n\",\n      \"| params/Dense_1/bias         | (128,)     | 128    | 0.0       | 0.0    |\\n\",\n      \"| params/Dense_1/kernel       | (256, 128) | 32,768 | -0.000737 | 0.0623 |\\n\",\n      \"| params/LayerNorm_0/bias     | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/LayerNorm_0/scale    | (256,)     | 256    | 1.0       | 0.0    |\\n\",\n      \"| params/LayerNorm_1/bias     | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/LayerNorm_1/scale    | (256,)     | 256    | 1.0       | 0.0    |\\n\",\n      \"| params/LayerNorm_2/bias     | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/LayerNorm_2/scale    | (256,)     | 256    | 1.0       | 0.0    |\\n\",\n      \"| params/LayerNorm_3/bias     | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/LayerNorm_3/scale    | (256,)     | 256    | 1.0       | 0.0    |\\n\",\n      \"| params/LayerNorm_4/bias     | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/LayerNorm_4/scale    | (256,)     | 256    | 1.0       | 0.0    |\\n\",\n      \"| params/MLP_0/Dense_0/bias   | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/MLP_0/Dense_0/kernel | (256, 256) | 65,536 | -0.000616 | 0.0625 |\\n\",\n      \"| params/MLP_0/Dense_1/bias   | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/MLP_0/Dense_1/kernel | (256, 256) | 65,536 | -0.000287 | 0.0626 |\\n\",\n      \"| params/MLP_1/Dense_0/bias   | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/MLP_1/Dense_0/kernel | (256, 256) | 65,536 | 0.000217  | 0.0628 |\\n\",\n      \"| params/MLP_1/Dense_1/bias   | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/MLP_1/Dense_1/kernel | (256, 256) | 65,536 | -0.00055  | 0.0625 |\\n\",\n      \"| params/MLP_2/Dense_0/bias   | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/MLP_2/Dense_0/kernel | (256, 256) | 65,536 | 0.000274  | 0.0625 |\\n\",\n      \"| params/MLP_2/Dense_1/bias   | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/MLP_2/Dense_1/kernel | (256, 256) | 65,536 | -0.000227 | 0.0626 |\\n\",\n      \"| params/MLP_3/Dense_0/bias   | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/MLP_3/Dense_0/kernel | (256, 256) | 65,536 | -0.000224 | 0.0625 |\\n\",\n      \"| params/MLP_3/Dense_1/bias   | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/MLP_3/Dense_1/kernel | (256, 256) | 65,536 | 1.42e-05  | 0.0627 |\\n\",\n      \"| params/MLP_4/Dense_0/bias   | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/MLP_4/Dense_0/kernel | (256, 256) | 65,536 | 0.000319  | 0.0623 |\\n\",\n      \"| params/MLP_4/Dense_1/bias   | (256,)     | 256    | 0.0       | 0.0    |\\n\",\n      \"| params/MLP_4/Dense_1/kernel | (256, 256) | 65,536 | -0.000191 | 0.0624 |\\n\",\n      \"+-----------------------------+------------+--------+-----------+--------+\\n\",\n      \"Total: 695,936\\n\",\n      \"INFO:absl:Checkpoint.restore_or_initialize() ...\\n\",\n      \"INFO:absl:No checkpoint specified. Restore the latest checkpoint.\\n\",\n      \"INFO:absl:Checkpoint None does not exist.\\n\",\n      \"INFO:absl:Checkpoint.save() ...\\n\",\n      \"INFO:absl:Checkpoint.save() finished after 0.09s.\\n\",\n      \"INFO:absl:Checkpoint.restore_or_initialize() finished after 0.10s.\\n\",\n      \"INFO:absl:Starting training.\\n\",\n      \"INFO:absl:Finished training step 1.\\n\",\n      \"INFO:absl:Finished training step 2.\\n\",\n      \"INFO:absl:Finished training step 3.\\n\",\n      \"INFO:absl:Finished training step 4.\\n\",\n      \"INFO:absl:Finished training step 5.\\n\",\n      \"INFO:absl:Finished training step 6.\\n\",\n      \"INFO:absl:Finished training step 7.\\n\",\n      \"INFO:absl:Finished training step 8.\\n\",\n      \"INFO:absl:Finished training step 9.\\n\",\n      \"INFO:absl:Finished training step 10.\\n\",\n      \"INFO:absl:Created artifact [10] Profile of type ArtifactType.URL and value None.\\n\",\n      \"INFO:absl:Setting work unit notes: 2.0 steps/s, 12.4% (124/1000), ETA: 7m\\n\",\n      \"INFO:absl:[124] steps_per_sec=2.048078\\n\",\n      \"INFO:absl:Setting work unit notes: 2.1 steps/s, 25.2% (252/1000), ETA: 5m\\n\",\n      \"INFO:absl:[252] steps_per_sec=2.127820\\n\",\n      \"INFO:absl:Setting work unit notes: 2.1 steps/s, 38.0% (380/1000), ETA: 4m\\n\",\n      \"INFO:absl:[380] steps_per_sec=2.116415\\n\",\n      \"INFO:absl:[500] train_accuracy=0.9849439859390259, train_loss=0.06188433617353439\\n\",\n      \"INFO:absl:[500] validation_accuracy=0.9839196801185608, validation_loss=0.06475622951984406, validation_mean_average_precision=0.036894\\n\",\n      \"INFO:absl:[500] test_accuracy=0.9832062125205994, test_loss=0.06711383163928986, test_mean_average_precision=0.037820\\n\",\n      \"INFO:absl:Setting work unit notes: 0.4 steps/s, 50.1% (501/1000), ETA: 23m (8m : 31.9% eval)\\n\",\n      \"INFO:absl:[501] steps_per_sec=0.351547\\n\",\n      \"INFO:absl:Setting work unit notes: 2.1 steps/s, 62.8% (628/1000), ETA: 2m (9m : 28.7% eval)\\n\",\n      \"INFO:absl:[628] steps_per_sec=2.109877\\n\",\n      \"INFO:absl:Setting work unit notes: 2.1 steps/s, 75.6% (756/1000), ETA: 1m (10m : 26.1% eval)\\n\",\n      \"INFO:absl:[756] steps_per_sec=2.128443\\n\",\n      \"INFO:absl:Setting work unit notes: 2.1 steps/s, 88.4% (884/1000), ETA: 0m (11m : 23.9% eval)\\n\",\n      \"INFO:absl:[884] steps_per_sec=2.122990\\n\",\n      \"INFO:absl:[999] train_accuracy=0.9868282079696655, train_loss=0.052991833537817\\n\",\n      \"INFO:absl:[999] validation_accuracy=0.984052836894989, validation_loss=0.06297025829553604, validation_mean_average_precision=0.049514\\n\",\n      \"INFO:absl:Checkpoint.save() ...\\n\",\n      \"INFO:absl:[999] test_accuracy=0.9833315014839172, test_loss=0.06533924490213394, test_mean_average_precision=0.050113\\n\",\n      \"INFO:absl:Checkpoint.save() finished after 0.06s.\\n\",\n      \"INFO:absl:Setting work unit notes: 1.7 steps/s, 100.0% (1000/1000), ETA: 0m (13m : 0.0% checkpoint, 22.5% eval)\\n\",\n      \"INFO:absl:[1000] steps_per_sec=1.697496\\n\",\n      \"INFO:absl:[1000] train_accuracy=0.9897193908691406, train_loss=0.04319273680448532\\n\",\n      \"INFO:absl:[1000] validation_accuracy=0.9840479493141174, validation_loss=0.0629250779747963, validation_mean_average_precision=0.049538\\n\",\n      \"INFO:absl:[1000] test_accuracy=0.9833057522773743, test_loss=0.06529100239276886, test_mean_average_precision=0.050135\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Training loop\\n\",\n    \"\\n\",\n    \"# Use a Colab GPU runtime to speed up training.\\n\",\n    \"# We don't use TPUs in this Colab because we do not distribute our\\n\",\n    \"# training using pmap() - if you're looking for an example using TPUs\\n\",\n    \"# checkout the below Colab notebook:\\n\",\n    \"# https://colab.research.google.com/github/google/flax/blob/main/examples/imagenet/imagenet.ipynb\\n\",\n    \"\\n\",\n    \"config.num_train_steps = 1000\\n\",\n    \"config.log_every_steps = 500\\n\",\n    \"config.eval_every_steps = 500\\n\",\n    \"\\n\",\n    \"# Construct the model and start the main training loop.\\n\",\n    \"# The default config corresponds to a 5-layer Graph Convolutional Model\\n\",\n    \"# with skip-connections and mean-pooling.\\n\",\n    \"state = train.train_and_evaluate(config, workdir=f'./models')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {\n    \"cellView\": \"form\"\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"#@title Upload to TensorBoard.dev\\n\",\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  #@markdown You can upload the training results directly to [TensorBoard.dev](https://tensorboard.dev).\\n\",\n    \"  #@markdown\\n\",\n    \"  #@markdown Note that anyone with the link will be able to see the data.\\n\",\n    \"  upload_data = 'no' #@param ['yes', 'no']\\n\",\n    \"  if upload_data == 'yes':\\n\",\n    \"    !tensorboard dev upload --one_shot --logdir ./models --name 'Flax examples/ogbg_molpcba'\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Inference\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Create deterministic evaluation model.\\n\",\n    \"eval_net = train.create_model(config, deterministic=True)\\n\",\n    \"eval_state = state.replace(apply_fn=eval_net.apply)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {\n    \"outputId\": \"51242dc2-5260-4279-b41a-9d7614b33c97\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Load dataset info from /root/tensorflow_datasets/ogbg_molpcba/0.1.2\\n\",\n      \"INFO:absl:Reusing dataset ogbg_molpcba (/root/tensorflow_datasets/ogbg_molpcba/0.1.2)\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset ogbg_molpcba for split train, from /root/tensorflow_datasets/ogbg_molpcba/0.1.2\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset ogbg_molpcba for split validation, from /root/tensorflow_datasets/ogbg_molpcba/0.1.2\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset ogbg_molpcba for split test, from /root/tensorflow_datasets/ogbg_molpcba/0.1.2\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Compute accuracy and mean average precision on validation and test sets.\\n\",\n    \"datasets = input_pipeline.get_datasets(\\n\",\n    \"    config.batch_size,\\n\",\n    \"    add_virtual_node=config.add_virtual_node,\\n\",\n    \"    add_undirected_edges=config.add_undirected_edges,\\n\",\n    \"    add_self_loops=config.add_self_loops)\\n\",\n    \"eval_metrics = train.evaluate_model(eval_state, datasets,\\n\",\n    \"                                    splits=['validation', 'test'])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {\n    \"outputId\": \"2a529c9f-fed6-4221-a738-a8ffd9049d7e\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"validation\\n\",\n      \"- accuracy: 0.984\\n\",\n      \"- loss: 0.063\\n\",\n      \"- mean_average_precision: 0.050\\n\",\n      \"test\\n\",\n      \"- accuracy: 0.983\\n\",\n      \"- loss: 0.065\\n\",\n      \"- mean_average_precision: 0.050\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"for split in ['validation', 'test']:\\n\",\n    \"  split_metrics = eval_metrics[split].compute()\\n\",\n    \"  print(split)\\n\",\n    \"  for metric_name, metric in split_metrics.items():\\n\",\n    \"    print(f'- {metric_name}: {metric:.3f}')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Helper functions for formatting labels and predictions.\\n\",\n    \"def get_formatted_label_for_task(labels, task):\\n\",\n    \"  label_for_task = labels[task]\\n\",\n    \"  if np.isnan(label_for_task):\\n\",\n    \"    return 'Unknown'\\n\",\n    \"  elif label_for_task == 0:\\n\",\n    \"    return 'Inactive'\\n\",\n    \"  elif label_for_task == 1:\\n\",\n    \"    return 'Active'\\n\",\n    \"  raise ValueError('Invalid label.')\\n\",\n    \"\\n\",\n    \"# Predictions are computed with a threshold of 0 for the logits.\\n\",\n    \"# This is the same threshold used to compute the accuracy.\\n\",\n    \"def get_formatted_prediction_for_task(logits, task):\\n\",\n    \"  predictions = logits > 0\\n\",\n    \"  prediction_for_task = predictions[task]\\n\",\n    \"  if prediction_for_task == 0:\\n\",\n    \"    return 'Inactive'\\n\",\n    \"  elif prediction_for_task == 1:\\n\",\n    \"    return 'Active'\\n\",\n    \"  raise ValueError('Invalid prediction.')\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can choose one of the 128 different tasks and see how the model predictions\\n\",\n    \"match up with the true labels.\\n\",\n    \"\\n\",\n    \"You can change this visualization to use any of the 128 tasks in this dataset, by changing the `task` variable below.  See the appendix [here](https://arxiv.org/pdf/1502.02072.pdf) to understand what these tasks mean.\\n\",\n    \"\\n\",\n    \"The default is task PCBA-686978 (indexed at 93).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Define which task to plot labels for.\\n\",\n    \"task = 93\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 17,\n   \"metadata\": {\n    \"outputId\": \"38934096-13a6-4701-823a-ac83f3b7eaac\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:Load dataset info from /root/tensorflow_datasets/ogbg_molpcba/0.1.2\\n\",\n      \"INFO:absl:Reusing dataset ogbg_molpcba (/root/tensorflow_datasets/ogbg_molpcba/0.1.2)\\n\",\n      \"INFO:absl:Constructing tf.data.Dataset ogbg_molpcba for split test, from /root/tensorflow_datasets/ogbg_molpcba/0.1.2\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\",\n      \"text/plain\": [\n       \"<Figure size 1080x3600 with 30 Axes>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# Take a batch of graphs from the test set, and compute predictions.\\n\",\n    \"graphs = next(datasets['test'].take(1).as_numpy_iterator())\\n\",\n    \"labels = graphs.globals\\n\",\n    \"logits = train.get_predicted_logits(eval_state, graphs, rngs=None)\\n\",\n    \"graphs = jraph.unbatch(graphs)\\n\",\n    \"\\n\",\n    \"# Create plot to visualize individual molecules.\\n\",\n    \"ds, ds_info = tfds.load('ogbg_molpcba', split='test', with_info=True)\\n\",\n    \"fig = tfds.visualization.show_examples(ds, ds_info,\\n\",\n    \"                                       node_color_fn=node_color_fn,\\n\",\n    \"                                       node_label_fn=node_label_fn,\\n\",\n    \"                                       edge_color_fn=edge_color_fn,\\n\",\n    \"                                       rows=10)\\n\",\n    \"\\n\",\n    \"# Update plot titles with true and predicted labels.\\n\",\n    \"for graph, graph_labels, graph_logits, ax in zip(graphs, labels, logits, fig.axes):\\n\",\n    \"  label_for_task = get_formatted_label_for_task(graph_labels, task)\\n\",\n    \"  predicted_label_for_task = get_formatted_prediction_for_task(graph_logits, task)\\n\",\n    \"  ax.set_title(f'True Label: {label_for_task}\\\\n'\\n\",\n    \"               f'Predicted Label: {predicted_label_for_task}')\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"accelerator\": \"GPU\",\n  \"language_info\": {\n   \"name\": \"python\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "examples/ogbg_molpcba/ogbg_molpcba_benchmark.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Benchmark for the ogbg_molpcba example.\"\"\"\n\nimport time\n\nfrom absl import flags\nfrom absl.testing import absltest\nfrom flax.testing import Benchmark\nimport jax\nimport numpy as np\n\nimport main\nfrom configs import default\nfrom configs import test\n\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\nFLAGS = flags.FLAGS\n\n\nclass OgbgMolpcbaBenchmark(Benchmark):\n  \"\"\"Benchmarks for the ogbg_molpcba Flax example.\"\"\"\n\n  def test_1x_v100(self):\n    \"\"\"Run training with default config for ogbg_molpcba on a v100 GPU.\"\"\"\n    workdir = self.get_tmp_model_dir()\n    config = default.get_config()\n\n    FLAGS.workdir = workdir\n    FLAGS.config = config\n\n    start_time = time.time()\n    main.main([])\n    benchmark_time = time.time() - start_time\n\n    summaries = self.read_summaries(workdir)\n\n    # Summaries contain all the information necessary for\n    # the regression metrics.\n    wall_time, _, test_accuracy = zip(*summaries['test_accuracy'])\n    wall_time = np.array(wall_time)\n    sec_per_epoch = np.mean(wall_time[1:] - wall_time[:-1])\n    end_test_accuracy = test_accuracy[-1]\n\n    _, _, test_aps = zip(*summaries['test_mean_average_precision'])\n    end_test_mean_average_precision = test_aps[-1]\n\n    _, _, validation_accuracy = zip(*summaries['validation_accuracy'])\n    end_validation_accuracy = validation_accuracy[-1]\n\n    _, _, validation_aps = zip(*summaries['validation_mean_average_precision'])\n    end_validation_mean_average_precision = validation_aps[-1]\n\n    # Assertions are deferred until the test finishes, so the metrics are\n    # always reported and benchmark success is determined based on *all*\n    # assertions.\n    self.assertGreaterEqual(end_test_mean_average_precision, 0.24)\n    self.assertGreaterEqual(end_validation_mean_average_precision, 0.25)\n\n    # Use the reporting API to report single or multiple metrics/extras.\n    self.report_wall_time(benchmark_time)\n    self.report_metrics({\n        'sec_per_epoch': sec_per_epoch,\n        'test_accuracy': end_test_accuracy,\n        'test_mean_average_precision': end_test_mean_average_precision,\n        'validation_accuracy': end_validation_accuracy,\n        'validation_mean_average_precision': (\n            end_validation_mean_average_precision\n        ),\n    })\n    self.report_extras({\n        'model_name': 'Graph Convolutional Network',\n        'description': 'GPU (1x V100) test for ogbg_molpcba.',\n        'implementation': 'linen',\n    })\n\n  def test_cpu(self):\n    \"\"\"Run training with test config for ogbg_molpcba on CPU.\"\"\"\n    workdir = self.get_tmp_model_dir()\n    config = test.get_config()\n\n    FLAGS.workdir = workdir\n    FLAGS.config = config\n\n    start_time = time.time()\n    main.main([])\n    benchmark_time = time.time() - start_time\n\n    summaries = self.read_summaries(workdir)\n\n    # Summaries contain all the information necessary for\n    # the regression metrics.\n    wall_time, _, test_accuracy = zip(*summaries['test_accuracy'])\n    wall_time = np.array(wall_time)\n    sec_per_epoch = np.mean(wall_time[1:] - wall_time[:-1])\n    end_test_accuracy = test_accuracy[-1]\n\n    _, _, test_aps = zip(*summaries['test_mean_average_precision'])\n    end_test_mean_average_precision = test_aps[-1]\n\n    _, _, validation_accuracy = zip(*summaries['validation_accuracy'])\n    end_validation_accuracy = validation_accuracy[-1]\n\n    _, _, validation_aps = zip(*summaries['validation_mean_average_precision'])\n    end_validation_mean_average_precision = validation_aps[-1]\n\n    # Use the reporting API to report single or multiple metrics/extras.\n    self.report_wall_time(benchmark_time)\n    self.report_metrics({\n        'sec_per_epoch': sec_per_epoch,\n        'test_accuracy': end_test_accuracy,\n        'test_mean_average_precision': end_test_mean_average_precision,\n        'validation_accuracy': end_validation_accuracy,\n        'validation_mean_average_precision': (\n            end_validation_mean_average_precision\n        ),\n    })\n    self.report_extras({\n        'model_name': 'Graph Convolutional Network',\n        'description': 'CPU test for ogbg_molpcba.',\n        'implementation': 'linen',\n    })\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/ogbg_molpcba/requirements.txt",
    "content": "absl-py==1.0.0\nclu==0.0.6\nflax==0.4.1\njax==0.3.4\n--find-links https://storage.googleapis.com/jax-releases/jax_releases.html\njaxlib==0.3.2+cuda11.cudnn82  # Make sure CUDA version matches the base image.\njraph==0.0.2.dev0\nml-collections==0.1.0\nnumpy==1.22.0\noptax==0.1.0\nsklearn==0.0\ntensorflow==2.11.1\ntensorflow-datasets==4.4.0\n"
  },
  {
    "path": "examples/ogbg_molpcba/train.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Library file for executing training and evaluation on ogbg-molpcba.\"\"\"\n\nimport os\nfrom typing import Any, Dict, Tuple, Optional\nfrom collections.abc import Iterable\n\nfrom absl import logging\nfrom clu import checkpoint\nfrom clu import metric_writers\nfrom clu import metrics\nfrom clu import parameter_overview\nfrom clu import periodic_actions\nimport flax\nimport flax.core\nimport flax.linen as nn\nfrom flax.training import train_state\nimport jax\nimport jax.numpy as jnp\nimport jraph\nimport ml_collections\nimport numpy as np\nimport optax\nimport sklearn.metrics\nimport tensorflow as tf\n\nimport input_pipeline\nimport models\n\n\ndef create_model(\n    config: ml_collections.ConfigDict, deterministic: bool\n) -> nn.Module:\n  \"\"\"Creates a Flax model, as specified by the config.\"\"\"\n  if config.model == 'GraphNet':\n    return models.GraphNet(\n        latent_size=config.latent_size,\n        num_mlp_layers=config.num_mlp_layers,\n        message_passing_steps=config.message_passing_steps,\n        output_globals_size=config.num_classes,\n        dropout_rate=config.dropout_rate,\n        skip_connections=config.skip_connections,\n        layer_norm=config.layer_norm,\n        use_edge_model=config.use_edge_model,\n        deterministic=deterministic,\n    )\n  if config.model == 'GraphConvNet':\n    return models.GraphConvNet(\n        latent_size=config.latent_size,\n        num_mlp_layers=config.num_mlp_layers,\n        message_passing_steps=config.message_passing_steps,\n        output_globals_size=config.num_classes,\n        dropout_rate=config.dropout_rate,\n        skip_connections=config.skip_connections,\n        layer_norm=config.layer_norm,\n        deterministic=deterministic,\n    )\n  raise ValueError(f'Unsupported model: {config.model}.')\n\n\ndef create_optimizer(\n    config: ml_collections.ConfigDict,\n) -> optax.GradientTransformation:\n  \"\"\"Creates an optimizer, as specified by the config.\"\"\"\n  if config.optimizer == 'adam':\n    return optax.adam(learning_rate=config.learning_rate)\n  if config.optimizer == 'sgd':\n    return optax.sgd(\n        learning_rate=config.learning_rate, momentum=config.momentum\n    )\n  raise ValueError(f'Unsupported optimizer: {config.optimizer}.')\n\n\ndef binary_cross_entropy_with_mask(\n    *, logits: jnp.ndarray, labels: jnp.ndarray, mask: jnp.ndarray\n):\n  \"\"\"Binary cross entropy loss for unnormalized logits, with masked elements.\"\"\"\n  assert logits.shape == labels.shape == mask.shape\n  assert len(logits.shape) == 2\n\n  # To prevent propagation of NaNs during grad().\n  # We mask over the loss for invalid targets later.\n  labels = jnp.where(mask, labels, -1)\n\n  # Numerically stable implementation of BCE loss.\n  # This mimics TensorFlow's tf.nn.sigmoid_cross_entropy_with_logits().\n  positive_logits = logits >= 0\n  relu_logits = jnp.where(positive_logits, logits, 0)\n  abs_logits = jnp.where(positive_logits, logits, -logits)\n  return relu_logits - (logits * labels) + (jnp.log(1 + jnp.exp(-abs_logits)))\n\n\ndef predictions_match_labels(\n    *, logits: jnp.ndarray, labels: jnp.ndarray, **kwargs\n) -> jnp.ndarray:\n  \"\"\"Returns a binary array indicating where predictions match the labels.\"\"\"\n  del kwargs  # Unused.\n  preds = logits > 0\n  return (preds == labels).astype(jnp.float32)\n\n\ndef add_prefix_to_keys(result: dict[str, Any], prefix: str) -> dict[str, Any]:\n  \"\"\"Adds a prefix to the keys of a dict, returning a new dict.\"\"\"\n  return {f'{prefix}_{key}': val for key, val in result.items()}\n\n\n@flax.struct.dataclass\nclass MeanAveragePrecision(\n    metrics.CollectingMetric.from_outputs(('labels', 'logits', 'mask'))\n):\n  \"\"\"Computes the mean average precision (mAP) over different tasks.\"\"\"\n\n  def compute(self):\n    # Matches the official OGB evaluation scheme for mean average precision.\n    values = super().compute()\n    labels = values['labels']\n    logits = values['logits']\n    mask = values['mask']\n\n    assert logits.shape == labels.shape == mask.shape\n    assert len(logits.shape) == 2\n\n    probs = jax.nn.sigmoid(logits)\n    num_tasks = labels.shape[1]\n    average_precisions = np.full(num_tasks, np.nan)\n\n    for task in range(num_tasks):\n      # AP is only defined when there is at least one negative data\n      # and at least one positive data.\n      is_labeled = mask[:, task]\n      if len(np.unique(labels[is_labeled, task])) >= 2:\n        average_precisions[task] = sklearn.metrics.average_precision_score(\n            labels[is_labeled, task], probs[is_labeled, task]\n        )\n\n    # When all APs are NaNs, return NaN. This avoids raising a RuntimeWarning.\n    if np.isnan(average_precisions).all():\n      return np.nan\n    return np.nanmean(average_precisions)\n\n\n@flax.struct.dataclass\nclass EvalMetrics(metrics.Collection):\n  accuracy: metrics.Average.from_fun(predictions_match_labels)\n  loss: metrics.Average.from_output('loss')\n  mean_average_precision: MeanAveragePrecision\n\n\n@flax.struct.dataclass\nclass TrainMetrics(metrics.Collection):\n  accuracy: metrics.Average.from_fun(predictions_match_labels)\n  loss: metrics.Average.from_output('loss')\n\n\ndef replace_globals(graphs: jraph.GraphsTuple) -> jraph.GraphsTuple:\n  \"\"\"Replaces the globals attribute with a constant feature for each graph.\"\"\"\n  return graphs._replace(globals=jnp.ones([graphs.n_node.shape[0], 1]))\n\n\ndef get_predicted_logits(\n    state: train_state.TrainState,\n    graphs: jraph.GraphsTuple,\n    rngs: dict[str, jnp.ndarray] | None,\n) -> jnp.ndarray:\n  \"\"\"Get predicted logits from the network for input graphs.\"\"\"\n  pred_graphs = state.apply_fn(state.params, graphs, rngs=rngs)\n  logits = pred_graphs.globals\n  return logits\n\n\ndef get_valid_mask(\n    labels: jnp.ndarray, graphs: jraph.GraphsTuple\n) -> jnp.ndarray:\n  \"\"\"Gets the binary mask indicating only valid labels and graphs.\"\"\"\n  # We have to ignore all NaN values - which indicate labels for which\n  # the current graphs have no label.\n  labels_mask = ~jnp.isnan(labels)\n\n  # Since we have extra 'dummy' graphs in our batch due to padding, we want\n  # to mask out any loss associated with the dummy graphs.\n  # Since we padded with `pad_with_graphs` we can recover the mask by using\n  # get_graph_padding_mask.\n  graph_mask = jraph.get_graph_padding_mask(graphs)\n\n  # Combine the mask over labels with the mask over graphs.\n  return labels_mask & graph_mask[:, None]\n\n\n@jax.jit\ndef train_step(\n    state: train_state.TrainState,\n    graphs: jraph.GraphsTuple,\n    rngs: dict[str, jnp.ndarray],\n) -> tuple[train_state.TrainState, metrics.Collection]:\n  \"\"\"Performs one update step over the current batch of graphs.\"\"\"\n\n  def loss_fn(params, graphs):\n    curr_state = state.replace(params=params)\n\n    # Extract labels.\n    labels = graphs.globals\n\n    # Replace the global feature for graph classification.\n    graphs = replace_globals(graphs)\n\n    # Compute logits and resulting loss.\n    logits = get_predicted_logits(curr_state, graphs, rngs)\n    mask = get_valid_mask(labels, graphs)\n    loss = binary_cross_entropy_with_mask(\n        logits=logits, labels=labels, mask=mask\n    )\n    mean_loss = jnp.sum(jnp.where(mask, loss, 0)) / jnp.sum(mask)\n\n    return mean_loss, (loss, logits, labels, mask)\n\n  grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n  (_, (loss, logits, labels, mask)), grads = grad_fn(state.params, graphs)\n  state = state.apply_gradients(grads=grads)\n\n  metrics_update = TrainMetrics.single_from_model_output(\n      loss=loss, logits=logits, labels=labels, mask=mask\n  )\n  return state, metrics_update\n\n\n@jax.jit\ndef evaluate_step(\n    state: train_state.TrainState,\n    graphs: jraph.GraphsTuple,\n) -> metrics.Collection:\n  \"\"\"Computes metrics over a set of graphs.\"\"\"\n\n  # The target labels our model has to predict.\n  labels = graphs.globals\n\n  # Replace the global feature for graph classification.\n  graphs = replace_globals(graphs)\n\n  # Get predicted logits, and corresponding probabilities.\n  logits = get_predicted_logits(state, graphs, rngs=None)\n\n  # Get the mask for valid labels and graphs.\n  mask = get_valid_mask(labels, graphs)\n\n  # Compute the various metrics.\n  loss = binary_cross_entropy_with_mask(logits=logits, labels=labels, mask=mask)\n\n  return EvalMetrics.single_from_model_output(\n      loss=loss, logits=logits, labels=labels, mask=mask\n  )\n\n\ndef evaluate_model(\n    state: train_state.TrainState,\n    datasets: dict[str, tf.data.Dataset],\n    splits: Iterable[str],\n) -> dict[str, metrics.Collection]:\n  \"\"\"Evaluates the model on metrics over the specified splits.\"\"\"\n\n  # Loop over each split independently.\n  eval_metrics = {}\n  for split in splits:\n    split_metrics = None\n\n    # Loop over graphs.\n    for graphs in datasets[split].as_numpy_iterator():\n      split_metrics_update = evaluate_step(state, graphs)\n\n      # Update metrics.\n      if split_metrics is None:\n        split_metrics = split_metrics_update\n      else:\n        split_metrics = split_metrics.merge(split_metrics_update)\n    eval_metrics[split] = split_metrics\n\n  return eval_metrics  # pytype: disable=bad-return-type\n\n\ndef train_and_evaluate(\n    config: ml_collections.ConfigDict, workdir: str\n) -> train_state.TrainState:\n  \"\"\"Execute model training and evaluation loop.\n\n  Args:\n    config: Hyperparameter configuration for training and evaluation.\n    workdir: Directory where the TensorBoard summaries are written to.\n\n  Returns:\n    The train state (which includes the `.params`).\n  \"\"\"\n  # We only support single-host training.\n  assert jax.process_count() == 1\n\n  # Create writer for logs.\n  writer = metric_writers.create_default_writer(workdir)\n  writer.write_hparams(config.to_dict())\n\n  # Get datasets, organized by split.\n  logging.info('Obtaining datasets.')\n  datasets = input_pipeline.get_datasets(\n      config.batch_size,\n      add_virtual_node=config.add_virtual_node,\n      add_undirected_edges=config.add_undirected_edges,\n      add_self_loops=config.add_self_loops,\n  )\n  train_iter = iter(datasets['train'])\n\n  # Create and initialize the network.\n  logging.info('Initializing network.')\n  rng = jax.random.key(0)\n  rng, init_rng = jax.random.split(rng)\n  init_graphs = next(datasets['train'].as_numpy_iterator())\n  init_graphs = replace_globals(init_graphs)\n  init_net = create_model(config, deterministic=True)\n  params = jax.jit(init_net.init)(init_rng, init_graphs)\n  parameter_overview.log_parameter_overview(params)\n\n  # Create the optimizer.\n  tx = create_optimizer(config)\n\n  # Create the training state.\n  net = create_model(config, deterministic=False)\n  state = train_state.TrainState.create(\n      apply_fn=net.apply, params=params, tx=tx\n  )\n\n  # Set up checkpointing of the model.\n  # The input pipeline cannot be checkpointed in its current form,\n  # due to the use of stateful operations.\n  checkpoint_dir = os.path.join(workdir, 'checkpoints')\n  ckpt = checkpoint.Checkpoint(checkpoint_dir, max_to_keep=2)\n  state = ckpt.restore_or_initialize(state)\n  initial_step = int(state.step) + 1\n\n  # Create the evaluation state, corresponding to a deterministic model.\n  eval_net = create_model(config, deterministic=True)\n  eval_state = state.replace(apply_fn=eval_net.apply)\n\n  # Hooks called periodically during training.\n  report_progress = periodic_actions.ReportProgress(\n      num_train_steps=config.num_train_steps, writer=writer\n  )\n  profiler = periodic_actions.Profile(num_profile_steps=5, logdir=workdir)\n  hooks = [report_progress, profiler]\n\n  # Begin training loop.\n  logging.info('Starting training.')\n  train_metrics = None\n  for step in range(initial_step, config.num_train_steps + 1):\n    # Split PRNG key, to ensure different 'randomness' for every step.\n    rng, dropout_rng = jax.random.split(rng)\n\n    # Perform one step of training.\n    with jax.profiler.StepTraceAnnotation('train', step_num=step):\n      graphs = jax.tree_util.tree_map(np.asarray, next(train_iter))\n      state, metrics_update = train_step(\n          state, graphs, rngs={'dropout': dropout_rng}\n      )\n\n      # Update metrics.\n      if train_metrics is None:\n        train_metrics = metrics_update\n      else:\n        train_metrics = train_metrics.merge(metrics_update)\n\n    # Quick indication that training is happening.\n    logging.log_first_n(logging.INFO, 'Finished training step %d.', 10, step)\n    for hook in hooks:\n      hook(step)\n\n    # Log, if required.\n    is_last_step = step == config.num_train_steps - 1\n    if step % config.log_every_steps == 0 or is_last_step:\n      writer.write_scalars(\n          step, add_prefix_to_keys(train_metrics.compute(), 'train')\n      )\n      train_metrics = None\n\n    # Evaluate on validation and test splits, if required.\n    if step % config.eval_every_steps == 0 or is_last_step:\n      eval_state = eval_state.replace(params=state.params)\n\n      splits = ['validation', 'test']\n      with report_progress.timed('eval'):\n        eval_metrics = evaluate_model(eval_state, datasets, splits=splits)\n      for split in splits:\n        writer.write_scalars(\n            step, add_prefix_to_keys(eval_metrics[split].compute(), split)\n        )\n\n    # Checkpoint model, if required.\n    if step % config.checkpoint_every_steps == 0 or is_last_step:\n      with report_progress.timed('checkpoint'):\n        ckpt.save(state)\n\n  return state\n"
  },
  {
    "path": "examples/ogbg_molpcba/train_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.examples.ogbg_molpcba.train.\"\"\"\n\nimport functools\nimport pathlib\nimport tempfile\nfrom typing import Dict, Optional\nimport warnings\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nimport flax\nfrom flax.training import train_state\nimport jax\nfrom jax import numpy as jnp\nimport jraph\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\nimport numpy as np\n\nfrom configs import default\nfrom configs import test\nimport input_pipeline\nimport train\n\n\ndef average_with_mask(arr: jnp.ndarray, mask: jnp.ndarray):\n  \"\"\"Returns the average over elements where mask is True.\"\"\"\n  arr = jnp.where(mask, arr, 0)\n  return jnp.sum(arr) / jnp.sum(mask)\n\n\ndef get_dummy_raw_datasets(dataset_length) -> dict[str, tf.data.Dataset]:\n  \"\"\"Returns dummy datasets, mocking tfds.DatasetBuilder.as_dataset().\"\"\"\n\n  # The dummy graph.\n  num_nodes = 3\n  num_edges = 4\n  dummy_graph = {\n      'edge_feat': tf.zeros((num_edges, 3), dtype=tf.float32),\n      'edge_index': tf.zeros((num_edges, 2), dtype=tf.int64),\n      'labels': tf.ones((128,), dtype=tf.float32),\n      'node_feat': tf.zeros((num_nodes, 9), dtype=tf.float32),\n      'num_edges': tf.expand_dims(num_edges, axis=0),\n      'num_nodes': tf.expand_dims(num_nodes, axis=0),\n  }\n  dummy_graph_spec = {\n      'edge_feat': tf.TensorSpec(shape=(None, 3), dtype=tf.float32),\n      'edge_index': tf.TensorSpec(shape=(None, 2), dtype=tf.int64),\n      'labels': tf.TensorSpec(shape=(128,), dtype=tf.float32),\n      'node_feat': tf.TensorSpec(shape=(None, 9), dtype=tf.float32),\n      'num_edges': tf.TensorSpec(shape=(None,), dtype=tf.int64),\n      'num_nodes': tf.TensorSpec(shape=(None,), dtype=tf.int64),\n  }\n\n  def get_dummy_graphs():\n    for _ in range(dataset_length):\n      yield dummy_graph\n\n  datasets = {}\n  for split in ['train', 'validation', 'test']:\n    datasets[split] = tf.data.Dataset.from_generator(\n        get_dummy_graphs, output_signature=dummy_graph_spec\n    )\n  return datasets\n\n\ndef get_dummy_datasets(\n    dataset_length: int, batch_size: int | None = None\n) -> dict[str, tf.data.Dataset]:\n  \"\"\"Returns dummy datasets, mocking input_pipeline.get_datasets().\"\"\"\n\n  datasets = get_dummy_raw_datasets(dataset_length)\n\n  # Construct the GraphsTuple converter function.\n  convert_to_graphs_tuple_fn = functools.partial(\n      input_pipeline.convert_to_graphs_tuple,\n      add_virtual_node=True,\n      add_undirected_edges=True,\n      add_self_loops=True,\n  )\n\n  # Process each split separately.\n  for split_name in datasets:\n    # Convert to GraphsTuple.\n    datasets[split_name] = datasets[split_name].map(\n        convert_to_graphs_tuple_fn,\n        num_parallel_calls=tf.data.AUTOTUNE,\n        deterministic=True,\n    )\n\n  # If batch size is None, do not batch.\n  if batch_size is not None:\n    budget = input_pipeline.estimate_padding_budget_for_batch_size(\n        datasets['train'], batch_size, num_estimation_graphs=1\n    )\n\n    # Pad an example graph to see what the output shapes will be.\n    # We will use this shape information when creating the tf.data.Dataset.\n    example_graph = next(datasets['train'].as_numpy_iterator())\n    example_padded_graph = jraph.pad_with_graphs(example_graph, *budget)\n    padded_graphs_spec = input_pipeline.specs_from_graphs_tuple(\n        example_padded_graph\n    )\n\n    # Batch and pad each split separately.\n    for split, dataset_split in datasets.items():\n      batching_fn = functools.partial(\n          jraph.dynamically_batch,\n          graphs_tuple_iterator=iter(dataset_split),\n          n_node=budget.n_node,\n          n_edge=budget.n_edge,\n          n_graph=budget.n_graph,\n      )\n      datasets[split] = tf.data.Dataset.from_generator(\n          batching_fn, output_signature=padded_graphs_spec\n      )\n  return datasets\n\n\nclass OgbgMolpcbaTrainTest(parameterized.TestCase):\n\n  def setUp(self):\n    super().setUp()\n    # Make sure tf does not allocate gpu memory.\n    tf.config.experimental.set_visible_devices([], 'GPU')\n\n    # Print the current platform (the default device).\n    platform = jax.local_devices()[0].platform\n    print('Running on platform:', platform.upper())\n\n    # Create PRNG keys.\n    self.rng = jax.random.key(0)\n\n    # Create dummy datasets.\n    self.datasets = get_dummy_datasets(dataset_length=20, batch_size=10)\n    self.raw_datasets = get_dummy_raw_datasets(dataset_length=20)\n\n  @parameterized.product(\n      probs=[[[0.8, 0.9, 0.3, 0.5]]],\n      labels=[\n          [[1, 0, 1, 1]],\n          [[1, 0, 1, jnp.nan]],\n          [[1, 0, jnp.nan, jnp.nan]],\n          [[1, jnp.nan, jnp.nan, jnp.nan]],\n      ],\n  )\n  def test_binary_cross_entropy_loss(self, probs, labels):\n    probs = jnp.asarray(probs)\n    labels = jnp.asarray(labels)\n\n    logits = jnp.log(probs / (1 - probs))\n    mask = ~jnp.isnan(labels)\n\n    loss_array = train.binary_cross_entropy_with_mask(\n        logits=logits, labels=labels, mask=mask\n    )\n    loss = average_with_mask(loss_array, mask)\n    expected_loss_array = -(jnp.log(probs) * labels) - (\n        jnp.log(1 - probs) * (1 - labels)\n    )\n    expected_loss = average_with_mask(expected_loss_array, mask)\n\n    self.assertAlmostEqual(loss, expected_loss, places=5)\n\n  @parameterized.named_parameters(\n      dict(\n          testcase_name='no_valid_tasks',\n          logits=[[-1.0, 1.0], [1.0, 1.0], [2.0, -1.0]],\n          labels=[[jnp.nan, jnp.nan], [jnp.nan, jnp.nan], [jnp.nan, jnp.nan]],\n          expected_result=jnp.nan,\n      ),\n      dict(\n          testcase_name='1_valid_task',\n          logits=[[-1.0, 1.0], [1.0, 1.0], [2.0, -1.0]],\n          labels=[[0, jnp.nan], [1, jnp.nan], [1, jnp.nan]],\n          expected_result=1.0,\n      ),\n      dict(\n          testcase_name='2_valid_tasks',\n          logits=[[-1.0, 1.0], [1.0, 1.0], [2.0, -1.0]],\n          labels=[[0, jnp.nan], [1, 0], [1, 1]],\n          expected_result=0.75,\n      ),\n  )\n  def test_mean_average_precision(self, logits, labels, expected_result):\n    logits = jnp.asarray(logits)\n    labels = jnp.asarray(labels)\n    mask = ~jnp.isnan(labels)\n\n    mean_average_precision = train.MeanAveragePrecision.from_model_output(\n        logits=logits, labels=labels, mask=mask\n    ).compute()\n\n    if jnp.isnan(expected_result):\n      self.assertTrue(jnp.isnan(mean_average_precision))\n    else:\n      self.assertAlmostEqual(expected_result, mean_average_precision)\n\n  @parameterized.parameters(\n      dict(\n          loss=[[0.5, 1.0], [1.5, 1.3], [2.0, 1.2]],\n          logits=[[-1.0, 1.0], [1.0, 1.0], [2.0, 0.0]],\n          labels=[[0, jnp.nan], [1, 0], [0, 1]],\n          mask=[[True, False], [True, True], [False, False]],\n          expected_results={\n              'loss': 1.1,\n              'accuracy': 2 / 3,\n              'mean_average_precision': 1.0,\n          },\n      ),\n  )\n  def test_eval_metrics(self, loss, logits, labels, mask, expected_results):\n    loss = jnp.asarray(loss)\n    logits = jnp.asarray(logits)\n    labels = jnp.asarray(labels)\n    mask = jnp.asarray(mask)\n\n    # Ignore RuntimeWarning caused by MeanAveragePrecision calculation.\n    with warnings.catch_warnings():\n      warnings.simplefilter('ignore', category=RuntimeWarning)\n      eval_metrics = train.EvalMetrics.single_from_model_output(\n          loss=loss, logits=logits, labels=labels, mask=mask\n      ).compute()\n\n    for metric in expected_results:\n      self.assertAlmostEqual(expected_results[metric], eval_metrics[metric])\n\n  @parameterized.parameters(\n      dict(\n          loss=[[0.5, 1.0], [1.5, 1.3], [2.0, 1.2]],\n          logits=[[-1.0, 1.0], [1.0, 1.0], [2.0, 0.0]],\n          labels=[[0, jnp.nan], [1, 0], [0, 1]],\n          mask=[[True, False], [True, True], [False, False]],\n          expected_results={'loss': 1.1, 'accuracy': 2 / 3},\n      ),\n  )\n  def test_train_metrics(self, loss, logits, labels, mask, expected_results):\n    loss = jnp.asarray(loss)\n    logits = jnp.asarray(logits)\n    labels = jnp.asarray(labels)\n    mask = jnp.asarray(mask)\n\n    train_metrics = train.TrainMetrics.single_from_model_output(\n        loss=loss, logits=logits, labels=labels, mask=mask\n    ).compute()\n    for metric in expected_results:\n      self.assertAlmostEqual(expected_results[metric], train_metrics[metric])\n\n  def test_train_step(self):\n    # Get the default configuration.\n    config = default.get_config()\n\n    # Initialize the network with a dummy graph.\n    rng, init_rng = jax.random.split(self.rng)\n    init_graphs = next(self.datasets['train'].as_numpy_iterator())\n    init_graphs_preprocessed = train.replace_globals(init_graphs)\n    init_net = train.create_model(config, deterministic=True)\n    params = jax.jit(init_net.init)(init_rng, init_graphs_preprocessed)\n\n    # Create the optimizer.\n    optimizer = train.create_optimizer(config)\n\n    # Create the training state.\n    net = train.create_model(config, deterministic=False)\n    state = train_state.TrainState.create(\n        apply_fn=net.apply, params=params, tx=optimizer\n    )\n\n    # Perform one step of updates.\n    # We use the same batch of graphs that we used for initialization.\n    state, train_metrics = train.train_step(\n        state, init_graphs, rngs={'dropout': rng}\n    )\n\n    # Check that none of the parameters are NaNs!\n    params = flax.core.unfreeze(state.params)\n    flat_params = {\n        '/'.join(k): v\n        for k, v in flax.traverse_util.flatten_dict(params).items()\n    }\n    for array in flat_params.values():\n      self.assertTrue(jnp.all(~jnp.isnan(array)))\n\n    # Check that the metrics are well defined.\n    train_metrics_vals = train_metrics.compute()\n    self.assertGreaterEqual(train_metrics_vals['loss'], 0)\n    self.assertGreaterEqual(train_metrics_vals['accuracy'], 0)\n    self.assertLessEqual(train_metrics_vals['accuracy'], 1)\n\n  def test_evaluate_step(self):\n    # Get the default configuration.\n    config = default.get_config()\n\n    # Initialize the network with a dummy graph.\n    _, init_rng = jax.random.split(self.rng)\n    init_graphs = next(self.datasets['train'].as_numpy_iterator())\n    init_graphs_preprocessed = init_graphs._replace(\n        globals=jnp.zeros([init_graphs.n_node.shape[0], 1])\n    )\n    init_net = train.create_model(config, deterministic=True)\n    params = jax.jit(init_net.init)(init_rng, init_graphs_preprocessed)\n\n    # Create the optimizer.\n    optimizer = train.create_optimizer(config)\n\n    # Create the evaluation state.\n    eval_net = train.create_model(config, deterministic=True)\n    eval_state = train_state.TrainState.create(\n        apply_fn=eval_net.apply, params=params, tx=optimizer\n    )\n\n    # Perform one step of evaluation.\n    # We use the same batch of graphs that we used for initialization.\n    evaluate_metrics = train.evaluate_step(eval_state, init_graphs)\n\n    # Check that the metrics are well defined.\n    evaluate_metrics_vals = evaluate_metrics.compute()\n    self.assertGreaterEqual(evaluate_metrics_vals['loss'], 0)\n    self.assertGreaterEqual(evaluate_metrics_vals['accuracy'], 0)\n    self.assertLessEqual(evaluate_metrics_vals['accuracy'], 1)\n    self.assertTrue(np.isnan(evaluate_metrics_vals['mean_average_precision']))\n\n  def test_train_and_evaluate(self):\n    # Create a temporary directory where TensorBoard metrics are written.\n    workdir = tempfile.mkdtemp()\n\n    # Go two directories up to the root of the flax directory.\n    flax_root_dir = pathlib.Path(__file__).parents[2]\n    data_dir = str(flax_root_dir) + '/.tfds/metadata'  # pylint: disable=unused-variable\n\n    # Get the test configuration.\n    config = test.get_config()\n\n    # Ensure train_and_evaluate() runs without any errors!\n    def as_dataset_fn(*args, **kwargs):\n      del args\n      split = kwargs['split']\n      return self.raw_datasets[split]\n\n    with tfds.testing.mock_data(as_dataset_fn=as_dataset_fn):\n      train.train_and_evaluate(config=config, workdir=workdir)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/ppo/README.md",
    "content": "# Proximal Policy Optimization\n\nUses the Proximal Policy Optimization algorithm ([Schulman et al., 2017](https://arxiv.org/abs/1707.06347))\nto learn playing Atari games.\n\n## Requirements\n\nThis example depends on the `gymnasium[atari,accept-rom-license]`, `opencv-python` packages\nin addition to `jax` and `flax`.\n\n## Supported setups\n\nThe example should run with other configurations and hardware, but was explicitly\ntested on the following:\n\n| Hardware | Game | Training time | Total frames seen | TensorBoard.dev |\n| --- | --- | --- | --- | --- |\n| 1x V100 GPU  | Breakout  |  9h 15m 15s | 40M | [2020-10-02](https://tensorboard.dev/experiment/pY7D2qYQQLO9ZT5lA9PFPA) |\n\n> **Note**\n> It is possible to improve training efficiency through further optimizations. For example, CleanRL's PPO ([ppo_atari_envpool_xla_jax_scan.py](https://docs.cleanrl.dev/rl-algorithms/ppo/#ppo_atari_envpool_xla_jax_scanpy)) can achieve the same level of results in just 30 minutes with an RTX 2080 TI, 8 CPU, and the same hyperparameters — **a 1850% speedup end-to-end**. It achieves this by using [EnvPool](https://envpool.readthedocs.io/en/latest/), a library for fast parallelizing environments, jitting the entire rollout through [EnvPool's XLA interface](https://envpool.readthedocs.io/en/latest/content/xla_interface.html), storing data more efficiently, and `jax.scan`.\n\n## How to run\n\nRunning `python ppo_main.py` will run the example with default\n(hyper)parameters, i.e. for 40M frames on the Pong game.\n\nBy default logging info and checkpoints will be stored in `/tmp/ppo_training`\ndirectory. This can be overridden as follows:\n\n```python ppo_main.py --config=configs/default.py --workdir=/my_fav_directory```\n\nYou can also override the default (hyper)parameters, for example\n\n```python ppo_main.py --config=configs/default.py --config.game=Seaquest --config.total_frames=20000000 --config.decaying_lr_and_clip_param=False --workdir=/tmp/seaquest```\n\nwill train the model on 20M Seaquest frames with constant (i.e. not linearly\ndecaying) learning rate and PPO clipping parameter. Checkpoints and tensorboard\nfiles will be saved in `/tmp/seaquest`.\n\nUnit tests can be run using `python ppo_lib_test.py`.\n\n## How to run on Google Cloud TPU\n\nIt is also possible to run this code on Google Cloud TPU. For detailed\ninstructions on the required setup, please refer to the [WMT example readme](https://github.com/google/flax/tree/main/examples/wmt).\n\n## Owners\n\nJonathan Heek @jheek, Wojciech Rzadkowski @wrzadkow\n"
  },
  {
    "path": "examples/ppo/agent.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Agent utilities, incl. choosing the move and running in separate process.\"\"\"\n\nimport collections\nimport functools\nimport multiprocessing\nfrom typing import Any\nfrom collections.abc import Callable\n\nimport flax\nimport jax\nimport numpy as np\n\nimport env_utils\n\n\n@functools.partial(jax.jit, static_argnums=0)\ndef policy_action(\n    apply_fn: Callable[..., Any],\n    params: flax.core.frozen_dict.FrozenDict,\n    state: np.ndarray,\n):\n  \"\"\"Forward pass of the network.\n\n  Args:\n    params: the parameters of the actor-critic model\n    module: the actor-critic model\n    state: the input for the forward pass\n\n  Returns:\n    out: a tuple (log_probabilities, values)\n  \"\"\"\n  out = apply_fn({'params': params}, state)\n  return out\n\n\nExpTuple = collections.namedtuple(\n    'ExpTuple', ['state', 'action', 'reward', 'value', 'log_prob', 'done']\n)\n\n\nclass RemoteSimulator:\n  \"\"\"Wrap functionality for an agent emulating Atari in a separate process.\n\n  An object of this class is created for every agent.\n  \"\"\"\n\n  def __init__(self, game: str):\n    \"\"\"Start the remote process and create Pipe() to communicate with it.\"\"\"\n    parent_conn, child_conn = multiprocessing.Pipe()\n    self.proc = multiprocessing.Process(\n        target=rcv_action_send_exp, args=(child_conn, game)\n    )\n    self.proc.daemon = True\n    self.conn = parent_conn\n    self.proc.start()\n\n\ndef rcv_action_send_exp(conn, game: str):\n  \"\"\"Run the remote agents.\n\n  Receive action from the main learner, perform one step of simulation and\n  send back collected experience.\n  \"\"\"\n  env = env_utils.create_env(game, clip_rewards=True)\n  while True:\n    obs = env.reset()\n    done = False\n    # Observations fetched from Atari env need additional batch dimension.\n    state = obs[None, ...]\n    while not done:\n      conn.send(state)\n      action = conn.recv()\n      obs, reward, done, _ = env.step(action)\n      next_state = obs[None, ...] if not done else None\n      experience = (state, action, reward, done)\n      conn.send(experience)\n      if done:\n        break\n      state = next_state\n"
  },
  {
    "path": "examples/ppo/configs/default.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Definitions of default hyperparameters.\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the default configuration.\n\n  The default hyperparameters originate from PPO paper arXiv:1707.06347\n  and openAI baselines 2::\n  https://github.com/openai/baselines/blob/master/baselines/ppo2/defaults.py\n  \"\"\"\n  config = ml_collections.ConfigDict()\n  # The Atari game used.\n  config.game = 'Pong'\n  # Total number of frames seen during training.\n  config.total_frames = 40000000\n  # The learning rate for the Adam optimizer.\n  config.learning_rate = 2.5e-4\n  # Batch size used in training.\n  config.batch_size = 256\n  # Number of agents playing in parallel.\n  config.num_agents = 8\n  # Number of steps each agent performs in one policy unroll.\n  config.actor_steps = 128\n  # Number of training epochs per each unroll of the policy.\n  config.num_epochs = 3\n  # RL discount parameter.\n  config.gamma = 0.99\n  # Generalized Advantage Estimation parameter.\n  config.lambda_ = 0.95\n  # The PPO clipping parameter used to clamp ratios in loss function.\n  config.clip_param = 0.1\n  # Weight of value function loss in the total loss.\n  config.vf_coeff = 0.5\n  # Weight of entropy bonus in the total loss.\n  config.entropy_coeff = 0.01\n  # Linearly decay learning rate and clipping parameter to zero during\n  # the training.\n  config.decaying_lr_and_clip_param = True\n  return config\n"
  },
  {
    "path": "examples/ppo/env_utils.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Utilities for handling the Atari environment.\"\"\"\n\nimport collections\n\nimport gymnasium as gym\nimport numpy as np\n\nimport seed_rl_atari_preprocessing\n\n\nclass ClipRewardEnv(gym.RewardWrapper):\n  \"\"\"Adapted from OpenAI baselines.\n\n  github.com/openai/baselines/blob/master/baselines/common/atari_wrappers.py\n  \"\"\"\n\n  def __init__(self, env):\n    gym.RewardWrapper.__init__(self, env)\n\n  def reward(self, reward):\n    \"\"\"Bin reward to {+1, 0, -1} by its sign.\"\"\"\n    return np.sign(reward)\n\n\nclass FrameStack:\n  \"\"\"Implements stacking of `num_frames` last frames of the game.\n\n  Wraps an AtariPreprocessing object.\n  \"\"\"\n\n  def __init__(\n      self,\n      preproc: seed_rl_atari_preprocessing.AtariPreprocessing,\n      num_frames: int,\n  ):\n    self.preproc = preproc\n    self.num_frames = num_frames\n    self.frames = collections.deque(maxlen=num_frames)\n\n  def reset(self):\n    ob = self.preproc.reset()\n    for _ in range(self.num_frames):\n      self.frames.append(ob)\n    return self._get_array()\n\n  def step(self, action: int):\n    ob, reward, done, info = self.preproc.step(action)\n    self.frames.append(ob)\n    return self._get_array(), reward, done, info\n\n  def _get_array(self):\n    assert len(self.frames) == self.num_frames\n    return np.concatenate(self.frames, axis=-1)\n\n\ndef create_env(game: str, clip_rewards: bool):\n  \"\"\"Create a FrameStack object that serves as environment for the `game`.\"\"\"\n  env = gym.make(game)\n  if clip_rewards:\n    env = ClipRewardEnv(env)  # bin rewards to {-1., 0., 1.}\n  preproc = seed_rl_atari_preprocessing.AtariPreprocessing(env)\n  stack = FrameStack(preproc, num_frames=4)\n  return stack\n\n\ndef get_num_actions(game: str):\n  \"\"\"Get the number of possible actions of a given Atari game.\n\n  This determines the number of outputs in the actor part of the\n  actor-critic model.\n  \"\"\"\n  env = gym.make(game)\n  return env.action_space.n\n"
  },
  {
    "path": "examples/ppo/models.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Class and functions to define and initialize the actor-critic model.\"\"\"\n\nfrom flax import linen as nn\nimport jax.numpy as jnp\n\n\nclass ActorCritic(nn.Module):\n  \"\"\"Class defining the actor-critic model.\"\"\"\n\n  num_outputs: int\n\n  @nn.compact\n  def __call__(self, x):\n    \"\"\"Define the convolutional network architecture.\n\n    Architecture originates from \"Human-level control through deep reinforcement\n    learning.\", Nature 518, no. 7540 (2015): 529-533.\n    Note that this is different than the one from  \"Playing atari with deep\n    reinforcement learning.\" arxiv.org/abs/1312.5602 (2013)\n\n    Network is used to both estimate policy (logits) and expected state value;\n    in other words, hidden layers' params are shared between policy and value\n    networks, see e.g.:\n    github.com/openai/baselines/blob/master/baselines/ppo1/cnn_policy.py\n    \"\"\"\n    dtype = jnp.float32\n    x = x.astype(dtype) / 255.0\n    x = nn.Conv(\n        features=32,\n        kernel_size=(8, 8),\n        strides=(4, 4),\n        name='conv1',\n        dtype=dtype,\n    )(x)\n    x = nn.relu(x)\n    x = nn.Conv(\n        features=64,\n        kernel_size=(4, 4),\n        strides=(2, 2),\n        name='conv2',\n        dtype=dtype,\n    )(x)\n    x = nn.relu(x)\n    x = nn.Conv(\n        features=64,\n        kernel_size=(3, 3),\n        strides=(1, 1),\n        name='conv3',\n        dtype=dtype,\n    )(x)\n    x = nn.relu(x)\n    x = x.reshape((x.shape[0], -1))  # flatten\n    x = nn.Dense(features=512, name='hidden', dtype=dtype)(x)\n    x = nn.relu(x)\n    logits = nn.Dense(features=self.num_outputs, name='logits', dtype=dtype)(x)\n    policy_log_probabilities = nn.log_softmax(logits)\n    value = nn.Dense(features=1, name='value', dtype=dtype)(x)\n    return policy_log_probabilities, value\n"
  },
  {
    "path": "examples/ppo/ppo_lib.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Library file which executes the PPO training.\"\"\"\n\nimport functools\nfrom typing import Any\nfrom collections.abc import Callable\n\nfrom absl import logging\nimport flax\nfrom flax import linen as nn\nimport agent\nimport models\nimport test_episodes\nfrom flax.metrics import tensorboard\nfrom flax.training import checkpoints\nfrom flax.training import train_state\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\nimport numpy as np\nimport optax\n\n\n@jax.jit\n@functools.partial(jax.vmap, in_axes=(1, 1, 1, None, None), out_axes=1)\ndef gae_advantages(\n    rewards: np.ndarray,\n    terminal_masks: np.ndarray,\n    values: np.ndarray,\n    discount: float,\n    gae_param: float,\n):\n  \"\"\"Use Generalized Advantage Estimation (GAE) to compute advantages.\n\n  As defined by eqs. (11-12) in PPO paper arXiv: 1707.06347. Implementation uses\n  key observation that A_{t} = delta_t + gamma*lambda*A_{t+1}.\n\n  Args:\n    rewards: array shaped (actor_steps, num_agents), rewards from the game\n    terminal_masks: array shaped (actor_steps, num_agents), zeros for terminal\n                    and ones for non-terminal states\n    values: array shaped (actor_steps, num_agents), values estimated by critic\n    discount: RL discount usually denoted with gamma\n    gae_param: GAE parameter usually denoted with lambda\n\n  Returns:\n    advantages: calculated advantages shaped (actor_steps, num_agents)\n  \"\"\"\n  assert rewards.shape[0] + 1 == values.shape[0], (\n      'One more value needed; Eq. '\n      '(12) in PPO paper requires '\n      'V(s_{t+1}) for delta_t'\n  )\n  advantages = []\n  gae = 0.0\n  for t in reversed(range(len(rewards))):\n    # Masks used to set next state value to 0 for terminal states.\n    value_diff = discount * values[t + 1] * terminal_masks[t] - values[t]\n    delta = rewards[t] + value_diff\n    # Masks[t] used to ensure that values before and after a terminal state\n    # are independent of each other.\n    gae = delta + discount * gae_param * terminal_masks[t] * gae\n    advantages.append(gae)\n  advantages = advantages[::-1]\n  return jnp.array(advantages)\n\n\ndef loss_fn(\n    params: flax.core.FrozenDict,\n    apply_fn: Callable[..., Any],\n    minibatch: tuple,\n    clip_param: float,\n    vf_coeff: float,\n    entropy_coeff: float,\n):\n  \"\"\"Evaluate the loss function.\n\n  Compute loss as a sum of three components: the negative of the PPO clipped\n  surrogate objective, the value function loss and the negative of the entropy\n  bonus.\n\n  Args:\n    params: the parameters of the actor-critic model\n    apply_fn: the actor-critic model's apply function\n    minibatch: tuple of five elements forming one experience batch:\n               states: shape (batch_size, 84, 84, 4)\n               actions: shape (batch_size, 84, 84, 4)\n               old_log_probs: shape (batch_size,)\n               returns: shape (batch_size,)\n               advantages: shape (batch_size,)\n    clip_param: the PPO clipping parameter used to clamp ratios in loss function\n    vf_coeff: weighs value function loss in total loss\n    entropy_coeff: weighs entropy bonus in the total loss\n\n  Returns:\n    loss: the PPO loss, scalar quantity\n  \"\"\"\n  states, actions, old_log_probs, returns, advantages = minibatch\n  log_probs, values = agent.policy_action(apply_fn, params, states)\n  values = values[:, 0]  # Convert shapes: (batch, 1) to (batch, ).\n  probs = jnp.exp(log_probs)\n\n  value_loss = jnp.mean(jnp.square(returns - values), axis=0)\n\n  entropy = jnp.sum(-probs * log_probs, axis=1).mean()\n\n  log_probs_act_taken = jax.vmap(lambda lp, a: lp[a])(log_probs, actions)\n  ratios = jnp.exp(log_probs_act_taken - old_log_probs)\n  # Advantage normalization (following the OpenAI baselines).\n  advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)\n  pg_loss = ratios * advantages\n  clipped_loss = advantages * jax.lax.clamp(\n      1.0 - clip_param, ratios, 1.0 + clip_param\n  )\n  ppo_loss = -jnp.mean(jnp.minimum(pg_loss, clipped_loss), axis=0)\n\n  return ppo_loss + vf_coeff * value_loss - entropy_coeff * entropy\n\n\n@functools.partial(jax.jit, static_argnums=(2,))\ndef train_step(\n    state: train_state.TrainState,\n    trajectories: tuple,\n    batch_size: int,\n    *,\n    clip_param: float,\n    vf_coeff: float,\n    entropy_coeff: float,\n):\n  \"\"\"Compilable train step.\n\n  Runs an entire epoch of training (i.e. the loop over minibatches within\n  an epoch is included here for performance reasons).\n\n  Args:\n    state: the train state\n    trajectories: tuple of the following five elements forming the experience:\n                  states: shape (steps_per_agent*num_agents, 84, 84, 4)\n                  actions: shape (steps_per_agent*num_agents, 84, 84, 4)\n                  old_log_probs: shape (steps_per_agent*num_agents, )\n                  returns: shape (steps_per_agent*num_agents, )\n                  advantages: (steps_per_agent*num_agents, )\n    batch_size: the minibatch size, static argument\n    clip_param: the PPO clipping parameter used to clamp ratios in loss function\n    vf_coeff: weighs value function loss in total loss\n    entropy_coeff: weighs entropy bonus in the total loss\n\n  Returns:\n    optimizer: new optimizer after the parameters update\n    loss: loss summed over training steps\n  \"\"\"\n  iterations = trajectories[0].shape[0] // batch_size\n  trajectories = jax.tree_util.tree_map(\n      lambda x: x.reshape((iterations, batch_size) + x.shape[1:]), trajectories\n  )\n  loss = 0.0\n  for batch in zip(*trajectories):\n    grad_fn = jax.value_and_grad(loss_fn)\n    l, grads = grad_fn(\n        state.params, state.apply_fn, batch, clip_param, vf_coeff, entropy_coeff\n    )\n    loss += l\n    state = state.apply_gradients(grads=grads)\n  return state, loss\n\n\ndef get_experience(\n    state: train_state.TrainState,\n    simulators: list[agent.RemoteSimulator],\n    steps_per_actor: int,\n):\n  \"\"\"Collect experience from agents.\n\n  Runs `steps_per_actor` time steps of the game for each of the `simulators`.\n  \"\"\"\n  all_experience = []\n  # Range up to steps_per_actor + 1 to get one more value needed for GAE.\n  for _ in range(steps_per_actor + 1):\n    sim_states = []\n    for sim in simulators:\n      sim_state = sim.conn.recv()\n      sim_states.append(sim_state)\n    sim_states = np.concatenate(sim_states, axis=0)\n    log_probs, values = agent.policy_action(\n        state.apply_fn, state.params, sim_states\n    )\n    log_probs, values = jax.device_get((log_probs, values))\n    probs = np.exp(np.array(log_probs))\n    for i, sim in enumerate(simulators):\n      probabilities = probs[i]\n      action = np.random.choice(probs.shape[1], p=probabilities)\n      sim.conn.send(action)\n    experiences = []\n    for i, sim in enumerate(simulators):\n      sim_state, action, reward, done = sim.conn.recv()\n      value = values[i, 0]\n      log_prob = log_probs[i][action]\n      sample = agent.ExpTuple(sim_state, action, reward, value, log_prob, done)\n      experiences.append(sample)\n    all_experience.append(experiences)\n  return all_experience\n\n\ndef process_experience(\n    experience: list[list[agent.ExpTuple]],\n    actor_steps: int,\n    num_agents: int,\n    gamma: float,\n    lambda_: float,\n):\n  \"\"\"Process experience for training, including advantage estimation.\n\n  Args:\n    experience: collected from agents in the form of nested lists/namedtuple\n    actor_steps: number of steps each agent has completed\n    num_agents: number of agents that collected experience\n    gamma: dicount parameter\n    lambda_: GAE parameter\n\n  Returns:\n    trajectories: trajectories readily accessible for `train_step()` function\n  \"\"\"\n  obs_shape = (84, 84, 4)\n  exp_dims = (actor_steps, num_agents)\n  values_dims = (actor_steps + 1, num_agents)\n  states = np.zeros(exp_dims + obs_shape, dtype=np.float32)\n  actions = np.zeros(exp_dims, dtype=np.int32)\n  rewards = np.zeros(exp_dims, dtype=np.float32)\n  values = np.zeros(values_dims, dtype=np.float32)\n  log_probs = np.zeros(exp_dims, dtype=np.float32)\n  dones = np.zeros(exp_dims, dtype=np.float32)\n\n  for t in range(len(experience) - 1):  # experience[-1] only for next_values\n    for agent_id, exp_agent in enumerate(experience[t]):\n      states[t, agent_id, ...] = exp_agent.state\n      actions[t, agent_id] = exp_agent.action\n      rewards[t, agent_id] = exp_agent.reward\n      values[t, agent_id] = exp_agent.value\n      log_probs[t, agent_id] = exp_agent.log_prob\n      # Dones need to be 0 for terminal states.\n      dones[t, agent_id] = float(not exp_agent.done)\n  for a in range(num_agents):\n    values[-1, a] = experience[-1][a].value\n  advantages = gae_advantages(rewards, dones, values, gamma, lambda_)\n  returns = advantages + values[:-1, :]\n  # After preprocessing, concatenate data from all agents.\n  trajectories = (states, actions, log_probs, returns, advantages)\n  trajectory_len = num_agents * actor_steps\n  trajectories = tuple(\n      map(\n          lambda x: np.reshape(x, (trajectory_len,) + x.shape[2:]), trajectories\n      )\n  )\n  return trajectories\n\n\n@functools.partial(jax.jit, static_argnums=1)\ndef get_initial_params(key: jax.Array, model: nn.Module):\n  input_dims = (1, 84, 84, 4)  # (minibatch, height, width, stacked frames)\n  init_shape = jnp.ones(input_dims, jnp.float32)\n  initial_params = model.init(key, init_shape)['params']\n  return initial_params\n\n\ndef create_train_state(\n    params,\n    model: nn.Module,\n    config: ml_collections.ConfigDict,\n    train_steps: int,\n) -> train_state.TrainState:\n  if config.decaying_lr_and_clip_param:\n    lr = optax.linear_schedule(\n        init_value=config.learning_rate,\n        end_value=0.0,\n        transition_steps=train_steps,\n    )\n  else:\n    lr = config.learning_rate\n  tx = optax.adam(lr)\n  state = train_state.TrainState.create(\n      apply_fn=model.apply, params=params, tx=tx\n  )\n  return state\n\n\ndef train(\n    model: models.ActorCritic, config: ml_collections.ConfigDict, model_dir: str\n):\n  \"\"\"Main training loop.\n\n  Args:\n    model: the actor-critic model\n    config: object holding hyperparameters and the training information\n    model_dir: path to dictionary where checkpoints and logging info are stored\n\n  Returns:\n    optimizer: the trained optimizer\n  \"\"\"\n\n  game = config.game + 'NoFrameskip-v4'\n  simulators = [agent.RemoteSimulator(game) for _ in range(config.num_agents)]\n  summary_writer = tensorboard.SummaryWriter(model_dir)\n  summary_writer.hparams(dict(config))\n  loop_steps = config.total_frames // (config.num_agents * config.actor_steps)\n  log_frequency = 40\n  checkpoint_frequency = 500\n  # train_step does multiple steps per call for better performance\n  # compute number of steps per call here to convert between the number of\n  # train steps and the inner number of optimizer steps\n  iterations_per_step = (\n      config.num_agents * config.actor_steps // config.batch_size\n  )\n\n  initial_params = get_initial_params(jax.random.key(0), model)\n  state = create_train_state(\n      initial_params,\n      model,\n      config,\n      loop_steps * config.num_epochs * iterations_per_step,\n  )\n  del initial_params\n  state = checkpoints.restore_checkpoint(model_dir, state)\n  # number of train iterations done by each train_step\n\n  start_step = int(state.step) // config.num_epochs // iterations_per_step\n  logging.info('Start training from step: %s', start_step)\n\n  for step in range(start_step, loop_steps):\n    # Bookkeeping and testing.\n    if step % log_frequency == 0:\n      score = test_episodes.policy_test(1, state.apply_fn, state.params, game)\n      frames = step * config.num_agents * config.actor_steps\n      summary_writer.scalar('game_score', score, frames)\n      logging.info(\n          'Step %s:\\nframes seen %s\\nscore %s\\n\\n', step, frames, score\n      )\n\n    # Core training code.\n    alpha = (\n        1.0 - step / loop_steps if config.decaying_lr_and_clip_param else 1.0\n    )\n    all_experiences = get_experience(state, simulators, config.actor_steps)\n    trajectories = process_experience(\n        all_experiences,\n        config.actor_steps,\n        config.num_agents,\n        config.gamma,\n        config.lambda_,\n    )\n    clip_param = config.clip_param * alpha\n    for _ in range(config.num_epochs):\n      permutation = np.random.permutation(\n          config.num_agents * config.actor_steps\n      )\n      trajectories = tuple(x[permutation] for x in trajectories)\n      state, _ = train_step(\n          state,\n          trajectories,\n          config.batch_size,\n          clip_param=clip_param,\n          vf_coeff=config.vf_coeff,\n          entropy_coeff=config.entropy_coeff,\n      )\n    if (step + 1) % checkpoint_frequency == 0:\n      checkpoints.save_checkpoint(model_dir, state, step + 1)\n  return state\n"
  },
  {
    "path": "examples/ppo/ppo_lib_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Unit tests for the PPO example.\"\"\"\n\nfrom absl.testing import absltest\nfrom flax.training import train_state\nimport jax\nimport ml_collections\nimport numpy as np\nimport numpy.testing as np_testing\n\nimport agent\nimport env_utils\nimport models\nimport ppo_lib\nimport gymnasium as gym\nimport ale_py\n\ngym.register_envs(ale_py)\n\n\n# test GAE\nclass TestGAE(absltest.TestCase):\n\n  def test_gae_shape_on_random(self):\n    # create random data, simulating 4 parallel envs and 20 time_steps\n    envs, steps = 10, 100\n    rewards = np.random.choice(\n        [-1.0, 0.0, 1.0], size=(steps, envs), p=[0.01, 0.98, 0.01]\n    )\n    terminal_masks = np.ones(shape=(steps, envs), dtype=np.float64)\n    values = np.random.random(size=(steps + 1, envs))\n    discount = 0.99\n    gae_param = 0.95\n    adv = ppo_lib.gae_advantages(\n        rewards, terminal_masks, values, discount, gae_param\n    )\n    self.assertEqual(adv.shape, (steps, envs))\n\n  def test_gae_hardcoded(self):\n    # test on small example that can be verified by hand\n    rewards = np.array([[1.0, 0.0], [0.0, 0.0], [-1.0, 1.0]])\n    # one of the two episodes terminated in the middle\n    terminal_masks = np.array([[1.0, 1.0], [0.0, 1.0], [1.0, 1.0]])\n    values = np.array([[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]])\n    discount = 0.5\n    gae_param = 0.25\n    correct_gae = np.array([[0.375, -0.5546875], [-1.0, -0.4375], [-1.5, 0.5]])\n    actual_gae = ppo_lib.gae_advantages(\n        rewards, terminal_masks, values, discount, gae_param\n    )\n    np_testing.assert_allclose(actual_gae, correct_gae)\n\n\n# test environment and preprocessing\nclass TestEnvironmentPreprocessing(absltest.TestCase):\n\n  def choose_random_game(self):\n    games = [\n        'BeamRider',\n        'Breakout',\n        'Pong',\n        'Qbert',\n        'Seaquest',\n        'SpaceInvaders',\n    ]\n    ind = np.random.choice(len(games))\n    return games[ind] + 'NoFrameskip-v4'\n\n  def test_creation(self):\n    frame_shape = (84, 84, 4)\n    game = self.choose_random_game()\n    env = env_utils.create_env(game, clip_rewards=True)\n    obs = env.reset()\n    self.assertEqual(obs.shape, frame_shape)\n\n  def test_step(self):\n    frame_shape = (84, 84, 4)\n    game = self.choose_random_game()\n    env = env_utils.create_env(game, clip_rewards=True)\n    obs = env.reset()\n    actions = [1, 2, 3, 0]\n    for a in actions:\n      obs, reward, done, info = env.step(a)\n      self.assertEqual(obs.shape, frame_shape)\n      self.assertTrue(reward <= 1.0 and reward >= -1.0)\n      self.assertTrue(isinstance(done, bool))\n      self.assertTrue(isinstance(info, dict))\n\n\n# test the model (creation and forward pass)\nclass TestModel(absltest.TestCase):\n\n  def choose_random_outputs(self):\n    return np.random.choice([4, 5, 6, 7, 8, 9])\n\n  def test_model(self):\n    outputs = self.choose_random_outputs()\n    module = models.ActorCritic(num_outputs=outputs)\n    params = ppo_lib.get_initial_params(jax.random.key(0), module)\n    test_batch_size, obs_shape = 10, (84, 84, 4)\n    random_input = np.random.random(size=(test_batch_size,) + obs_shape)\n    log_probs, values = agent.policy_action(module.apply, params, random_input)\n    self.assertEqual(values.shape, (test_batch_size, 1))\n    sum_probs = np.sum(np.exp(log_probs), axis=1)\n    self.assertEqual(sum_probs.shape, (test_batch_size,))\n    np_testing.assert_allclose(\n        sum_probs, np.ones((test_batch_size,)), atol=1e-6\n    )\n\n\n# test one optimization step\nclass TestOptimizationStep(absltest.TestCase):\n\n  def generate_random_data(self, num_actions):\n    data_len = 256  # equal to one default-sized batch\n    state_shape = (84, 84, 4)\n    states = np.random.randint(0, 255, size=((data_len,) + state_shape))\n    actions = np.random.choice(num_actions, size=data_len)\n    old_log_probs = np.random.random(size=data_len)\n    returns = np.random.random(size=data_len)\n    advantages = np.random.random(size=data_len)\n    return states, actions, old_log_probs, returns, advantages\n\n  def test_optimization_step(self):\n    num_outputs = 4\n    trn_data = self.generate_random_data(num_actions=num_outputs)\n    clip_param = 0.1\n    vf_coeff = 0.5\n    entropy_coeff = 0.01\n    batch_size = 256\n    module = models.ActorCritic(num_outputs)\n    initial_params = ppo_lib.get_initial_params(jax.random.key(0), module)\n    config = ml_collections.ConfigDict({\n        'learning_rate': 2.5e-4,\n        'decaying_lr_and_clip_param': True,\n    })\n    state = ppo_lib.create_train_state(initial_params, module, config, 1000)\n    state, _ = ppo_lib.train_step(\n        state,\n        trn_data,\n        batch_size,\n        clip_param=clip_param,\n        vf_coeff=vf_coeff,\n        entropy_coeff=entropy_coeff,\n    )\n    self.assertIsInstance(state, train_state.TrainState)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/ppo/ppo_main.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# See issue #620.\n# pytype: disable=wrong-keyword-args\n\nfrom absl import app\nfrom absl import flags\nfrom ml_collections import config_flags\nimport tensorflow as tf\n\nimport env_utils\nimport models\nimport ppo_lib\nimport gymnasium as gym\nimport ale_py\n\ngym.register_envs(ale_py)\n\n\nFLAGS = flags.FLAGS\n\nflags.DEFINE_string(\n    'workdir',\n    default='/tmp/ppo_training',\n    help='Directory to save checkpoints and logging info.',\n)\n\nconfig_flags.DEFINE_config_file(\n    'config',\n    'configs/default.py',\n    'File path to the default configuration file.',\n    lock_config=True,\n)\n\n\ndef main(argv):\n  # Make sure tf does not allocate gpu memory.\n  tf.config.experimental.set_visible_devices([], 'GPU')\n  config = FLAGS.config\n  game = config.game + 'NoFrameskip-v4'\n  num_actions = env_utils.get_num_actions(game)\n  print(f'Playing {game} with {num_actions} actions')\n  model = models.ActorCritic(num_outputs=num_actions)\n  ppo_lib.train(model, config, FLAGS.workdir)\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": "examples/ppo/requirements.txt",
    "content": "absl-py==1.0.0\natari-py==0.2.5\nopencv-python==4.5.4.60\nflax==0.3.6\ngym==0.18.3\ngymnasium[atari, accept-rom-license]==0.29.0\nml-collections==0.1.0\nnumpy==1.22.0\noptax==0.1.5\ntensorflow==2.11.1\n"
  },
  {
    "path": "examples/ppo/seed_rl_atari_preprocessing.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# coding=utf-8\n# Copyright 2019 The SEED Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"A class implementing minimal Atari 2600 preprocessing.\nAdapted from SEED RL, originally adapted from Dopamine.\n\"\"\"\n\nimport cv2\nimport gymnasium as gym\nfrom gymnasium.spaces.box import Box\nimport numpy as np\n\n\nclass AtariPreprocessing:\n  \"\"\"A class implementing image preprocessing for Atari 2600 agents.\n  Specifically, this provides the following subset from the JAIR paper\n  (Bellemare et al., 2013) and Nature DQN paper (Mnih et al., 2015):\n    * Frame skipping (defaults to 4).\n    * Terminal signal when a life is lost (off by default).\n    * Grayscale and max-pooling of the last two frames.\n    * Downsample the screen to a square image (defaults to 84x84).\n  More generally, this class follows the preprocessing guidelines set down in\n  Machado et al. (2018), \"Revisiting the Arcade Learning Environment:\n  Evaluation Protocols and Open Problems for General Agents\".\n  It also provides random starting no-ops, which are used in the Rainbow, Apex\n  and R2D2 papers.\n  \"\"\"\n\n  def __init__(\n      self,\n      environment: gym.Env,\n      frame_skip=4,\n      terminal_on_life_loss=False,\n      screen_size=84,\n      max_random_noops=0,\n  ):\n    \"\"\"Constructor for an Atari 2600 preprocessor.\n    Args:\n      environment: Gym environment whose observations are preprocessed.\n      frame_skip: int, the frequency at which the agent experiences the game.\n      terminal_on_life_loss: bool, If True, the step() method returns\n        is_terminal=True whenever a life is lost. See Mnih et al. 2015.\n      screen_size: int, size of a resized Atari 2600 frame.\n      max_random_noops: int, maximum number of no-ops to apply at the beginning\n        of each episode to reduce determinism. These no-ops are applied at a\n        low-level, before frame skipping.\n    Raises:\n      ValueError: if frame_skip or screen_size are not strictly positive.\n    \"\"\"\n    if frame_skip <= 0:\n      raise ValueError(\n          f'Frame skip should be strictly positive, got {frame_skip}'\n      )\n    if screen_size <= 0:\n      raise ValueError(\n          'Target screen size should be strictly positive, got {}'.format(\n              screen_size\n          )\n      )\n\n    self.environment = environment\n    self.terminal_on_life_loss = terminal_on_life_loss\n    self.frame_skip = frame_skip\n    self.screen_size = screen_size\n    self.max_random_noops = max_random_noops\n\n    obs_dims = self.environment.observation_space\n    # Stores temporary observations used for pooling over two successive\n    # frames.\n    self.screen_buffer = [\n        np.empty((obs_dims.shape[0], obs_dims.shape[1]), dtype=np.uint8),\n        np.empty((obs_dims.shape[0], obs_dims.shape[1]), dtype=np.uint8),\n    ]\n\n    self.game_over = False\n    self.lives = 0  # Will need to be set by reset().\n\n  @property\n  def observation_space(self):\n    # Return the observation space adjusted to match the shape of the processed\n    # observations.\n    return Box(\n        low=0,\n        high=255,\n        shape=(self.screen_size, self.screen_size, 1),\n        dtype=np.uint8,\n    )\n\n  @property\n  def action_space(self):\n    return self.environment.action_space\n\n  @property\n  def reward_range(self):\n    return self.environment.reward_range  # type: ignore\n\n  @property\n  def metadata(self):\n    return self.environment.metadata\n\n  def close(self):\n    return self.environment.close()\n\n  def apply_random_noops(self):\n    \"\"\"Steps self.environment with random no-ops.\"\"\"\n    if self.max_random_noops <= 0:\n      return\n    # Other no-ops implementations actually always do at least 1 no-op. We\n    # follow them.\n    no_ops = self.environment.np_random.randint(1, self.max_random_noops + 1)\n    for _ in range(no_ops):\n      _, _, game_over, _, _ = self.environment.step(0)\n      if game_over:\n        self.environment.reset()\n\n  def reset(self):\n    \"\"\"Resets the environment.\n    Returns:\n      observation: numpy array, the initial observation emitted by the\n        environment.\n    \"\"\"\n    self.environment.reset()\n    self.apply_random_noops()\n\n    self.lives = self.environment.unwrapped.ale.lives()  # pytype:disable=attribute-error\n    self._fetch_grayscale_observation(self.screen_buffer[0])\n    self.screen_buffer[1].fill(0)\n    return self._pool_and_resize()\n\n  def render(self, mode):\n    \"\"\"Renders the current screen, before preprocessing.\n    This calls the Gym API's render() method.\n    Args:\n      mode: Mode argument for the environment's render() method.\n        Valid values (str) are:\n          'rgb_array': returns the raw ALE image.\n          'human': renders to display via the Gym renderer.\n    Returns:\n      if mode='rgb_array': numpy array, the most recent screen.\n      if mode='human': bool, whether the rendering was successful.\n    \"\"\"\n    return self.environment.render(mode)  # pytype:disable=wrong-arg-count\n\n  def step(self, action):\n    \"\"\"Applies the given action in the environment.\n    Remarks:\n      * If a terminal state (from life loss or episode end) is reached, this may\n        execute fewer than self.frame_skip steps in the environment.\n      * Furthermore, in this case the returned observation may not contain valid\n        image data and should be ignored.\n    Args:\n      action: The action to be executed.\n    Returns:\n      observation: numpy array, the observation following the action.\n      reward: float, the reward following the action.\n      is_terminal: bool, whether the environment has reached a terminal state.\n        This is true when a life is lost and terminal_on_life_loss, or when the\n        episode is over.\n      info: Gym API's info data structure.\n    \"\"\"\n    accumulated_reward = 0.0\n\n    for time_step in range(self.frame_skip):\n      # We bypass the Gym observation altogether and directly fetch the\n      # grayscale image from the ALE. This is a little faster.\n      _, reward, game_over, _, info = self.environment.step(action)\n      accumulated_reward += float(reward)\n\n      if self.terminal_on_life_loss:\n        new_lives = self.environment.unwrapped.ale.lives()  # pytype:disable=attribute-error\n        is_terminal = game_over or new_lives < self.lives\n        self.lives = new_lives\n      else:\n        is_terminal = game_over\n\n      if is_terminal:\n        break\n      # We max-pool over the last two frames, in grayscale.\n      elif time_step >= self.frame_skip - 2:\n        t = time_step - (self.frame_skip - 2)\n        self._fetch_grayscale_observation(self.screen_buffer[t])\n\n    # Pool the last two observations.\n    observation = self._pool_and_resize()\n\n    self.game_over = game_over\n    return observation, accumulated_reward, is_terminal, info\n\n  def _fetch_grayscale_observation(self, output):\n    \"\"\"Returns the current observation in grayscale.\n    The returned observation is stored in 'output'.\n    Args:\n      output: numpy array, screen buffer to hold the returned observation.\n    Returns:\n      observation: numpy array, the current observation in grayscale.\n    \"\"\"\n    self.environment.unwrapped.ale.getScreenGrayscale(output)  # pytype:disable=attribute-error\n    return output\n\n  def _pool_and_resize(self):\n    \"\"\"Transforms two frames into a Nature DQN observation.\n    For efficiency, the transformation is done in-place in self.screen_buffer.\n    Returns:\n      transformed_screen: numpy array, pooled, resized screen.\n    \"\"\"\n    # Pool if there are enough screens to do so.\n    if self.frame_skip > 1:\n      np.maximum(\n          self.screen_buffer[0],\n          self.screen_buffer[1],\n          out=self.screen_buffer[0],\n      )\n\n    transformed_image = cv2.resize(\n        self.screen_buffer[0],\n        (self.screen_size, self.screen_size),\n        interpolation=cv2.INTER_LINEAR,\n    )\n    int_image = np.asarray(transformed_image, dtype=np.uint8)\n    return np.expand_dims(int_image, axis=2)\n"
  },
  {
    "path": "examples/ppo/test_episodes.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Test policy by playing a full Atari game.\"\"\"\n\nimport itertools\nfrom typing import Any\nfrom collections.abc import Callable\n\nimport flax\nimport numpy as np\n\nimport agent\nimport env_utils\n\n\ndef policy_test(\n    n_episodes: int,\n    apply_fn: Callable[..., Any],\n    params: flax.core.frozen_dict.FrozenDict,\n    game: str,\n):\n  \"\"\"Perform a test of the policy in Atari environment.\n\n  Args:\n    n_episodes: number of full Atari episodes to test on\n    apply_fn: the actor-critic apply function\n    params: actor-critic model parameters, they define the policy being tested\n    game: defines the Atari game to test on\n\n  Returns:\n    total_reward: obtained score\n  \"\"\"\n  test_env = env_utils.create_env(game, clip_rewards=False)\n  for _ in range(n_episodes):\n    obs = test_env.reset()\n    state = obs[None, ...]  # add batch dimension\n    total_reward = 0.0\n    for t in itertools.count():\n      log_probs, _ = agent.policy_action(apply_fn, params, state)\n      probs = np.exp(np.array(log_probs, dtype=np.float32))\n      probabilities = probs[0] / probs[0].sum()\n      action = np.random.choice(probs.shape[1], p=probabilities)\n      obs, reward, done, _ = test_env.step(action)\n      total_reward += reward\n      next_state = obs[None, ...] if not done else None\n      state = next_state\n      if done:\n        break\n  return total_reward\n"
  },
  {
    "path": "examples/seq2seq/README.md",
    "content": "## seq2seq addition\n\nThis example trains a simple LSTM on a sequence-to-sequence addition task using\nan encoder-decoder architecture. The data is generated on the fly.\n\nColab lets you edit the source files and interact with the model:\n\nhttps://colab.research.google.com/github/google/flax/blob/main/examples/seq2seq/seq2seq.ipynb\n\n### Example output\n\nFrom Colab run that also generated [tfhub.dev]\n\n```\nINFO:absl:[1800] accuracy=1.0, loss=0.0020284138154238462\nINFO:absl:DECODE: 14+381 = 395 (CORRECT)\nINFO:absl:DECODE: 68+91 = 159 (CORRECT)\nINFO:absl:DECODE: 0+807 = 707 (INCORRECT) correct=807\nINFO:absl:DECODE: 95+532 = 627 (CORRECT)\nINFO:absl:DECODE: 6+600 = 606 (CORRECT)\n```\n\n[tfhub.dev]: https://tensorboard.dev/experiment/TwvKVBqzTaKWgEbyebillw/#scalars&_smoothingWeight=0\n\n### How to run\n\n`python train.py`\n\nThe total runtime for 1200 steps on CPU (3.5GHz Intel Core i7, 16GB memory) is\nabout 4 minutes.\n"
  },
  {
    "path": "examples/seq2seq/configs/default.py",
    "content": "# Copyright 2025 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Default Hyperparameter configuration.\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = ml_collections.ConfigDict()\n\n  config.workdir = '/tmp/seq2seq'\n  config.learning_rate = 0.003\n  config.batch_size = 128\n  config.hidden_size = 512\n  config.num_train_steps = 10000\n  config.decode_frequency = 200\n  config.max_len_query_digit = 3\n\n  return config\n"
  },
  {
    "path": "examples/seq2seq/input_pipeline.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Input pipeline for seq2seq addition example.\"\"\"\n\nimport random\nfrom typing import Any, Dict, Optional, Tuple\nfrom collections.abc import Generator\n\nimport jax.numpy as jnp\nimport numpy as np\n\nArray = Any  # pylint: disable=invalid-name\n\n\nclass CharacterTable:\n  \"\"\"Encodes/decodes between strings and integer representations.\"\"\"\n\n  def __init__(self, chars: str, max_len_query_digit: int = 3) -> None:\n    self._chars = sorted(set(chars))\n    self._char_indices = {ch: idx + 2 for idx, ch in enumerate(self._chars)}\n    self._indices_char = {idx + 2: ch for idx, ch in enumerate(self._chars)}\n    self._indices_char[self.pad_id] = '_'\n    # Maximum length of a single input digit.\n    self._max_len_query_digit = max_len_query_digit\n\n  @property\n  def pad_id(self) -> int:\n    return 0\n\n  @property\n  def eos_id(self) -> int:\n    return 1\n\n  @property\n  def vocab_size(self) -> int:\n    # All characters + pad token and eos token.\n    return len(self._chars) + 2\n\n  @property\n  def max_input_len(self) -> int:\n    \"\"\"Returns the max length of an input sequence.\"\"\"\n    # The input has the form \"digit1+digit2<eos>\", so the max input length is\n    # the length of two digits plus two tokens for \"+\" and the EOS token.\n    return self._max_len_query_digit * 2 + 2\n\n  @property\n  def max_output_len(self) -> int:\n    \"\"\"Returns the max length of an output sequence.\"\"\"\n    # The output has the form \"=digit<eos>\". If `digit` is the result of adding\n    # two digits of max length x, then max length of `digit` is x+1.\n    # Additionally, we require two more tokens for \"=\" and \"<eos\".\n    return self._max_len_query_digit + 3\n\n  @property\n  def encoder_input_shape(self) -> tuple[int, int, int]:\n    return (1, self.max_input_len, self.vocab_size)\n\n  @property\n  def decoder_input_shape(self) -> tuple[int, int, int]:\n    return (1, self.max_output_len, self.vocab_size)\n\n  def encode(self, inputs: str) -> np.ndarray:\n    \"\"\"Encodes from string to list of integers.\"\"\"\n    return np.array(\n        [self._char_indices[char] for char in inputs] + [self.eos_id]\n    )\n\n  def decode(self, inputs: Array) -> str:\n    \"\"\"Decodes from list of integers to string.\"\"\"\n    chars = []\n    for elem in inputs.tolist():\n      if elem == self.eos_id:\n        break\n      chars.append(self._indices_char[elem])\n    return ''.join(chars)\n\n  def one_hot(self, tokens: np.ndarray) -> np.ndarray:\n    vecs = np.zeros((tokens.size, self.vocab_size), dtype=np.float32)\n    vecs[np.arange(tokens.size), tokens] = 1\n    return vecs\n\n  def encode_onehot(\n      self, batch_inputs: Array, max_len: int | None = None\n  ) -> np.ndarray:\n    \"\"\"One-hot encodes a string input.\"\"\"\n\n    if max_len is None:\n      max_len = self.max_input_len\n\n    def encode_str(s):\n      tokens = self.encode(s)\n      unpadded_len = len(tokens)\n      if unpadded_len > max_len:\n        raise ValueError(f\"Sequence too long ({len(tokens)}>{max_len}): '{s}'\")\n      tokens = np.pad(tokens, [(0, max_len - len(tokens))], mode='constant')\n      return self.one_hot(tokens)\n\n    return np.array([encode_str(inp) for inp in batch_inputs])\n\n  def decode_onehot(self, batch_inputs: Array) -> np.ndarray:\n    \"\"\"Decodes a batch of one-hot encoding to strings.\"\"\"\n    decode_inputs = lambda inputs: self.decode(inputs.argmax(axis=-1))\n    return np.array(list(map(decode_inputs, batch_inputs)))\n\n  def generate_examples(\n      self, num_examples: int\n  ) -> Generator[tuple[str, str], None, None]:\n    \"\"\"Yields `num_examples` examples.\"\"\"\n    for _ in range(num_examples):\n      max_digit = pow(10, self._max_len_query_digit) - 1\n      # TODO(marcvanzee): Use jax.random here.\n      key = tuple(sorted((random.randint(0, 99), random.randint(0, max_digit))))\n      inputs = f'{key[0]}+{key[1]}'\n      # Preprend output by the decoder's start token.\n      outputs = '=' + str(key[0] + key[1])\n      yield (inputs, outputs)\n\n  def get_batch(self, batch_size: int) -> dict[str, np.ndarray]:\n    \"\"\"Returns a batch of example of size @batch_size.\"\"\"\n    inputs, outputs = zip(*self.generate_examples(batch_size))\n    return {\n        'query': self.encode_onehot(inputs),\n        'answer': self.encode_onehot(outputs),\n    }\n\n\ndef mask_sequences(sequence_batch: Array, lengths: Array) -> Array:\n  \"\"\"Sets positions beyond the length of each sequence to 0.\"\"\"\n  return sequence_batch * (\n      lengths[:, np.newaxis] > np.arange(sequence_batch.shape[1])[np.newaxis]\n  )\n\n\ndef get_sequence_lengths(sequence_batch: Array, eos_id: int) -> Array:\n  \"\"\"Returns the length of each one-hot sequence, including the EOS token.\"\"\"\n  # sequence_batch.shape = (batch_size, seq_length, vocab_size)\n  eos_row = sequence_batch[:, :, eos_id]\n  eos_idx = jnp.argmax(eos_row, axis=-1)  # returns first occurrence\n  # `eos_idx` is 0 if EOS is not present, so we use full length in that case.\n  return jnp.where(\n      eos_row[jnp.arange(eos_row.shape[0]), eos_idx],\n      eos_idx + 1,\n      sequence_batch.shape[1],  # if there is no EOS, use full length\n  )\n"
  },
  {
    "path": "examples/seq2seq/main.py",
    "content": "# Copyright 2025 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Main script for seq2seq example.\"\"\"\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nimport train\nfrom ml_collections import config_flags\n\nFLAGS = flags.FLAGS\n\nconfig_flags.DEFINE_config_file(\n    'config',\n    None,\n    'File path to the training hyperparameter configuration.',\n    lock_config=True,\n)\n\n\ndef main(argv):\n  del argv\n\n  config = FLAGS.config\n\n  # Set train.FLAGS values from config\n  train.FLAGS.workdir = config.workdir\n  train.FLAGS.learning_rate = config.learning_rate\n  train.FLAGS.batch_size = config.batch_size\n  train.FLAGS.hidden_size = config.hidden_size\n  train.FLAGS.num_train_steps = config.num_train_steps\n  train.FLAGS.decode_frequency = config.decode_frequency\n  train.FLAGS.max_len_query_digit = config.max_len_query_digit\n\n  logging.info('Starting training with config: %s', config)\n  _ = train.train_and_evaluate(train.FLAGS.workdir)\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": "examples/seq2seq/models.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"seq2seq example: Mode code.\"\"\"\n\n# See issue #620.\n# pytype: disable=wrong-keyword-args\n\nfrom typing import Tuple\n\nfrom flax import linen as nn\nimport jax\nimport jax.numpy as jnp\n\nArray = jax.Array\nPRNGKey = jax.Array\nLSTMCarry = tuple[Array, Array]\n\n\nclass DecoderLSTMCell(nn.RNNCellBase):\n  \"\"\"DecoderLSTM Module wrapped in a lifted scan transform.\n\n  Attributes:\n    teacher_force: See docstring on Seq2seq module.\n    vocab_size: Size of the vocabulary.\n  \"\"\"\n\n  features: int\n  teacher_force: bool\n  vocab_size: int\n\n  @nn.compact\n  def __call__(\n      self, carry: tuple[LSTMCarry, Array], x: Array\n  ) -> tuple[tuple[LSTMCarry, Array], tuple[Array, Array]]:\n    \"\"\"Applies the DecoderLSTM model.\"\"\"\n    lstm_state, last_prediction = carry\n    if not self.teacher_force:\n      x = last_prediction\n    lstm_state, y = nn.LSTMCell(self.features)(lstm_state, x)\n    logits = nn.Dense(features=self.vocab_size)(y)\n    # Sample the predicted token using a categorical distribution over the\n    # logits.\n    categorical_rng = self.make_rng('lstm')\n    predicted_token = jax.random.categorical(categorical_rng, logits)\n    # Convert to one-hot encoding.\n    prediction = jax.nn.one_hot(\n        predicted_token, self.vocab_size, dtype=jnp.float32\n    )\n\n    return (lstm_state, prediction), (logits, prediction)\n\n  @property\n  def num_feature_axes(self) -> int:\n    return 1\n\n\nclass Seq2seq(nn.Module):\n  \"\"\"Sequence-to-sequence class using encoder/decoder architecture.\n\n  Attributes:\n    teacher_force: whether to use `decoder_inputs` as input to the decoder at\n      every step. If False, only the first input (i.e., the \"=\" token) is used,\n      followed by samples taken from the previous output logits.\n    hidden_size: int, the number of hidden dimensions in the encoder and decoder\n      LSTMs.\n    vocab_size: the size of the vocabulary.\n    eos_id: EOS id.\n  \"\"\"\n\n  teacher_force: bool\n  hidden_size: int\n  vocab_size: int\n  eos_id: int = 1\n\n  @nn.compact\n  def __call__(\n      self, encoder_inputs: Array, decoder_inputs: Array\n  ) -> tuple[Array, Array]:\n    \"\"\"Applies the seq2seq model.\n\n    Args:\n      encoder_inputs: [batch_size, max_input_length, vocab_size].\n        padded batch of input sequences to encode.\n      decoder_inputs: [batch_size, max_output_length, vocab_size].\n        padded batch of expected decoded sequences for teacher forcing.\n        When sampling (i.e., `teacher_force = False`), only the first token is\n        input into the decoder (which is the token \"=\"), and samples are used\n        for the following inputs. The second dimension of this tensor determines\n        how many steps will be decoded, regardless of the value of\n        `teacher_force`.\n\n    Returns:\n      Pair (logits, predictions), which are two arrays of length `batch_size`\n      containing respectively decoded logits and predictions (in one hot\n      encoding format).\n    \"\"\"\n    # Encode inputs.\n    encoder = nn.RNN(\n        nn.LSTMCell(self.hidden_size), return_carry=True, name='encoder'\n    )\n    decoder = nn.RNN(\n        DecoderLSTMCell(\n            decoder_inputs.shape[-1], self.teacher_force, self.vocab_size\n        ),\n        split_rngs={'params': False, 'lstm': True},\n        name='decoder',\n    )\n\n    seq_lengths = self.get_seq_lengths(encoder_inputs)\n\n    encoder_state, _ = encoder(encoder_inputs, seq_lengths=seq_lengths)\n    logits, predictions = decoder(\n        decoder_inputs[:, :-1],\n        initial_carry=(encoder_state, decoder_inputs[:, 0]),\n    )\n\n    return logits, predictions\n\n  def get_seq_lengths(self, inputs: Array) -> Array:\n    \"\"\"Get segmentation mask for inputs.\"\"\"\n    # undo one-hot encoding\n    inputs = jnp.argmax(inputs, axis=-1)\n    # calculate sequence lengths\n    seq_lengths = jnp.argmax(inputs == self.eos_id, axis=-1)\n\n    return seq_lengths\n"
  },
  {
    "path": "examples/seq2seq/requirements.txt",
    "content": "absl-py==1.0.0\nclu==0.0.6\nflax==0.3.6\nnumpy==1.22.0\noptax==0.1.0\n"
  },
  {
    "path": "examples/seq2seq/seq2seq.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Flax seq2seq Example\\n\",\n    \"\\n\",\n    \"<a href=\\\"https://colab.research.google.com/github/google/flax/blob/main/examples/seq2seq/seq2seq.ipynb\\\" ><img src=\\\"https://colab.research.google.com/assets/colab-badge.svg\\\" alt=\\\"Open In Colab\\\"/></a>\\n\",\n    \"\\n\",\n    \"Demonstration notebook for\\n\",\n    \"https://github.com/google/flax/tree/main/examples/seq2seq\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The **Flax Notebook Workflow**:\\n\",\n    \"\\n\",\n    \"1. Run the entire notebook end-to-end and check out the outputs.\\n\",\n    \"   - This will open Python files in the right-hand editor!\\n\",\n    \"   - You'll be able to interactively explore metrics in TensorBoard.\\n\",\n    \"2. Change some of the hyperparameters in the command-line flags in `train.py` for different hyperparameters. Check out the updated TensorBoard plots.\\n\",\n    \"3. Update the code in `train.py`, `models.py`, and `input_pipeline.py`. \\n\",\n    \"   Thanks to `%autoreload`, any changes you make in the file will \\n\",\n    \"   automatically appear in the notebook. Some ideas to get you started:\\n\",\n    \"   - Change the model.\\n\",\n    \"   - Log some per-batch metrics during training.\\n\",\n    \"   - Add new hyperparameters to `models.py` and use them in `train.py`.\\n\",\n    \"   - Train on a different vocabulary by initializing `CharacterTable` with a\\n\",\n    \"     different character set.\\n\",\n    \"4. At any time, feel free to paste code from the source code into the notebook\\n\",\n    \"   and modify it directly there!\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Setup\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"outputId\": \"4c0a705c-8d7e-44cc-d851-873a40ac115e\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\u001b[K     |████████████████████████████████| 77 kB 3.1 MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 176 kB 30.2 MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 77 kB 5.2 MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 136 kB 45.5 MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 65 kB 2.8 MB/s \\n\",\n      \"\\u001b[K     |████████████████████████████████| 462 kB 44.3 MB/s \\n\",\n      \"\\u001b[?25h  Building wheel for ml-collections (setup.py) ... \\u001b[?25l\\u001b[?25hdone\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Install CLU & Flax.\\n\",\n    \"!pip install -q clu flax\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"tags\": []\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"example_directory = 'examples/seq2seq'\\n\",\n    \"editor_relpaths = ('train.py', 'input_pipeline.py', 'models.py')\\n\",\n    \"\\n\",\n    \"repo, branch = 'https://github.com/google/flax', 'main'\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"cellView\": \"form\",\n    \"outputId\": \"4801432e-4090-4b13-f0f2-d99a3039ce47\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Cloning into 'flaxrepo'...\\n\",\n      \"remote: Enumerating objects: 349, done.\\u001b[K\\n\",\n      \"remote: Counting objects: 100% (349/349), done.\\u001b[K\\n\",\n      \"remote: Compressing objects: 100% (286/286), done.\\u001b[K\\n\",\n      \"remote: Total 349 (delta 63), reused 220 (delta 51), pack-reused 0\\u001b[K\\n\",\n      \"Receiving objects: 100% (349/349), 2.12 MiB | 13.39 MiB/s, done.\\n\",\n      \"Resolving deltas: 100% (63/63), done.\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/html\": [\n       \"<h1 style=\\\"color:red;\\\" class=\\\"blink\\\">WARNING : Editing in VM - changes lost after reboot!!</h1>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.HTML object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/seq2seq/train.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/seq2seq/input_pipeline.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"application/javascript\": [\n       \"\\n\",\n       \"      ((filepath) => {{\\n\",\n       \"        if (!google.colab.kernel.accessAllowed) {{\\n\",\n       \"          return;\\n\",\n       \"        }}\\n\",\n       \"        google.colab.files.view(filepath);\\n\",\n       \"      }})(\\\"/content/examples/seq2seq/models.py\\\")\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Javascript object>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# (If you run this code in Jupyter[lab], then you're already in the\\n\",\n    \"#  example directory and nothing needs to be done.)\\n\",\n    \"\\n\",\n    \"#@markdown **Fetch newest Flax, copy example code**\\n\",\n    \"#@markdown\\n\",\n    \"#@markdown **If you select no** below, then the files will be stored on the\\n\",\n    \"#@markdown *ephemeral* Colab VM. **After some time of inactivity, this VM will\\n\",\n    \"#@markdown be restarted and any changes are lost**.\\n\",\n    \"#@markdown\\n\",\n    \"#@markdown **If you select yes** below, then you will be asked for your\\n\",\n    \"#@markdown credentials to mount your personal Google Drive. In this case, all\\n\",\n    \"#@markdown changes you make will be *persisted*, and even if you re-run the\\n\",\n    \"#@markdown Colab later on, the files will still be the same (you can of course\\n\",\n    \"#@markdown remove directories inside your Drive's `flax/` root if you want to\\n\",\n    \"#@markdown manually revert these files).\\n\",\n    \"\\n\",\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  import os\\n\",\n    \"  os.chdir('/content')\\n\",\n    \"  # Download Flax repo from Github.\\n\",\n    \"  if not os.path.isdir('flaxrepo'):\\n\",\n    \"    !git clone --depth=1 -b $branch $repo flaxrepo\\n\",\n    \"  # Copy example files & change directory.\\n\",\n    \"  mount_gdrive = 'no' #@param ['yes', 'no']\\n\",\n    \"  if mount_gdrive == 'yes':\\n\",\n    \"    DISCLAIMER = 'Note : Editing in your Google Drive, changes will persist.'\\n\",\n    \"    from google.colab import drive\\n\",\n    \"    drive.mount('/content/gdrive')\\n\",\n    \"    example_root_path = f'/content/gdrive/My Drive/flax/{example_directory}'\\n\",\n    \"  else:\\n\",\n    \"    DISCLAIMER = 'WARNING : Editing in VM - changes lost after reboot!!'\\n\",\n    \"    example_root_path = f'/content/{example_directory}'\\n\",\n    \"    from IPython import display\\n\",\n    \"    display.display(display.HTML(\\n\",\n    \"        f'<h1 style=\\\"color:red;\\\" class=\\\"blink\\\">{DISCLAIMER}</h1>'))\\n\",\n    \"  if not os.path.isdir(example_root_path):\\n\",\n    \"    os.makedirs(example_root_path)\\n\",\n    \"    !cp -r flaxrepo/$example_directory/* \\\"$example_root_path\\\"\\n\",\n    \"  os.chdir(example_root_path)\\n\",\n    \"  from google.colab import files\\n\",\n    \"  for relpath in editor_relpaths:\\n\",\n    \"    s = open(f'{example_root_path}/{relpath}').read()\\n\",\n    \"    open(f'{example_root_path}/{relpath}', 'w').write(\\n\",\n    \"        f'## {DISCLAIMER}\\\\n' + '#' * (len(DISCLAIMER) + 3) + '\\\\n\\\\n' + s)\\n\",\n    \"    files.view(f'{example_root_path}/{relpath}')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"outputId\": \"a292a7a2-ae3c-4518-af28-9c2fa0ed2d7b\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"/content/examples/seq2seq\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Note : In Colab, above cell changed the working directory.\\n\",\n    \"!pwd\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Imports\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from absl import app\\n\",\n    \"app.parse_flags_with_usage(['seq2seq'])\\n\",\n    \"\\n\",\n    \"from absl import logging\\n\",\n    \"logging.set_verbosity(logging.INFO)\\n\",\n    \"\\n\",\n    \"import jax\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"outputId\": \"7e1a29ce-9d8b-4715-ce60-9eae100a1df3\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"The autoreload extension is already loaded. To reload it, use:\\n\",\n      \"  %reload_ext autoreload\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# Local imports from current directory - auto reload.\\n\",\n    \"# Any changes you make to the three imported files will appear automatically.\\n\",\n    \"%load_ext autoreload\\n\",\n    \"%autoreload 2\\n\",\n    \"import input_pipeline\\n\",\n    \"import models\\n\",\n    \"import train\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Dataset\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"outputId\": \"cb5f7f6e-1e6f-40ff-e0d6-5b428511d75b\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"[('72+789', '=861'),\\n\",\n       \" ('58+858', '=916'),\\n\",\n       \" ('77+358', '=435'),\\n\",\n       \" ('99+264', '=363'),\\n\",\n       \" ('94+115', '=209')]\"\n      ]\n     },\n     \"execution_count\": 11,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# Examples are generated on the fly.\\n\",\n    \"ctable = input_pipeline.CharacterTable('0123456789+= ')\\n\",\n    \"list(ctable.generate_examples(5))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"outputId\": \"b58ea813-e757-4cc5-f3ba-3cb0f05d35a6\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"array([[0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\\n\",\n       \"       [0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\\n\",\n       \"       [0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],\\n\",\n       \"       [0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],\\n\",\n       \"       [0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],\\n\",\n       \"       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\\n\",\n       \"       [1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\\n\",\n       \"       [1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]],\\n\",\n       \"      dtype=float32)\"\n      ]\n     },\n     \"execution_count\": 13,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"batch = ctable.get_batch(5)\\n\",\n    \"# A single query (/answer) is one-hot encoded.\\n\",\n    \"batch['query'][0]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"outputId\": \"3b33e061-f0b5-42d7-ad49-5058e8fd3b90\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"array(['1+243'], dtype='<U5')\"\n      ]\n     },\n     \"execution_count\": 16,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# Note how CTABLE encodes PAD=0, EOS=1, '0'=2, '1'=3, ...\\n\",\n    \"ctable.decode_onehot(batch['query'][:1])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Training\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Get a live update during training - use the \\\"refresh\\\" button!\\n\",\n    \"# (In Jupyter[lab] start \\\"tensorboard\\\" in the local directory instead.)\\n\",\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  %load_ext tensorboard\\n\",\n    \"  %tensorboard --logdir=./workdirs\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import time\\n\",\n    \"workdir = f'./workdirs/{int(time.time())}'\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"outputId\": \"e49554e2-9336-4d97-a1e2-82b9e98407da\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"['seq2seq']\"\n      ]\n     },\n     \"execution_count\": 20,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# Train 2k steps & log 20 times.\\n\",\n    \"app.parse_flags_with_usage([\\n\",\n    \"    'seq2seq',\\n\",\n    \"    '--num_train_steps=2000',\\n\",\n    \"    '--decode_frequency=100',\\n\",\n    \"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"outputId\": \"49396889-35b0-4a11-8b8a-e67624be32a7\"\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"INFO:absl:[100] accuracy=0.015625, loss=0.6936355233192444\\n\",\n      \"INFO:absl:DECODE: 96+964 = 1002 (INCORRECT) correct=1060\\n\",\n      \"INFO:absl:DECODE: 71+545 = 608 (INCORRECT) correct=616\\n\",\n      \"INFO:absl:DECODE: 42+729 = 730 (INCORRECT) correct=771\\n\",\n      \"INFO:absl:DECODE: 28+588 = 684 (INCORRECT) correct=616\\n\",\n      \"INFO:absl:DECODE: 39+648 = 618 (INCORRECT) correct=687\\n\",\n      \"INFO:absl:[200] accuracy=0.03125, loss=0.5422528982162476\\n\",\n      \"INFO:absl:DECODE: 18+70 = 70 (INCORRECT) correct=88\\n\",\n      \"INFO:absl:DECODE: 43+123 = 145 (INCORRECT) correct=166\\n\",\n      \"INFO:absl:DECODE: 72+406 = 460 (INCORRECT) correct=478\\n\",\n      \"INFO:absl:DECODE: 53+443 = 492 (INCORRECT) correct=496\\n\",\n      \"INFO:absl:DECODE: 74+844 = 936 (INCORRECT) correct=918\\n\",\n      \"INFO:absl:[300] accuracy=0.0703125, loss=0.462927907705307\\n\",\n      \"INFO:absl:DECODE: 40+598 = 643 (INCORRECT) correct=638\\n\",\n      \"INFO:absl:DECODE: 2+72 = 75 (INCORRECT) correct=74\\n\",\n      \"INFO:absl:DECODE: 70+742 = 814 (INCORRECT) correct=812\\n\",\n      \"INFO:absl:DECODE: 22+943 = 963 (INCORRECT) correct=965\\n\",\n      \"INFO:absl:DECODE: 85+890 = 975 (CORRECT)\\n\",\n      \"INFO:absl:[400] accuracy=0.0390625, loss=0.4398380219936371\\n\",\n      \"INFO:absl:DECODE: 79+562 = 647 (INCORRECT) correct=641\\n\",\n      \"INFO:absl:DECODE: 47+987 = 1043 (INCORRECT) correct=1034\\n\",\n      \"INFO:absl:DECODE: 92+959 = 1064 (INCORRECT) correct=1051\\n\",\n      \"INFO:absl:DECODE: 38+291 = 334 (INCORRECT) correct=329\\n\",\n      \"INFO:absl:DECODE: 95+476 = 571 (CORRECT)\\n\",\n      \"INFO:absl:[500] accuracy=0.0703125, loss=0.4079001545906067\\n\",\n      \"INFO:absl:DECODE: 9+871 = 872 (INCORRECT) correct=880\\n\",\n      \"INFO:absl:DECODE: 58+633 = 696 (INCORRECT) correct=691\\n\",\n      \"INFO:absl:DECODE: 6+98 = 113 (INCORRECT) correct=104\\n\",\n      \"INFO:absl:DECODE: 8+325 = 345 (INCORRECT) correct=333\\n\",\n      \"INFO:absl:DECODE: 43+540 = 580 (INCORRECT) correct=583\\n\",\n      \"INFO:absl:[600] accuracy=0.0390625, loss=0.4265238344669342\\n\",\n      \"INFO:absl:DECODE: 67+554 = 613 (INCORRECT) correct=621\\n\",\n      \"INFO:absl:DECODE: 38+189 = 233 (INCORRECT) correct=227\\n\",\n      \"INFO:absl:DECODE: 28+843 = 878 (INCORRECT) correct=871\\n\",\n      \"INFO:absl:DECODE: 11+948 = 969 (INCORRECT) correct=959\\n\",\n      \"INFO:absl:DECODE: 9+121 = 148 (INCORRECT) correct=130\\n\",\n      \"INFO:absl:[700] accuracy=0.078125, loss=0.38418614864349365\\n\",\n      \"INFO:absl:DECODE: 38+528 = 562 (INCORRECT) correct=566\\n\",\n      \"INFO:absl:DECODE: 57+317 = 372 (INCORRECT) correct=374\\n\",\n      \"INFO:absl:DECODE: 64+246 = 300 (INCORRECT) correct=310\\n\",\n      \"INFO:absl:DECODE: 73+296 = 373 (INCORRECT) correct=369\\n\",\n      \"INFO:absl:DECODE: 14+321 = 336 (INCORRECT) correct=335\\n\",\n      \"INFO:absl:[800] accuracy=0.1171875, loss=0.3908345401287079\\n\",\n      \"INFO:absl:DECODE: 26+902 = 931 (INCORRECT) correct=928\\n\",\n      \"INFO:absl:DECODE: 15+818 = 843 (INCORRECT) correct=833\\n\",\n      \"INFO:absl:DECODE: 19+348 = 359 (INCORRECT) correct=367\\n\",\n      \"INFO:absl:DECODE: 79+878 = 959 (INCORRECT) correct=957\\n\",\n      \"INFO:absl:DECODE: 64+824 = 889 (INCORRECT) correct=888\\n\",\n      \"INFO:absl:[900] accuracy=0.203125, loss=0.3021599352359772\\n\",\n      \"INFO:absl:DECODE: 6+477 = 482 (INCORRECT) correct=483\\n\",\n      \"INFO:absl:DECODE: 11+18 = 39 (INCORRECT) correct=29\\n\",\n      \"INFO:absl:DECODE: 35+777 = 814 (INCORRECT) correct=812\\n\",\n      \"INFO:absl:DECODE: 45+156 = 200 (INCORRECT) correct=201\\n\",\n      \"INFO:absl:DECODE: 11+432 = 445 (INCORRECT) correct=443\\n\",\n      \"INFO:absl:[1000] accuracy=0.6015625, loss=0.16906459629535675\\n\",\n      \"INFO:absl:DECODE: 41+156 = 198 (INCORRECT) correct=197\\n\",\n      \"INFO:absl:DECODE: 45+177 = 222 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 94+845 = 937 (INCORRECT) correct=939\\n\",\n      \"INFO:absl:DECODE: 10+73 = 93 (INCORRECT) correct=83\\n\",\n      \"INFO:absl:DECODE: 62+598 = 661 (INCORRECT) correct=660\\n\",\n      \"INFO:absl:[1100] accuracy=0.6796875, loss=0.12802045047283173\\n\",\n      \"INFO:absl:DECODE: 95+134 = 218 (INCORRECT) correct=229\\n\",\n      \"INFO:absl:DECODE: 69+508 = 577 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 74+636 = 710 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 11+522 = 533 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 0+620 = 620 (CORRECT)\\n\",\n      \"INFO:absl:[1200] accuracy=0.96875, loss=0.027045272290706635\\n\",\n      \"INFO:absl:DECODE: 93+376 = 469 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 65+474 = 549 (INCORRECT) correct=539\\n\",\n      \"INFO:absl:DECODE: 33+35 = 58 (INCORRECT) correct=68\\n\",\n      \"INFO:absl:DECODE: 87+527 = 615 (INCORRECT) correct=614\\n\",\n      \"INFO:absl:DECODE: 40+77 = 107 (INCORRECT) correct=117\\n\",\n      \"INFO:absl:[1300] accuracy=0.9453125, loss=0.025903644040226936\\n\",\n      \"INFO:absl:DECODE: 9+341 = 350 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 29+59 = 88 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 83+473 = 556 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 51+339 = 390 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 29+63 = 92 (CORRECT)\\n\",\n      \"INFO:absl:[1400] accuracy=0.890625, loss=0.03969202935695648\\n\",\n      \"INFO:absl:DECODE: 77+177 = 254 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 66+692 = 759 (INCORRECT) correct=758\\n\",\n      \"INFO:absl:DECODE: 24+321 = 345 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 23+180 = 203 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 67+898 = 965 (CORRECT)\\n\",\n      \"INFO:absl:[1500] accuracy=0.984375, loss=0.009904923848807812\\n\",\n      \"INFO:absl:DECODE: 13+119 = 132 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 34+467 = 501 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 20+661 = 681 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 51+779 = 830 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 12+101 = 113 (CORRECT)\\n\",\n      \"INFO:absl:[1600] accuracy=0.9375, loss=0.021177640184760094\\n\",\n      \"INFO:absl:DECODE: 61+275 = 336 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 48+831 = 879 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 25+628 = 653 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 87+933 = 1020 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 75+405 = 480 (CORRECT)\\n\",\n      \"INFO:absl:[1700] accuracy=0.96875, loss=0.015190182253718376\\n\",\n      \"INFO:absl:DECODE: 83+347 = 430 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 45+832 = 877 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 47+867 = 914 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 39+450 = 489 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 2+357 = 359 (CORRECT)\\n\",\n      \"INFO:absl:[1800] accuracy=0.9921875, loss=0.008457459509372711\\n\",\n      \"INFO:absl:DECODE: 64+97 = 161 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 66+335 = 401 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 15+656 = 671 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 40+689 = 729 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 42+104 = 146 (CORRECT)\\n\",\n      \"INFO:absl:[1900] accuracy=0.90625, loss=0.026923568919301033\\n\",\n      \"INFO:absl:DECODE: 34+92 = 116 (INCORRECT) correct=126\\n\",\n      \"INFO:absl:DECODE: 52+466 = 518 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 46+47 = 93 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 61+240 = 301 (CORRECT)\\n\",\n      \"INFO:absl:DECODE: 86+951 = 1037 (CORRECT)\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"state = train.train_and_evaluate(workdir=workdir)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"cellView\": \"form\",\n    \"outputId\": \"2beaf4e9-b10b-4156-d2d9-187777306de0\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\n\",\n      \"***** TensorBoard Uploader *****\\n\",\n      \"\\n\",\n      \"This will upload your TensorBoard logs to https://tensorboard.dev/ from\\n\",\n      \"the following directory:\\n\",\n      \"\\n\",\n      \"./workdirs\\n\",\n      \"\\n\",\n      \"This TensorBoard will be visible to everyone. Do not upload sensitive\\n\",\n      \"data.\\n\",\n      \"\\n\",\n      \"Your use of this service is subject to Google's Terms of Service\\n\",\n      \"<https://policies.google.com/terms> and Privacy Policy\\n\",\n      \"<https://policies.google.com/privacy>, and TensorBoard.dev's Terms of Service\\n\",\n      \"<https://tensorboard.dev/policy/terms/>.\\n\",\n      \"\\n\",\n      \"This notice will not be shown again while you are logged into the uploader.\\n\",\n      \"To log out, run `tensorboard dev auth revoke`.\\n\",\n      \"\\n\",\n      \"Continue? (yes/NO) yes\\n\",\n      \"\\n\",\n      \"Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=373649185512-8v619h5kft38l4456nm2dj4ubeqsrvh6.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&state=IjociK9llsm6dSiC1TDFvFmJksFy49&prompt=consent&access_type=offline\\n\",\n      \"Enter the authorization code: 4/1AX4XfWi6J9MqoDpbZ5Z_jd1AVheW7277VuUoTOEz5_8NRs_3oP9M7S4T81c\\n\",\n      \"\\n\",\n      \"\\n\",\n      \"New experiment created. View your TensorBoard at: https://tensorboard.dev/experiment/pgfmdFlaQTy9odov72ZvVQ/\\n\",\n      \"\\n\",\n      \"\\u001b[1m[2022-02-25T09:34:21]\\u001b[0m Started scanning logdir.\\n\",\n      \"\\u001b[1m[2022-02-25T09:34:22]\\u001b[0m Total uploaded: 38 scalars, 0 tensors, 0 binary objects\\n\",\n      \"\\u001b[1m[2022-02-25T09:34:22]\\u001b[0m Done scanning logdir.\\n\",\n      \"\\n\",\n      \"\\n\",\n      \"Done. View your TensorBoard at https://tensorboard.dev/experiment/pgfmdFlaQTy9odov72ZvVQ/\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  #@markdown You can upload the training results directly to https://tensorboard.dev\\n\",\n    \"  #@markdown\\n\",\n    \"  #@markdown Note that everybody with the link will be able to see the data.\\n\",\n    \"  upload_data = 'yes' #@param ['yes', 'no']\\n\",\n    \"  if upload_data == 'yes':\\n\",\n    \"    !tensorboard dev upload --one_shot --logdir ./workdirs --name 'Flax examples/seq2seq (Colab)'\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Inference\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"outputId\": \"e22b7208-5413-4a63-abfb-b510af60f340\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"(1, 8, 15)\"\n      ]\n     },\n     \"execution_count\": 23,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"inputs = ctable.encode_onehot(['2+40'])\\n\",\n    \"# batch, max_length, vocab_size\\n\",\n    \"inputs.shape\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Using different random seeds generates different samples.\\n\",\n    \"preds = train.decode(state.params, inputs, jax.random.key(0), ctable)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"outputId\": \"e5cdfd75-2c66-4165-8ab7-9fdecde5062a\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"array(['42'], dtype='<U2')\"\n      ]\n     },\n     \"execution_count\": 27,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"ctable.decode_onehot(preds)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"accelerator\": \"GPU\",\n  \"language_info\": {\n   \"name\": \"python\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "examples/seq2seq/train.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"seq2seq addition example.\"\"\"\n\n# See issue #620.\n# pytype: disable=wrong-keyword-args\n\nimport functools\nfrom typing import Any, Dict, Tuple\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nfrom clu import metric_writers\nfrom flax import linen as nn\nfrom flax.training import train_state\nimport jax\nimport jax.numpy as jnp\nimport optax\n\nimport models\nfrom input_pipeline import CharacterTable as CTable\nfrom input_pipeline import get_sequence_lengths\nfrom input_pipeline import mask_sequences\n\n\nArray = Any\nFLAGS = flags.FLAGS\nPRNGKey = Any\n\nflags.DEFINE_string('workdir', default='.', help='Where to store log output.')\n\nflags.DEFINE_float(\n    'learning_rate',\n    default=0.003,\n    help='The learning rate for the Adam optimizer.',\n)\n\nflags.DEFINE_integer('batch_size', default=128, help='Batch size for training.')\n\nflags.DEFINE_integer(\n    'hidden_size', default=512, help='Hidden size of the LSTM.'\n)\n\nflags.DEFINE_integer(\n    'num_train_steps', default=10000, help='Number of train steps.'\n)\n\nflags.DEFINE_integer(\n    'decode_frequency',\n    default=200,\n    help='Frequency of decoding during training, e.g. every 1000 steps.',\n)\n\nflags.DEFINE_integer(\n    'max_len_query_digit',\n    default=3,\n    help='Maximum length of a single input digit.',\n)\n\n\ndef get_model(ctable: CTable, *, teacher_force: bool = False) -> models.Seq2seq:\n  return models.Seq2seq(\n      teacher_force=teacher_force,\n      hidden_size=FLAGS.hidden_size,\n      eos_id=ctable.eos_id,\n      vocab_size=ctable.vocab_size,\n  )\n\n\ndef get_initial_params(\n    model: models.Seq2seq, rng: PRNGKey, ctable: CTable\n) -> dict[str, Any]:\n  \"\"\"Returns the initial parameters of a seq2seq model.\"\"\"\n  rng1, rng2 = jax.random.split(rng)\n  variables = model.init(\n      {'params': rng1, 'lstm': rng2},\n      jnp.ones(ctable.encoder_input_shape, jnp.float32),\n      jnp.ones(ctable.decoder_input_shape, jnp.float32),\n  )\n  return variables['params']\n\n\ndef get_train_state(rng: PRNGKey, ctable: CTable) -> train_state.TrainState:\n  \"\"\"Returns a train state.\"\"\"\n  model = get_model(ctable)\n  params = get_initial_params(model, rng, ctable)\n  tx = optax.adam(FLAGS.learning_rate)\n  state = train_state.TrainState.create(\n      apply_fn=model.apply, params=params, tx=tx\n  )\n  return state\n\n\ndef cross_entropy_loss(\n    logits: Array, labels: Array, lengths: Array\n) -> jax.Array:\n  \"\"\"Returns cross-entropy loss.\"\"\"\n  xe = jnp.sum(nn.log_softmax(logits) * labels, axis=-1)\n  masked_xe = jnp.mean(mask_sequences(xe, lengths))\n  return -masked_xe\n\n\ndef compute_metrics(\n    logits: Array, labels: Array, eos_id: int\n) -> dict[str, jax.Array]:\n  \"\"\"Computes metrics and returns them.\"\"\"\n  lengths = get_sequence_lengths(labels, eos_id)\n  loss = cross_entropy_loss(logits, labels, lengths)\n  # Computes sequence accuracy, which is the same as the accuracy during\n  # inference, since teacher forcing is irrelevant when all output are correct.\n  token_accuracy = jnp.argmax(logits, -1) == jnp.argmax(labels, -1)\n  sequence_accuracy = (\n      jnp.sum(mask_sequences(token_accuracy, lengths), axis=-1) == lengths\n  )\n  accuracy = jnp.mean(sequence_accuracy)\n  metrics = {\n      'loss': loss,\n      'accuracy': accuracy,\n  }\n  return metrics\n\n\n@jax.jit\ndef train_step(\n    state: train_state.TrainState, batch: Array, lstm_rng: PRNGKey, eos_id: int\n) -> tuple[train_state.TrainState, dict[str, jax.Array]]:\n  \"\"\"Trains one step.\"\"\"\n  labels = batch['answer'][:, 1:]\n  lstm_key = jax.random.fold_in(lstm_rng, state.step)\n\n  def loss_fn(params):\n    logits, _ = state.apply_fn(\n        {'params': params},\n        batch['query'],\n        batch['answer'],\n        rngs={'lstm': lstm_key},\n    )\n    loss = cross_entropy_loss(\n        logits, labels, get_sequence_lengths(labels, eos_id)\n    )\n    return loss, logits\n\n  grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n  (_, logits), grads = grad_fn(state.params)\n  state = state.apply_gradients(grads=grads)\n  metrics = compute_metrics(logits, labels, eos_id)\n\n  return state, metrics\n\n\ndef log_decode(question: str, inferred: str, golden: str):\n  \"\"\"Logs the given question, inferred query, and correct query.\"\"\"\n  suffix = (\n      '(CORRECT)' if inferred == golden else (f'(INCORRECT) correct={golden}')\n  )\n  logging.info('DECODE: %s = %s %s', question, inferred, suffix)\n\n\n@functools.partial(jax.jit, static_argnums=3)\ndef decode(\n    params: dict[str, Any], inputs: Array, decode_rng: PRNGKey, ctable: CTable\n) -> Array:\n  \"\"\"Decodes inputs.\"\"\"\n  init_decoder_input = ctable.one_hot(ctable.encode('=')[0:1])\n  init_decoder_inputs = jnp.tile(\n      init_decoder_input, (inputs.shape[0], ctable.max_output_len, 1)\n  )\n  model = get_model(ctable, teacher_force=False)\n  _, predictions = model.apply(\n      {'params': params}, inputs, init_decoder_inputs, rngs={'lstm': decode_rng}\n  )\n  return predictions\n\n\ndef decode_batch(\n    state: train_state.TrainState,\n    batch: dict[str, Array],\n    decode_rng: PRNGKey,\n    ctable: CTable,\n):\n  \"\"\"Decodes and log results for a batch.\"\"\"\n  inputs, outputs = batch['query'], batch['answer'][:, 1:]\n  decode_rng = jax.random.fold_in(decode_rng, state.step)\n  inferred = decode(state.params, inputs, decode_rng, ctable)\n  questions = ctable.decode_onehot(inputs)\n  infers = ctable.decode_onehot(inferred)\n  goldens = ctable.decode_onehot(outputs)\n\n  for question, inferred, golden in zip(questions, infers, goldens):\n    log_decode(question, inferred, golden)\n\n\ndef train_and_evaluate(workdir: str) -> train_state.TrainState:\n  \"\"\"Trains for a fixed number of steps and decode during training.\"\"\"\n\n  # TODO(marcvanzee): Integrate ctable with train_state.\n  ctable = CTable('0123456789+= ', FLAGS.max_len_query_digit)\n  rng = jax.random.key(0)\n  state = get_train_state(rng, ctable)\n\n  writer = metric_writers.create_default_writer(workdir)\n  for step in range(FLAGS.num_train_steps):\n    batch = ctable.get_batch(FLAGS.batch_size)\n    state, metrics = train_step(state, batch, rng, ctable.eos_id)\n    if step and step % FLAGS.decode_frequency == 0:\n      writer.write_scalars(step, metrics)\n      batch = ctable.get_batch(5)\n      decode_batch(state, batch, rng, ctable)\n\n  return state\n\n\ndef main(_):\n  _ = train_and_evaluate(FLAGS.workdir)\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": "examples/seq2seq/train_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.examples.seq2seq.train.\"\"\"\n\nimport functools\n\nfrom absl.testing import absltest\nfrom flax.training import train_state\nimport jax\nfrom jax import random\nimport numpy as np\nimport optax\n\nimport input_pipeline\nimport train\nimport models\n\njax.config.parse_flags_with_absl()\n\n\ndef create_ctable(chars='0123456789+= '):\n  return input_pipeline.CharacterTable(chars)\n\n\ndef create_train_state(ctable):\n  model = models.Seq2seq(\n      teacher_force=False,\n      hidden_size=train.FLAGS.hidden_size,\n      vocab_size=ctable.vocab_size,\n  )\n  params = train.get_initial_params(model, jax.random.key(0), ctable)\n  tx = optax.adam(train.FLAGS.learning_rate)\n  state = train_state.TrainState.create(\n      apply_fn=model.apply, params=params, tx=tx\n  )\n  return state\n\n\nclass TrainTest(absltest.TestCase):\n\n  def test_character_table(self):\n    ctable = create_ctable()\n    text = '410+19'\n    enc_text = ctable.encode(text)\n    dec_text = ctable.decode(enc_text)\n    # The text is possibly padded with whitespace, but the trimmed output should\n    # be equal to the input.\n    self.assertEqual(text, dec_text.strip())\n\n  def test_mask_sequences(self):\n    np.testing.assert_equal(\n        input_pipeline.mask_sequences(\n            np.arange(1, 13).reshape((4, 3)), np.array([3, 2, 1, 0])\n        ),\n        np.array([[1, 2, 3], [4, 5, 0], [7, 0, 0], [0, 0, 0]]),\n    )\n\n  def test_get_sequence_lengths(self):\n    oh_sequence_batch = jax.vmap(\n        functools.partial(jax.nn.one_hot, num_classes=4)\n    )(np.array([[0, 1, 0], [1, 0, 2], [1, 2, 0], [1, 2, 3]]))\n    np.testing.assert_equal(\n        input_pipeline.get_sequence_lengths(oh_sequence_batch, eos_id=0),\n        np.array([1, 2, 3, 3], np.int32),\n    )\n    np.testing.assert_equal(\n        input_pipeline.get_sequence_lengths(oh_sequence_batch, eos_id=1),\n        np.array([2, 1, 1, 1], np.int32),\n    )\n    np.testing.assert_equal(\n        input_pipeline.get_sequence_lengths(oh_sequence_batch, eos_id=2),\n        np.array([3, 3, 2, 2], np.int32),\n    )\n\n  def test_train_one_step(self):\n    ctable = create_ctable()\n    batch = ctable.get_batch(128)\n\n    state = create_train_state(ctable)\n    key = random.key(0)\n    _, train_metrics = train.train_step(state, batch, key, ctable.eos_id)\n\n    self.assertLessEqual(train_metrics['loss'], 5)\n    self.assertGreaterEqual(train_metrics['accuracy'], 0)\n\n  def test_decode_batch(self):\n    ctable = create_ctable()\n    batch = ctable.get_batch(5)\n    key = random.key(0)\n    state = create_train_state(ctable)\n    train.decode_batch(state, batch, key, ctable)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/sst2/README.md",
    "content": "## SST-2 classification\n\nTrains a simple text classifier on the SST-2 sentiment classification dataset.\n\nYou can run this code and even modify it directly in Google Colab, no\ninstallation required:\n\nhttps://colab.research.google.com/github/google/flax/blob/main/examples/sst2/sst2.ipynb\n\n### Requirements\n* TensorFlow dataset `glue/sst2` will be downloaded and prepared automatically, if necessary.\n\n### Example output\n\n| Name    | Platform  |  Epochs | Walltime   | Accuracy   | Metrics                                                                                                               | Workdir                                                                                                                        |\n|:--------|:--------|--------:|:-----------|:-----------------|:----------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------|\n| default | TPU     |     10 | 4.3m    | 85.21%           | [tensorboard.dev](https://tensorboard.dev/experiment/yTQjjRY9RlGRrZzg8h9PJw/) | |\n\n```\nINFO:absl:train epoch 010 loss 0.1918 accuracy 92.41\nINFO:absl:eval  epoch 010 loss 0.4144 accuracy 85.21\n```\n\n### How to run\n\n```bash\npython main.py --workdir=/tmp/sst2 --config=configs/default.py`\n```\n\n#### Overriding Hyperparameter configurations\n\nThe SST2 example allows specifying a hyperparameter configuration by means of\nsetting the `--config` flag. The configuration flag is defined using\n[config_flags](https://github.com/google/ml_collections/tree/master#config-flags).\n`config_flags` allows overriding configuration fields. This can be done as\nfollows:\n\n```shell\npython main.py \\\n    --workdir=/tmp/sst2 --config=configs/default.py \\\n    --config.learning_rate=0.05 --config.num_epochs=5\n```\n"
  },
  {
    "path": "examples/sst2/build_vocabulary.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"A vocabulary builder that generates vocab.txt to be used for training.\"\"\"\n\nimport time\nfrom collections.abc import Iterable, Sequence\n\nfrom absl import logging\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\nimport tensorflow_text as tftext\n\nimport vocabulary\n\n\ndef get_tokenized_sequences(\n    dataset: tf.data.Dataset,\n    tokenizer: tftext.Tokenizer = tftext.WhitespaceTokenizer(),\n    input_key: str = 'sentence',\n) -> Iterable[Sequence[bytes]]:\n  \"\"\"Returns tokenized sequences for vocabulary building.\"\"\"\n  dataset = dataset.map(\n      lambda example: tokenizer.tokenize(example[input_key]),\n      num_parallel_calls=tf.data.experimental.AUTOTUNE,\n  )\n  yield from tfds.as_numpy(dataset)\n\n\nif __name__ == '__main__':\n  logging.set_verbosity(logging.INFO)\n  start_time = time.time()\n\n  # Loads the dataset to build the vocabulary from. We use the train split.\n  dataset = tfds.load('glue/sst2', split='train')\n\n  # Tokenizes the sequences in the dataset and keeps only those.\n  tokenized_sequences = get_tokenized_sequences(dataset)\n\n  # Builds the vocabulary from the tokenized sequences.\n  # A token needs to appear at least 3 times to be in the vocabulary. You can\n  # play with this. It is there to make sure we don't overfit on rare words.\n  vocab = vocabulary.Vocabulary(\n      tokenized_sequences=tokenized_sequences, min_freq=3\n  )\n  vocab.save('vocab.txt')\n\n  logging.info('Total time elapsed: %f s', time.time() - start_time)\n"
  },
  {
    "path": "examples/sst2/configs/default.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Default hyperparameter configuration for SST-2.\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = ml_collections.ConfigDict()\n\n  config.embedding_size = 300\n  config.hidden_size = 256\n  config.vocab_size = None\n  config.output_size = 1\n\n  config.vocab_path = 'vocab.txt'\n  config.max_input_length = 60\n\n  config.dropout_rate = 0.5\n  config.word_dropout_rate = 0.1\n  config.unk_idx = 1\n\n  config.learning_rate = 0.1\n  config.momentum = 0.9\n  config.weight_decay = 3e-6\n\n  config.batch_size = 64\n  config.bucket_size = 8\n  config.num_epochs = 10\n\n  config.seed = 0\n\n  return config\n"
  },
  {
    "path": "examples/sst2/input_pipeline.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"SST-2 input pipeline.\"\"\"\n\nimport sys\nfrom typing import Any, Dict, Optional\n\nfrom absl import logging\nimport numpy as np\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\nif sys.version_info < (3, 13):\n  import tensorflow_text as text\n\nimport vocabulary\n\n\nAUTOTUNE = tf.data.experimental.AUTOTUNE\nExample = dict[str, tf.Tensor]\n\n\ndef get_bucket_boundaries(bucket_size: int, max_size: int) -> np.ndarray:\n  \"\"\"Bucket boundaries with `bucket_size` items per bucket, up to `max_size`.\n\n  Example:\n  ```\n  get_bucket_boundaries(8, 24)\n  [9, 17, 25]\n  ```\n  E.g., the first boundary covers items with sizes 0-8, the next boundary covers\n  items with sizes 9-16, and the last bucket covers sizes 17-24. Each bucket\n  covers 8 different sizes (e.g., sentence lengths).\n\n  Args:\n   bucket_size: The number of different items per bucket.\n   max_size: The maximum size to be expected for a bucket.\n\n  Returns:\n    A list of (exclusive) bucket boundaries.\n  \"\"\"\n  return np.arange(bucket_size, max_size + bucket_size, bucket_size) + 1\n\n\ndef get_num_examples(dataset: tf.data.Dataset) -> int:\n  \"\"\"Returns the number of examples in the dataset by iterating over it once.\"\"\"\n  return dataset.reduce(np.int64(0), lambda x, _: x + 1).numpy()\n\n\ndef get_bucketed_batches(\n    dataset: tf.data.Dataset,\n    batch_size: int,\n    bucket_size: int,\n    max_length: int,\n    padded_shapes: Any,\n    example_size_fn: Any,\n    shuffle: bool = False,\n    shuffle_seed: int | None = None,\n    drop_remainder: bool = False,\n) -> tf.data.Dataset:\n  \"\"\"Returns padded batches of shuffled examples bucketed by length.\n\n  This shuffles the examples randomly each epoch. The random order is\n  deterministic and controlled by the seed.\n\n  Batches are padded because sentences have different lengths.\n  Sentences that are shorter in a batch will get 0s added at the end, until\n  all sentences in the batch have the same length.\n\n  For performance, examples of similar lengths are bucketed together. However,\n  the contents of the buckets and their order is random each epoch, and\n  controlled by the seed.\n\n  Args:\n    dataset: A TF Dataset with SST examples to be shuffled and batched.\n    batch_size: The size of each batch. The remainder is dropped.\n    bucket_size: How many different lengths go in each bucket.\n    max_length: The maximum length to provide a bucket for.\n    padded_shapes: A nested structure representing the shape to which the\n      respective component of each input element should be padded prior to\n      batching. See `tf.data.Dataset.padded_batch` for examples.\n    example_size_fn: A TF function that returns the size of an example to\n      determine in which bucket it goes. E.g., the sentence length.\n    shuffle: Shuffle the dataset each epoch using seed.\n    shuffle_seed: The seed that determines the shuffling order, with a\n      different order each epoch.\n    drop_remainder: Drop the last batch if it is not of size batch_size.\n\n  Returns:\n    A TF Dataset containing padded bucketed batches.\n  \"\"\"\n  if shuffle:\n    assert shuffle_seed is not None, 'When shuffling you must provide a seed.'\n\n  # For bucket_size 8 and max length 24, we get bucket boundaries [9, 17, 25].\n  bucket_boundaries = get_bucket_boundaries(bucket_size, max_length)\n  logging.info('Batching bucket boundaries: %r', bucket_boundaries)\n\n  # One batch size for each bucket plus one additional one (per requirement).\n  bucket_batch_sizes = [batch_size] * (len(bucket_boundaries) + 1)\n  bucket_fn = tf.data.experimental.bucket_by_sequence_length(\n      example_size_fn,\n      bucket_boundaries,\n      bucket_batch_sizes,\n      padded_shapes=padded_shapes,\n      pad_to_bucket_boundary=True,\n      drop_remainder=drop_remainder,\n  )\n\n  if shuffle:\n    # For shuffling we need to know how many training examples we have.\n    num_examples = get_num_examples(dataset)\n    num_batches = num_examples // batch_size\n    return (\n        dataset.shuffle(\n            num_examples, seed=shuffle_seed, reshuffle_each_iteration=True\n        )\n        .apply(bucket_fn)\n        .shuffle(num_batches, seed=shuffle_seed, reshuffle_each_iteration=True)\n        .prefetch(tf.data.experimental.AUTOTUNE)\n    )\n  return dataset.apply(bucket_fn).prefetch(tf.data.experimental.AUTOTUNE)\n\n\ndef vocab_to_hashtable(\n    vocab: vocabulary.Vocabulary, unk_idx: int\n) -> tf.lookup.StaticHashTable:\n  \"\"\"Returns a TF lookup table (token -> ID) from a vocabulary.\"\"\"\n  return tf.lookup.StaticHashTable(\n      tf.lookup.KeyValueTensorInitializer(\n          list(vocab.keys()), list(vocab.values())\n      ),\n      default_value=unk_idx,\n  )\n\n\ndef vocab_to_inverse_hashtable(\n    vocab: vocabulary.Vocabulary, unk_token: bytes\n) -> tf.lookup.StaticHashTable:\n  \"\"\"Returns an inverse TF lookup table (ID -> token) from a vocabulary.\"\"\"\n  return tf.lookup.StaticHashTable(\n      tf.lookup.KeyValueTensorInitializer(\n          list(vocab.values()),\n          list(vocab.keys()),\n          key_dtype=tf.int64,\n          value_dtype=tf.string,\n      ),\n      default_value=unk_token,\n  )\n\n\ndef _is_text_field(feature_name_and_type):\n  \"\"\"Identifies a text field when given a feature (name, type) pair.\"\"\"\n  _, feature_type = feature_name_and_type\n  return isinstance(feature_type, tfds.features.Text)\n\n\ndef _is_class_label(feature_name_and_type):\n  \"\"\"Identifies a class label field when given a feature (name, type) pair.\"\"\"\n  _, feature_type = feature_name_and_type\n  return isinstance(feature_type, tfds.features.ClassLabel)\n\n\nclass TextDataset:\n  \"\"\"A text dataset with one sequence as input and a label.\"\"\"\n\n  def __init__(\n      self,\n      tfds_name: str = 'glue/sst2',\n      vocab_path: str = 'vocab.txt',\n      tokenizer=None,\n      split='train',\n  ):\n    \"\"\"Initializes the SST2 data source.\"\"\"\n    self.dataset, self.info = tfds.load(tfds_name, split=split, with_info=True)\n\n    # Look up the feature name of the text and label in the dataset.\n    # We assume there is one text input and one label.\n    text_fields = filter(_is_text_field, self.info.features.items())\n    label_fields = filter(_is_class_label, self.info.features.items())\n    self.text_feature_name, _ = next(text_fields)\n    self.label_feature_name, _ = next(label_fields)\n\n    # Load the vocabulary.\n    self.vocab = vocabulary.Vocabulary(vocab_path=vocab_path)\n\n    # Convert the sentences to sequences of token IDs and compute length.\n    if tokenizer is None:\n       tokenizer = text.WhitespaceTokenizer()\n    self.tokenizer = tokenizer\n    self.tf_vocab = vocab_to_hashtable(self.vocab, unk_idx=self.vocab.unk_idx)\n    self.examples = self.dataset.map(\n        self.prepare_example, num_parallel_calls=AUTOTUNE\n    ).cache()\n\n  @property\n  def padded_shapes(self):\n    \"\"\"The padded shapes used by batching functions.\"\"\"\n    # None means variable length; pads to the longest sequence in the batch.\n    return {'idx': [], 'token_ids': [None], 'label': [], 'length': []}\n\n  def example_length_fn(self, example: Example) -> tf.Tensor:\n    \"\"\"Returns the length of the example for the purpose of the bucketing.\"\"\"\n    return tf.size(example['token_ids'])\n\n  def add_bos_eos(self, sequence: tf.Tensor) -> tf.Tensor:\n    \"\"\"Prepends BOS ID and appends EOS ID to a sequence of token IDs.\"\"\"\n    return tf.concat([[self.vocab.bos_idx], sequence, [self.vocab.eos_idx]], 0)\n\n  def prepare_example(self, example: Example) -> Example:\n    \"\"\"Prepares an example by converting text to token IDs.\"\"\"\n    tokens = self.tokenizer.tokenize(example[self.text_feature_name])\n    label = example[self.label_feature_name]\n    del example[self.text_feature_name]\n    del example[self.label_feature_name]\n    example['token_ids'] = self.add_bos_eos(self.tf_vocab.lookup(tokens))\n    example['length'] = tf.size(example['token_ids'])\n    example['label'] = label\n    return example\n\n  def get_batches(\n      self,\n      batch_size: int,\n      drop_remainder: bool = False,\n      shuffle: bool = False,\n      shuffle_seed: int | None = None,\n      fixed_pad_length: int | None = None,\n      dataset: tf.data.Dataset | None = None,\n  ):\n    \"\"\"Returns an iterator with padded batches for the provided dataset.\"\"\"\n    if dataset is None:\n      dataset = self.examples\n    if shuffle:\n      buffer_size = get_num_examples(dataset)\n      dataset = dataset.shuffle(\n          buffer_size, seed=shuffle_seed, reshuffle_each_iteration=True\n      )\n    padded_shapes = {k: v for k, v in self.padded_shapes.items()}\n    if fixed_pad_length is not None:\n      padded_shapes['token_ids'] = fixed_pad_length\n    return dataset.padded_batch(\n        batch_size, padded_shapes=padded_shapes, drop_remainder=drop_remainder\n    )\n\n  def get_bucketed_batches(\n      self,\n      batch_size: int,\n      bucket_size: int,\n      max_input_length: int,\n      drop_remainder: bool = False,\n      shuffle: bool = False,\n      shuffle_seed: int | None = None,\n      dataset: tf.data.Dataset | None = None,\n  ):\n    \"\"\"Returns an iterator with bucketed batches for the provided dataset.\"\"\"\n    if dataset is None:\n      dataset = self.examples\n    return get_bucketed_batches(\n        dataset,\n        batch_size,\n        bucket_size,\n        max_input_length,\n        self.padded_shapes,\n        self.example_length_fn,\n        shuffle=shuffle,\n        shuffle_seed=shuffle_seed,\n        drop_remainder=drop_remainder,\n    )\n"
  },
  {
    "path": "examples/sst2/input_pipeline_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport pathlib\nimport sys\nimport tempfile\n\nfrom absl.testing import absltest\nimport tensorflow_datasets as tfds\n\nimport input_pipeline\nimport vocabulary\n\n\nclass InputPipelineTest(absltest.TestCase):\n\n  def setUp(self):\n    super().setUp()\n    if sys.version_info >= (3, 13):\n      self.skipTest('Test (and tensorflow-text) does not suport Python 3.13+')\n    self.vocab_path = self._get_vocab_path()\n    self.dataset = self._get_dataset(self.vocab_path)\n\n  def _get_vocab_path(self):\n    \"\"\"Prepares a mock vocabulary and returns a path to it.\"\"\"\n    vocab_path = os.path.join(tempfile.mkdtemp(), 'vocab.txt')\n    tokenized_sequences = (\n        (b'this', b'is', b'a', b'test', b'sentence'),\n        (b'this', b'is', b'a', b'test', b'sentence'),\n        (b'this', b'is', b'a', b'test', b'sentence'),\n    )\n    vocab = vocabulary.Vocabulary(tokenized_sequences=tokenized_sequences)\n    vocab.save(vocab_path)\n    return vocab_path\n\n  def _get_dataset(self, vocab_path):\n    \"\"\"Uses mock data to create the dataset.\"\"\"\n    # Go two directories up to the root of the flax directory.\n    flax_root_dir = pathlib.Path(__file__).parents[2]\n    data_dir = str(flax_root_dir) + '/.tfds/metadata'  # pylint: disable=unused-variable\n    with tfds.testing.mock_data(num_examples=128, data_dir=data_dir):\n      return input_pipeline.TextDataset(vocab_path=vocab_path, split='train')\n\n  def test_bucketed_dataset(self):\n    \"\"\"Each batch should have a length that is a multiple of bucket_size.\"\"\"\n    batch_size = 2\n    bucket_size = 8\n    for batch in self.dataset.get_bucketed_batches(\n        batch_size=batch_size,\n        bucket_size=bucket_size,\n        max_input_length=60,\n        shuffle=False,\n    ).take(3):\n      # Because of bucketing, sequence length must be multiple of bucket_size.\n      length = batch['token_ids'].numpy().shape[-1]\n      self.assertEqual(0, length % bucket_size)\n      self.assertEqual((batch_size,), batch['length'].numpy().shape)\n      self.assertEqual((batch_size,), batch['label'].numpy().shape)\n\n  def test_batched_dataset(self):\n    \"\"\"Tests that the length of a batch matches the longest sequence.\"\"\"\n    batch_size = 2\n    for batch in self.dataset.get_batches(\n        batch_size=batch_size, shuffle=False\n    ).take(1):\n      # Each batch is padded to the maximum sentence length in the batch.\n      max_length_in_batch = max(batch['length'].numpy())\n      length = batch['token_ids'].numpy().shape[-1]\n      self.assertEqual(max_length_in_batch, length)\n      self.assertEqual((batch_size,), batch['length'].numpy().shape)\n      self.assertEqual((batch_size,), batch['label'].numpy().shape)\n\n  def test_batched_dataset_fixed_length(self):\n    \"\"\"Tests that each batch has the fixed length.\"\"\"\n    batch_size = 2\n    fixed_pad_length = 77\n    for batch in self.dataset.get_batches(\n        batch_size=batch_size, shuffle=False, fixed_pad_length=fixed_pad_length\n    ).take(1):\n      length = batch['token_ids'].numpy().shape[-1]\n      self.assertEqual(fixed_pad_length, length)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/sst2/main.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Main file for running the SST2 example.\nThis file is intentionally kept short. The majority for logic is in libraries\nthat can be easily tested and imported in Colab.\n\"\"\"\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nfrom clu import platform\nimport jax\nfrom ml_collections import config_flags\nimport tensorflow as tf\n\nimport train\n\n\nFLAGS = flags.FLAGS\n\nflags.DEFINE_string('workdir', None, 'Directory to store model data.')\nconfig_flags.DEFINE_config_file(\n    'config',\n    None,\n    'File path to the training hyperparameter configuration.',\n    lock_config=True,\n)\nflags.mark_flags_as_required(['config', 'workdir'])\n\n\ndef main(argv):\n  if len(argv) > 1:\n    raise app.UsageError('Too many command-line arguments.')\n\n  # Hide any GPUs from TensorFlow. Otherwise TF might reserve memory and make\n  # it unavailable to JAX.\n  tf.config.experimental.set_visible_devices([], 'GPU')\n\n  logging.info('JAX process: %d / %d', jax.process_index(), jax.process_count())\n  logging.info('JAX local devices: %r', jax.local_devices())\n\n  # Add a note so that we can tell which task is which JAX host.\n  # (Depending on the platform task 0 is not guaranteed to be host 0)\n  platform.work_unit().set_task_status(\n      f'process_index: {jax.process_index()}, '\n      f'process_count: {jax.process_count()}'\n  )\n  platform.work_unit().create_artifact(\n      platform.ArtifactType.DIRECTORY, FLAGS.workdir, 'workdir'\n  )\n\n  train.train_and_evaluate(FLAGS.config, FLAGS.workdir)\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": "examples/sst2/models.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"A text classification model.\"\"\"\n\nimport functools\nfrom typing import Any, Optional\nfrom collections.abc import Callable\n\nfrom flax import linen as nn\nimport jax\nfrom jax import numpy as jnp\n\nArray = jnp.ndarray\n\n\ndef sequence_mask(lengths: Array, max_length: int) -> Array:\n  \"\"\"Computes a boolean mask over sequence positions for each given length.\n\n  Example:\n  ```\n  sequence_mask([1, 2], 3)\n  [[True, False, False],\n   [True, True, False]]\n  ```\n\n  Args:\n    lengths: The length of each sequence. <int>[batch_size]\n    max_length: The width of the boolean mask. Must be >= max(lengths).\n\n  Returns:\n    A mask with shape: <bool>[batch_size, max_length] indicating which\n    positions are valid for each sequence.\n  \"\"\"\n  return jnp.arange(max_length)[None] < lengths[:, None]\n\n\n@jax.vmap\ndef flip_sequences(inputs: Array, lengths: Array) -> Array:\n  \"\"\"Flips a sequence of inputs along the time dimension.\n\n  This function can be used to prepare inputs for the reverse direction of a\n  bidirectional LSTM. It solves the issue that, when naively flipping multiple\n  padded sequences stored in a matrix, the first elements would be padding\n  values for those sequences that were padded. This function keeps the padding\n  at the end, while flipping the rest of the elements.\n\n  Example:\n  ```python\n  inputs = [[1, 0, 0],\n            [2, 3, 0]\n            [4, 5, 6]]\n  lengths = [1, 2, 3]\n  flip_sequences(inputs, lengths) = [[1, 0, 0],\n                                     [3, 2, 0],\n                                     [6, 5, 4]]\n  ```\n\n  Args:\n    inputs: An array of input IDs <int>[batch_size, seq_length].\n    lengths: The length of each sequence <int>[batch_size].\n\n  Returns:\n    An ndarray with the flipped inputs.\n  \"\"\"\n  # Note: since this function is vmapped, the code below is effectively for\n  # a single example.\n  max_length = inputs.shape[0]\n  return jnp.flip(jnp.roll(inputs, max_length - lengths, axis=0), axis=0)\n\n\nclass WordDropout(nn.Module):\n  \"\"\"Applies word dropout to a batch of input IDs.\n\n  This is basically the same as `nn.Dropout`, but allows specifying the\n  value of dropped out items.\n  \"\"\"\n\n  dropout_rate: float\n  unk_idx: int\n  deterministic: bool | None = None\n\n  @nn.compact\n  def __call__(self, inputs: Array, deterministic: bool | None = None):\n    deterministic = nn.module.merge_param(\n        'deterministic', self.deterministic, deterministic\n    )\n    if deterministic or self.dropout_rate == 0.0:\n      return inputs\n    rng = self.make_rng('dropout')\n    mask = jax.random.bernoulli(rng, p=self.dropout_rate, shape=inputs.shape)\n    return jnp.where(mask, jnp.array([self.unk_idx]), inputs)\n\n\nclass Embedder(nn.Module):\n  \"\"\"Embeds batches of token IDs into feature space.\n\n  Attributes:\n    vocab_size: The size of the vocabulary (i.e., the number of embeddings).\n    embedding_size: The dimensionality of the embeddings.\n    embedding_init: The initializer used to initialize the embeddings.\n    frozen: Freezes the embeddings table, keeping it fixed at initial values.\n    dropout_rate: Percentage of units to drop after embedding the inputs.\n    word_dropout_rate: Percentage of input words to replace with unk_idx.\n    unk_idx: The index (integer) to use to replace inputs for word dropout.\n  \"\"\"\n\n  vocab_size: int\n  embedding_size: int\n  embedding_init: Callable[..., Array] = nn.initializers.normal(stddev=0.1)\n  frozen: bool = False\n  dropout_rate: float = 0.0\n  word_dropout_rate: float = 0.0\n  unk_idx: int | None = None\n  deterministic: bool | None = None\n  dtype: jnp.dtype = jnp.float32\n\n  def setup(self):\n    self.embedding = self.param(\n        'embedding',\n        self.embedding_init,\n        (self.vocab_size, self.embedding_size),\n        self.dtype,\n    )\n    self.dropout_layer = nn.Dropout(rate=self.dropout_rate)\n    self.word_dropout_layer = WordDropout(\n        dropout_rate=self.word_dropout_rate, unk_idx=self.unk_idx\n    )\n\n  def __call__(\n      self, inputs: Array, deterministic: bool | None = None\n  ) -> Array:\n    \"\"\"Embeds the input sequences and applies word dropout and dropout.\n\n    Args:\n      inputs: Batch of input token ID sequences <int64>[batch_size, seq_length].\n      deterministic: Disables dropout when set to True.\n\n    Returns:\n      The embedded inputs, shape: <float32>[batch_size, seq_length,\n      embedding_size].\n    \"\"\"\n    deterministic = nn.module.merge_param(\n        'deterministic', self.deterministic, deterministic\n    )\n    inputs = self.word_dropout_layer(inputs, deterministic=deterministic)\n    embedded_inputs = self.embedding[inputs]\n\n    # Keep the embeddings fixed at initial (e.g. pretrained) values.\n    if self.frozen:\n      embedded_inputs = jax.lax.stop_gradient(embedded_inputs)\n\n    return self.dropout_layer(embedded_inputs, deterministic=deterministic)\n\n\nclass SimpleLSTM(nn.Module):\n  \"\"\"A simple unidirectional LSTM.\"\"\"\n\n  hidden_size: int\n\n  @functools.partial(\n      nn.transforms.scan,\n      variable_broadcast='params',\n      in_axes=1,\n      out_axes=1,\n      split_rngs={'params': False},\n  )\n  @nn.compact\n  def __call__(self, carry, x):\n    return nn.OptimizedLSTMCell(self.hidden_size)(carry, x)\n\n  def initialize_carry(self, input_shape):\n    # Use fixed random key since default state init fn is just zeros.\n    return nn.OptimizedLSTMCell(self.hidden_size, parent=None).initialize_carry(\n        jax.random.key(0), input_shape\n    )\n\n\nclass SimpleBiLSTM(nn.Module):\n  \"\"\"A simple bi-directional LSTM.\"\"\"\n\n  hidden_size: int\n\n  def setup(self):\n    self.forward_lstm = SimpleLSTM(self.hidden_size)\n    self.backward_lstm = SimpleLSTM(self.hidden_size)\n\n  def __call__(self, embedded_inputs, lengths):\n    # Forward LSTM.\n    initial_state = self.forward_lstm.initialize_carry(\n        embedded_inputs[:, 0].shape\n    )\n    _, forward_outputs = self.forward_lstm(initial_state, embedded_inputs)\n\n    # Backward LSTM.\n    reversed_inputs = flip_sequences(embedded_inputs, lengths)\n    initial_state = self.backward_lstm.initialize_carry(\n        reversed_inputs[:, 0].shape\n    )\n    _, backward_outputs = self.backward_lstm(initial_state, reversed_inputs)\n    backward_outputs = flip_sequences(backward_outputs, lengths)\n\n    # Concatenate the forward and backward representations.\n    outputs = jnp.concatenate([forward_outputs, backward_outputs], -1)\n    return outputs\n\n\nclass MLP(nn.Module):\n  \"\"\"A simple Multilayer perceptron with 1 hidden layer.\n\n  Attributes:\n    hidden_size: The size of the hidden layer.\n    output_size: The size of the output.\n    activation: The activation function to apply to the hidden layer.\n    dropout_rate: The dropout rate applied to the hidden layer.\n    output_bias: If False, do not use a bias term in the last layer.\n    deterministic: Disables dropout if set to True.\n  \"\"\"\n\n  hidden_size: int\n  output_size: int\n  activation: Callable[..., Any] = nn.tanh\n  dropout_rate: float = 0.0\n  output_bias: bool = False\n  deterministic: bool | None = None\n\n  def setup(self):\n    self.intermediate_layer = nn.Dense(self.hidden_size)\n    self.output_layer = nn.Dense(self.output_size, use_bias=self.output_bias)\n    self.dropout_layer = nn.Dropout(rate=self.dropout_rate)\n\n  def __call__(self, inputs: Array, deterministic: bool | None = None):\n    \"\"\"Applies the MLP to the last dimension of the inputs.\n\n    Args:\n      inputs: <float32>[batch_size, ..., input_features].\n      deterministic: Disables dropout when set to True.\n\n    Returns:\n      The MLP output <float32>[batch_size, ..., output_size]\n    \"\"\"\n    deterministic = nn.module.merge_param(\n        'deterministic', self.deterministic, deterministic\n    )\n    hidden = self.intermediate_layer(inputs)\n    hidden = self.activation(hidden)\n    hidden = self.dropout_layer(hidden, deterministic=deterministic)\n    output = self.output_layer(hidden)\n    return output\n\n\nclass KeysOnlyMlpAttention(nn.Module):\n  \"\"\"Computes MLP-based attention scores based on keys alone, without a query.\n\n  Attention scores are computed by feeding the keys through an MLP. This\n  results in a single scalar per key, and for each sequence the attention\n  scores are normalized using a softmax so that they sum to 1. Invalid key\n  positions are ignored as indicated by the mask. This is also called\n  \"Bahdanau attention\" and was originally proposed in:\n  ```\n  Bahdanau et al., 2015. Neural Machine Translation by Jointly Learning to\n  Align and Translate. ICLR. https://arxiv.org/abs/1409.0473\n  ```\n\n  Attributes:\n    hidden_size: The hidden size of the MLP that computes the attention score.\n  \"\"\"\n\n  hidden_size: int\n\n  @nn.compact\n  def __call__(self, keys: Array, mask: Array) -> Array:\n    \"\"\"Applies model to the input keys and mask.\n\n    Args:\n      keys: The inputs for which to compute an attention score. Shape:\n        <float32>[batch_size, seq_length, embeddings_size].\n      mask: A mask that determines which values in `keys` are valid. Only\n        values for which the mask is True will get non-zero attention scores.\n        <bool>[batch_size, seq_length].\n\n    Returns:\n      The normalized attention scores. <float32>[batch_size, seq_length].\n    \"\"\"\n    hidden = nn.Dense(self.hidden_size, name='keys', use_bias=False)(keys)\n    energy = nn.tanh(hidden)\n    scores = nn.Dense(1, name='energy', use_bias=False)(energy)\n    scores = scores.squeeze(-1)  # New shape: <float32>[batch_size, seq_len].\n    scores = jnp.where(mask, scores, -jnp.inf)  # Using exp(-inf) = 0 below.\n    scores = nn.softmax(scores, axis=-1)\n\n    # Captures the scores if 'intermediates' is mutable, otherwise does nothing.\n    self.sow('intermediates', 'attention', scores)\n\n    return scores\n\n\nclass AttentionClassifier(nn.Module):\n  \"\"\"A classifier that uses attention to summarize the inputs.\n\n  Attributes:\n    hidden_size: The hidden size of the MLP classifier.\n    output_size: The number of output classes for the classifier.\n    dropout_rate: The dropout rate applied over the encoded_inputs, the summary\n      of the inputs, and inside the MLP. Applied when `deterministic` is False.\n    deterministic: Disables dropout if True.\n  \"\"\"\n\n  hidden_size: int\n  output_size: int\n  dropout_rate: float = 0.0\n  deterministic: bool | None = None\n\n  def setup(self):\n    self.dropout_layer = nn.Dropout(rate=self.dropout_rate)\n    self.keys_only_mlp_attention = KeysOnlyMlpAttention(\n        hidden_size=self.hidden_size\n    )\n    self.mlp = MLP(\n        hidden_size=self.hidden_size,\n        output_size=self.output_size,\n        output_bias=False,\n        dropout_rate=self.dropout_rate,\n    )\n\n  def __call__(\n      self,\n      encoded_inputs: Array,\n      lengths: Array,\n      deterministic: bool | None = None,\n  ) -> Array:\n    \"\"\"Applies model to the encoded inputs.\n\n    Args:\n      encoded_inputs: The inputs (e.g., sentences) that have already been\n        encoded by some encoder, e.g., an LSTM. <float32>[batch_size,\n        seq_length, encoded_inputs_size].\n      lengths: The lengths of the inputs. <int64>[batch_size].\n      deterministic: Disables dropout when set to True.\n\n    Returns:\n      An array of logits <float32>[batch_size, output_size].\n    \"\"\"\n    deterministic = nn.module.merge_param(\n        'deterministic', self.deterministic, deterministic\n    )\n    encoded_inputs = self.dropout_layer(\n        encoded_inputs, deterministic=deterministic\n    )\n\n    # Compute attention. attention.shape: <float32>[batch_size, seq_len].\n    mask = sequence_mask(lengths, encoded_inputs.shape[1])\n    attention = self.keys_only_mlp_attention(encoded_inputs, mask)\n\n    # Summarize the inputs by taking their weighted sum using attention scores.\n    context = jnp.expand_dims(attention, 1) @ encoded_inputs\n    context = context.squeeze(1)  # <float32>[batch_size, encoded_inputs_size]\n    context = self.dropout_layer(context, deterministic=deterministic)\n\n    # Make the final prediction from the context vector (the summarized inputs).\n    logits = self.mlp(context, deterministic=deterministic)\n    return logits\n\n\nclass TextClassifier(nn.Module):\n  \"\"\"A Text Classification model.\"\"\"\n\n  embedding_size: int\n  hidden_size: int\n  vocab_size: int\n  output_size: int\n\n  dropout_rate: float\n  word_dropout_rate: float\n  unk_idx: int = 1\n  deterministic: bool | None = None\n\n  def setup(self):\n    self.embedder = Embedder(\n        vocab_size=self.vocab_size,\n        embedding_size=self.embedding_size,\n        dropout_rate=self.dropout_rate,\n        word_dropout_rate=self.word_dropout_rate,\n        unk_idx=self.unk_idx,\n    )\n    self.encoder = SimpleBiLSTM(hidden_size=self.hidden_size)\n    self.classifier = AttentionClassifier(\n        hidden_size=self.hidden_size,\n        output_size=self.output_size,\n        dropout_rate=self.dropout_rate,\n    )\n\n  def embed_token_ids(\n      self, token_ids: Array, deterministic: bool | None = None\n  ) -> Array:\n    deterministic = nn.module.merge_param(\n        'deterministic', self.deterministic, deterministic\n    )\n    return self.embedder(token_ids, deterministic=deterministic)\n\n  def logits_from_embedded_inputs(\n      self,\n      embedded_inputs: Array,\n      lengths: Array,\n      deterministic: bool | None = None,\n  ) -> Array:\n    deterministic = nn.module.merge_param(\n        'deterministic', self.deterministic, deterministic\n    )\n    encoded_inputs = self.encoder(embedded_inputs, lengths)\n    return self.classifier(encoded_inputs, lengths, deterministic=deterministic)\n\n  def __call__(\n      self,\n      token_ids: Array,\n      lengths: Array,\n      deterministic: bool | None = None,\n  ) -> Array:\n    \"\"\"Embeds the token IDs, encodes them, and classifies with attention.\"\"\"\n    embedded_inputs = self.embed_token_ids(\n        token_ids, deterministic=deterministic\n    )\n    logits = self.logits_from_embedded_inputs(\n        embedded_inputs, lengths, deterministic=deterministic\n    )\n    return logits\n"
  },
  {
    "path": "examples/sst2/models_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for sst2.models.\"\"\"\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nimport models\nimport jax\nimport jax.test_util\nimport numpy as np\n\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass ModelTest(parameterized.TestCase):\n\n  def test_embedder_returns_correct_output_shape(self):\n    \"\"\"Tests if the embedder returns the correct shape.\"\"\"\n    vocab_size = 5\n    embedding_size = 3\n    model = models.Embedder(\n        vocab_size=vocab_size, embedding_size=embedding_size\n    )\n    rng = jax.random.key(0)\n    token_ids = np.array([[2, 4, 3], [2, 6, 3]], dtype=np.int32)\n    output, _ = model.init_with_output(rng, token_ids, deterministic=True)\n    self.assertEqual((token_ids.shape) + (embedding_size,), output.shape)\n\n  def test_lstm_returns_correct_output_shape(self):\n    \"\"\"Tests if the simple LSTM returns the correct shape.\"\"\"\n    batch_size = 2\n    seq_len = 3\n    embedding_size = 4\n    hidden_size = 5\n    model = models.SimpleLSTM(5)\n    rng = jax.random.key(0)\n    inputs = np.random.RandomState(0).normal(\n        size=[batch_size, seq_len, embedding_size]\n    )\n    initial_state = model.initialize_carry(inputs[:, 0].shape)\n    (_, output), _ = model.init_with_output(rng, initial_state, inputs)\n    self.assertEqual((batch_size, seq_len, hidden_size), output.shape)\n\n  def test_bilstm_returns_correct_output_shape(self):\n    \"\"\"Tests if the simple BiLSTM returns the correct shape.\"\"\"\n    batch_size = 2\n    seq_len = 3\n    embedding_size = 4\n    hidden_size = 5\n    model = models.SimpleBiLSTM(hidden_size=hidden_size)\n    rng = jax.random.key(0)\n    inputs = np.random.RandomState(0).normal(\n        size=[batch_size, seq_len, embedding_size]\n    )\n    lengths = np.array([2, 3], dtype=np.int32)\n    outputs, _ = model.init_with_output(rng, inputs, lengths)\n    # We expect 2*hidden_size because we concatenate forward+backward LSTMs.\n    self.assertEqual((batch_size, seq_len, 2 * hidden_size), outputs.shape)\n\n  def test_text_classifier_returns_correct_output_shape(self):\n    \"\"\"Tests if a TextClassifier model returns the correct shape.\"\"\"\n    embedding_size = 3\n    hidden_size = 7\n    vocab_size = 5\n    output_size = 3\n    dropout_rate = 0.1\n    word_dropout_rate = 0.2\n    unk_idx = 1\n\n    model = models.TextClassifier(\n        embedding_size=embedding_size,\n        hidden_size=hidden_size,\n        vocab_size=vocab_size,\n        output_size=output_size,\n        dropout_rate=dropout_rate,\n        word_dropout_rate=word_dropout_rate,\n        unk_idx=unk_idx,\n        deterministic=True,\n    )\n\n    rng = jax.random.key(0)\n    token_ids = np.array([[2, 4, 3], [2, 6, 3]], dtype=np.int32)\n    lengths = np.array([2, 3], dtype=np.int32)\n    output, _ = model.init_with_output(rng, token_ids, lengths)\n    batch_size = token_ids.shape[0]\n    self.assertEqual((batch_size, output_size), output.shape)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/sst2/requirements.txt",
    "content": "absl-py==1.0.0\nclu==0.0.6\nflax==0.3.6\nml-collections==0.1.0\nnumpy==1.22.0\noptax==0.1.0\ntensorflow==2.11.1\ntensorflow-datasets==4.4.0\ntensorflow-text==2.7.0\n"
  },
  {
    "path": "examples/sst2/sst2.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"id\": \"29fb3c7c\",\n   \"cell_type\": \"markdown\",\n   \"source\": [\n    \"# Flax SST-2 Example\\n\",\n    \"\\n\",\n    \"\\u003ca href=\\\"https://colab.research.google.com/github/google/flax/blob/main/examples/sst2/sst2.ipynb\\\" \\u003e\\u003cimg src=\\\"https://colab.research.google.com/assets/colab-badge.svg\\\" alt=\\\"Open In Colab\\\"/\\u003e\\u003c/a\\u003e\\n\",\n    \"\\n\",\n    \"Demonstration notebook for\\n\",\n    \"https://github.com/google/flax/tree/main/examples/sst2\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"5b526b04\",\n   \"cell_type\": \"markdown\",\n   \"source\": [\n    \"**Before you start:** Select Runtime -\\u003e Change runtime type -\\u003e GPU.\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"89ec78c1\",\n   \"cell_type\": \"markdown\",\n   \"source\": [\n    \"The **Flax Notebook Workflow**:\\n\",\n    \"\\n\",\n    \"1. Run the entire notebook end-to-end and check out the outputs.\\n\",\n    \"   - This will open Python files in the right-hand editor!\\n\",\n    \"   - You'll be able to interactively explore metrics in TensorBoard.\\n\",\n    \"2. Change `config` and train for different hyperparameters. Check out the\\n\",\n    \"   updated TensorBoard plots.\\n\",\n    \"3. Update the code in `train.py`. Thanks to `%autoreload`, any changes you\\n\",\n    \"   make in the file will automatically appear in the notebook. Some ideas to\\n\",\n    \"   get you started:\\n\",\n    \"   - Change the model.\\n\",\n    \"   - Log some per-batch metrics during training.\\n\",\n    \"   - Add new hyperparameters to `configs/default.py` and use them in\\n\",\n    \"     `train.py`.\\n\",\n    \"4. At any time, feel free to paste code from `train.py` into the notebook\\n\",\n    \"   and modify it directly there!\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"7e4ba0dc\",\n   \"cell_type\": \"markdown\",\n   \"source\": [\n    \"## Setup\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"ee8021b9\",\n   \"cell_type\": \"code\",\n   \"source\": [\n    \"example_directory = 'examples/sst2'\\n\",\n    \"editor_relpaths = ('configs/default.py', 'train.py', 'models.py')\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"36dab290\",\n   \"cell_type\": \"code\",\n   \"source\": [\n    \"# (If you run this code in Jupyter[lab], then you're already in the\\n\",\n    \"#  example directory and nothing needs to be done.)\\n\",\n    \"\\n\",\n    \"#@markdown **Fetch newest Flax, copy example code**\\n\",\n    \"#@markdown\\n\",\n    \"#@markdown **If you select no** below, then the files will be stored on the\\n\",\n    \"#@markdown *ephemeral* Colab VM. **After some time of inactivity, this VM will\\n\",\n    \"#@markdown be restarted an any changes are lost**.\\n\",\n    \"#@markdown\\n\",\n    \"#@markdown **If you select yes** below, then you will be asked for your\\n\",\n    \"#@markdown credentials to mount your personal Google Drive. In this case, all\\n\",\n    \"#@markdown changes you make will be *persisted*, and even if you re-run the\\n\",\n    \"#@markdown Colab later on, the files will still be the same (you can of course\\n\",\n    \"#@markdown remove directories inside your Drive's `flax/` root if you want to\\n\",\n    \"#@markdown manually revert these files).\\n\",\n    \"\\n\",\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  import os\\n\",\n    \"  os.chdir('/content')\\n\",\n    \"  # Download Flax repo from Github.\\n\",\n    \"  if not os.path.isdir('flaxrepo'):\\n\",\n    \"    pass\\n\",\n    \"    !git clone --depth=1 https://github.com/google/flax flaxrepo\\n\",\n    \"  # Copy example files \\u0026 change directory.\\n\",\n    \"  mount_gdrive = 'no' #@param ['yes', 'no']\\n\",\n    \"  if mount_gdrive == 'yes':\\n\",\n    \"    DISCLAIMER = 'Note: Editing in your Google Drive, changes will persist.'\\n\",\n    \"    from google.colab import drive\\n\",\n    \"    drive.mount('/content/gdrive')\\n\",\n    \"    example_root_path = f'/content/gdrive/My Drive/flax/{example_directory}'\\n\",\n    \"  else:\\n\",\n    \"    DISCLAIMER = 'WARNING: Editing in VM - changes lost after reboot!!'\\n\",\n    \"    example_root_path = f'/content/{example_directory}'\\n\",\n    \"    from IPython import display\\n\",\n    \"    display.display(display.HTML(\\n\",\n    \"        f'\\u003ch1 style=\\\"color:red;\\\" class=\\\"blink\\\"\\u003e{DISCLAIMER}\\u003c/h1\\u003e'))\\n\",\n    \"  if not os.path.isdir(example_root_path):\\n\",\n    \"    os.makedirs(example_root_path)\\n\",\n    \"    !cp -r flaxrepo/$example_directory/* \\\"$example_root_path\\\"\\n\",\n    \"  os.chdir(example_root_path)\\n\",\n    \"  from google.colab import files\\n\",\n    \"  for relpath in editor_relpaths:\\n\",\n    \"    s = open(f'{example_root_path}/{relpath}').read()\\n\",\n    \"    open(f'{example_root_path}/{relpath}', 'w').write(\\n\",\n    \"        f'## {DISCLAIMER}\\\\n' + '#' * (len(DISCLAIMER) + 3) + '\\\\n\\\\n' + s)\\n\",\n    \"    files.view(f'{example_root_path}/{relpath}')\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"700d9428\",\n   \"cell_type\": \"code\",\n   \"source\": [\n    \"# Note: In Colab, above cell changed the working directory.\\n\",\n    \"!pwd\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"2fbc3e64\",\n   \"cell_type\": \"code\",\n   \"source\": [\n    \"# Install SST-2 dependencies.\\n\",\n    \"!pip install -q -r requirements.txt\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"e40c50cf\",\n   \"cell_type\": \"markdown\",\n   \"source\": [\n    \"## Imports / Helpers\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"703f04fb\",\n   \"cell_type\": \"code\",\n   \"source\": [\n    \"import os\\n\",\n    \"os.environ['XLA_FLAGS'] = '--xla_force_host_platform_device_count=8'\\n\",\n    \"\\n\",\n    \"import jax\\n\",\n    \"jax.devices()\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"32e12a97\",\n   \"cell_type\": \"code\",\n   \"source\": [\n    \"from absl import logging\\n\",\n    \"import flax\\n\",\n    \"import jax.numpy as jnp\\n\",\n    \"import numpy as np\\n\",\n    \"import tensorflow as tf\\n\",\n    \"import tensorflow_datasets as tfds\\n\",\n    \"import time\\n\",\n    \"logging.set_verbosity(logging.INFO)\\n\",\n    \"\\n\",\n    \"# Make sure the GPU is for JAX, not for TF.\\n\",\n    \"tf.config.experimental.set_visible_devices([], 'GPU')\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"94ece24d\",\n   \"cell_type\": \"code\",\n   \"source\": [\n    \"# Local imports from current directory - auto reload.\\n\",\n    \"# Any changes you make to train.py will appear automatically.\\n\",\n    \"%load_ext autoreload\\n\",\n    \"%autoreload 2\\n\",\n    \"try:\\n\",\n    \"  import train\\n\",\n    \"  import models\\n\",\n    \"  import vocabulary\\n\",\n    \"  import input_pipeline\\n\",\n    \"  from configs import default as config_lib\\n\",\n    \"except ModuleNotFoundError:\\n\",\n    \"  # Local imports may not be available in all contexts\\n\",\n    \"  pass\\n\",\n    \"config = config_lib.get_config()\"\n   ],\n   \"metadata\": {\n    \"tags\": []\n   },\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"c8a6ec00\",\n   \"cell_type\": \"markdown\",\n   \"source\": [\n    \"## Dataset\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"5c615ca6\",\n   \"cell_type\": \"code\",\n   \"source\": [\n    \"# Get datasets. \\n\",\n    \"# If you get an error you need to install tensorflow_datasets from Github.\\n\",\n    \"train_dataset = input_pipeline.TextDataset(split='train')\\n\",\n    \"eval_dataset = input_pipeline.TextDataset(split='validation')\"\n   ],\n   \"metadata\": {\n    \"tags\": []\n   },\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"7d7c55cd\",\n   \"cell_type\": \"markdown\",\n   \"source\": [\n    \"## Training\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"df9d52ed\",\n   \"cell_type\": \"code\",\n   \"source\": [\n    \"# Get a live update during training - use the \\\"refresh\\\" button!\\n\",\n    \"# (In Jupyter[lab] start \\\"tensorboard\\\" in the local directory instead.)\\n\",\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  pass\\n\",\n    \"  %load_ext tensorboard\\n\",\n    \"  %tensorboard --logdir=.\"\n   ],\n   \"metadata\": {},\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"f159d072\",\n   \"cell_type\": \"code\",\n   \"source\": [\n    \"config.num_epochs = 10\\n\",\n    \"model_name = 'bilstm'\\n\",\n    \"start_time = time.time()\\n\",\n    \"optimizer = train.train_and_evaluate(config, workdir=f'./models/{model_name}')\\n\",\n    \"logging.info('Walltime: %f s', time.time() - start_time)\"\n   ],\n   \"metadata\": {\n    \"tags\": []\n   },\n   \"execution_count\": null\n  },\n  {\n   \"id\": \"b8e35c72\",\n   \"cell_type\": \"code\",\n   \"source\": [\n    \"if 'google.colab' in str(get_ipython()):\\n\",\n    \"  #@markdown You can upload the training results directly to https://tensorboard.dev\\n\",\n    \"  #@markdown\\n\",\n    \"  #@markdown Note that everbody with the link will be able to see the data.\\n\",\n    \"  upload_data = 'yes' #@param ['yes', 'no']\\n\",\n    \"  if upload_data == 'yes':\\n\",\n    \"    pass\\n\",\n    \"    !tensorboard dev upload --one_shot --logdir ./models --name 'Flax examples/mnist'\"\n   ],\n   \"metadata\": {\n    \"cellView\": \"form\",\n    \"tags\": []\n   },\n   \"execution_count\": null\n  }\n ],\n \"metadata\": {\n  \"accelerator\": \"GPU\"\n },\n \"nbformat_minor\": 0,\n \"nbformat\": 4\n}"
  },
  {
    "path": "examples/sst2/train.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Trains an SST2 text classifier.\"\"\"\nfrom typing import Any, Dict, Optional, Tuple, Union\nfrom collections.abc import Callable, Iterable, Sequence\n\nfrom absl import logging\nfrom flax import struct\nfrom flax.metrics import tensorboard\nfrom flax.training import train_state\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\nimport numpy as np\nimport optax\nimport tensorflow as tf\n\nimport input_pipeline\nimport models\n\n\nArray = jnp.ndarray\nExample = dict[str, Array]\nTrainState = train_state.TrainState\n\n\nclass Metrics(struct.PyTreeNode):\n  \"\"\"Computed metrics.\"\"\"\n\n  loss: Array\n  accuracy: Array\n  count: int | None = None\n\n\n@jax.vmap\ndef sigmoid_cross_entropy_with_logits(*, labels: Array, logits: Array) -> Array:\n  \"\"\"Sigmoid cross entropy loss.\"\"\"\n  zeros = jnp.zeros_like(logits, dtype=logits.dtype)\n  condition = logits >= zeros\n  relu_logits = jnp.where(condition, logits, zeros)\n  neg_abs_logits = jnp.where(condition, -logits, logits)\n  return relu_logits - logits * labels + jnp.log1p(jnp.exp(neg_abs_logits))\n\n\ndef get_initial_params(rng, model):\n  \"\"\"Returns randomly initialized parameters.\"\"\"\n  token_ids = jnp.ones((2, 3), jnp.int32)\n  lengths = jnp.ones((2,), dtype=jnp.int32)\n  variables = model.init(rng, token_ids, lengths, deterministic=True)\n  return variables['params']\n\n\ndef create_train_state(rng, config: ml_collections.ConfigDict, model):\n  \"\"\"Create initial training state.\"\"\"\n  params = get_initial_params(rng, model)\n  tx = optax.chain(\n      optax.sgd(learning_rate=config.learning_rate, momentum=config.momentum),\n      optax.add_decayed_weights(weight_decay=config.weight_decay),\n  )\n  state = TrainState.create(apply_fn=model.apply, params=params, tx=tx)\n  return state\n\n\ndef compute_metrics(*, labels: Array, logits: Array) -> Metrics:\n  \"\"\"Computes the metrics, summed across the batch if a batch is provided.\"\"\"\n  if labels.ndim == 1:  # Prevent the labels from broadcasting over the logits.\n    labels = jnp.expand_dims(labels, axis=1)\n  loss = sigmoid_cross_entropy_with_logits(labels=labels, logits=logits)\n  binary_predictions = logits >= 0.0\n  binary_accuracy = jnp.equal(binary_predictions, labels)\n  return Metrics(\n      loss=jnp.sum(loss),\n      accuracy=jnp.sum(binary_accuracy),\n      count=logits.shape[0],\n  )\n\n\ndef model_from_config(config: ml_collections.ConfigDict):\n  \"\"\"Builds a text classification model from a config.\"\"\"\n  model = models.TextClassifier(\n      embedding_size=config.embedding_size,\n      hidden_size=config.hidden_size,\n      vocab_size=config.vocab_size,\n      output_size=config.output_size,\n      dropout_rate=config.dropout_rate,\n      word_dropout_rate=config.word_dropout_rate,\n      unk_idx=config.unk_idx,\n  )\n  return model\n\n\ndef train_step(\n    state: TrainState,\n    batch: dict[str, Array],\n    rngs: dict[str, Any],\n) -> tuple[TrainState, Metrics]:\n  \"\"\"Train for a single step.\"\"\"\n  # Make sure to get a new RNG at every step.\n  step = state.step\n  rngs = {name: jax.random.fold_in(rng, step) for name, rng in rngs.items()}\n\n  def loss_fn(params):\n    variables = {'params': params}\n    logits = state.apply_fn(\n        variables,\n        batch['token_ids'],\n        batch['length'],\n        deterministic=False,\n        rngs=rngs,\n    )\n\n    labels = batch['label']\n    if labels.ndim == 1:\n      labels = jnp.expand_dims(labels, 1)\n    loss = jnp.mean(\n        sigmoid_cross_entropy_with_logits(labels=labels, logits=logits)\n    )\n    return loss, logits\n\n  grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n  value, grads = grad_fn(state.params)\n  (_, logits) = value\n\n  new_state = state.apply_gradients(grads=grads)\n  metrics = compute_metrics(labels=batch['label'], logits=logits)\n  return new_state, metrics\n\n\ndef eval_step(\n    state: TrainState, batch: dict[str, Array], rngs: dict[str, Any]\n) -> Metrics:\n  \"\"\"Evaluate for a single step. Model should be in deterministic mode.\"\"\"\n  variables = {'params': state.params}\n  logits = state.apply_fn(\n      variables,\n      batch['token_ids'],\n      batch['length'],\n      deterministic=True,\n      rngs=rngs,\n  )\n  metrics = compute_metrics(labels=batch['label'], logits=logits)\n  return metrics\n\n\ndef normalize_batch_metrics(batch_metrics: Sequence[Metrics]) -> Metrics:\n  \"\"\"Consolidates and normalizes a list of per-batch metrics dicts.\"\"\"\n  # Here we sum the metrics that were already summed per batch.\n  total_loss = np.sum([metrics.loss for metrics in batch_metrics])\n  total_accuracy = np.sum([metrics.accuracy for metrics in batch_metrics])\n  total = np.sum([metrics.count for metrics in batch_metrics])\n  # Divide each metric by the total number of items in the data set.\n  return Metrics(\n      loss=total_loss.item() / total, accuracy=total_accuracy.item() / total\n  )\n\n\ndef batch_to_numpy(batch: dict[str, Array]) -> dict[str, Array]:\n  \"\"\"Converts a batch with TF tensors to a batch of NumPy arrays.\"\"\"\n  # _numpy() reuses memory, does not make a copy.\n  # pylint: disable=protected-access\n  return jax.tree_util.tree_map(lambda x: x._numpy(), batch)\n\n\ndef evaluate_model(\n    eval_step_fn: Callable[..., Any],\n    state: TrainState,\n    batches: Iterable[Example] | tf.data.Dataset,\n    epoch: int,\n    rngs: dict[str, Any] | None = None,\n) -> Metrics:\n  \"\"\"Evaluate a model on a dataset.\"\"\"\n  batch_metrics = []\n  for i, batch in enumerate(batches):\n    batch = batch_to_numpy(batch)\n    if rngs is not None:  # New RNG for each step.\n      rngs = {name: jax.random.fold_in(rng, i) for name, rng in rngs.items()}\n\n    metrics = eval_step_fn(state, batch, rngs)\n    batch_metrics.append(metrics)\n\n  batch_metrics = jax.device_get(batch_metrics)\n  metrics = normalize_batch_metrics(batch_metrics)\n  logging.info(\n      'eval  epoch %03d loss %.4f accuracy %.2f',\n      epoch,\n      metrics.loss,\n      metrics.accuracy * 100,\n  )\n  return metrics\n\n\ndef train_epoch(\n    train_step_fn: Callable[..., tuple[TrainState, Metrics]],\n    state: TrainState,\n    train_batches: tf.data.Dataset,\n    epoch: int,\n    rngs: dict[str, Any] | None = None,\n) -> tuple[TrainState, Metrics]:\n  \"\"\"Train for a single epoch.\"\"\"\n  batch_metrics = []\n  for batch in train_batches:\n    batch = batch_to_numpy(batch)\n    state, metrics = train_step_fn(state, batch, rngs)\n    batch_metrics.append(metrics)\n\n  # Compute the metrics for this epoch.\n  batch_metrics = jax.device_get(batch_metrics)\n  metrics = normalize_batch_metrics(batch_metrics)\n\n  logging.info(\n      'train epoch %03d loss %.4f accuracy %.2f',\n      epoch,\n      metrics.loss,\n      metrics.accuracy * 100,\n  )\n\n  return state, metrics\n\n\ndef train_and_evaluate(\n    config: ml_collections.ConfigDict, workdir: str\n) -> TrainState:\n  \"\"\"Execute model training and evaluation loop.\n\n  Args:\n    config: Hyperparameter configuration for training and evaluation.\n    workdir: Directory where the tensorboard summaries are written to.\n  Returns:\n    The final train state that includes the trained parameters.\n  \"\"\"\n  # Prepare datasets.\n  train_dataset = input_pipeline.TextDataset(\n      tfds_name='glue/sst2', split='train'\n  )\n  eval_dataset = input_pipeline.TextDataset(\n      tfds_name='glue/sst2', split='validation'\n  )\n  train_batches = train_dataset.get_bucketed_batches(\n      config.batch_size,\n      config.bucket_size,\n      max_input_length=config.max_input_length,\n      drop_remainder=True,\n      shuffle=True,\n      shuffle_seed=config.seed,\n  )\n  eval_batches = eval_dataset.get_batches(batch_size=config.batch_size)\n\n  # Keep track of vocab size in the config so that the embedder knows it.\n  config.vocab_size = len(train_dataset.vocab)\n\n  # Compile step functions.\n  train_step_fn = jax.jit(train_step)\n  eval_step_fn = jax.jit(eval_step)\n\n  # Create model and a state that contains the parameters.\n  rng = jax.random.key(config.seed)\n  model = model_from_config(config)\n  state = create_train_state(rng, config, model)\n\n  summary_writer = tensorboard.SummaryWriter(workdir)\n  summary_writer.hparams(dict(config))\n\n  # Main training loop.\n  logging.info('Starting training...')\n  for epoch in range(1, config.num_epochs + 1):\n    # Train for one epoch.\n    rng, epoch_rng = jax.random.split(rng)\n    rngs = {'dropout': epoch_rng}\n    state, train_metrics = train_epoch(\n        train_step_fn, state, train_batches, epoch, rngs\n    )\n\n    # Evaluate current model on the validation data.\n    eval_metrics = evaluate_model(eval_step_fn, state, eval_batches, epoch)\n\n    # Write metrics to TensorBoard.\n    summary_writer.scalar('train_loss', train_metrics.loss, epoch)\n    summary_writer.scalar('train_accuracy', train_metrics.accuracy * 100, epoch)\n    summary_writer.scalar('eval_loss', eval_metrics.loss, epoch)\n    summary_writer.scalar('eval_accuracy', eval_metrics.accuracy * 100, epoch)\n\n  summary_writer.flush()\n  return state\n"
  },
  {
    "path": "examples/sst2/train_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for sst2.train.\"\"\"\nimport sys\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nimport jax\nimport jax.test_util\nimport numpy as np\n\nfrom configs import default as default_config\nimport train\n\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass TrainTest(parameterized.TestCase):\n\n  def test_train_step_updates_parameters(self):\n    \"\"\"Tests if the train step updates the parameters in train state.\"\"\"\n    # Create model and a state that contains the parameters.\n    config = default_config.get_config()\n    config.vocab_size = 13\n    rng = jax.random.key(config.seed)\n    model = train.model_from_config(config)\n    state = train.create_train_state(rng, config, model)\n\n    token_ids = np.array([[2, 4, 3], [2, 6, 3]], dtype=np.int32)\n    lengths = np.array([2, 3], dtype=np.int32)\n    labels = np.zeros_like(lengths)\n    batch = {'token_ids': token_ids, 'length': lengths, 'label': labels}\n    rngs = {'dropout': rng}\n    train_step_fn = jax.jit(train.train_step)\n    new_state, metrics = train_step_fn(state, batch, rngs)\n    self.assertIsInstance(new_state, train.TrainState)\n    self.assertIsInstance(metrics, train.Metrics)\n    old_param_values = jax.tree_util.tree_leaves(state.params)\n    new_param_values = jax.tree_util.tree_leaves(new_state.params)\n    for old_array, new_array in zip(old_param_values, new_param_values):\n      # Make sure parameters were updated.\n      self.assertFalse(np.allclose(old_array, new_array))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/sst2/vocab.txt",
    "content": "<pad>\n<unk>\n<s>\n</s>\nthe\n,\na\nand\nof\n.\nto\n's\nis\nthat\nin\nit\nas\nwith\nan\nfilm\nits\nfor\nmovie\nthis\nyou\nbut\nbe\non\nn't\nby\nmore\n--\none\nat\nthan\nhas\nnot\nabout\nhis\nfrom\nare\nlike\nso\nor\nall\nhave\nmost\nstory\n'\ngood\n...\ninto\nout\ntoo\nwho\n)\nup\ncharacters\ni\nfunny\n(\ncomedy\nif\njust\nno\ndoes\nmuch\nwhat\ncan\neven\n`\nyour\ntheir\nwill\ntime\nsome\n``\nbad\nlittle\n''\nvery\nway\nwhich\nbest\nany\nlove\nbeen\nlife\nmake\nwork\nenough\nthere\nonly\nhe\nmakes\nus\nnew\nmovies\nnever\nsomething\ndo\nthey\nhumor\nthrough\nwas\nwell\naction\ngreat\nwould\nown\nmade\ndirector\nmany\nwe\nreally\nperformances\nplot\ndrama\nher\nhow\ncould\nfilms\nsense\nsee\nsuch\nbetter\nother\nfun\naudience\npeople\nevery\noff\ntwo\nwithout\ncast\nnothing\nfeel\nboth\nwhen\nbeing\nlook\ncharacter\nmay\nshould\nentertaining\nacting\never\nreal\noften\nperformance\nthem\nlong\n:\nwhile\nstill\nworld\nbecause\nscript\nalso\ninteresting\nanother\nheart\nkind\n're\nthose\ndialogue\nhollywood\nminutes\nwatch\nfirst\nscreen\ndown\nfew\nget\nbig\nover\nfar\nthriller\nmight\nhard\nless\nhuman\nmoments\nactors\ntale\ncompelling\nromantic\ncinema\nhad\nrather\nyear\nfamily\nalmost\nmaterial\nend\nwatching\nseen\n-\nworth\n've\nseem\nitself\npicture\noriginal\ntake\nbefore\nmy\ndocumentary\nseems\nwere\nemotional\nafter\nour\nquite\nfind\nold\nthese\nvisual\ncomes\nman\nthings\nback\nfascinating\nmoving\nsweet\nright\nworks\nbetween\nfeels\nhere\nscenes\nfull\ncome\npiece\ndirection\ncare\nyet\n;\nmusic\ngo\ndull\nme\ngoing\ntakes\nspecial\nultimately\nyears\nca\nyoung\nkeep\nmaking\n'll\nanything\nlaughs\namerican\ntimes\nwhy\nsmart\nworst\ngive\ncomic\nexperience\nenjoyable\nleast\ncinematic\nlot\npart\nwhere\nbeautiful\nentertainment\nhistory\nstyle\nsometimes\nthing\nthough\nart\nclever\nkids\naway\ngives\nagain\nhim\ntogether\nbit\nshe\nintelligence\ndark\ngets\nidea\namusing\nengaging\ntheater\npowerful\nsame\ngenre\nintelligent\nonce\nstar\nwomen\nenergy\nsubject\ndid\ncharming\nsurprisingly\nactually\nsummer\nanyone\ncharm\nscreenplay\nwant\npoint\nfilmmaking\nplace\nshort\nnarrative\nsolid\npretty\nflick\naround\nfeeling\nnearly\nfeature\nsilly\nsimply\nwhose\nmanages\nstrong\nface\npredictable\nenjoy\nthink\ntruly\nwar\nwit\noffers\nsay\nshow\ndeeply\ngoes\nknow\nperfect\nsatisfying\nthen\nfans\npower\nneed\nwhole\nalways\neffort\nbecomes\ndone\nspirit\nfresh\nbeautifully\npremise\ntrue\ntrying\nhalf\nquirky\nsince\nthree\nfilmmakers\nsuspense\ndramatic\nhilarious\nportrait\ntone\nhorror\nlast\nunder\nfine\ninterest\neffects\nflat\nrare\nhigh\nrich\nseries\nhours\nprobably\nchildren\neveryone\nromance\nideas\ntouching\n?\nfamiliar\nlooking\nmodern\nremarkable\nstudy\n'd\nespecially\nimagination\npleasure\nwonderful\nboring\nclassic\neasy\neverything\nsmall\nexercise\nleave\nset\ninstead\nlevel\ntitle\nhonest\nstuff\nculture\npast\ndumb\nintriguing\ntv\nwo\nfilmmaker\nlight\nvideo\nactor\nalready\nturn\naudiences\nsad\nstorytelling\nlack\nmatter\nrecent\nstories\nmind\nobvious\ndespite\nput\ntalent\nwritten\nending\nfrench\nimages\nmemorable\nproject\nterrific\nvisually\nserious\nadventure\ncompletely\nwoman\nbecome\nopera\nbeauty\ncamera\ngentle\nlikely\ntalented\nlooks\nemotionally\nmess\nfails\nday\nride\nslow\nsure\ncliches\ncold\nhaving\nhead\nhimself\nreason\nbeyond\ndirected\ngorgeous\ninside\njokes\nleft\nmr.\nbland\nmen\nmelodrama\nproves\nshot\nimpossible\nlow\nways\neasily\nrun\nabove\nhour\nstupid\nthoughtful\ncontrived\nexcellent\nmust\nsimple\ncomplex\ndebut\ndifferent\nelse\neyes\ntired\nugly\nde\nfairly\nlacks\notherwise\nviewer\nbelieve\nbrilliant\ncomedies\neach\nshows\nsort\nviewers\npassion\nwarmth\nattempt\nblack\ncertainly\nparticularly\nturns\nwriting\nplay\nviolence\nwelcome\nwrong\ncheap\nformula\nlost\nsocial\ngenuine\nsoap\nthemselves\nanimation\nbook\ncrime\ndelightful\neither\npersonal\nplays\nrole\nsequences\nthoroughly\nhero\nline\nbarely\nhistorical\nimpressive\nsex\nversion\nappealing\nfact\nhome\nnor\nseeing\nalong\ngags\nquality\nchange\nclichés\ngot\nworse\nadults\nfound\nlives\nmiddle\nold-fashioned\nsurprising\nambitious\ndeath\nengrossing\ngirl\nmessage\nnext\nrunning\ncreative\nfantasy\nimportant\nable\nlive\nnow\npretentious\nworthy\n!\n'm\ndecent\npsychological\nsequel\nends\nimagine\nnice\ntragedy\nwarm\nentirely\nmichael\nnone\nperfectly\nact\ncreepy\nwaste\ndeep\nremains\nsit\nconcept\ninventive\njob\njourney\nlaugh\npictures\nrock\nunfunny\nunsettling\ncool\ninsight\npainful\ntry\nusual\nvision\nwinning\nattention\nconvincing\njohn\nunique\nbring\nmoral\nneither\nmystery\nsatire\nstylish\nagainst\nbelievable\nknows\nleaves\nmaster\nnature\nside\nsuccess\nthin\nartist\nawful\nelements\nlacking\nlead\nreality\nseat\ntedious\nmood\nshallow\nworking\nappeal\nconsiderable\nepic\nfalls\nmoment\nperiod\nprovocative\nsituations\nview\ncreate\ndays\npolitical\nsentimental\ncinematography\nendearing\nhackneyed\nroad\nscene\nsensitive\nwatchable\ncolorful\nearnest\nfinally\ngetting\ngreatest\nhighly\nparents\ndifficult\nhelp\nloud\nuses\namount\ncall\ndelivers\ndry\ngame\npace\nproduction\nspy\nunderstand\nutterly\nwitty\nadaptation\nflaws\nfuture\nhold\nmostly\npossible\nrelationships\nscary\nsecond\nsharp\nstand\nthemes\ncliché\ndepth\nemotions\nkeeps\nmasterpiece\nodd\nsci-fi\nskill\nthroughout\nduring\nedge\ngiven\ngone\npoorly\nseveral\nsuccessful\nsustain\ntouch\ntries\nanimated\ncoming-of-age\ndrag\nexamination\nfour\nmindless\npointless\npromise\nuse\ndisney\nepisode\nflicks\nhit\ntell\ncaptures\ndefinitely\nexecution\nform\nmagic\nmajor\npieces\nscore\nsubtle\nsurprise\ntwists\nunexpected\nwhether\ncomplete\ndeserves\nensemble\ngrand\nintense\nones\npoor\npure\nspielberg\nacross\nactress\nboy\ndepressing\nintimate\nmemory\noscar\npacing\nproblem\nsomewhat\nstraight\nability\napproach\ncareer\ncouple\nexciting\nforced\nintrigue\nlovely\nsurprises\nthinking\ntold\nyork\ndelicate\ngoofy\nmanner\noffer\nrecommend\nstarts\ntaste\ntension\nthought\ntragic\naffection\nbrings\nfinal\ngrace\nmediocre\nsexy\ncontemporary\neye\nfemale\nheartfelt\nhumanity\noccasionally\novercome\nsets\nslightly\nsomeone\ntribute\nwords\nair\nallows\ncore\ncultural\nequally\nevil\nfully\ngeneric\nhand\nmanipulative\nnicely\npleasant\nplenty\npurpose\nremake\nstale\nstrange\ntoday\ntwisted\nuneven\nworthwhile\ncase\nevents\nfailure\nfelt\nframe\nfunnier\nmoney\nmonster\npainfully\nplayful\nsophisticated\nstriking\ntelling\nvivid\nweird\nwild\ncar\ncrafted\ndelight\ndoing\nhuge\nneeds\noffensive\nroom\nstoryline\nterrible\nclear\ncoming\nexpect\nlet\nliving\nmean\nmotion\nplaying\npoetry\nremarkably\nsingle\nsoul\nthanks\nurban\nacted\ncasting\nclarity\ndevoid\ninsightful\ninspiring\nsounds\nsupposed\nultimate\namong\nclass\nforget\nimaginative\njustice\nrarely\nclearly\ncompany\nenergetic\nexactly\nissues\nquiet\nsandler\nwants\nchilling\nclose\ndead\ndog\nfigure\nfunniest\ngenuinely\nparty\nrefreshing\nrespect\nruns\nsituation\nsmile\nspiritual\nstrangely\ntoward\nwildly\ncalled\ncountry\nempty\nextraordinary\nfestival\nguys\nminor\nnight\nproduct\nrealism\nschool\nsincere\nsloppy\nstep\ntiresome\napart\nchemistry\ncrowd\ncut\nmeditation\nmix\nshare\nsitting\ntaking\nteen\ntreat\ntruth\nviewing\nwasted\nwriter-director\nyourself\nago\nalone\nannoying\nbegins\neffective\ngripping\nms.\noriginality\npolitics\nridiculous\nwonderfully\nadd\nages\nbrain\ncute\ndream\nentire\nname\nproblems\nquestions\nsuperficial\ntarget\nuniversal\nvalue\nwise\n90\nchance\nconclusion\nderivative\nexcitement\nfare\nfire\nii\ninterested\nlame\nloss\nmerely\nplain\nsexual\nsick\ntwice\n10\nabsolutely\namateurish\namerica\ncreated\ndetailed\nextremely\nfirst-rate\nflawed\nimpact\nkid\nlatest\nliked\nputs\nshots\nslapstick\nsteven\nsucceeds\ntaken\nunnecessary\nwell-made\nattraction\nbattle\nbritish\nexcept\nhardly\nlots\nnatural\npopcorn\nrobert\nsappy\nstart\nsuffers\ntriumph\ndate\nfrustrating\nheartwarming\nhorrible\njoy\nmark\nmeandering\nplaces\nprevious\nread\nserved\nspectacle\ntypical\nusually\nalive\nattempts\nchan\ncity\ncredits\ndisaster\ndownright\ndreary\nhope\nincredibly\nmesmerizing\nnovel\nopportunity\nperformers\nseriously\ntreatment\nbehind\ncertain\nforgettable\nfrequently\ngirls\nholds\nindie\noverall\nrealistic\nrefreshingly\nrest\nstars\nsuspenseful\ntop\nused\nabsorbing\nallen\nbalance\nbrown\ncredit\ndelivery\nelegant\ngrant\nguilty\ninept\noddly\nprovides\nriveting\nstunning\ntrip\nweak\nwish\nyarn\navoid\nconventional\nconviction\ndazzling\ndoubt\nfinds\nfranchise\nperson\npop\nprocess\nreveals\nshame\nsuperb\nuntil\nvisuals\nwaiting\n2002\namazing\nbadly\nbased\ncrisp\ndavid\nediting\nguy\nlazy\nplease\npoetic\nthrills\ntrouble\n2\nachievement\nalthough\nbullock\ncollege\ncommercial\ncomplicated\ndamned\nfalse\nhate\nidentity\nincluding\nleads\nmaybe\nsubjects\nwonder\nadmirable\nb\nbehavior\nbore\nbright\ncomedic\ncreates\ndreams\nescape\nexquisite\nfashion\nfears\nlistless\nludicrous\nmatch\nnumber\nroutine\nsequels\nskin\nsubstance\nundeniably\ncleverly\nfavor\nlousy\nmeans\nmurphy\nnasty\npathetic\nrip-off\ntalents\nthought-provoking\nbizarre\ncause\nexcuse\nfiction\nglory\ngrief\ninspired\nmainstream\npay\npoignant\nquickly\nsentimentality\nsheer\nstandard\ntotally\nvarious\nwater\nwin\nwriter\nzone\nage\nauthentic\nbusiness\ncentral\ncheesy\ndamage\ndeal\ndie\nevocative\nfaith\ngay\nhappy\nhell\nholes\nphilosophical\npromising\nrepetitive\nsides\nstarring\nsuperior\ntender\nvoice\naspects\nbeneath\ncharisma\ncolor\ncreativity\ndevastating\nfall\nforgotten\nfriendship\nfront\ngifted\nglimpse\nheavy\nhumorous\nlargely\nmembers\nreach\nremember\nrevealing\nsequence\nsound\nsubtlety\ntheaters\nblue\ncruel\ndelightfully\ndeserve\ndesigned\ndirectors\nfast\nfilled\nfive\nflair\nfrank\ngeneral\ngross-out\nhaunting\nintended\ninvolving\nothers\nperspective\npoints\nraw\nunpleasant\nword\naccomplished\nconfusing\ndocument\nendless\nexhilarating\nfit\ngrowing\nintensity\nlaughter\nlevels\nparticular\nretread\nsign\nsongs\nspark\nstay\nstock\nstudio\nvirtually\nacts\nartificial\nbudget\nchallenging\ncliched\nconsistently\nentertain\nexploration\nfeel-good\nfinding\nforce\nformulaic\ngenerally\ngeorge\nhappens\njackson\nlines\nmodest\nmurder\nobservations\npossibly\nprovide\nquietly\nrhythms\nstereotypes\ntheme\nutter\nvibrant\naccessible\naffair\napparent\nbears\ndecades\nessentially\nfarce\nfatal\ngroup\nhappen\nhouse\nimax\nmarvelous\nnoir\noffering\npull\nrhythm\nsoundtrack\nstructure\nbelly\nbored\nbreak\nbuoyant\ncrazy\ndelights\ndeliver\nfake\nfavorite\nhip\nindustry\njoke\nlets\nmissing\nobnoxious\npersona\npotential\nsight\nslight\nsum\ntelevision\nunconvincing\nupon\naddition\nanswers\nchase\nclumsy\nenjoyed\nextreme\nflashy\nfurther\nhugely\nirritating\nloses\nlow-budget\nmain\nmasterful\nmention\nniro\nred\nrewarding\nsaw\nsouth\nspent\ntimely\nvehicle\nwashington\nachieves\nastonishing\nattractive\nbeginning\nbody\nbother\ncarries\ncommunity\ncomputer\ndeadly\ndisappointing\neloquent\nempathy\nera\nfrustration\ngradually\nhearts\nheaven\nlogic\nmixed\nnecessary\nolder\nplotting\nseason\nself-conscious\nsitcom\nstruggle\ntalking\ntells\nunimaginative\nvisceral\nwait\ncrap\ndirectorial\nexpected\ngreen\nhoffman\nincoherent\nking\nlaughing\nlived\nmarks\nmayhem\nmildly\nmove\nmoviegoers\nonto\npair\nrelationship\nrepresents\nsea\nslice\nsource\nterms\nundeniable\nunfocused\nunpredictable\nuplifting\nviolent\nwannabe\nwholly\nwife\n20\nabsurd\nadult\naffecting\nbond\ncartoon\nchristmas\ncrude\ndevelopment\nexploitative\nfan\nfat\ngangster\nholiday\nhowever\njoyous\nlarge\nmelodramatic\noffbeat\noutstanding\npoem\nresist\nsaturday\nsave\nsetting\nstandards\nsupporting\ntear\nwedding\namounts\nassured\nboys\nchoppy\ndeft\nemotion\nfantastic\ngames\ngrandeur\nhorrifying\nintoxicating\nlist\nlyrical\nmature\noffice\npleasures\nprecious\nreading\nroll\nsensual\nsmarter\nsomehow\nstuck\nsympathy\ntough\nvulgar\nweight\nwickedly\natmosphere\nbrought\ncaptivating\ncharmer\nconflict\ncontent\ncourage\ncrush\nedited\nendure\nkiddie\nlady\nlength\nmachine\nmother\npale\npassionate\npat\npersonality\nprice\nrealize\nrevelatory\nseagal\nshoot\nsoderbergh\ntears\ntrademark\nturned\nvapid\nwalk\nwell-crafted\nwry\n$\naverage\nbanal\ncheck\ncollection\ndetail\ndigital\ndirty\nendeavor\nguns\nhits\nhopeful\nintricate\nkiller\nlawrence\nmissed\nmurky\npack\npain\npositive\npotentially\nrealized\nrendered\nresonant\nservice\nsimplistic\nsink\nsour\nsplendid\nstage\nteenagers\nunlikable\nunoriginal\nvividly\nyes\nabsurdity\nadam\nbiting\nbittersweet\nblade\nbrothers\nbunch\ncharismatic\nconstructed\ndeftly\neventually\nfilmed\nfood\nfreshness\ngem\nheartbreaking\njapanese\nlate\nordinary\npoignancy\nportrayal\nsomewhere\nspirits\nstrength\nwars\namericans\nblend\ncombination\ncult\ndamn\ndvd\neffect\nemerges\nenthusiasm\nessence\nexample\nfear\nfootage\nfrightening\ngod\ngrow\ninstantly\ninvolved\nloved\nlush\nminds\nmixture\nplodding\npresent\nproceedings\nprofound\npulls\nresults\nricher\nsaid\nself-indulgent\nsell\nsimultaneously\nstrain\nstreet\nsurface\nterritory\ntouches\nunfolds\nwilling\n9\nartists\nband\nbold\nbreathtaking\ncapture\ncommentary\ncompassion\ndaring\ndeeper\ndignity\nexceptional\nfaster\nfight\nfriend\nhonestly\nknowledge\nleaving\nmeaningful\nnotice\npeter\nqueen\nrelease\nreturn\nrush\nsoon\nspooky\nsports\ntrite\nwhatever\nadventurous\nangst\nawkward\nbite\nbottom\ncapable\nchanging\nchannel\nchild\ncondition\ndesperate\ndetails\ndetermined\ndrawn\nendlessly\nfanciful\nfate\nimmensely\nincreasingly\nindian\ninsights\nkevin\nmagnificent\nmeasure\nmoore\nnightmare\nnowhere\nodds\nover-the-top\noverly\npaced\npic\npreachy\npredecessor\npresents\nquick\nryan\nsadly\nsane\nsmug\nsoulful\nspace\nspectacular\nstirring\nstring\nsweetly\nsweetness\nteam\ntiming\ntwist\nunforced\nunusual\nvalues\nwears\nwhite\nwilliams\nanimal\nb-movie\nbitter\nbroad\ncarried\ncomfortable\ncommitted\ndelivered\ndemands\ndish\nfeatures\nfriends\ngrab\nground\nharrowing\nheavy-handed\ninspiration\njames\nknowing\nlaughable\nliterary\nlosing\nmagical\nmovements\nneeded\npaper-thin\npatience\npolished\npretends\nprotagonist\nquestion\nrank\nrelentlessly\nshapeless\ntype\nunderstanding\nvital\nweek\nwhom\nwow\namused\nappalling\nartistic\nasks\nbigger\nburns\ncliche\nconfident\nconvinced\ncrass\ncredible\nearly\nfaithful\nfriday\ngrows\nhair\nhands\nhappening\nhomage\nitalian\nlonger\nmartin\nmonty\nnomination\npass\nprove\npublic\nreal-life\nrelatively\nrelief\nresourceful\nsadness\nscience\nscorsese\nsketchy\nstartling\nstrike\nsympathetic\nthird\ntiny\ntop-notch\nuncompromising\nunfortunately\nambition\napparently\nappear\naward\nbroken\ncelebration\ncreating\ndegree\ndespair\ndirect-to-video\ndisguise\nexploitation\nfair\nfinale\nfinish\nflimsy\nforgive\ngritty\nhappiness\nhip-hop\nhonesty\ninnocence\nkey\nlesson\nloves\nmusical\npleasing\npresence\nroles\nscreenwriter\nseek\nserves\nsigns\nsnow\nsparkling\nstate\nteacher\nticket\nuninspired\nwinner\nwitch\nwitless\nahead\naplomb\nappears\nbarbershop\nbelt\nbiggest\nbrutally\nbuy\ncharms\ncloying\nconcerned\ncop\ncorny\ncynical\ndepiction\ndeveloped\nearnestness\nenthusiastic\nescapism\nevent\nfights\nfolks\ngiant\nguessing\nhastily\nimagery\nimpression\ninevitable\nleaden\nlee\nmaudlin\nmerit\nmetaphor\nnote\noutrageous\nperverse\nrecycled\nrelies\nreport\nrooting\nsand\nscenery\nseconds\nshocking\nshooting\nskip\nslap\nstrikes\nstructured\nsuffering\ntechnical\nthrill\ntortured\ntreasure\ntrick\nwalked\nworked\n&\nabandon\naccept\nadmire\nalternative\nbenigni\nbreathtakingly\ncharacterizations\ncharmless\nchilly\ncohesive\ncomfort\ncommunal\nconvenient\ndelicious\ndelivering\ndesperately\ndoze\ndrive\ndumbed-down\nexecuted\nfeat\nfighting\ngiving\nherzog\ninsanely\nintentions\nirresistible\njuvenile\nlifeless\nlingering\nlively\nmale\nmanage\nmessages\nmiss\nmysterious\nnevertheless\nniche\noverwrought\npotent\npresented\nracial\nreasonably\nreferences\nsoulless\nspirited\nstaged\nterribly\nterrifying\ntestament\ntom\ntotal\nunfaithful\nurge\nvampire\nvariety\nverve\nwilde\nadventures\nastonishingly\nbelow\nbreaks\nbrutal\ncame\nclumsily\nconstant\ndeceptively\ndescribe\ndesire\ndisplay\ndubbed\nexceedingly\nforces\nfree\ngood-natured\nheard\nintegrity\ninternational\nlimited\nmoviemaking\nnational\node\npark\npile\npowers\npsychology\nquirks\nrises\nsake\nsecret\nshelf\nsilliness\nsmartly\nson\nspite\nsplendor\nstop\ntheatrical\nvillain\nvisit\nwarning\nalternately\nappreciate\nbasic\nbelongs\nblast\nchaotic\nchris\ncoherent\nconnection\ndemanding\ndirecting\ndistasteful\ndraws\ndullest\nearth\nembarrassment\nexotic\nfeces\ngently\ngraphic\nhalfway\nhelps\nhot\njason\nlabored\nlandscape\nlocations\nlooked\nmouth\noccasional\noverwhelming\nphony\nplayed\nplots\nplotted\npolice\nprivate\nproduced\nproud\nremain\nrent\nresult\nrevenge\nrising\nroger\nscratch\nscreenwriting\nsensational\nshamelessly\nsignificant\nsomber\nspare\nspell\nstraightforward\nsuck\nsweeping\ntasty\ntouched\ntraditional\ntrash\nturning\nunlikely\nwallace\nx\nyounger\nachingly\nanalyze\nblood\nbox\nburied\nchildhood\nchoices\nconsidered\ncontrol\ncourse\ndisappointed\ndiscovery\ndiversion\ndogs\ndrugs\ndue\ndying\neastwood\nembraces\nenthralling\nequivalent\nevening\nextended\nfactor\nfarts\nflow\nhandled\nimitation\nindeed\ningenious\ninherent\njones\nmotivated\nnation\nnear\nopen\nopenness\npaper\npolanski\nprofessional\nrace\nrichly\nrowdy\nsaccharine\nsafe\nscreaming\nscreenwriters\nshowtime\nshyamalan\nslack\nsociety\nsoggy\nsuited\nthumbs\ntradition\ntricks\nunlike\nwanting\nweirdly\nwooden\nafraid\nambiguous\narea\nartifice\nattract\nbeach\nbehold\nbroomfield\ncandy\ncapacity\ncheese\nchinese\nconsider\ndelicately\nefforts\nemerge\nengaged\nexpense\nexperiences\nfierce\nfish\nfollow\ngenerous\ngloriously\ngreek\nhandsome\nheld\ninane\nincompetent\ninspirational\nintermittently\njim\njunk\nlackluster\nlanguage\nmaintains\nmartha\nnonsensical\nonscreen\npath\npeculiar\nperhaps\npersonalities\npick\nplayers\npolitically\nquest\nrelevant\nrequired\nrewards\nsees\nsentiment\nstarted\nsubstantial\nthrows\ntragedies\ntranscends\nuncanny\nunflinching\nusing\nvague\nvitality\nwhimsy\nwindow\nwithin\nwondering\nwoody\n90-minute\nactresses\nbeat\nbringing\ncomplexity\ndestination\ndevices\nengage\nenglish\nenter\nexplore\nfantasies\nfather\nfill\nfinely\nfuzzy\ngarbage\ngeneration\ninsipid\ninspire\nintellectual\nintimacy\niranian\nkeeping\nkissinger\nkline\nknew\nlayers\nlocales\nmainly\nmakers\nmarriage\nmere\nmisery\nmorning\nmyself\nphotography\npossibilities\nranks\nrehash\nreturns\nrevolution\nrude\nrushed\nsarandon\nscream\nserve\nslapdash\nslip\nslowly\nsluggish\nstays\nstomach\nsubtly\nsumptuous\ntechnology\nterm\nthematic\ntopic\ntrailer\ntrappings\ntremendous\nuncommonly\nunderstands\nverbal\nvideos\nwent\nwon\n***\narts\naside\nastounding\nattitude\nawe\nbag\nbegin\nbreakthrough\nbreezy\nbuffs\ncelebrates\ncelluloid\nchoice\ncleverness\ncomplications\ncontext\nconvince\ncover\ndaily\ndealing\ndisgusting\ndistance\ndistinctive\ndistinguished\ndrags\ndramatically\ndysfunctional\nedgy\nelaborate\nembarrassed\nencouraging\nexistential\nfamilial\nfamous\nfiring\ngraceless\nholocaust\nhoping\nimprobable\nincisive\njack\nlikable\nloose\nlopez\nlow-key\nmean-spirited\nmild\noverlong\npacked\npaid\nparker\npleasurable\nportrays\nproblematic\nproper\nrefusal\nregret\nreleased\nreminds\nsecrets\nshaped\nshrewd\nsincerity\nsinging\nstands\nstation\nstereotypical\nsucceed\nsupremely\ntalk\ntext\nunbelievable\nunexpectedly\nvs.\nweeks\nweirdness\nwind\n/\naccents\nactual\nadolescent\naltogether\nask\nbrilliantly\nburn\ncamp\ncentury\nchances\nchills\ncount\ndancing\ndesperation\ndiverting\ndrawing\neager\neat\nelegantly\nenormously\nexcess\nexisted\nexit\nexpectation\nexposition\nfisher\nfollowing\ngenial\ngiddy\ngyllenhaal\nharry\nhokum\nhopkins\nhumorless\nhybrid\nignored\nincoherence\ninconsequential\ninfidelity\ninstincts\ninsulting\ninvention\nkicking\nkiss\nkong\nlends\nloads\nlovers\nmistake\nmovement\nneat\nnerve\nnewcomer\npathology\nplanet\nredundant\nreminiscent\nreplaced\nresolutely\nrespectable\nresponsible\nsatirical\nsavvy\nscenario\nseats\nsettles\nshake\nshocks\nsubculture\nsurely\ntalky\ntechnically\ntimeless\ntrees\ntrifle\nweb\nwhimsical\nadds\naims\nballot\nblair\nbloody\ncartoonish\nclueless\nconcern\nconfused\ncopy\ncreation\ncrucial\ncuriously\ndismissed\ndownbeat\nelevate\nenduring\nenormous\nexistence\nfinest\nfreaks\ngangs\nghetto\ngraceful\nhallmark\nhighest\nhistorically\nhunter\nhusband\nimpeccable\ninsult\nintolerable\njarring\njolt\nmartial\nmeaning\nmidnight\nnoise\nnotion\nobviously\npaul\npity\nponder\npopular\npredictably\nrandom\nrecord\nscattered\nsevere\nshining\nsimilarly\nsolondz\nsouls\nspoof\ntitles\nunfulfilling\nunusually\nuseless\nya-ya\n'70s\naccurate\naimless\nblind\nblockbuster\nbrooding\nchops\nclue\ncobbled\ncomparison\ncontinues\ncritics\ndenial\ndesign\ndickens\ndivine\ndomestic\ndragon\neasier\neddie\nenterprise\nexploiting\nflavor\nfluid\nformer\nfrailty\nhandful\nheroine\nimplausible\ninviting\njackie\nlearn\nmarginal\nmasterfully\nmiddle-aged\nmiracle\nmoved\nmuddled\nmythic\nnecessarily\nnoble\nnuanced\nobservation\nphenomenal\npianist\nponderous\npow\npreposterous\npresentation\nprotagonists\nproviding\nrating\nresonance\nrollicking\nrote\nsade\nsatisfies\nsaving\nseven\nsimilar\nsketch\nsong\nsordid\nsorvino\nspend\nstilted\nstretches\nteenage\nthinks\ntrapped\nunabashedly\nvanity\nwitherspoon\nwoo\nabsolute\nacademy\naccount\nadded\nallow\nand/or\nanybody\nartful\nblank\nbritney\nbroadway\nbuilt\ncalm\ncaper\ncares\ncaught\nchabrol\nclassics\ncollapses\ncombined\ncompetent\nconservative\nconstruct\ncreature\ncuriosity\ncurrent\ndangerous\ndeadpan\ndemented\ndisposable\ndrop\nelegance\nempowerment\nenjoyment\nequilibrium\nevelyn\nfancy\nfigures\nfrozen\nfrustratingly\nghost\ngorgeously\ngratuitous\ngrown-up\nhong\nhugh\nhype\nincongruous\ninhabit\ninnovative\nkill\nkung\nlump\nmalaise\nmanufactured\nmarvelously\nmorality\nmorally\nmushy\nobligatory\noutside\npitch-perfect\npostcard\npoverty\npretension\nprimarily\nradical\nrecipe\nrecovery\nremembering\nremote\nreplete\nscripts\nspider-man\nstinker\nsudden\nsurrounding\nswallow\ntasteful\ntepid\ntranslation\nvincent\nwave\n30\nappropriate\nawkwardness\nbill\nbits\nblandness\nbogs\nboredom\nbreath\ncaring\ncatch\ncautionary\ncharge\nclassical\ncommon\ncross\ndawns\ndawson\ndim-witted\ndoses\ndrown\nease\neffectively\nelement\nenjoyably\nequal\nesther\nevident\nexcesses\nexpressive\nfilling\nfluff\nfreeman\nharsh\nhideously\nhoot\nimpersonal\nimpressed\nincessant\ninconsistent\nirish\nironic\nkidman\nknown\nland\nlanguorous\nlarger\nletting\nlighting\nloneliness\nlurid\nmachismo\nmadonna\nmemories\nmisogyny\nmoves\nmusicians\nnotable\nnumbers\nobservant\npantheon\npertinent\npsyche\npulling\npulpy\nrecording\nredemption\nreel\nreign\nreputation\nrise\nruined\nseeking\nsensitivity\nshenanigans\nsleep\nsneaks\nsobering\nsomebody\nspectacularly\nspot\nstiff\nstore\nstunts\nthick\ntravel\ntruthful\nunsentimental\nvillains\nwell-written\nwet\nwillis\nwinds\nwonders\n1/2\n101\na-list\nache\nadmit\naging\nalbum\nalienation\nbaffling\nbecoming\nblown\nbone\nbound\nbracing\nbusy\ncarol\ncarvey\ncatches\nconcert\ncredibility\ncrimes\ndahmer\ndarker\ndemons\ndemonstrates\ndifference\neerie\nemptiness\nexceptionally\nexpectations\nexpress\nfamilies\nfiennes\nfills\nfits\nflashes\nfragile\ngodard\nheal\nhoward\nice\nidiotic\nimmediate\nimpressions\njennifer\nlonging\nmisfire\nmoody\nnaturally\nnonsense\npanache\nparody\nphones\npiano\npositively\npretensions\nproperly\npungent\nravishing\nrun-of-the-mill\nsanta\nscope\nself-consciously\nshameless\nshanghai\nshines\nshock\nslick\nsly\nsolidly\nsophomoric\nspin\nstatic\nsteal\nstylistic\ntable\ntextbook\ntoilet\ntorture\nunderbelly\nunderstated\nvintage\nwanted\nwhiny\nwrapped\n88-minute\nabuse\nadvantage\naffirming\nanger\nantwone\narnold\nartistry\nartsy\naspect\nauthority\nawfully\nbalanced\nbeast\nblow\ncapturing\ncarry\ncell\ncharacteristic\nchoose\nclaustrophobic\nclimactic\nclothes\nconcession\ncontradictory\ncontribution\ncontrivances\nconvey\ncost\ncostly\ndare\ndroll\ndud\nerotic\nethnic\nexaggerated\nfable\nfailing\nfallen\nfilming\nflamboyant\nflourishes\nformat\ngood-hearted\ngrowth\nhack\nhappened\nheroes\nhigh-concept\nhigher\nhitting\nhole\nhollow\nholly\nholm\nideal\nidiots\nilluminating\nincomprehensible\ninsistent\nirony\njustify\nkinda\nkorean\nlayered\nlessons\nliberating\nlie\nlifts\nlightweight\nload\nloving\nlust\nmarket\nmassive\nmeaningless\nmeat\nmile\nmomentum\nmoviegoing\nnose\nobjective\norganic\npayoff\npays\npit\npointed\npokemon\nprofile\npunch\nquarter\nraised\nremembered\nrohmer\nseemingly\nself-discovery\nsensibilities\nshockingly\nsisters\nsnipes\nspeak\nsplash\nstately\nstatus\nstruggled\nstunt\nsubversive\nsusan\nswinging\nteeth\ntest\nthousands\nthrillers\ntragically\ntreats\nuncertain\nuncomfortable\nuninteresting\nuntalented\nwholesome\nwide\nwondrous\n1\n15\narrive\nawkwardly\nbackground\nbeings\nbelieving\nblatant\nbodies\nbombastic\nbrother\nbruce\nbuilding\ncable\nchest\nchildlike\nchord\nchristian\nclichéd\ncloser\nconceits\nconsciousness\ncontains\nconveying\ncorner\ncriminal\ndentist\ndetermination\ndirects\ndisappointment\ndisjointed\ndocumentaries\ne.t.\nellen\nentertained\nexcruciating\nfoul\nfrance\ngenerate\ngenerates\nglaring\nhedonistic\nhilarity\nhorribly\nhumane\nimage\nimprovised\nincredible\ninformative\ninnocent\ninsane\ninsultingly\nkaufman\nkwan\nlater\nlatter\nlunacy\nmatters\nmonsters\nnarcissism\nnews\nnobody\nobscure\nopaque\nourselves\nouttakes\npaints\nparable\nparts\nperceptive\npitfalls\nplotless\npokes\npompeo\npredecessors\nproportions\npurely\nralph\nrecommendation\nrecommended\nrely\nreviews\nring\nsacrificing\nscores\nsearch\nself-satisfied\nsensibility\nseparate\nshowing\nshrill\nsimone\nsinks\nslo-mo\nspring\nsquanders\nsubplots\nsugar\nsurreal\nsurvival\ntediously\ntends\nthrilling\ntomorrow\ntones\ntorn\nunabashed\nunconventional\nunintentionally\nunnerving\nunsympathetic\nupper\nvictims\nwear\nwoefully\nwriter/director\naccent\naction-packed\nafrican-americans\nalabama\nalluring\namazingly\nanimals\nart-house\nassassin\natlantic\navary\navoids\nbar\nbarney\nbig-screen\nblame\nbleak\nbrand\nbridge\nbuilds\ncasual\ncategory\nchanges\nchina\ncircumstances\nclockstoppers\ncombines\nconfidence\nconspiracy\nconstantly\ncritical\ncry\ncurious\ncuteness\ncutting\ndares\ndarkly\ndefeated\ndefies\ndepartment\ndisappointingly\ndisguised\ndistant\ndreadful\ndrug\neccentric\neventual\nevokes\nexact\nexpert\nexplosion\nexpression\nexterior\nfail\nfearlessness\nfeathers\nfeaturing\nflaccid\nfocus\nglorious\nglossy\ngory\ngreater\nheads\nidealism\nimpenetrable\nimpressively\nimprovement\nincludes\nindependent\nindifference\nindulgent\ninnuendo\ninsomnia\ninsurance\njazzy\njordan\nkahlo\nleaky\nlife-affirming\nmeasured\nmike\nmillion\nminimalist\nnifty\nno.\nparade\nparanoia\nphotographed\npicture-perfect\npie\npleaser\nplight\npool\nprecision\nprison\nproduce\npryor\nqualities\nraise\nrambling\nrefuses\nrental\nresemble\nrevelation\nsacrifice\nschwarzenegger\nscratching\nsecretary\nself-deprecating\nself-important\nshoddy\nsignificance\nsimplicity\nsolutions\nsorry\nspeaking\nsuccumbs\nsuits\nsustains\ntap\ntaylor\ntexture\nties\ntour\ntrashy\nundercuts\nunrewarding\nupbeat\nvaluable\nwallop\nwebsite\nwell-acted\n20th\nabstract\naimed\nalien\nam\namiable\nanderson\narc\nbaggage\nbase\nboasts\nbrightly\nbrilliance\nbrooklyn\ncasts\nchuckle\nclinic\nclosing\ncomedian\ncomedy-drama\ncompellingly\nconsequences\ncontroversy\nconventions\ncostume\ndashing\ndavis\ndeaths\ndeceptions\ndecidedly\ndeniro\ndesired\ndishonest\ndisregard\ndiverse\ndose\ndramas\ndrunken\nduvall\nears\nemphasis\nepisodes\ner\nevans\nextraordinarily\nfathers\nfirst-time\nfix\nfly\nfoolish\nforeign\nfrida\ngave\ngifts\nglass\ngoals\ngosling\ngravity\ngrey\nguaranteed\nhalloween\nham-fisted\nharvard\nheated\nhundred\nhuston\ninnovation\ninterminable\nj.\njerry\njuicy\njumps\njunior\nlane\nlast-minute\nliberation\nlies\nlighthearted\nlimitations\nliterate\nmanhattan\nmeditative\nmonotonous\nnicholas\nnonjudgmental\nnostalgic\nnotorious\nnuance\nopens\npalma\npants\nparadiso\npopulated\npowerfully\nproducer\nprofane\nprofessionals\nprojects\npromised\npromises\npsychologically\npuerile\nputting\nradiant\nrefugees\nrichard\nromp\nrussian\nsandra\nsatisfyingly\nsavor\nsaying\nscare\nscott\nscreening\nshadows\nshoulders\nslightest\nspears\nstomach-turning\nstyles\nsublime\nsucked\nsun\nsundance\nsurprised\nswooping\nteens\ntense\ntight\ntool\ntosses\ntranscend\ntransforms\ntried\ntrust\nturf\nunassuming\nuncomfortably\nwallet\nwinners\nyearning\n3d\nacceptable\naccess\nacclaim\naffected\naliens\nangry\nappeals\nargentine\nasian\naspires\nassurance\nattal\naudacious\nawards\nbirthday\nboat\nbourne\ncage\ncelebrated\ncerebral\ncho\nclown\ncon\ncondescending\ncontinuity\ndeals\ndemonstrate\ndeserved\ndevelop\ndirectly\ndiscover\ndozen\ndragonfly\ndynamic\nego\neight\nencounter\nenvironmental\nextra\nfailed\nfeature-length\nferocity\nfirst-class\nfocused\nfollows\nforceful\ngang\ngel\ngrating\nhammy\nheights\nhey\nhilariously\nhoped\nhuppert\nidealistic\nidiosyncratic\nimmature\nimport\nimprove\nimprovisation\ninescapable\ninfuses\ninspires\nintensely\nirrelevant\njaglom\njapan\njell\njoyless\njudd\nknack\nkorea\nlarger-than-life\nlaugh-out-loud\nleading\nlegacy\nlesser\nlimp\nlit\nloaded\nlofty\nlosers\nmanhood\nmedium\nmental\nmind-numbingly\nmorvern\nmuddle\nmust-see\nnarratively\nneighborhood\nno-nonsense\nnonexistent\nobsession\nobsessive\noffend\noverbearing\noverexposed\nparticipants\npenetrating\npilot\nprime\nprocession\nprofoundly\npunching\npunk\npushes\nrapid-fire\nrecite\nrecognize\nreduced\nrejected\nremind\nremotely\nrolling\nsaga\nsalton\nscenic\nscience-fiction\nsecondhand\nsenses\nsinister\nsleepless\nsmack\nspecimen\nspontaneity\nsteve\nstrictly\nstrongest\nstrongly\nstudent\nstylized\nsuffer\nsuggests\nsystem\ntarantino\nteeming\nthematically\nthrowing\nthrown\ntongue-in-cheek\ntraditions\ntriangle\ntsai\nundoubtedly\nuneasy\nuneventful\nunforgettable\nunimaginable\nunless\nunsuspecting\nventure\nvictim\nwake\nwhale\nwins\nyiddish\nzero\n100\nachieve\nadrenaline\nafter-school\naggressively\nai\nalike\nallowed\nanthony\narbitrary\nartistes\nassembled\nattempted\nbard\nbarrel\nbasis\nbilked\nblockbusters\nboasting\nboldly\nborders\nbow\nbreadth\nbruckheimer\ncaine\ncallar\ncanny\ncenter\nchiller\ncoffee\ncollectively\ncollision\ncomposed\ncomposition\ncompromise\ncontest\nconvictions\ncrudup\ncultures\ncylinders\ndamning\ndance\ndecade\ndefinitive\ndetached\ndisappoint\ndong\ndreck\ndressed\ndrowned\neffortlessly\nempire\nentry\nexcited\nexplores\nfaces\nfairy\nfavors\nfield\nflat-out\nfrequent\ngear\ngenres\ngift\ngreatness\ngrin\ngross\nguess\nharmless\nhartley\nhas-been\nheady\nhelped\nhenry\nhewitt\nhost\nhurt\nilk\ninclination\ningredients\ninner\nintellect\nintelligently\nintentionally\ninventiveness\njewish\njoel\njumble\nkick\nlaudable\nlaughed\nlifestyle\nlisten\nloathsome\nloosely\nlunch\nmaintain\nmaintaining\nmanipulation\nmatinee\nmedical\nmediocrity\nmiserable\nmisguided\nmonstrous\nmoronic\nnarrator\nnary\nnicholson\nnijinsky\nnormally\nnot-very-funny\nnotes\nocean\nopening\novercooked\npathos\npatient\npeak\nphysical\npicked\npitch\npractically\npretend\npride\nprogram\nrandall\nrealizing\nrelative\nroberto\nroman\nrouge\nsack\nscrooge\nsegment\nsend\nshakespeare\nsisterhood\nsleeve\nsmash\nsquarely\nstrict\nstruggling\nsubstitute\nsubtitles\nsubtlest\nsuperficiality\nsupernatural\nsurrender\ntackling\ntensions\nthousand\ntook\ntraffic\ntrap\nundone\nvein\nwal-mart\nwarfare\nwarrior\nwarriors\nwearing\nweighty\nwell-meaning\nwhenever\nwilson\nwisdom\nwishing\nzany\n1958\n4ever\n51\nabroad\naccurately\nadmittedly\naimlessly\nallegory\nangels\nastute\nathletes\natrocious\nauteuil\nawakening\nawe-inspiring\nbelievability\nbenefit\nbiopic\nblack-and-white\nblaxploitation\nbrash\nbrave\ncanvas\ncat\ncgi\ncharacterization\nchristopher\ncircuit\nclub\nco-writer\ncomplexities\nconcoction\nconsistent\nconspicuous\ncrack\ncraft\ndaniel\ndialog\ndrew\ndrivel\ndriven\ndrooling\nduration\ndustin\nduty\nelsewhere\nemperor\nemphasizes\nenjoying\nentertainingly\neuropean\nexcels\nflailing\nflashbacks\nfreddy\nfreedom\ngasp\ngenerosity\ngoo\ngrim\ngun\nheaded\nhear\nhidden\nhitchcockian\nhotel\nhumanistic\nhumanly\nhumans\nhypnotic\niconoclastic\nimmediately\nindividual\ninexplicably\ninoffensive\ninvaluable\nironies\nirreparable\njealousy\njolts\nlapses\nlegendary\nleon\nlifetime\nlikeable\nliterally\nlola\nlongest\nlord\nlow-grade\nluck\nlunatic\nmaddeningly\nmelancholy\nmexican\nmira\nmolly\nmugging\nmundane\nnickleby\norder\npeek\nphilosophy\nplympton\nporridge\nprecisely\npredictability\nprefer\npuzzling\nramsay\nreefs\nrelaxed\nreliable\nremaining\nrestroom\nrewarded\nridiculousness\nrigor\nripe\nroots\nrowling\nschneider\nseams\nsensuality\nshowcases\nsickening\nsilver\nskillfully\nsloppily\nsmooth\nsmoother\nspike\nsportsmen\nsprightly\nsqueeze\nstealing\nstick\nstone\nstooping\nstorylines\nsubtext\nsuccessor\nsuccumb\nsucks\nsuggest\nsuit\nsuperbly\nsupport\nswear\nswim\ntales\ntheories\nthree-hour\ntoss\ntowards\ntrace\ntransformation\ntransformed\nturgid\nundead\nundercut\nunderneath\nuniquely\nuniverse\nunlimited\nvietnam\nwanders\nweaknesses\nwillingness\nwondrously\n11\nabsurdities\nadequate\nadmission\naffleck\nalert\nall-time\namuse\namy\nanatomical\nanswer\narrest\narrives\naudacity\nbackstage\nbaffled\nbands\nbeats\nbible\nbluster\nbogus\nbonus\nboobs\nbravery\nbreathes\nbride\nbrisk\nbrosnan\nbullets\nbuttons\nbuñuel\ncallow\nchaos\nchicago\nchick\nchosen\nclooney\ncolored\ncolors\ncoma\nconduct\nconjured\ncontrivance\ncram\ncrammed\ncranky\ncraven\ncreepiness\ncrisis\ncrossover\ndead-end\ndelectable\ndepressed\nderived\nderrida\ndevolves\ndiesel\ndiscipline\ndistanced\ndistraction\ndramatics\ndude\nechoes\nembrace\nembraced\nended\nendurance\nenigma\nenjoys\nestablished\nexcruciatingly\nextravaganza\nfabric\nfabulous\nfarcical\nfearless\nfeast\nfellow\nfewer\nfizz\nfollowed\nfool\nfulfill\nfury\ngender\ngerman\ngibson\nglorified\nglum\ngolden\ngracefully\ngrade\ngrew\ngrounded\nguide\nhates\nhopeless\nhopelessly\nhour-and-a-half\nhumming\nian\nicon\nidiocy\nillustrates\nimproved\ninimitable\ninsecure\ninsults\nintroduction\nisolation\njaw-dropping\njournalism\nkapur\nkilled\nknockout\nlavish\nlight-hearted\nlips\nlittered\nlock\nlodging\nlucky\nlurches\nmanaged\nmanners\nmarvel\nmasses\nmaze\nmel\nmetaphors\nmoralizing\nmountain\nmovie-going\nnames\nnash\nnaturalistic\nnotwithstanding\nnumbing\no\none-dimensional\noverused\npainted\npalette\npalpable\npar\npaxton\npedigree\nperformed\nperfunctory\nplotline\npraise\npraises\nprospect\nracist\nrandomness\nrange\nrapes\nreached\nreaching\nreacting\nready\nrecklessness\nrecycling\nrelentless\nreminder\nrescue\nresistance\nrevealed\nrevel\nrevelations\nrobin\nromances\nrotten\nsardonic\nscares\nsearching\nseas\nseeks\nshabby\nship\nshowcase\nskies\nskillful\nsoldiers\nsounding\nsources\nspaces\nspeaks\nspirituality\nspotlight\nstarting\nstarving\nstuart\nsubmarine\nsuitable\nswept\nthirty\nthoughtfulness\nthrow\ntimid\ntoback\ntranscendent\ntransparent\ntwo-dimensional\nurine\nvain\nvietnamese\nwell-intentioned\nwitnessed\nworry\nx.\nyesterday\nyouthful\n12-year-old\n13\nadams\nadding\nadequately\nafternoon\nanalysis\nanguish\nanime\nantonia\nappreciated\naspirations\nassayas\natmospheric\nbackdrops\nbackmasking\nbank\nbargain-basement\nbarry\nbeguiling\nbegun\nbucks\nburger\ncanon\ncapra\ncarrying\ncarved\nchateau\ncheeky\nchew\ncia\nclad\nclause\nclunky\nco-written\ncomics\ncompared\ncomplaint\nconcentrates\nconceptual\nconsolation\ncrafty\ncreations\ncrippled\ncumbersome\ncurves\ncuts\ndana\ndarkness\ndebate\ndeception\ndepraved\ndime-store\ndimension\ndisplays\ndistinct\ndistinguish\ndisturbing\nditsy\ndoofus\ndopey\ndozens\ndubious\nduke\ndupe\ndutiful\ndynamics\nedits\neffortless\nelizabeth\nengagingly\nenigmatic\nenlightening\nenticing\nerrors\nessential\nevade\nexamines\nexpecting\nexperienced\nexperiment\nexploit\nextravagant\nfade\nfascination\nfixating\nflawless\nfloor\nfluffy\nfolk\nformal\nforth\nfull-bodied\ngenius\ngrasp\ngrisly\ngrotesque\ngut-wrenching\nhall\nheat\nheightened\nhighs\nhill\nhistrionics\nhokey\nhot-button\ninappropriate\ninauthentic\nincorporates\nindescribably\ninexplicable\ninfectious\ninject\ninsensitivity\ninteraction\ninterestingly\nirresistibly\njackass\njagged\njake\njolly\njuliette\njumbo\nlanes\nlayer\nleather\nlegal\nlegged\nlevity\nlonely\nmad\nmajestic\nmakeup\nmaternal\nmechanical\nmelancholic\nmerits\nmidway\nmodesty\nmonths\nmysteries\nnair\nnegative\nnostalgia\nnoticeably\none-liners\nordeal\noutcome\noverrun\novertly\npackage\npaint-by-number\npandering\npastiche\npayami\npedestrian\nperform\nphone\npiercing\nplacement\npollution\nportray\npredicament\nprocedural\nproductions\npulse\npush\nraises\nreceived\nrelate\nreluctant\nrepellent\nrequisite\nresearch\nresonate\nrings\nritchie\nrival\nrobinson\nrocky\nsara\nschmaltz\nschmidt\nscreens\nscrewball\nself-caricature\nself-esteem\nsentence\nsequel-for-the-sake\nserenity\nsessions\nset-up\nshared\nshower\nshred\nshtick\nsignificantly\nsmiles\nsmugly\nso-called\nsober\nsorority\nsoufflé\nspice\nspontaneous\nsteers\nsticky\nstinks\nstoop\nstrangers\nstrengths\nstunningly\nsublimely\nsudsy\nsuspect\nsymbolic\ntart\ntattered\nteachers\ntechnique\ntempting\nterror\ntown\ntrek\nturd\nunbearably\nundercurrent\nunmemorable\nunsatisfying\nunwatchable\nurgently\nvan\nvastly\nvictimized\nvideotape\nweep\n1999\n40\n86\nabsurdly\naccomplish\nachievements\nachieving\nacute\nafterthought\nagent\nambiguity\nangel\nantics\napplies\narguments\natop\naustin\nball\nbanality\nben\nbile\nbinoche\nbiography\nbrainless\nbrains\nbrits\nbrushes\nbuild\nbump\nbuzz\ncalibrated\ncardboard\nchanneling\nchasing\ncleaner\nclear-eyed\nclimax\nclung-to\ncohesion\ncomprehension\ncomputer-generated\nconquer\nconversations\ncooper\ncostner\ncovers\ndamaged\ndarling\ndead-on\ndean\ndeliberately\ndepression\ndeserving\ndestined\ndirections\ndisquieting\ndowntown\ndownward\ndust\nearn\neastern\neducation\nencounters\nenervating\nepisodic\nevoking\nexperimental\nexpertly\nexplode\nexploitive\nexploring\nfacile\nfalling\nfascinate\nfast-paced\nfeet\nfest\nfondness\nfontaine\nforcefulness\nfun-for-fun\nguilt\ngutless\nguts\nhampered\nharris\nheart-pounding\nhitchcock\nhopes\nhypocritical\ninsignificance\ninstance\ninstitution\ninterpretation\ninterviews\njaded\njolie\nlaborious\nlobby\nloveable\nlows\nmadness\nmcdormand\nmechanics\nmember\nmerchant\nmercy\nmetropolitan\nmisanthropic\nmoods\nnamed\nnebrida\nnelson\nnourishing\nnumerous\none-of-a-kind\noozing\nopened\nopera-ish\noptimistic\norlando\npainting\nparent-child\npasses\npassing\npeculiarly\npeppered\nperils\npervasive\npileup\npitched\npompous\npossibility\npratfalls\npre-credit\nproficient\npulp\nradar\nrarity\nraunchy\nreasons\nrecognized\nregardless\nregion\nrequire\nreserved\nretaliation\nrhetoric\nrichness\nrubbo\nsanctimony\nscruffy\nsealed\nself-examination\nsensuous\nsermon\nsetups\nshape-shifting\nshapes\nshifting\nshine\nshrek\nsimpleminded\nsin\nslackers\nsleeper\nslippery\nsmartest\nsmirk\nsmoochy\nsnore\nsoccer\nspan\nspins\nsplit\nsquare\nsteady\nsteeped\nstepped\nsticks\nstill-inestimable\nsting\nstretched\nstroke\nsuffocating\nsuper\nsurefire\nsurfing\nsway\ntawdry\nthinly\nthinness\nthreadbare\nthree-dimensional\nticking\ntop-billed\ntraditionally\ntripe\ntug\ntuxedo\ntwisty\ntwohy\nugliness\nunbearable\nups\nvulnerable\nwalsh\nwastes\nwaters\nweave\nweepy\nwhat-if\nwitness\nworthless\nwound\n19th-century\nabomination\nabundant\nabyss\nactuary\nadorably\naftertaste\naids\nalfred\nall-enveloping\namateur\namorality\nanticipated\naplenty\narty\nattached\nattack\nawarded\naware\nbaby\nbang\nbarrymore\nbesson\nbetrayal\nblah\nbloated\nbogdanovich\nbogged\nbolt\nborn\nbrush\nburning\nburr\nbutt\ncandid\ncannes\ncarmen\ncarpenter\ncheaper\ncheated\nchekhov\nchelsea\nchiefly\nchoreographed\ncinderella\nclaim\ncliché-riddled\ncluelessness\ncoen\ncollapse\ncollateral\ncollective\ncompanionable\ncompetition\ncompulsively\nconnect-the-dots\nconsidering\ncrackle\ncrew\ncrowd-pleasing\ncutesy\ncutting-edge\ndad\ndeliberate\ndelinquent\ndeparture\ndesiccated\ndevastatingly\ndevelops\ndiaries\ndilemma\ndinner\ndiplomacy\ndips\ndisappointments\ndive\ndonovan\ndough\ndover\ndrains\ndriver\ndrowsy\ndual\ndullard\ndv\near\neducates\neerily\nefficient\neric\nethereal\nexpects\nexperimentation\nextremes\nfessenden\nfiercely\nflabby\nflash\nflesh\nforcefully\nforest\nformed\nfourth\nfrantic\nfresh-faced\nfreshly\ngasping\ngere\nglib\nglimpses\ngoofball\ngrinder\nhandsomely\nhaphazard\nhardened\nheartening\nheist\nhelm\nheroic\nhide\nhitler\nhole-ridden\nhooting\nhorrors\nice-t\nidemoto\nill-conceived\nill-fitting\nimaginatively\nimpersonation\nimproves\ninevitably\ninfomercial\ninformation\ninner-city\ninsightfully\nintact\ninterview\nintroducing\ninvolve\nkicks\nkilling\nkindness\nkosashvili\nlark\nlearning\nlibrarian\nlimits\nloyal\nlucy\nlulls\nlumbering\nm.\nmarching\nmasculine\nmeets\nmessiness\nmetaphorical\nmill\nmiller\nminute\nmisses\nmiyazaki\nmonumental\nnaughty\nneatly\nneil\nnon-stop\nnonstop\nnoticing\nnovels\nnuances\nobstacles\nodyssey\nofferings\noperates\nouting\noutsiders\noverproduced\noverwritten\npasse\npassions\npayne\npg-13\npinocchio\nplainly\npleasantly\npop-induced\npossess\npost\npotshots\npressed\nprevents\npropaganda\nprose\npushed\nqualify\nquestionable\nquivering\nr.\nrap\nrattling\nreaction\nrediscover\nredundancy\nrefused\nregard\nreleases\nreminded\nreruns\nrestrictive\nreveal\nriver\nrock-solid\nrollerball\nroot\nroutines\nrugrats\nrumination\nrut\nsatisfactory\nsaved\nscarcely\nscarier\nself-glorification\nsensationalism\nseptic\nshafer\nshaggy\nshape\nsoar\nsoars\nsouthern\nspader\nspecifically\nspeculation\nspeedy\nspellbinding\nspookiness\nstagy\nstaring\nstoked\nstrays\nstrokes\nstronger\nstruggles\nstupidity\nsubstances\nsucker\nsummertime\nsurfeit\nsurvive\nswan\nsweat\ntacky\ntactics\ntank\ntatters\nterminally\ntestimony\nthornberrys\ntitular\ntolerance\ntranscendence\ntranslate\ntraveler\ntrenchant\ntroubled\ntwaddle\ntwisting\nuh\nunamusing\nuncle\nunconditional\nuniformly\nunknown\nunpretentious\nunrelentingly\nvisible\nwan\nwander\nwaves\nweaving\nwelsh\nwendigo\nwesley\nwestern\nwhimper\nwhodunit\nwicked\nwilco\nwilliam\nwood\nwriters\nyu\n#\n1.8\n1970s\n21st\n6\n8\naccepting\naesthetic\naged\nallegedly\nallowing\nanachronistic\nanemic\nanimatronic\nanne\napparatus\nararat\nargue\narresting\narticulate\nassault\nassociated\nassumes\nastray\natmospherics\natrociously\naustralia\nawry\nbad-movie\nbankrupt\nbarrels\nbasketball\nbeacon\nbehave\nbielinsky\nbirot\nblanket\nblip\nbluescreen\nblurry\nboisterous\nbonds\nboozy\nborrowed\nborrows\nbouncy\nbowling\nbrawny\nbrief\nbroiling\nbrutality\nbudding\nbumbling\ncackles\ncalifornia\ncaptain\ncar-wreck\ncarefully\ncathartic\nchampion\nchaplin\ncheat\ncheesiest\nchief\nchopsocky\nchuckles\nchurn\ncodswallop\ncombat\ncomically\ncompanion\ncomplacency\ncomprehend\nconceit\nconception\ncondensed\nconfined\nconnect\nconned\nconvention\ncorniest\ncounterparts\ncourageous\ncriticism\ncrocodile\ncuban\ncynicism\ndanger\ndaughters\ndearth\ndecrepit\ndedication\ndeepest\ndefinition\ndeliciously\ndenied\ndenying\ndepths\ndetermine\ndiary\ndiscernible\ndiscovering\ndisgust\ndistracted\ndragons\ndredge\nedit\nefficiently\negregious\nelectric\nelephant\nelevates\nembracing\nenchanting\nengages\nenhances\nenters\nestablishes\nethical\nexamine\nexpressing\nexpressions\neyelids\nfabricated\nfantasma\nfellowship\nflatly\nflaw\nfondly\nfootnote\nford\nforgiven\nforgiveness\nfoul-mouthed\nfragmented\nfreighter\nfrenetic\nfused\nfuture-world\ngang-raped\ngimmicky\ngood-time\ngoodfellas\ngosford\ngratefully\ngrind\ngrown-ups\ngrownups\nguillen\ngunfight\ngutter\nhamming\nhawaiian\nhead-trip\nhealthy\nheart-wrenching\nherself\nhint\nholographic\nhopefully\nhorse\nhostile\nhumility\nhurry\nhysterics\nignore\nill\nill-considered\nill-timed\nimmediacy\nimmersed\nimpossibly\nimpulsive\ninarticulate\nincarnation\nindecent\nindians\nindie-heads\ninfatuated\ninfluenced\ninjects\nintricately\njaw-droppingly\njerking\njoie\njonah\njoys\nkalvert\nkids-and-family-oriented\nkinetic\nkinky\nkitchen\nlasting\nlaughably\nlaurice\nlengths\nlengthy\nlewis\nliberties\nlights\nlisping\nliteral\nlogical\nloopy\nlose\nlowbrow\nluckiest\nluridly\nmacabre\nmalkovich\nmamet\nmanic\nmedia\nmendes\nmentally\nmired\nmolested\nmorgan\nmother/daughter\nmtv\nmyth\nna\nnarration\nnatter\nneedlessly\nninth\nno-frills\nnonetheless\nnovak\nnoyce\nnumbingly\nnutty\nof-a-sequel\noliver\nought\nout-bad-act\noverdoing\noverflowing\noverladen\noverly-familiar\nowes\npairing\npaperbacks\nparanoid\npartly\nperdition\nperfection\npersonified\nphrase\npill\npink\npinnacle\npizza\nplayer\npreemptive\nprimitive\nprior\nprism\nprofanity\npuberty\npunny\nrapt\nraunch\nreactionary\nreceiving\nrecognizable\nredeeming\nrehashes\nrelic\nreminding\nrepeatedly\nrepetition\nrepresented\nreptilian\nresembling\nretains\nrevisionist\nriot\nritter\nroberts\nrotoscope\ns/m\nsag\nsalt\nsatisfy\nsavage\nsavagely\nsavory\nscale\nschaeffer\nschedule\nschumacher\nscreenings\nseattle\nsection\nseduce\nself-indulgence\nsemen\nseriousness\nsexually\nshadowy\nshop\nshortcomings\nshorter\nsillier\nsits\nsledgehammer\nsmoking\nsnl\nsolemn\nsoon-to-be-forgettable\nspider\nspiked\nspine\nsporadic\nsport\nsprung\nsquad\nstark\nstephen\nstopped\nstream\nstuffiest\nsub\nsubordinate\nsubtler\nsucceeded\nsuccessfully\nsupply\nsurvivors\nsuspects\nswooning\nt\ntapping\ntask\ntastelessness\ntea\ntelegraphed\ntendency\nthomas\ntissues\ntoilet-humor\ntolerate\ntravesty\ntrue-to-life\ntuned\nun-bear-able\nunable\nunappealing\nunderdone\nunderlying\nunimaginatively\nuninvolving\nunleashes\nunnoticed\nupdating\nupscale\nvibe\nviewed\nvignettes\nviolently\nvoice-over\nwasting\nwayward\nwell-shaped\nwest\nwhatsoever\nwhining\nwitnesses\nwork-in-progress\nwoven\nxerox\nxxx\nyouth\nzippy\n'60s\n1984\n2000\n3\n8-year-old\nabc\naccomplishes\naccomplishment\nactions\nadrenalin\nafrican\naisle\nalice\nalienate\nall-too-familiar\nanyway\nanywhere\napply\narduous\nareas\nargument\narms\nastoundingly\naudiard\nausterity\nauthentically\navarice\nbadness\nbalancing\nballistic\nbaroque\nbear\nbeaten\nbeliever\nbeloved\nbiased\nblasphemous\nbliss\nblonde\nbloodsucker\nbollywood\nbones\nbooks\nboomer\nbothers\nboundaries\nbowel\nbubba\ncampy\ncaptivates\ncareer-best\ncareers\ncartoons\ncatalyst\ncelebrity\ncharged\ncharles\nchasm\ncheering\nchurch\ncircle\nclaptrap\nclean\ncliche-ridden\nclimate\ncocktail\ncode\ncomments\nconceive\nconditioning\nconflicts\nconsigned\nconstraints\ncontemplation\ncontinuation\ncontrast\ncontroversial\ncops\ncounter-cultural\ncredulous\ncreek\ncreeping\ncruelty\ncrypt\ndamon\ndated\ndaydreams\ndecided\ndeeds\ndefine\ndepict\ndestiny\ndestroy\ndestruction\ndey\ndickensian\ndirect\ndisabilities\ndisintegrating\ndisorientated\ndon\ndoor\ndragged\ndreamed\ndroning\ndullness\ndurable\nearned\nebullient\nechelons\nedition\nedward\neffecting\nelbowed\nelevated\nemploys\nenchantment\nencourage\nenriched\nenvironment\netc.\nevaluate\nevenings\neverlasting\neveryday\nevolved\nexists\nexpands\nexplosions\nexuberance\nfairy-tale\nfashioned\nfees\nfeisty\nferrara\nfiascos\nfingered\nfires\nflagrantly\nflatter\nflickering\nflight\nflorid\nfoibles\nfolly\nforcing\nfriendships\nfuriously\nfuss\nfutile\ngaping\ngaze\ngenteel\ngeriatric\ngoers\ngoldmember\ngong\ngoods\ngreene\nguarantee\ngusto\nhalf-baked\nharder\nheart-warming\nheartache\nheritage\nhigh-wattage\nho-tep\nhomicide\nhonor\nhook\nhuman-scale\nhumanism\nimpart\nimportance\nimpress\nimproperly\ninclude\ninexperienced\ninfantile\ninfusion\ningenuity\ninherently\ninnovations\ninquisitive\nintent\nintrospective\ninuit\ninvasion\nironically\nissue\njagger\njet\nk-19\nkeen\nkieslowski\nkittenish\nknee-jerk\nlampoon\nlandscapes\nlate-twenty-somethings\nlaw\nlegally\nlegend\nletdown\nliability\nlift\nlikes\nlip-non-synching\nlittle-known\nlosses\nlustrous\nmajor-league\nmatthew\nmaturity\nmawkish\nmessenger\nmessy\nmetropolis\nmichel\nmine\nmisplaced\nmolestation\nmonsoon\nmorbidity\nmotions\nmotivations\nnails\nnaked\nnarc\nnavigate\nneck\nnerves\nnudity\noedekerk\noh-so\nopportunities\norgasm\norwell\noutdated\nover-blown\npacino\nparking\npencil\npickup\nplucking\nplus\npotato\npractice\nprecise\nprejudice\npreliminary\nprep-school\npress\nprevails\npreview\nproducers\npromisingly\nproposal\nproving\nprovoke\npseudo-educational\npsycho\npuff\npulled\npurposeless\npurposes\npushiness\nputrid\nquentin\nquirkily\nray\nreal-time\nrealistically\nrealm\nreassuring\nreceive\nredolent\nreflection\nreflective\nrefreshed\nrefreshes\nregalia\nreggio\nreinforcement\nrelegated\nreliance\nrepulsive\nretro\nrevenge-of-the-nerds\nreverent\nrevisiting\nriveted\nrobotically\nrobust\nrocks\nromancer\nsanctimonious\nsays\nscarifying\nschmaltzy\nscientists\nscrewed-up\nsearing\nsecond-guess\nseductive\nself-destructive\nsensation\nserials\nserving\nshadyac\nshakes\nsham\nsharpener\nsheerly\nshorts\nshout\nshowdown\nshowgirls\nsibling\nsignposts\nsing\nskewed\nslanted\nslap-happy\nslim\nsmacks\nsneak\nsneering\nsonny\nsorrow\nspiced\nspiffy\nspiteful\nsquandering\nstaggeringly\nstanley\nstitch\nstoops\nstops\nstrains\nstretch\nstubbornly\nstudios\nsuitably\nsummer-camp\nsurf\nsurvivable\ntaps\ntear-jerking\nteeth-clenching\ntheatre\ntheory\nthreatened\nthrown-together\ntosca\ntriteness\ntrope\ntropes\ntunney\nturkey\nunapologetic\nunder-inspired\nunderappreciated\nunderscore\nundisciplined\nunexplainable\nunintentional\nunrealized\nvaliantly\nvictories\nvolume\nvulnerability\nwalker\nwalls\nwater-born\nwaydowntown\nwinking\nwonderous\nword-of-mouth\nworn-out\nwrestling\nwryly\nyawning\nyoungsters\nzings\n'em\n146\n18-year-old\n80\naccumulates\naccuracy\nace\nacknowledges\nacumen\nadjective\nadmirably\naffirms\naficionados\naggressive\nalienating\nanchoring\nancient\nannals\nannoyed\nanomie\nantic\nantitrust\nappearing\nappointed\narray\nash\nashley\nassume\nastonish\nauditorium\nautobiographical\navailable\navant-garde\nbalances\nbanter\nbaran\nbarn-burningly\nbenefits\nbewitched\nblithe\nblood-curdling\nbodily\nbores\nbrainpower\nbravado\nbucked\nburrito\ncalls\ncamaraderie\ncapability\ncaptivated\ncard\ncatching\ncatholic\nchair\nchases\ncheapo\nchoosing\ncircus\nclash\ncleverest\nclosely\ncloyingly\nco-winner\ncompanionship\nconclusions\nconcocted\nconfidently\nconfront\nconfrontations\nconvoluted\ncorrupt\ncorruption\ncounts\ncourt\ncraftsmen\ncreatures\ncredited\ncrowd-pleaser\ncultivation\ncup\ncure\ncurse\ndash\ndead-undead\ndealer\ndear\ndecibel\ndefiance\ndeja\ndemand\ndemme\ndemonstrating\ndenis\nderailed\ndescribes\ndevotees\ndidactic\ndifferences\ndigest\ndimensions\ndimness\ndiscordant\ndiscussion\ndismiss\ndistinctly\ndisturbance\nditty\ndoing-it-for\ndope\ndrab\ndumped\nearlier\nearthly\neast\neating\necks\nelbows\neloquence\nelusive\nemployment\nencompassing\nenthusiasts\nentity\nenveloping\neven-flowing\neverybody\neverywhere\nevidence\nevolve\nexceeds\nexcites\nexecutive\nexist\nexuberant\nfacet\nfamily-friendly\nfascinates\nfatter\nfeeble\nfeelings\nfi\nfiend\nfilmgoing\nfishy\nflavorful\nflip-flop\nfluent\nfluke\nfoot\nfoul-natured\nfraction\nfreddie\nfrightfest\nfrissons\nfrontal\nfulfilling\ngary\ngeared\ngenerations\ngenial-rogue\nglass-shattering\nglimmer\nglow\ngoddammit\ngondry\ngoofiest\ngoofiness\ngraphics\ngratingly\ngrave\ngrueling\nguilty-pleasure\nguy-in-a-dress\nhalf-asleep\nhalf-hearted\nhallmarks\nhaneke\nharbor\nhawk\nhayek\nhelpings\nhigh-minded\nhighlight\nhindered\nhoary\nhodgepodge\nholding\nhungry-man\nhunk\nidentification\nillness\nimpassive\nimprobability\nin-depth\nindigestion\nineptly\ninjuries\ninstallment\nintegrated\nintro\ninvited\nirksome\nirrelevancy\nisrael\nitalicized\nj.k.\njane\njealous\njessica\njfk\njoe\njr.\njuice\njulia\njunkie\nkissing\nknock\nknockabout\nlaid\nlandmark\nlanguid\nlanguishing\nlaugh-a-minute\nleash\nleavened\nleftovers\nleguizamo\nlibertine\nlight-footed\nlilo\nlistening\nloser\nlow-rent\nlower\nluster\nlyricism\nmaelstrom\nmaneuvers\nmanifesto\nmann\nmannerisms\nmarkets\nmarveling\nmcgrath\nmemorial\nmeticulous\nmilestones\nmilquetoast\nminority\nmiraculous\nmiramax\nmiscalculation\nmission\nmist\nmistress\nmisuse\nmodern-day\nmonday\nmorph\nmultiplex\nnegligible\nnegotiate\nnet\nnewfangled\nnickelodeon-esque\nnorton\nnot-so-funny\nnoticeable\nnæss\nobject\nobjectivity\noily\nolivier\non-screen\none-trick\nopen-ended\norgy\nours\nout-to-change-the-world\nouter-space\noutright\noverwhelmed\noverworked\npadded\npains\npainterly\npassable\npassed\npawn\npearl\npeas\npenchant\npermeates\npetter\nphoenix\nphoney-feeling\nphoniness\npile-ups\npipeline\npony\npopularity\nportions\npower-lunchers\nprim\nprincipal\nprinciples\nprint\nprivy\nprobing\nproceeds\npubescent\npuddle\npumpkin\npunishment\npurposeful\npuzzle\nquandaries\nqueens\nquieter\nraindrop\nrally\nrant\nrate\nraucously\nrazzle-dazzle\nreadily\nreconstruction\nreflects\nrelying\nreno\nreprieve\nrerun\nresponsibility\nretitle\nrevitalize\nridicule\nrigid\nringing\nroughshod\nround\nrousing\nsadistic\nsan\nsassy\nschizophrenia\nsci\nscooped\nscooping\nscorpion\nselection\nself-aggrandizing\nself-aware\nself-awareness\nself-congratulation\nself-hatred\nself-righteousness\nsentiments\nserviceable\nsever\nsewage\nshakespearean\nshe-cute\nshown\nshrieky\nshudder\nsiegel\nsimple-minded\nsizzle\nskills\nslapping\nso-so\nsoft-core\nsolaris\nsorrowful\nsought\nspeed\nsquashed\nstages\nstalls\nstamp\nstand-up\nstates\nstellar\nstevens\nstifles\nstirs\nstores\nstrained\nstrangeness\nstreets\nstuffing\nstuffy\nstylists\nsubjugate\nsubzero\nsuccumbing\nsucking\nsuffered\nsuggested\nsung\nsuperhuman\nswedish\nsweetheart\nsymbolism\ntackles\ntagline\ntall\ntame\ntattoo\ntaut\ntavernier\ntax\nteach\ntearjerker\nteen-driven\nterrorism\nterrorists\ntestud\nthe-cash\nthreat\nthunderstorms\ntian\ntightrope\ntimelessness\ntoes\ntopless\ntopple\ntract\ntremble\ntwo-day\ntwo-hour-and-fifteen-minute\nunadorned\nunblinking\nuncreative\nundermining\nunfilmable\nunifying\nunited\nunmentionable\nunmistakable\nunrecoverable\nunsubtle\nuptight\nurgency\nurgent\nvacant\nvaguely\nvaliant\nvariation\nveers\nveracity\nvia\nvisionary\nvisualize\nvivre\nvon\nvu\nwaking\nwashed\nwaterlogged\nweary\nwednesday\nwell-balanced\nwell-deserved\nwell-developed\nwell-done\nwherever\nwide-smiling\nwig\nwildlife\nwinningly\nwore\nworking-class\nworkplace\nworld-renowned\nzingers\nzombie\n'30s\n'40s\n+\n22-year-old\n25\n3000\n4\n5\n84\n91-minute\nabove-average\nabsurdist\naccepts\naccident\naccused\naction-adventure\nadorable\nadvised\naesthetically\naffable\nagenda\nages-old\naggressiveness\naggrieved\nah\nairless\nalbeit\nall-star\nallusions\namateurishly\namazement\namish\namnesiac\namusement\nanciently\nangle\nanguished\nanonymous\nanymore\napocalypse\nappreciation\nargento\narmed\nartfully\nasia\nassassination\nastronomically\nawake\nawed\nbabak\nbackgrounds\nballplayer\nbanderas\nbartlett\nbathroom\nbest-sustained\nbiographical\nbludgeoning\nboilerplate\nbolero\nboost\nboots\nboundary-hopping\nbourgeois\nbratty\nbrim\nbrio\nbronx\nbug-eyed\nburst\nby-the-numbers\ncal\ncalculated\ncalculating\ncampus\ncardiac\ncaretakers\ncaricatures\ncarlin\ncastro\ncelebratory\nchallenge\nchallenges\ncharacteristically\ncharade\ncheapened\ncheatfully\nchecking\ncheer\ncheesier\ncherish\nchick-flicks\nchimes\nchump\ncleavage\ncletis\ncomedy/drama\ncommercialism\ncommunicates\ncompassionate\ncompetence\ncomplexly\nconcepts\nconditions\nconnected\nconstructs\nconsumed\ncontemptible\ncoolness\ncopout\ncostumes\ncotton\ncoupled\ncourageousness\ncraig\ncrane\ncrawl\ncreeps\ncricket\ncrossing-over\ncrossroads\ncrummy\ncrummy-looking\ncrushingly\ncuisine\ncurmudgeon\ndamaged-goods\ndamme\ndamon/bourne\ndangerously\ndaughter\ndavies\ndaytime\ndaytime-drama\ndebated\ndebt\ndecide\ndecision\ndefiantly\ndemise\ndemographic\ndenzel\ndetract\ndevastated\ndevelopments\ndevice\ndiabolical\ndiane\ndiner\ndirectionless\ndisastrous\ndisconnects\ndiscreet\ndisdain\ndismay\nditched\ndiversity\ndolls\ndoubts\ndown-to-earth\ndownhill\ndownsizing\ndraw\ndread\ndreaming\ndreamlike\ndrips\ndrowns\nduck\neagerness\nearplugs\necological\neconomical\necstasy\nedges\nelling\nenables\nencompasses\nensure\nepiphany\nerotically\nerror\nescort\nespionage\nestranged\nethics\nevaporates\nexasperating\nexhausted\nexpiration\nexpresses\nextent\nextreme-sports\neyre\nfacetious\nfactors\nfaltering\nfamiliarity\nfantasia\nfar-flung\nfast-moving\nfirst-timer\nflesh-and-blood\nfleshed-out\nflinching\nflippant\nfluidity\nfore\nforemost\nforgets\nforum\nfour-hour\nfreakshow\nfright\nfumbled\nfunction\nfundamentally\nfunniness\ngabbiest\ngaining\ngiant-screen\ngirlfriend\ngoodly\ngraduated\ngrandkids\ngrayish\ngremlins\ngrieving\ngriffiths\ngrips\ngrown\ngrumble\nguessed\nguffaw\ngullets\nhalf-step\nhandle\nhandling\nhard-hitting\nhaynes\nheart-felt\nheavily\nhennings\nhigh-octane\nhitch\nhorrendously\nhour-and-a-half-long\nhousehold\nhouses\nhug\nhughes\nhumanizing\nhumankind\nhungry\nhusband-and-wife\nignorant\niii\nills\nimpulses\ninadvertent\nincident\nincorporate\nindication\ninduce\ninhale\ninitial\ninsignificant\ninstinct\ninstructive\ninsufferable\ninterrogation\nintroduce\ninvestigate\nirresponsible\nirwin\nitalicizes\niwai\njacques\njean-claud\njesse\njoan\nkennedy\nkinetically-charged\nkosminsky\nkurt\nlabute\nladder\nlags\nlake\nlamer\nlan\nlarge-screen\nlarry\nlatin\nlearned\nliar\nlicense\nlife-changing\nlightness\nlilia\nlobotomy\nlong-faced\nlong-on-the-shelf\nlouis\nlucratively\nlumpen\nmacdowell\nmagnetic\nmakhmalbaf\nmall\nmargin\nmarquis\nmarshall\nmeal\nmeanderings\nmeara\nmenace\nmergers\nmid\nmiddling\nminded\nmormon\nmuddy\nmultilayered\nmumbo\nmutates\nmuzak\nmystical\nmétier\nnatured\nnaïveté\nnervy\nniches\nnights\nno-brainer\nnoon\nnoteworthy\nnumb\nobnoxiously\nobsessions\nodorous\noften-funny\nominous\non-camera\none-star\nops\norchestrated\noscar-winning\noscar-worthy\noverblown\novercomes\noverplayed\noverview\nozpetek\npact\npage\npanoramic\npaths\npersuasive\nphilip\nphlegmatic\nphoto\npillages\npitiful\nplacid\nplacing\nplane\nplateau\npleas\npleasuring\nploddingly\nplunging\npoet\npopulation\npregnant\nprevent\nprincipals\nprivileged\nproficiency\npropels\npsychotic\npub\npump\npunchy\npurer\nquick-witted\nrea\nreaches\nreasonable\nrecall\nreception\nreckless\nrecords\nredone\nreduces\nregain\nrelevance\nrembrandt\nrepeated\nresident\nretooling\nrevulsion\nripening\nrob\nrocket\nromanek\nrough-hewn\nrunteldat\nrustic\nsabotage\nsamira\nsatin\nsatisfied\nscandalous\nscathing\nschemes\nschlocky\nscotland\nscottish\nscreams\nscreenplays\nseamless\nseamy\nsedate\nseemed\nseeming\nsegal\nself-assured\nself-empowering\nself-preservation\nsexist\nsexuality\nshadow\nshainberg\nshaking\nshapable\nsharper\nsharply\nsheridan\nshockwaves\nshoe\nshoplifts\nshovel\nshu\nsignals\nsimulate\nsiuation\nsketched\nskins\nslob\nslog\nslovenly\nsmart-aleck\nsmarts\nsmeary\nsmith\nsmoothly\nsocially\nsoothing\nsoul-searching\nspecies\nspreads\nsprinkled\nsquareness\nstare\nsteals\nstewart\nstillborn\nstolid\nstomach-knotting\nstrings\nstud\nstyled\nsub-sophomoric\nsubgenre\nsubjected\nsuddenly\nsuperfluous\nsupermarket\nsurroundings\nsuspend\nsustenance\ntabloids\ntangled\ntargeted\ntechnological\nterrorist\ntheatrically\nthrowback\nthumbing\ntickets\ntickles\ntier\ntime-consuming\ntinny\ntitled\ntooth\ntoothless\ntopical\ntransfigures\ntransform\ntransgressive\ntravail\ntreasured\ntries-so-hard-to-be-cool\ntruck\ntumbleweeds\ntunes\nturbulent\ntwin\ntwitchy\ntwo-thirds\ntykwer\nunattractive\nundergrad\nunderrated\nundo\nunfakable\nungainly\nunhappy\nunholy\nuninhibited\nunknowable\nunmotivated\nunsurprising\nunturned\nupdate\nvacation\nveered\nveiling\nveneer\nverges\nversus\nveteran\nvicious\nvidgame\nvirulent\nvisions\nvolcano\nvowing\nwanderers\nwang\nwannabe-hip\nweaponry\nweasels\nwell-executed\nwell-lensed\nwell-thought\nwildean\nwindtalkers\nwiseman\nwistful\nwithered\nworm\nwounds\nwrenching\nyard\n10-year\n103-minute\n105\n110\n1937\n1975\n1989\n1993\n300\n451\n48\n50\n83\n94\n95-minute\nabandoned\nabhorrent\nably\naction-comedy\naddict\nadmirers\nadrift\nadvice\naerial\naffirm\nafterwards\nagreeably\nailments\naisles\nall-night\nall-woman\namaze\namble\nambrose\nanalytical\nanne-sophie\nanniversary\nannoyance\nanteing\nanti-semitism\nappeared\napproaches\narcane\nargentinian\narrangements\narrogant\nasset\nathlete\natrocities\nauteur\nautopsy\nawesome\nawfulness\nbale\nbang-up\nbare\nbarf\nbarlow\nbask\nbearing\nbeen-there\nbeen-told-a\nbehalf\nbela\nbender\nbenjamins\nbig-wave\nbio-pic\nbloodletting\nbloodstream\nblowing\nboard\nbomb\nbombs\nbone-chilling\nboom-bam\nbothered\nbottom-feeder\nbouquet\nbravura\nbreathing\nbrian\nbridget\nbrit\nbuddy\nbull\nbursts\ncaffeinated\ncafé\ncake\ncameos\ncamouflage\ncamouflaging\ncanned\ncaptured\ncaricature\ncat-and-mouse\ncatapulting\ncelebrate\ncentering\ncenturies\nceremonies\ncertified\nchafing\nchatter\nchen\ncherry\nchewy\nchiaroscuro\nchicken\nchocolate\nchoppiness\nclamoring\nclamorous\nclams\nclancy\nclashing\ncleansing\ncliché-laden\nclients\nclose-ups\nclubs\nco-operative\ncoal\ncockettes\ncollide\ncollinwood\ncolumbia\ncolumn\ncombustible\ncommanding\ncommonplace\ncompare\ncompatible\ncompendium\ncomposure\ncompressed\ncondescension\nconfronting\nconjures\nconsciously\nconsummate\ncontinue\ncontriving\ncoping\ncoppola\ncorners\ncorporate\ncorpse\ncourtship\ncows\ncrackles\ncrappy\ncreators\ncrescendo\ncringe\ncrises\ncrooks\ncrossed\nculkin\ndante\ndass\ndates\ndawdle\ndazzle\ndecay\ndecency\ndedicated\ndeep-seated\ndefensible\ndeflated\ndelay\ndelusional\ndemeaning\ndemographically\ndepressingly\ndescends\ndesert\ndesirable\ndespicable\ndestructive\ndetailing\ndetective\ndeuces\ndevastation\ndevote\ndevotion\ndigital-effects-heavy\ndigs\ndisbelief\ndiscarded\ndiscerned\ndiscord\ndisguising\ndisingenuous\ndispassionate\ndistaste\ndocumentary-making\ndocumented\ndocumenting\ndoles\ndoltish\ndouglas\ndour\ndowner\ndownfall\ndramatized\ndramaturgy\ndrink\ndrives\ndumbness\nduplicate\neats\ned\neileen\neloquently\nembarrassingly\nemerging\nencountering\nendangered\nendear\nenforced\nentertains\nentranced\nequals\nescapes\nevery-joke-has\nevocation\nevolution\nexasperated\nexcepting\nexistentialism\nexpose\nexposes\nextension\nexxon\neyeballs\nfaced\nfaked\nfame\nfamine\nfarewell-to-innocence\nfarrelly\nfaulty\nfeardotcom.com\nfell\nfemale-bonding\nfeminine\nfence\nferrera\nfervently\nfine-looking\nfinesse\nfingers\nfinished\nflowering\nflowers\nflowery\nfollies\nfootball\nforthright\nfoster\nfour-star\nfragment\nframing\nfreud\nfrighteningly\nfunky\nfussy\nfuturistic\ngag\ngap\ngaps\ngenerating\ngermanic\ngleaned\nglued\ngoggles\ngooding\ngrandparents\ngreasy\ngrievous\ngrit\ngrotesquely\ngroundbreaking\nguessable\nguilt-trip\nguitar\ngutsy\nhal\nhalfwit\nhallucinatory\nhang-ups\nhappily\nhard-to-predict\nhard-won\nhaunted\nhawn\nhazy\nhealing\nheavyweight\nhellish\nhelmer\nheralds\nhiatus\nhigh-profile\nhilary\nhispanic\nhistoric\nhomes\nhooks\nhooliganism\nhospital\nhostage\nhot-blooded\nhubert\nhumble\nhumdrum\nhurley\nhustler\ni.q.\nideally\nignoring\nillogical\nillusion\nillustrating\nimaginable\nimagined\nimitator\nimmaturity\ninadequately\ninc.\nincapable\nincinerates\nincrease\nindicative\nindictment\nindisputably\ninexcusable\ninfluence\ninfuriating\ningratiating\ninsufferably\ninterplay\ninvest\ninvigorating\ninvincible\niran\nirrepressible\nirreverent\nitch\nitems\nj\njagjit\njay\njimmy\njohnny\njonathan\njunior-high\njust-above-average\nkate\nkept\nkieran\nkinds\nkingsley\nknoxville\nkooky\nlabeled\nlabyrinthine\nlaid-back\nlaissez-passer\nlargest-ever\nlashing\nlazier\nlaziest\nlean\nleap\nlevy\nliberal\nlingers\nlogically\nloquacious\nlore\nlow-brow\nlugosi\nluminous\nlumpy\nmaddening\nmalarkey\nmannered\nmap\nmargarita\nmary\nmatron\nmcculloch\nmeander\nmeanspirited\nmeaty\nmegaplex\nmegaplexes\nmelodramas\nmemento\nmenacing\nmends\nmeow\nmeticulously\nmick\nmiddle-of-the-road\nmidlife\nmilk\nmisconceived\nmisconstrued\nmishmash\nmistaken\nmodeled\nmodernize\nmodestly\nmonotone\nmonster/science\nmoonlight\nmorrison\nmothers\nmothman\nmotives\nmournful\nmovie-star\nmusicals\nmusty\nmyopic\nnachtwey\nname-calling\nnamesake\nnationwide\nnative\nnazi\nnerds\nnincompoop\nnominated\nnot-at-all-good\nnotably\nnow-cliched\noff-beat\nold-fashioned-movie\nold-hat\noperative\nopposite\noppressively\noptimism\norchard\norchestrating\nout-sized\noutshined\noverstays\npained\npaint\npaintings\npanic\npared\nparent\npartisans\npassages\npasta-fagioli\npasty\npatchwork\npathetically\npatriot\npatronizing\npb\npbs\npeace\npedestal\npeploe\nperforming\npermits\nphilippe\nphillip\npique\npitifully\nplayed-out\npleased\npleasingly\npoint-and-shoot\npolemic\npoorly-constructed\npopulace\nporno\nportrayed\nprecocious\npredict\npredisposed\nprefabricated\nprepare\nprimer\nprince\npristine\nprobe\nprobes\nprojectile\nprologue\nprolonged\npronounced\nprotect\npythonesque\nquaid\nquibble\nquirkiness\nraces\nrachel\nracism\nradioactive\nram\nrapid\nrarest\nraymond\nreaffirming\nrealizes\nreceives\nrecreates\nredeems\nreductions\nrefresh\nrelayed\nreleasing\nrendering\nrenders\nrenowned\nreprehensible\nresentment\nresolution\nresponsibilities\nrestrained\nrestraint\nrewritten\nribcage\nrifkin\nright-thinking\nrighteousness\nriot-control\nripoff\nrisks\nrobustness\nrodriguez\nromanticization\nromped\nrug\nrules\nrunyon\nrural\nrussell\nsabotaged\nsaddled\nsandwich\nsanguine\nsaps\nsaucy\nsaves\nschizo\nschlock\nscrape\nscrutiny\nscummy\nself\nself-destructiveness\nself-hating\nsensitivities\nseparation\nsexpot\nsharing\nshedding\nshekhar\nshimmering\nshmear\nshock-you-into-laughter\nshoots\nshow-stoppingly\nsickeningly\nsights\nsimmer\nsinger-turned\nsix\nskunk\nsleekness\nslimed\nsmall-budget\nsmallest\nsmile-button\nsmoky\nsneers\nsociology\nsons\nsophistication\nsophomore\nspectator\nspeeds\nspicy\nspiffing\nspinning\nspliced\nspots\nsputters\nstab\nstacked\nstasis\nstimulating\nstitched\nstoryteller\nstraining\nstrands\nstrip\nstrives\nstruck\nstructures\nstrung-together\nstultifyingly\nsturdiest\nstylistically\nsuccesses\nsugarman\nsultry\nsun-drenched\nsurgeon\nsurrenders\nsurrounded\nsuspected\nsustained\nswashbucklers\nswings\nswitchblade\nsympathies\nszpilman\ntaboo\ntambor\ntaxi\ntaymor\nteeny-bopper\nten\ntenor\ntequila\nterry\nthankfully\ntherapy\nthrew\nthrough-line\nthrust\ntickled\ntiresomely\ntongue\ntopics\ntossed\ntout\ntransparently\ntransporting\ntreating\ntreatise\ntremors\ntriangles\ntrotting\ntrouble-in-the-ghetto\ntucked\ntwo-way\nunbridled\nuncompelling\nundeterminable\nundistinguished\nunentertaining\nunequivocally\nunflattering\nunfolding\nunfussily\nunimpressively\nuninflected\nunmentionables\nunrelated\nvampires\nversions\nvh1\nvibrance\nvile\nvin\nvirtues\nvirtuoso\nvirtuous\nvistas\nvolletta\nvulgarity\nwall-to-wall\nwarmed\nwasp\nwaster\nwatered\nwayne\nweighs\nwell-wrought\nwelled\nwertmuller\nwhimsicality\nwidow\nwindshield\nwithholds\nwithstand\nwladyslaw\nwoe-is-me\nworkable\nworlds\nwriter/directors\nye\nyearnings\nyelling\nyellow\nyorker\nyoung-guns\nyours\nzeal\n'80s\n12th\n1957\n2,500\n295\n90-plus\n93\nabrupt\naccuse\naction-movie\nad\nadapted\naddress\nadmitting\nadolescence\nadoring\nadorns\nadultery\naesthetics\naffectionate\naffectionately\naggravating\nagitprop\nagonizing\nally\nalterations\nambitions\namicable\namid\namok\namours\namp\nanew\nannex\nanti-erotic\napproached\napproaching\narchly\narnie\narrogance\nartworks\nas-nasty\nassumption\nassuredly\nathleticism\nattach\nattracting\nauspicious\nauthenticate\nauthor\nauto\nautopilot\navid\nbackdrop\nbackward\nbaked\nballistic-pyrotechnic\nbanged\nbarbed\nbartleby\nbeer-fueled\nbefallen\nbegging\nbelieved\nbesotted\nbewildered\nbewilderingly\nbias\nbig-hearted\nbigelow\nbike\nbinks\nbirmingham\nblacked\nblackout\nblanks\nblazingly\nblowout\nblueprint\nbodice-ripper\nbona\nboorishness\nbout\nbrain-deadening\nbrand-new\nbrazenly\nbread\nbreaking\nbreathless\nbroadcast\nbrockovich\nbrow\nbruised\nbrusqueness\nbrussels\nbubble\nbubbly\nbueller\nburlap\nbyzantine\nc.h.o.\ncalamity\ncampanella\ncanadian\ncannibal\ncannon\ncared\ncarnage\ncarpets\ncartons\ncastrated\ncensure\nchain\nchamber\ncharlie\nchatty\ncheapening\ncheckout\ncheery\nchildish\nchomps\nchooses\nchore\nchâteau\ncinemantic\ncivic\nclaims\nclips\nclock\nclosest\nco-stars\ncocoon\ncoheres\ncoldest\ncommerce\ncomparisons\ncompensate\nconan\nconfessions\nconflagration\nconfusion\nconniving\nconspiratorial\nconversion\ncopies\ncostuming\ncoughed\ncounty\ncracker\ncradles\ncraftsmanship\ncraziness\ncreaky\ncreep\ncritique\ncrosses\ncrucifixion\ncrudely\ncurling\ncushion\ndanny\ndarned\ndeadeningly\ndebrauwer\ndeceit\ndecorating\ndefensive\ndefined\ndefinite\ndemonstration\ndepleted\nderive\ndescribed\ndidacticism\ndifficulty\ndime\ndiminishing\ndimwits\ndirector/co-writer\ndisappearing/reappearing\ndiscomfort\ndiscourse\ndisease\ndissecting\ndisservice\ndistinguishing\ndivorce\ndog-paddle\ndogtown\ndollar\ndoomed\ndouble-pistoled\ndrastic\ndrawn-out\ndreaded\ndreadfulness\ndress\ndrill\ndrumline\nducts\ndull-witted\neagle\neccentricities\neclipses\neconomic\nefficiency\nelicit\nelicits\nelizabethans\nemergence\nempathizes\nemploy\nendeavors\nenergizes\nengross\nequalizer\nequation\nerin\nescaped\net\netc\neven-handedness\neventful\nexclamation\nexcursion\nexpressiveness\nextensive\nextra-dry\nextravagantly\nextremist\neye-popping\nfallibility\nfamily-film\nfart\nfast-edit\nfaults\nfeminist\nfiddle\nfide\nfidgeted\nfifteen-year-old\nfincher\nfireballs\nfireworks\nflavorless\nflop\nflopping\nfoundation\nframes\nframework\nfranc\nfreaky\nfringes\nfrontman\nfudged\nfull-length\nfumes\nfun-seeking\nfundamentals\nfussing\ngaiety\ngain\ngallery\ngallic\ngamble\ngantz\ngarnered\ngas\ngathering\ngawky\ngiggle\nglucose\ngoldbacher\ngoodwill\ngoofily\ngored\ngoth-vampire\ngrade-grubbers\ngrade-school\ngrandiloquent\ngrandness\ngray\ngrease\ngreatly\ngreenfingers\ngroan\ngroaners\ngroggy\ngrossly\nguest\ngulzar\ngut-busting\nhangover\nhankies\nhanks\nhaplessness\nhappenstance\nhard-bitten\nhard-hearted\nhard-pressed\nhard-sell\nhaul\nhead-turner\nheartwarmingly\nheathers\nheed\nherrings\nhigh-end\nhighlighted\nholofcener\nhonorable\nhonored\nhop\nhopped-up\nhussein\niconography\nidiotically\nidol\nillogic\nimage-mongering\nimbued\nimitative\nimplausibility\nimpudent\nimpulse\nincarnations\nincessantly\ninconceivable\nindefinitely\nindoor\ninferior\ninhalant\ninjustice\nintegrates\ninterdependence\ninterference\nintergalactic\nintoxication\ninvite\nisland\nitalics\njacket\njanice\njanine\njanklowicz-mann\njar\njeunet\njoseph\njournalist\njulianne\njunk-calorie\njuwanna\nkafka\nkeel\nkidnappings\nkids-cute\nkills\nkirkegaard\nknickknacks\nkrawczyk\nl.a.\nlabor\nladies\nlameness\nlanding\nlapping\nlaramie\nlarge-scale\nlast-place\nlaziness\nleague\nlear\nlegs\nlend\nleonine\nless-compelling\nlethargic\nletter\nlick\nlight-heartedness\nlika\nlimbs\nlion\nliterature\nll\nlovefest\nlow-wattage\nlower-wit\nlowered\nlowly\nlucia\nlulled\nmajority\nmale-ridden\nmanaging\nmarathons\nmarina\nmarked\nmarred\nmasochism\nmasquerade\nmattei\nmcdonald\nmelange\nmermaid\nmesmerize\nmessing\nmexico\nmgm\nmib\nmilked\nmimics\nmind-destroying\nmindset\nminutely\nminutiae\nmirren\nmisogynist\nmixed-up\nmoaning\nmoat\nmoderately\nmojo\nmonotony\nmopes\nmorbid\nmordantly\nmorsels\nmotherhood\nmotionless\nmoulin\nmounting\nmovie-of-the-week\nmuccino\nmug\nmulti-layered\nmulti-layers\nmultiple\nmusclefest\nmuttering\nmyers\nmyrtle\nnagging\nnaiveté\nnapoleon\nnegatives\nnervous\nnewcastle\nnewness\nnick\nnonconformist\nnone-too-funny\nnorth\nnuclear\no'fallon\nobscenely\noff-putting\nok\nold-school\noleander\none-hour\none-sided\noomph\nopen-hearted\noperational\noppressive\noprah\noption\nopts\norbits\nordered\norders\noscar-caliber\nouter\noutlandish\noutrage\noverhearing\noverkill\novermanipulative\noverripe\novershadows\noverweight\npablum\npackages\npadding\npapin\nparental\nparrots\nparticipate\npartnership\npatch\npaws\npeppering\nper\nperception\nperch\nperfected\npermission\nperversity\npiccoli\npizazz\nplague\nplagued\nplans\nplastic\nplatter\nplaylist\nplaywriting\nplethora\nplummer\nplunge\npoetics\npointing\npoo-poo\nporn\npornographic\nporous\npost-feminist\npostmodern\npotboiler\npractices\npreaching\nprecarious\npreciseness\npreordained\npresenting\npresident\npresiding\nprimal\nprofundity\nprojector\nprom\npromenade\nprotective\nproved\nprovokes\npun\npunctuated\npunctuation\npurity\npurposefully\npushing\nquick-buck\nr&d\nrae\nrah-rah\nramble\nransacked\nransom\nrat-a-tat\nraunch-fests\nreassuringly\nrecalls\nrecognizes\nreconciliation\nrecovers\nrecycle\nreeks\nreflect\nregarding\nreinvigorated\nrepetitious\nrepressed\nreputedly\nresembles\nrespite\nresulted\nresurrecting\nretelling\nretiring\nrevels\nrhapsodic\nrhapsodizes\nright-wing\nrip\nroad-trip\nrough-around-the-edges\nrow\nrueful\nruminations\nrumor\nrun-of-the-filth\nrunner\nruthlessly\ns.c.\nsacrifices\nsaddam\nsameness\nsands\nsap\nsatiric\nscant\nscherfig\nscooby\nscorn\nscrapbook\nseal\nseizures\nself-control\nself-mocking\nself-parody\nself-promoter\nself-promotion\nsenseless\nsentimentalizing\nserpent\nsetpieces\nsettle\nshades\nshameful\nshiner\nshiver-inducing\nshootings\nshreve\nshriek\nsignpost\nsillified\nsingh\nsitcom-worthy\nsketches\nskilfully\nslasher\nslender\nslopped\nslugfest\nslurs\nsmall-town\nsmallness\nsmeared\nsnail\nsnake\nsneaky\nsneeze\nsoap-opera\nsoliloquies\nsparks\nspeculative\nspends\nspiral\nspits\nsplendidly\nsprawling\nsquint\nsquirming\nstandbys\nstandoffish\nstandout\nstar-studded\nstature\nstaying\nsteaming\nstench\nsterling\nsticking\nstimulate\nstonehenge\nstones\nstreamed\nstreet-smart\nstripped\nstrong-minded\nstumbles\nstuttering\nsubstantive\nsuicidal\nsuper-dooper-adorability\nsuper-simple\nsuperstar\nsupposedly\nsurfacey\nsurrealism\nsurrendering\nswank\nswimfan\nswipe\nswitch\nsword\nswords\ntables\ntadpole\ntailor\ntapestry\ntargets\ntedium\nteenybopper\nteleprompter\ntenderness\ntens\nthesps\nthinkers\nthird-rate\nthoughtfully\nthrives\nthroat-singing\nthud\ntick\ntied\ntightened\ntime-switching\ntimeout\ntitus\ntolkien\ntonto\ntoo-conscientious\ntorrent\ntrain\ntransporter\ntransvestite\ntravails\ntreachery\ntreading\ntricky\ntriumphantly\ntrots\ntroubling\ntub-thumpingly\ntucker\ntundra\ntutorial\ntwenty\nu.n.\nultra-violent\nunacceptable\nunaffected\nunapologetically\nunbalanced\nuncertainties\nunchanged\nunclean\nundercover\nunderground\nundeserved\nundramatic\nunerring\nunexceptional\nunforgivingly\nunfortunate\nunhibited\nuni-dimensional\nunimpeachable\nuninventive\nunivac-like\nunpersuasive\nunprecedented\nunprovoked\nunrelenting\nunruly\nunsatisfied\nunshapely\nvagueness\nvainly\nvat\nveil\nvelocity\nvenice\nverisimilitude\nvices\nvictorious\nvirtuosic\nviscerally\nwacky\nwalking\nwalt\nwatery\nweakness\nweirdo\nwell-defined\nwell-paced\nwell-realized\nwelt\nwesterners\nwheedling\nwherein\nwide-eyed\nwild-and-woolly\nwizened\nwollter\nwoozy\nworldly\nwretchedly\nwrote\nyielded\nzaidan\nzealand\nzero-dimensional\n****\n10,000\n10th-grade\n13th\n163\n18\n1980s\n20-car\n21/2\n60\n66\n89\n95\n99\naaliyah\nabel\naccidental\naccountant\nachieved\naching\nacidic\nactivism\nactorly\nactress-producer\naddictive\naddressing\nadhering\nadjusting\nadvance\nafghani\nagile\naim\naladdin\nalas\nall-around\nall-inclusive\nall-male\nalleged\naloof\nambience\nambivalence\nample\namusements\nandie\nandroid\nanimations\nanticipation\nantiseptic\nantsy\nappealingly\napplying\nappreciates\nappropriately\nardent\nardently\narmageddon\narticulates\nartificiality\nartistically\nartless\nascends\nasleep\naspire\naspired\nastounds\nastringent\nautomatically\naverting\nb.s.\nbadder\nbags\nbalm\nbalto\nbanger\nbanter-filled\nbarbarism\nbarker\nbars\nbattlefield\nbeard\nbeer\nbefuddled\nbegley\nbenchmark\nbergmanesque\nberkley\nberry\nbitten\nbitterly\nblazing\nblight\nbling-bling\nblob\nblond\nbloodshed\nblues\nboatload\nboffo\nbogging\nbolstered\nboom\nbordering\nborderline\nbotched\nbracingly\nbreathe\nbrogue\nbrooms\nbuck\nbuffoons\nbuying\nbygone\ncad\ncagney\ncameo\ncanadians\ncancer\ncapped\ncardoso\ncareless\ncarl\ncars\ncassavetes\ncatch-22\ncatharsis\ncellophane-pop\nchaiken\ncheek\ncheered\ncheerfully\nchemically\nchicanery\nchillingly\nchin\nchoke\nchristianity\nchronicle\ncinematographer\nclassify\nclaustrophic\nclaw\nclayburgh\nclear-cut\nclerk\nclimb\nclone\nclouds\nclout\ncollaborative\ncollar\ncombining\ncomfortably\ncomfy\ncomical\ncommend\ncompassionately\nconcentration\nconclusive\nconscious\nconsiders\nconspicuously\nconstruction\nconsuming\ncontact\ncontender\ncontinuum\ncontradiction\ncontradicts\ncontrasting\ncontributions\ncope\ncorn\ncotswolds\ncox\ncrassly\ncrawls\ncreed\ncreepy-scary\ncribbing\ncrisper\ncrowds\ncrumb\ncube\ncue\ncussing\ncutoffs\ncyndi\nda\ndared\ndawn\ndeafening\ndebts\ndecisive\ndegraded\ndehumanizing\ndel\ndelusions\ndenouement\ndepartments\ndependable\nderek\nderring-do\ndesolate\ndevito\ndiaz\ndiggs\ndilutes\ndimming\ndip\ndisaffected-indie-film\ndisagree\ndisciplined\ndiscovered\ndisease-of-the-week\ndishonesty\ndisposition\ndisquietingly\ndissing\ndistances\ndistinctions\ndistracting\ndivisions\ndizzy\ndocumentarian\ndollars\ndolphin-gasm\ndone-that\ndorkier\ndouble-barreled\ndown-and-dirty\ndraft\ndraggy\ndreadfully\ndreamy\ndrifts\ndruggy\ndrying\ndug\ndumas\ndungpile\ndustbin\near-pleasing\nearmarks\nearns\neclair\nelegy\nelite\nelm\nembellished\nembroils\nenacted\nencumbers\nenergized\nenhanced\nensues\nensuing\nentered\nenthrall\nenthusiastically\nentrée\nenvirons\nerratic\nescapist\nesteemed\nethos\neuphoria\nevanescent\neverett\nevergreen\neviction\nevolves\nexalted\nexception\nexchanges\nexoticism\nexpanse\nexplains\nexplosive\nexpressionistic\nextra-large\nexudes\nfacial\nfaint\nfamily-oriented\nfanatics\nfantastically\nfar-fetched\nfavorably\nfeed\nfeel-bad\nfetishistic\nfighters\nfirmly\nfish-out-of-water\nfitting\nflakeball\nflame\nflatula\nflatulence\nflavors\nflck\nfleeing\nflexible\nflibbertigibbet\nflies\nfling\nfloating\nflourish\nfollowers\nfood-for-thought\nforbidden\nforming\nforster\nfragmentary\nfranklin\nfreak-outs\nfreakish\nfree-wheeling\nfreely\nfreezers\nfreshening\nfriggin\nfrom-television\nfrothy\nfrustrates\nfu\nfullness\nfumbles\nfunnybone\nfusion\ngamut\ngedeck\ngelati\ngender-provoking\ngeneralities\nghandi\nghosts\ngiants\ngibberish\ngigantic\ngiggles\ngirl-on-girl\ngive-and-take\nglee\nglides\nglobalizing\nglobe\nglobetrotters-generals\ngloom\ngloomy\ngloss\ngo-for-broke\ngobbler\ngods\ngon\ngood-bad\ngood-naturedly\ngoose\ngoosebumps\ngore\ngorefest\ngrad\ngrand-scale\ngraves\nhairs\nhallucinogenic\nhanded\nhandles\nhands-off\nhanging\nhardship\nhardy\nharm\nharrison\nhated\nhawke\nheart-string\nheartbreak\nhefty\nhell-bent\nhelluva\nhence\nhibernation\nhickenlooper\nhiding\nhigh-powered\nhigh-strung\nhighway\nhills\nhired\nhiss\nholden\nhollywood-action\nhomophobia\nhomosexual\nhoneys\nhorror/action\nhue\nhumanist\nhumidity\nhundreds\nhypocrisy\ni.e.\nick\niconic\nigby\nignite\nill-advised\nimbue\nimmaculate\nimperfect\nimposed\nimpostor\ninadequate\nincognito\nindia\nindifferent\nindoctrinated\nindomitability\nindulged\nindustrial-model\nineffective\ninelegant\ninescapably\ninfluential\ninnovators\ninnumerable\ninquiry\ninseparable\ninstilled\ninsufficiently\nintegrating\ninterests\ninterior\nintermezzo\nintrepid\nintroduces\nintrospection\ninvented\ninvites\ninvitingly\nirony-free\nirritates\nisraeli\nivan\njam\njar-jar\njersey\njokers\njokester\njostling\nkaige\nkeenest\nkenneth\nkid-vid\nkitsch\nl.\nlaboratory\nladen\nland-based\nlaptops\nlaser\nlaughingly\nlaundry\nlauper\nlax\nle\nleaps\nlectured\nlectures\nleigh\nlens\nless-is-more\nlibrary\nlieutenant\nlife-altering\nlife-embracing\nlightly\nliking\nlil\nlimit\nlinearity\nlocal\nloco\nlondon\nlong-suffering\nlong-winded\nlook-see\nlove-struck\nlower-class\nloyalty\nluis\nlurks\nluscious\nlynch\nlyne\nmacaroni\nmachines\nmade-for-movie\nmaid\nmaker\nmanager\nmarivaux\nmarking\nmarried\nmarveled\nmasterly\nmasterpieces\nmatrix\nmattel\nmattered\nmax\nmcbeal-style\nmeager\nmedicine\nmelancholia\nmelville\nmercilessly\nmessage-mongering\nmetal\nmetaphysical\nmidsection\nmikes\nmilder\nmind-numbing\nming-liang\nminimal\nmisdemeanor\nmissive\nmixing\nmode\nmodem\nmodus\nmoldering\nmom\nmoney-oriented\nmonitor\nmoralism\nmores\nmoron\nmorton\nmotivate\nmourns\nmuch-needed\nmuscles\nmuse\nmust-own\nmuy\nnaipaul\nnaive\nnarrow\nnba\nneedy\nneo-fascism\nneorealism\nnettelbeck\nnetwork\nneutral\nnew-agey\nnicest\nnicole\nnightmarish\nnohe\nnonchalant\nnonconformity\nnone-too-original\nnonfiction\nnorma\nnormal\nnosedive\nnot-nearly\nnot-so-bright\nnouvelle\nnuts\nobjects\nobligation\noddballs\noffended\noftentimes\nomission\nontiveros\nopportunists\norange\norigins\noscar-sweeping\nout-of-kilter\noutbursts\noutrageously\nover-dramatic\nover-indulgent\noverinflated\novertake\noveruse\nowed\nowen\npages\npainstaking\npalatable\nparochial\npasolini\npassably\npatiently\npatrolmen\npauly\npaunchy\npaying\npenn\nperennial\nperkiness\nperpetually\npessimism\npetri\nphilosophers\nphonce\npin-like\npiscopo\npissed\npithy\nplant\nplayfully\nplucks\nplummets\npocket\npoise\npokémon\npolite\npootie\npop-music\nportraits\nportraiture\nportuguese\nposition\nposturing\npot\npours\npowerpuff\npracticed\npranks\npreachy-keen\nprecedent\npreferably\nprepackaged\nprescient\nprescribed\npreserving\npretention\nprettiest\nprevention\npriceless\nprickly\nprimary\nprincess\nprincipled\nprocedure\nprolific\nproof\nproperties\nproposes\nproven\nproverbial\nprovided\nprovoked\npseudo-bio\npsychedelia\npsychopathic\npulpiness\npunchier\npunchlines\npuppets\npuppies\npurdy\npuzzled\nq\nquaint\nquaking\nquestioning\nquickie\nquietude\nrain\nrambles\nramblings\nrampant\nre-creations\nredeemed\nredundancies\nreeking\nrefined\nregistering\nrejigger\nreligion\nremoved\nreopens\nrepartee\nrepeating\nrepellantly\nrequiring\nresents\nreside\nresorting\nresorts\nrespond\nretail\nretard\nretro-refitting\nreverie\nreviewers\nrevolutionaries\nrife\nrivalry\nrode\nroiling\nrose\nrotting\nrough\nrumblings\nsaddest\nsaddle\nsags\nsalute\nsalvage\nsandbox\nsarah\nscandal\nscarily\nscheming\nschnitzler\nscientist\nscoring\nscripting\nscum\nscuzzy\nsean\nsecond-rate\nself-amused\nself-centered\nself-delusion\nself-glorified\nself-mutilation\nself-righteous\nselves\nsemi\nsense-spinning\nservants\nsewing\nshackles\nshakesperean\nshift\nshifted\nshindler\nshiri\nshoes\nshootout\nshore\nshouting\nshow-biz\nsica\nsiege\nsignature\nsilbersteins\nsimmering\nsincerely\nsinger\nsingles\nsitcomishly\nslaloming\nslam-dunk\nsleaziness\nsleazy\nsleek\nslope\nslumming\nslump\nsmashing\nsmear\nsmell\nsmokey\nsnail-like\nsnared\nsnickers\nsociological\nsolace\nsole\nsolidity\nsomeday\nsoothe\nsparking\nsparkles\nsparse\nspecific\nspill\nspirit-crushing\nspiritualism\nsplashed\nsplashy\nsplitting\nspoofy\nsprouts\nstable-full\nstagey\nstaggered\nstamina\nstandup\nstatements\nstein\nstepmother\nstills\nstockwell\nstorm\nstraight-ahead\nstraight-to-video\nstraight-up\nstrategic\nstrenuously\nstudied\nstunt-hungry\nsturdiness\nstylist\nsubstandard\nsubstitutable\nsuckers\nsugary\nsuggesting\nsuitcase\nsulky\nsunny\nsunshine\nsuper-powers\nsuperficially\nsuperhero\nsuppose\nsurehanded\nsurrealistic\nswaggers\nswaying\nswift\nswimming\ntang\ntanks\ntartly\ntaxicab\nteaming\ntech-geeks\nteeth-gnashing\ntelegrams\ntemple\nthe-night\nthirteen\nthoughts\nthree-minute\nthroat\nthrusts\ntics\ntodd\ntoddler\ntongue-tied\ntongues\ntoo-frosty\ntoo-long\ntorments\ntorpedo\ntoss-up\ntouchstone\ntoys\ntrade\ntreads\ntreated\ntried-and-true\ntrier\ntrifecta\ntrimmings\ntrio\ntriple\ntriumphant\ntube\ntuck\nturntablists\ntwinkle\nu.s.\nugly-looking\nuhf\nuhhh\nultra-loud\nunashamedly\nunburdened\nunclear\nundergo\nunderlay\nundernourished\nunderrehearsed\nunderstandable\nunderstatement\nundertaking\nundisputed\nunengaging\nunflappable\nunhidden\nuniversity\nunreligious\nunsettled\nunwieldy\nupfront\nuser-friendly\nutilizing\nvacuum\nvalentine\nvalid\nvalidated\nvalley\nvaporize\nvarying\nversatile\nvideo-game-based\nviewpoint\nvittorio\nvoices\nvoyeuristic\nwaldo\nwalled-off\nwallflower\nwarmest\nwashout\nwater-bound\nwatts\nweaves\nweighted\nwelcomed\nwell-characterized\nwell-timed\nwell-told\nwell-worn\nwerner\nwhiff\nwidowmaker\nwiggling\nwillies\nwind-in-the-hair\nwised-up\nwisely\nwittgenstein\nwonderment\nworkings\nworkshops\nworldly-wise\nworries\nwreaked\nwreck\nwreckage\nxmas\nyale\nyarn-spinner\nyou-are-there\nyourselves\nzelda\nzhang\nzipper\n'50s\n*\n10-course\n112-minute\n1920\n1938\n1952\n1995\n2001\n24/7\n2455\n80-minute\n=\nabject\nabsorb\nacademic\naccident-prone\naccumulated\nacid\nacquires\nactioners\nactorliness\nactory\nadage\nadherents\nadmitted\nadolescents\nadrenalized\naffluence\nafford\naffords\nafrican-american\nagency\nagitator\nahem\naimlessness\nairs\nalcatraz\nalexandre\nall-powerful\nallegiance\naltar\nambiguities\namerican-russian\namoral\nanchors\nangelina\nangling\naniston\nankle-deep\nanna\nante\nants\napartments\napex\napollo\nappetizing\napted\narbitrarily\narchitect\narchive\nardor\narguably\nargues\narmchair\narrow\nartwork\nas-it\nashamed\nasparagus\nasphalt\nassassins\nassaults\nassembles\nassets\nassigned\nate\nattackers\nattendant\natypically\nauschwitz\nawash\nbaaaaaaaaad\nbackhanded\nbadly-rendered\nbanquet\nbarrage\nbasically\nbeaches\nbean\nbecame\nbedside\nbefuddling\nbeginnings\nbegrudge\nberling\nbeseechingly\nbesides\nbest-known\nbestial\nbestowing\nbetters\nbeyond-lame\nbicentennial\nbid\nbidder\nbig-fisted\nbigger-name\nbind\nbisset\nbladerunner\nbled\nblender\nblessed\nbluff\nboardwalk\nbodacious\nboho\nboiling\nboils\nbolado\nbombshell\nbon\nboorish\nboot\nboss\nbottom-of-the-bill\nbouncing\nbouts\nbowser\nbrass\nbrats\nbraveheart\nbravo\nbrawn\nbrazen\nbrendan\nbrink\nbromides\nbryan\nbugsy\nbump-in\nbuoy\nburdened\nburnt\nburnt-out\nburstein\nbush\nbusinesses\nbusts\nbutterflies\nbutterworth\nbéart\nc.\ncacoyannis\ncalvin\ncampaign\ncampaign-trail\ncampfire\ncandidate\ncandy-coat\ncapably\ncareening\ncareer-defining\ncareful\ncarmichael\ncaruso\ncasings\ncatastrophic\ncaterer\ncatherine\ncaton-jones\ncattaneo\ncellular\ncentered\ncesspool\nchainsaw\nchampagne\ncharacterisations\ncharitable\ncharlotte\ncheap-looking\ncheeks\nchefs\ncherished\nchill\nchomp\nchords\nchronically\nchurlish\nchurns\ncircumstantial\nclare\nclassicism\nclassification\nclassified\ncliff-notes\nclinical\nclint\nclive\ncloak\nclownish\nclumsiness\ncockney\ncoda\ncoincidence\ncollaboration\ncollected\ncolumbine\ncoma-like\ncomatose\ncombine\ncoming-of-age/coming-out\ncommands\ncommunicate\ncomparatively\ncomplain\ncomplaining\ncompletion\ncompliment\ncomprehensible\ncompromising\nconcentrating\nconcoctions\nconfection\nconfuses\ncongratulate\nconjure\nconsiderably\ncontemplative\ncontenders\ncontentedly\nconversation\nconveyor\nconvolutions\ncookie-cutter\ncooler\ncoos\ncor-blimey-luv-a-duck\ncounterculture\ncourtesy\ncowardly\ncowering\ncrapulence\ncrawling\ncrazed\ncreatively\ncreepiest\ncriminals\ncritic-proof\ncronenberg\ncrudity\ncruelly\ncrystal\ncrystallize\ncumulative\ncuter\nd'etre\ndadaist\ndaft\ndancers\ndas\nday-to-day\ndeathly\ndebilitating\ndecipherable\ndecisions\ndecomposition\ndecorous\ndefend\ndefinitions\ndelighted\ndemocracies\ndenouements\ndenuded\ndepress\nderisive\ndevos\ndirect-to-video/dvd\ndisappoints\ndisarmingly\ndiscerning\ndisgracefully\ndisintegrates\ndislocation\ndistort\ndistract\ndistractions\ndoa\ndoc\ndocs\ndog-tag\ndone-to-death\ndowns\ndrain\ndreamworks\ndriver-esque\ndrops\ndullingly\ndummies\ndusty\ndwells\nego-destroying\neisenstein\nel\nelder\nelection\nem\nembellishment\nembody\neminently\nencomia\nencourages\nendorses\nengendering\nengland\nennui\nennui-hobbled\nenthusiasms\nentrapment\nequations\nequipment\nespn\neternal\nevasive\neve\never-growing\never-watchful\nexecutives\nexhaustion\nexpensive\nexplanation\nexpository\nexposure\nfabulously\nfabulousness\nfacing\nfahrenheit\nfailings\nfairytale\nfallible\nfalters\nfanatical\nfaraway\nfathom\nfaux-urban\nfax\nfertile\nfigured\nfilter\nfinery\nfinger\nfingering\nflames\nflashing\nfledgling\nflimsier\nflopped\nflops\nflower\nflower-power\nflush\nfluttering\nfluxing\nflying\nfolksy\nfood-spittingly\nforeground\nforsaken\nfortune\nfrancisco\nfranco\nfresher\nfreshman\nfreudianism\nfriel\nfrightful\nfrittered\nfrothing\nfrustrated\nfuddled\nfuel\nfuels\nfun-loving\nfunctions\nfuse\nfusty\ngadgets\ngall\nganesh\ngangster/crime\ngarage\ngarner\ngenerated\ngeniality\ngenitals\ngenre-busting\ngenre-curling\ngentlemen\ngestalt\nghastly\ngiggly\ngimmick\ngimmicks\ngirl-meets-girl\ngirlish\ngive-me-an-oscar\nglamorous\ngliding\nglinting\nglitter\ngodfrey\ngoldie\ngolf\ngood-naturedness\ngoodies\ngooeyness\ngovernment\ngovernments\ngraham\ngrain\ngrandfather\ngrandiosity\ngrandly\ngrasping\ngreenlight\ngrinds\ngrinning\ngrittily\ngroan-to-guffaw\ngroups\nguiding\nguilt-free\ngullible\ngum\ngunplay\nguru\ngut-clutching\nhalfhearted\nhandily\nhandy\nhang\nhannibal\nharanguing\nhard-driving\nhard-edged\nhard-to-swallow\nhardass\nhardhearted\nharmoniously\nharvesting\nhashiguchi\nhat-in-hand\nheartily\nheft\nhell-jaunt\nheyday\nhideous\nhijinks\nhippie-turned-yuppie\nholiday-season\nhomework\nhomogenized\nhorrified\nhudlin\nhuge-screen\nhurried\nhurts\nhyped\nhyper-realistic\nhypnotically\nhysteria\nichi\nicily\nicky\nilluminated\nimitations\nimmigrant\nimplies\nimponderably\nin-jokes\ninactive\ninconclusive\ninconsistencies\nindependent-community\nindignant\ninduces\ninexpressible\ninexpressive\ninfectiously\ninfrequently\ninfuse\ninfusing\ningredient\ninhabitants\ninquisitiveness\ninsider\ninsinuation\ninstallments\ninsulted\ninter-species\ninternalized\nintimidated\ninventing\ninvestigation\ninvulnerable\niota\nirrational\nirreconcilable\njabs\njams\njaw\njeff\njez\njoint\njon\njournalists\njulie\njump-in-your-seat\nkafka-inspired\nkaos\nkid-movie\nkid-pleasing\nkilt-wearing\nkiosks\nkitchen-sink\nklein\nknees\nknucklehead\nkoepp\nkouyate\nkraft\nla\nlack-of-attention\nlarson\nlate-inning\nlaugh-free\nlaugher\nlaugther\nlaurence\nlearns\nleatherbound\nlecture\nlesbian\nlet-down\nlewd\nlibretto\nlimpid\nlingerie\nlinking\nlisa\nlive-style\nlivelier\nliveliness\nliza\nlobbyists\nlong-lived\nlooney\nloopholes\nloosely-connected\nlothario\nlottery\nlovable-loser\nlucas\nlying\nlyrics\nm-16\nmacbeth\nmaggots\nmajors\nmake-believe\nmanipulating\nmankind\nmarine\nmarine/legal\nmarketable\nmask\nmasked\nmassoud\nmaximum\nmeant\nmeetings\nmelted\nmerrily\nmesmerised\nmidst\nmika\nmileage\nmillions\nminiseries\nmisanthropy\nmishandled\nmisty-eyed\nmisunderstood\nmixes\nmob\nmodels\nmoist\nmolina\nmoney-grubbing\nmonosyllabic\nmonument\nmoodiness\nmoralistic\nmorals\nmornings\nmouglalis\nmounted\nmourning\nmoviegoer\nmoviemakers\nmuck\nmulan\nmulti-character\nmumbo-jumbo\nmummy\nmurk\nmythology\nnalin\nnarcotized\nnatural-seeming\nnaturalism\nnausea\nnavel\nne\nnear-fatal\nneedless\nnegate\nneighbor\nnerve-rattling\nnewcomers\nnine-year-old\nno-surprise\nnoisy\nnolan\nnon-disney\nnon-exploitive\nnon-techies\nnorm\nnotches\nnurtured\nobscenity\noccur\noddity\nodor\nofficer\noft-brilliant\noh-so-hollywood\nol'\nold-time\nolympic\nomniscient\nonline\noperandi\noperatic\nopposed\nopposites\norganizing\noscar-nominated\notherworldly\nounce\noutlet\noutre\noutweighs\noverdue\noverstating\noverstimulated\noverture\noverwhelmingly\npaean\npageantry\npainless\npaint-by-numbers\npalate\npallid\nparalyzed\nparapsychological\nparis\nparlor\nparticipant\nparties\npassive-aggressive\npastry\npatchy\npathos-filled\npearce\npearls\npeels\npell-mell\npender\npent\npercentages\npercussion\nperoxide\nperry\npersonable\nphotos\npicaresque\npicpus\npiffle\npipe\npitted\npivotal\npixar\npixilated\nplayoff\npogue\npoint-of-view\npokey\npollute\npop-up\npopped\nportraying\nposing\nposterity\npreaches\npreciousness\npredominantly\nprejudices\nprescription\npresume\nprevalent\npreviously\nprobation\nproclaim\nprofiling\nprogresses\nprojection\nprominent\nprominently\npromotion\nprophet\npropriety-obsessed\nprostitute\nprovincial\nprovocations\nprowess\nprurient\npseudo-intellectual\npsychologizing\npublicists\npublicity\npunishable\npurists\npurr\npursuing\nputters\npyrotechnics\nquadrangle\nr\nracing\nraffish\nrage\nragged\nraison\nrake\nransacks\nraphael\nratio\nratliff\nravaging\nrazzie\nre-working\nreagan\nreassure\nreassures\nrebel\nrecharged\nrecompense\nrecovering\nrecruiting\nreduce\nreductive\nreef\nreek\nrefers\nrefracting\nrefuse\nregards\nregular\nrejiggering\nremarks\nremembrance\nreparations\nreplacing\nrepugnant\nresemblance\nrestage\nrestatement\nretaining\nreturning\nrevigorates\nrevives\nrevolting\nrevolutionary\nrice\nriled\nringside\nripping\nroach\nrobbed\nrobberies\nrodrigues\nrogue\nromanced\nron\nroster\nrounded\nruins\nrésumé\nsaddens\nsale\nsalle\nsally\nsalma\nsanctimoniousness\nsandlerian\nsanity\nsayles\nscariest\nscathingly\nscene-chewing\nschmucks\nschticky\nscientific\nscooby-doo\nscotches\nscreed\nscrewing\nscuttled\nseamstress\nsearches\nseasonal\nseizing\nself-absorbed\nself-absorption\nself-image\nself-knowledge\nself-revealing\nsemi-amusing\nsemimusical\nsent\nsermonize\nserrault\nsettings\nshaky\nshambles\nsheets\nshepard\nshivers\nshoot-em-up\nshrug\nsickly\nsidekicks\nsimpering\nsingular\nskeeved\nskeleton\nskidding\nskimpy\nskims\nskipping\nskirts\nskullduggery\nskulls\nslam-bang\nsleep-inducingly\nslickly\nslogans\nsloppiness\nslyly\nsmall-screen\nsmorgasbord\nsmothered\nsmutty\nsnagged\nsnap\nsnappy\nsnide\nsnooze\nso-five-minutes-ago\nsoft-porn\nsomebodies\nsopranos\nsorriest\nsorrowfully\nsouvlaki\nspanning\nspirals\nspiritless\nsplendid-looking\nsplit-screen\nspooks\nsports-movie\nspry\nsqueezed\nsquirm-inducing\nstabs\nstagings\nstains\nstar-power\nstardom\nsteam\nsteamy\nstepdad\nsterile\nstevenson\nstickiness\nstifling\nstiflingly\nstop-and-start\nstrangling\nstress-reducing\nstrolls\nstudents\nstudio-produced\nstylings\nsubconscious\nsubjective\nsuburban\nsuffocate\nsuggestion\nsupple\nsupplies\nsure-fire\nsurplus\nsurveillance\nsweet-tempered\nswill\nsword-and-sorcery\nswung\nsympathizing\nsytle\ntabloid\ntackled\ntactic\ntainted\ntalked\ntalks\ntardier\ntarzan\ntear-stained\ntearful\ntearing\ntechnologies\nteen-speak\ntelescope\ntemperamental\nterminal\nterrifically\ntestosterone-charged\ntheatrics\nthemed\ntherapy-dependent\nthirteen-year-old\nthorough\nthoughtless\nthrobbing\nthrowaway\nthurman\nticks\ntinged\ntinsel\ntirade\ntitillating\ntolerable\ntonight\ntons\ntops\ntotalitarian\ntougher\ntourists\ntoy\ntraces\ntraffics\ntransfixes\ntransparency\ntransported\ntraps\ntremendously\ntrend\ntrials\ntrudge\ntrue-crime\ntruest\ntruffaut\ntryingly\ntunnels\nturmoil\ntweener\ntyco\nunadulterated\nunblinkingly\nuncluttered\nuncouth\nund\nundemanding\nunderachiever\nunderconfident\nunderlies\nundermines\nunderscoring\nundertaken\nundertones\nunearth\nunendurable\nunfold\nunfulfilled\nunguarded\nunified\nuninspiring\nuniqueness\nunit\nunits\nunlikeable\nunpaid\nunparalleled\nunsaid\nunsalvageability\nunsatisfactorily\nunsuccessful\nunsung\nunveil\nunwary\nupdated\nupping\nuproarious\nurbane\nvariant\nvariations\nvaunted\nveering\nventura\nventuresome\nverdu\nverging\nvideo-cam\nvideodrome\nvigils\nvirtuosity\nvirulently\nvisitor\nvivacious\nvoid\nvonnegut\nvulgarities\nw.\nwafer-thin\nwaif\nwaited\nwalken\nwallowing\nwaltzed\nwar-movie\nwar-weary\nwarn\nwartime\nwatered-down\nweakly\nwelcomes\nwell-conceived\nwell-contructed\nwell-directed\nwell-edited\nwheezy\nwhippersnappers\nwhipping\nwhir\nwhirlwind\nwhite-knuckled\nwide-awake\nwidescreen\nwilder\nwimps\nwindows\nwishy-washy\nwispy\nwitlessness\nwizardry\nworkers\nworkmanlike\nworkout\nworkshop\nworried\nwould-be\nwrapping\nwriter-actor\nx-files\nyank\nzhuangzhuang\n129-minute\n140\n15-year\n1960s\n1998\n2-day\n3-d\n7th\n87\n9-11\n9/11\n?!?\na.c.\na.e.w.\nabrasive\nabsent\nacclaimed\naccompanies\naccompanying\naccomplishments\nacerbic\nacquire\nacting-workshop\nactivate\nactivities\nadaptations\nadhere\nadversity\nadvertised\nadvertisement\nadvises\naffability\naffectingly\naffinity\nafloat\nafterlife\naground\nahola\nakin\nal\naldrich\nall-out\nalmodovar\nalready-shallow\nalternating\namalgam\namusedly\nanarchist\nanchored\nanecdote\nannie\nanti-\napartheid\nappearance\nappetite\napple\naptitude\narchives\naristocracy\naristocrat\narliss\narrived\nassembly\nassert\nassociation\nassures\nastronaut\nattending\nattics\nattributable\naudience-pleaser\nautistic\navalanche\navert\nawakens\nawareness\nb-12\nbackbone\nbacked\nbacklash\nbai\nbalk\nballerinas\nballsy\nbalzac\nbang-bang\nbanking\nbarrie\nbarrow\nbasest\nbath\nbathtub\nbattered\nbatting\nbawdy\nbeat-the-clock\nbedfellows\nbedtime\nbeginners\nbehind-the-scenes\nbeliefs\nbelly-dancing\nbellyaching\nbenefited\nbenshan\nbernal\nbertrand\nbespeaks\nbestowed\nbetrayed\nbetting\nbeware\nbickle\nbikes\nbilling\nbinary\nbio\nbitchy\nblanchett\nbleed\nblended\nblips\nblock\nblockage\nblossom\nboll\nbolster\nbons\nborrow\nbottomlessly\nbought\nbounds\nbranagh\nbrat\nbrazil-like\nbreed\nbrimming\nbroader\nbrothers-style\nbruckheimeresque\nbuckaroo\nbucket\nbuild-up\nbuilt-in\nbullseye\nbumps\nbungle\nbushels\nbyatt\ncadence\ncalling\ncamerawork\ncandles\ncaptivatingly\ncarrey\ncautions\ncedar\ncelebi\ncharacterizes\nchoreography\nchouraqui\nchurch-wary\ncircular\ncivics\nclarke-williams\nclasses\nclassroom\nclassy\nclaude\nclaustrophobia\ncleaving\nclicking\ncling\nclocks\nclones\nclotted\nclunker\nclutch\nclyde\nco-writer/director\ncocky\ncogent\ncoherence\ncoke\ncold-hearted\ncombustion\ncomedically\ncommenting\ncommiserating\ncommitment\ncommon-man\ncommunicating\ncommunity-college\ncompels\nconceivable\nconcludes\nconcrete\nconducted\nconfigurations\nconnections\nconnoisseurs\nconrad\nconsistency\nconspirators\ncontained\ncontemporaries\ncontemptuous\ncontentious\ncontributed\nconundrum\nconverts\nconvincingly\nconvolution\ncooker\ncooly\ncop-flick\ncopenhagen\ncopycat\ncoral\ncorrectness\ncounter\ncounterproductive\ncourtroom\ncow\ncracked\ncracks\ncranked\ncrash-and-bash\ncrashing\ncrawlies\ncredulity\ncrispin\ncritically\ncriticizing\ncritiquing\ncross-country\ncrowdpleaser\nculture-clash\ncurtains\ncut-and-paste\ncutting-room\nd.w.\ndanish\ndarkest\ndate-night\ndave\nday-lewis\nday-old\ndeath-defying\ndecadent\ndecides\ndeckhand\ndecommissioned\ndeemed\ndefense\ndeferred\ndegrades\ndelineate\ndelving\ndemocracie\ndemonic\ndemonstrated\ndenlopp\ndeportment\nderisions\ndesires\ndespairing\ndesultory\ndevelopmentally\ndi\ndiamond\ndicaprio\ndiction\ndiego\ndim\ndirgelike\ndisabled\ndiscontent\ndiscoveries\ndismantle\ndismember\ndisneyland\ndisparate\ndistressing\ndistressingly\ndoctorate\ndoings\ndonald\ndoors\ndoorstep\ndouble-cross\ndoubling\ndoyle\ndozing\ndramatize\ndramatizing\ndrang\ndreamscape\ndropped\ndrug-induced\ndrunk\ndudsville\nduel\nduly\ndunno\ndunst\ndwindles\ndystopian\neducational\neee\neffectiveness\negocentricities\negypt\neh\nelemental\nelvis\nembalmed\nembedded\nemerged\nemphasizing\nemphatic\nemulates\nenergizing\nenforcement\nengine\nengorged\nenhance\nenhancing\nenlightenment\nentree\nentwined\nepics\neroded\neroti-comedy\nescapade\neudora\neurope\nevacuations\neven-toned\nex-girlfriend\nexamples\nexasperatingly\nexcellence\nexcessively\nexchange\nexercises\nexiled\nexpanded\nexpansion\nexpectant\nfabuleux\nfacades\nfamuyiwa\nfang-baring\nfantasti\nfarcically\nfashioning\nfastballs\nfatalism\nfateful\nfault\nfearlessly\nferal\nfictional\nfidel\nfiller\nfillers\nfillm\nfilmgoers\nfire-breathing\nfirth\nflabbergasting\nflag\nflag-waving\nfleeting\nflick-knife\nflinging\nflirts\nfloats\nflog\nflows\nfocuses\nfolds\nfollow-your-dream\nforefront\nforgettably\nformalist\nformidable\nforms\nforte\nfour-year-old\nfourth-rate\nfrankly\nfret\nfrosting\nfruit\nfull-fledged\nfutility\ngadzooks\ngai\ngainsbourg\ngalled\ngamesmanship\ngarcia\ngarcía\ngarde\ngarden\ngarish\ngates\ngay-niche\ngays\ngender-bending\ngenesis\ngestures\ngianni\ngifford\ngilliam\ngirls-behaving-badly\nglancing\nglasses\ngleefully\nglover\nglows\ngluing\ngold\ngoldberg\ngoldman\ngoliath\ngone-to-seed\ngoodall\ngoodness\ngordy\ngoth\ngotten\ngraces\ngracious\ngrandmother\ngrateful\ngratitude\ngrenade\ngriffith\ngroove\ngrossest\ngulpilil\ngushing\ngut\nguzman\nhail\nhalf-an-hour\nhalf-bad\nhallelujah\nhand-drawn\nhandbag-clutching\nhandheld\nhanna-barbera\nhanussen\nharangues\nhardware\nharmon\nharvey\nhatred\nheadline-fresh\nheap\nhears\nhearst\nheartbreakingly\nheck\nheller\nhelpful\nhelping\nhermocrates\nhews\nhibiscus\nhidebound\nhigh-spirited\nhigh-tech\nhighly-praised\nhipness\nhollowness\nholmes\nhonoring\nhopkins/rock\nhorizons\nhormonal\nhorrific\nhotter\nhounds\nhowlingly\nhubristic\nhumanize\nhumbling\nhumiliated\nhumor-seeking\nhuskies\nhustlers\nhyper-real\nhypothesis\nii-birkenau\nill-equipped\nill-wrought\nilluminates\nillumination\nillustrated\nimaginary\nimamura\nimmense\nimmersive\nimpacts\nimperious\nimpetus\nimpish\nimplodes\nimply\nin-your-face\nindignation\nindividuality\nindulgence\ninextricably\ninfamy\ninfatuation\ninflammatory\ninflate\ninfused\ninsecurity\ninsistence\ninsistently\ninstrument\nintacto\nintellectually\nintermediary\nintermingling\nintern\ninternal\nintersect\nintrusive\niris\nirritatingly\nitem\njanuary\njargon\njaunt\njazz\njie\njoin\njonze\njournalistic\njoyful\njugglers\njuiced\njuiceless\njustifies\njuxtaposition\njuxtapositions\nkathie\nkathy\nkatzenberg\nke\nkhan\nkibbitzes\nkid-empowerment\nkiddies\nkilt\nkim\nkingdom\nkirsten\nkitten\nknockaround\nknockoff\nlackadaisical\nlacked\nlad\nlagging\nlandbound\nlarded\nlarge-format\nlascivious-minded\nlathan\nlawn\nlaws\nlazily\nleaping\nleblanc\nlecherous\nlecter\nleontine\nleys\nlifelong\nlifting\nlily\nliman\nlimply\nlina\nliner\nlioness\nlip-gloss\nlip-reading\nliterarily\nlitmus\nlive-action\nlive-wire\nloathe\nlog\nlogistically\nlongtime\nlouiso\nlow-tech\nlukewarm\nlullaby\nlunar\nlushness\nmade-up\nmakeup-deep\nmakin\nmalapropisms\nmanchild\nmanoel\nmanual\nmarcken\nmarginally\nmargins\nmargolo\nmarilyn\nmarkedly\nmarketing\nmarry\nmason\nmasters\nmatches\nmaxim\nmcmullen\nmeddles\nmedia-soaked\nmeet\nmelanie\nmemoir\nmerchandised-to-the-max\nmerited\nmethodology\nmid-to-low\nmiddle-age\nmiike\nmilieu\nmillennial\nmillennium\nmillisecond\nmined\nminnie\nmiracles\nmiserably\nmisfiring\nmisunderstanding\nmockumentary\nmoldy-oldie\nmommy\nmonologues\nmonopoly\nmonth\nmopping\nmoratorium\nmordant\nmortal\nmother-daughter\nmotivation\nmount\nmountains\nmovie-esque\nmovie-making\nmush\nmuted\nmystic\nmélange\nnail\nnapoli\nnarcissistic\nnaval\nnavel-gazing\nnear-impossible\nnear-masterpiece\nneeson\nneglecting\nnemesis\nnicolas\nnighttime\nnimble\nno-bull\nnovelty\nnowheresville\nnubile\nnurses\nnurtures\nnymphette\noblivious\nobscured\nobserved\noccupation\nodoriferous\noff-kilter\nold-world\noliviera\nomits\nopen-mouthed\noppositions\nopulent\noral\nordinances\notherness\noutage\noutdoes\noutgag\noutpaces\noverachieving\novercoming-obstacles\noverdone\novereager\noverflows\noverlapping\noverlook\noverpowered\noversexed\noversimplification\noversized\noverstylized\npaeans\npainkillers\npaltrow\npan\nparables\nparadoxically\npardon\npasts\npatricio\npatriotic\npause\npeaks\npeevish\npellington\npenance\npeopled\nperceptiveness\nperiod-piece\npermitting\npersonnel\npet\npetty\nphenomena\nphenomenon\nphiladelphia\nphonograph\nphysician\npics\npiecing\npies\npiles\npillowcases\npiss\nplaced\nplaguing\nplastered\nplausible\nplaybook\nplug\nplumbing\nplumbs\npoking\npolitesse\npop-cyber\npop-influenced\npore\npornography\nposes\npositives\npossesses\npowerhouse\npowerment\nprank\npreceded\nprefeminist\npreoccupations\npreserves\npressure\nprey\nprisoners\npro\npro-wildlife\nprocessed\nprofessors\nprofundities\nprognosis\nprogressed\npropelled\nprophecies\npropulsive\nprovince\npublishing\npummel\npurge\npuréed\nqueasy\nqueasy-stomached\nquixotic\nquotations\nr&b\nrabbit-proof\nrainbows\nrampantly\nran\nrancorous\nranges\nrapport\nrash\nrat\nrates\nravel\nreader\nready-made\nrealities\nreams\nrecesses\nrecorded\nreeked\nreeses\nreference\nreferred\nrefusing\nregimen\nreincarnation\nrelays\nrelieved\nreligious\nreminders\nrenegade-cop\nrenner\nrepetitively\nreportedly\nreporting\nrepulse\nresolutions\nresources\nrespecting\nrespects\nrestoring\nretold\nretreats\nreturned\nrevelled\nrevived\nrewrite\nreynolds\nrhames\nribisi\nrick\nricture\nrigged\nrightly\nromanticism\nrooted\nrose-tinted\nroussillon\nrusi\ns1m0ne\nsalt-of-the-earth\nsalvation\nsappiness\nsatisfaction\nsc2\nscalds\nscared\nscarface\nscary-funny\nscented\nschool-age\nschoolers\nscooter\nscreenful\nscrutinize\nseater\nseedy\nseeping\nseesawing\nseinfeld\nseldhal\nself-critical\nself-defeatingly\nself-exploitation\nself-reflection\nself-satisfaction\nsend-up\nsending\nsends\nsense-of-humour\nserendipity\nserene\nsettled\nsexism\nshallows\nshapiro\nshear\nshell\nshirley\nshirt\nshocker\nshortness\nshriveled\nsidesplitting\nsidewalks\nsilence\nsilences\nsilent\nsilent-movie\nsilly-looking\nsimulation\nsingle-handedly\nsingle-minded\nsister\nsix-time\nsketchiest\nslapped\nslash-fest\nsleep-inducing\nsleeping\nslow-paced\nsmall-scale\nsmaller\nsnaps\nsnow-and-stuntwork\nsoft\nsoftheaded\nsolemnity\nsolo\nsomeplace\nsomnambulant\nsooooo\nsoporific\nsoul-stirring\nsoullessness\nspain\nspall\nspending\nspikes\nspiritually\nsplashing\nspoken\nspoofs\nsporting\nspotty\nspousal\nspring-break\nspringer\nsprings\nsquander\nstakes\nstammering\nstar-making\nsteinis\nstew\nstill-raw\nstodgy\nstranded\nstray\nstreaks\nstreamlined\nstudiously\nstuffs\nsturm\nstylishly\nsub-formulaic\nsub-par\nsub-tarantino\nsubliminally\nsubmerged\nsubplot\nsubtexts\nsufficient\nsufficiently\nsummery\nsunset\nsuper-serious\nsuper-stupid\nsuperlative\nsurface-effect\nsurrealist\nsuspecting\nswanson\nsweaty-palmed\nswiftly\nswirling\nswordfights\nsymbiotic\nsynergistic\nsyrup\nsystematically\ntacked\ntail\ntailor-made\ntalancón\ntamer\ntantamount\ntartakovsky\ntasteless\nteams\ntear-drenched\ntechniques\nteddy\nteen-exploitation\nteen-sleaze\nteetering\ntelevised\ntenacious\ntend\nterrified\ntested\ntesting\nthirst\nthornier\nthoroughfare\nthoughtlessly\nthreefold\nthrilled\nthrillingly\ntides\ntill\ntime-killer\ntiniest\ntits\nton\ntootsie\ntop-heavy\ntough-man\ntraced\ntrack\ntraits\ntrash-cinema\ntravel-agency\ntravis\ntrickery\ntrifling\ntrilogy\ntrims\ntriumphs\ntrombone\ntrove\ntruck-loving\ntruth-in-advertising\ntumult\ntumultuous\ntunisian\nturks\nturntablism\ntv-movie\ntwilight\ntwinkling\ntyped\ntypes\nugh\nuglier\numa\nunaware\nuncharismatically\nuncharted\nunconcerned\nuncool\nunder-10\nunderestimated\nundergraduate\nundertone\nundying\nunencouraging\nunlaughable\nunnamed\nunpleasantly\nunremittingly\nunstoppable\nupends\nupheaval\nuplifter\nurges\nuselessly\nvainglorious\nvary\nvast\nveins\nvibrantly\nvideo-shot\nvideologue\nviews\nvigorously\nvillainous\nving\nvolatile\nvolumes\nvulakoro\nwalking-dead\nwall\nwaltz\nwanes\nwarm-milk\nwarmed-over\nwarped\nwash\nwebcast\nweber\nweinstein\nweissman\nwell-behaved\nwell-formed\nwell-mounted\nwell-put-together\nwelty\nwewannour\nwhip-smart\nwhitewash\nwhoop\nwimmer\nwind-tunnel\nwinger\nwinter\nwise-beyond-her-years\nwisegirls\nwittier\nwoods\nwrap\nwrath\nwretched\nwritings\nyakusho\nyields\nyuen\nzhao\nzinger-filled\nzoning\nzzzzzzzzz\nélan\n!?\n'90s\n'til\n10-year-old\n120\n14-year-old\n1790\n1950s\n1959\n1960\n1962\n1973\n1997\n22\n3/4th\n6-year-old\n65-minute\n65-year-old\n71\n75\n88\n8th\n94-minute\n99-minute\na-bornin\nabbott\nabdul\nabiding\nabsence\nabsorbs\nabused\naccording\nachival\nacidity\naddresses\nadept\nadobo\nadroit\nadvancing\nadvocacy\naffectation\naffectation-free\naftermath\nagape\nagreed\nairy\nalagna\nalan\nall-over-the-map\nalternate\namélie\nanakin\nandrei\nangles\nanimated-movie\nanthropomorphic\nanti-adult\nanti-catholic\nanti-feminist\nantonio\nappetites\nappropriated\narchibald\narchival\nargot\naristocrats\narithmetic\narthur\nasiaphiles\nassaultive\nassuming\nattacks\nattentions\nattitudes\nattuned\naudrey\naustrian\nauthenticity\nauto-critique\navant\navoiding\naxel\nayatollah\nbabbitt\nbaboon\nback-stabbing\nbackseat\nbait-and-switch\nbaker\nballroom\nbanzai\nbarbarian\nbarking-mad\nbaseball\nbaseball-playing\nbatman\nbeachcombing\nbearable\nbeavis\nbecalmed\nbeers\nbeg\nbelgium\nbelittle\nbelong\nbermuda\nbernard\nbet\nbettany\nbettany/mcdowell\nbetter-focused\nbig-budget/all-star\nbilingual\nbilly\nbiologically\nbiscuit\nbjorkness\nblab\nblacklight\nblatantly\nblemishes\nbloodbath\nblown-out\nbluffs\nblustery\nbmw\nbo\nboiled\nbombay\nborder\nborscht\nbotches\nbotching\nbounces\nbowl\nbox-office\nbrady\nbray\nbrims\nbroke\nbronze\nbrooks\nbugged\nbullfighters\nbully\nbumper\nbundling\nburgeoning\nbutler\nbutter\nbutthead\nbyler\nbyron\nbytes\nbüttner\ncaesar\ncaliber\ncalories\ncanada\ncandor\ncantet\ncarré\ncash\ncassel\ncatcher\ncategorize\ncattle\ncaustic\nchabrolian\nchampionship\ncharacter-driven\ncharred\ncharting\nchastity\ncheap-shot\ncheapen\ncheats\nchips\nchou-chou\nchristian-themed\nchuckling\nchung\nchyna\ncinematically\nclarify\nclaws\nclean-cut\ncleaver\nclinch\nclinically\nclosed\nco-dependence\nco-writers\ncoaster\ncoinage\ncold-blooded\ncold-fish\ncollaborators\ncollect\ncollegiate\ncollie\ncoltish\ncolumbus\ncomeback\ncomforting\ncomic-book\ncommended\ncommunication\ncommunications\ncompass\ncompeting\ncomplicate\ncomprise\ncompromised\ncompulsive\nconceal\nconceited\nconceptions\nconcerning\nconcerns\nconduits\nconfuse\ncongrats\nconsoled\nconspiracies\nconsumerist\ncontagious\ncontours\ncontrol-alt-delete\ncontrolled\ncool-j\ncoordinated\ncorcuera\ncoupling\ncrave\ncream\ncreator\ncrematorium\ncringing\ncritic\ncriticizes\ncrooning\ncross-dressing\ncrudities\ncrushed\ncultivated\ncunning\ncurrently\ncurtsy\ncutes\ncyber\ndabbles\ndark-as-pitch\ndebris\ndecades-spanning\ndecent-enough\ndecline\ndeconstruction\ndeem\ndeficit\ndefines\ndegrading\ndelete\ndelhi\ndemi\ndemocracy\ndensely\ndeny\ndependent\ndepictions\ndepravity\ndescribing\ndestin\ndetractors\ndevils\ndevious\ndiatribes\ndictates\ndie-hard\ndies\ndipped\ndire\ndisadvantage\ndisagreeable\ndisgusted\ndislikable\ndismal\ndismissive\ndisoriented\ndispossessed\ndistinction\ndiver\ndivertissement\ndivided\ndocumentarians\ndodge\ndogme\ndolly\ndominate\ndosage\ndoting\ndoubles\ndoubtful\ndown-home\ndownplaying\ndrama/action\ndramatization\ndramedy\ndrawings\ndresses\ndridi\ndrinking\ndripping\ndriving\ndrudgery\ndrum\ndrumbeat\ndualistic\nduds\ndungeons\ndwarfs\ndylan\ndynamism\ndyslexia\neccentricity\neconomics\neditor\neditorial\neighth-grader\neinstein\nelectrocute\neludes\nemaciated\nemailed\nembodies\nemphasising\nemphasized\nenabling\nenact\nenactments\nencyclopedia\nendured\nenglish-language\nengulfed\nenlightened\nenlivens\nensures\nepitaph\nernest\nerrol\neternity\nexaggeration\nexalts\nexcite\nexemplify\nexhaustingly\nexhibitionism\nexhibits\nexisting\nexplicit\nexploratory\nextends\nextracting\nexuberantly\nexude\nfaceless\nfai\nfanboy\nfancies\nfarm\nfather-and-son\nfavored\nfeatured\nfeeds\nfencing\nfetishes\nfifty\nfiguring\nfiji\nfist\nfizzle\nflails\nflakiness\nflood\nfloundering\nflounders\nflurries\nfocusing\nfogging\nfollow-up\nforeman\nforged\nforgot\nformalism\nfortify\nforward\nfoundering\nfrat-boy\nfresh-squeezed\nfriendly\nfringe\nfrolic\nfruitful\nfrustrations\nfueled\nfuhrman\nfuneral\nfurious\nfuzziness\ng-rated\ngained\ngallo\ngarnish\ngaudy\ngaye\ngaza\ngenerational\ngesture\nglamour\nglosses\nglumly\ngodzilla\ngoose-pimple\ngore-free\ngovernmental\ngrabs\ngraced\ngraffiti\ngranted\ngraphically\ngratify\ngraze\ngroaner\nguarded\nguiltless\nguise\ngussied\nhackery\nhailed\nhalf-dozen\nhalf-lit\nham\nhandiwork\nhard-eyed\nhardest\nharshness\nhayao\nhead-on\nheadaches\nhearing\nheavyweights\nheels\nheidegger\nhellstenius\nhelms\nheroism\nhideousness\nhigh-adrenaline\nhigh-buffed\nhinges\nhistrionic\nhit-and-miss\nhit-man\nhitchens\nhobbled\nhogwash\nhollywood-itis\nhollywood-predictable\nholofcenter\nhomo-eroticism\nhoofing\nhooked\nhoopla\nhorrifyingly\nhorses\nhossein\nhotels\nhotsies\nhotter-two-years-ago\nhousing\nhowler\nhumbuggery\nhunger\nhunky\nhyper-artificiality\nhypermasculine\niced\nideology\nidling\nimbecilic\nimparted\nimpatient\nimplications\nimplicitly\nimplied\nimportantly\nimposter\nimpressionistic\nimprovise\nin-jokey\ninadvertently\nincurably\nindecipherable\nindistinct\nindoors\nindulge\nindulges\nineptitude\ninfantilized\ninfinitely\ninformed\ningenue\ningest\ninhuman\ninsatiable\ninside-show-biz\ninsouciance\ninstances\ninstruct\nintelligibility\ninterludes\ninterpreting\ninterweaves\nintolerant\nintroverted\ninvestment\ninvokes\niosseliani\niq\nirredeemably\nirvine\nirwins\nisabelle\nisraeli/palestinian\nitalian-language\nivory\njacobi\njacquot\njapanimator\njelly\njettisoned\njews\njoffé\njoined\njoins\njostles\njournalistically\njovial\njuan\njuliet/west\njury\njustifying\nka\nkahn\nkarim\nkazan\nkeenly\nkeg\nkendall\nkids-in-peril\nkinnear\nkitschy\nknocks\nkoury\nkubrick\nkumble\nkurys\nlab\nlabel\nlamentations\nlandau\nlarky\nlate-summer\nlawyers\nlds\nleader\nledger\nleery\nlemon\nleniency\nleroy\nlessen\nlethally\nli\nliberalism\nlife-at-arm\nlifted\nlighter\nlike-themed\nlin\nlinear\nlinger\nlint\nliotta\nlittle-remembered\nliu\nlizard\nlo\nlocation\nlolita\nlone\nloop\nlooseness\nlosin\nlovably\nlove-hate\nlugubrious\nlumps\nlungs\nlusty\nluther\nlynne\nmachinery\nmacho\nmacnaughton\nmadcap\nmagician\nmaintained\nmajidi\nmalcolm\nmalik\nmalleable\nmamá\nmandy\nmanhunter\nmapquest\nmarveilleux\nmarvin\nmarxian\nmasochistic\nmass\nmasterpeice\nmatched\nmateys\nmatinee-style\nmccann\nmckay\nmclaughlin\nmediterranean\nmedium-grade\nmelting\nmending\nmentioned\nmenu\nmerge\nmerges\nmeshes\nmeyjes\nmichele\nmichelle\nmicroscope\nmiddle-class\nmidwest\nmilitary\nmilking\nmilks\nminac\nmini\nminimum\nminor-league\nmire\nmiscalculates\nmiscalculations\nmisfortune\nmishandle\nmitch\nmitchell\nmoan\nmodel\nmodernizes\nmodicum\nmonkey\nmonologue\nmorlocks\nmorphs\nmorris\nmortality\nmovie-industry\nmovie-specific\nmugs\nmurder-on-campus\nmurderer\nmurders\nmurray\nmusketeer\nmyriad\nmystification\nmythologizing\nnada\nnail-biter\nnamely\nnaomi\nnarrated\nnatalie\nnaturalness\nnauseating\nneglects\nneo-nazism\nneophyte\nnesbitt\nneurotic\nneurotics\nneverland\nnietzsche-referencing\nninety\nnon-fan\nnon-narrative\nnon-porn\nnorthwest\nnorwegian\nnot-so-divine\nnot-so-small\nnotch\nnoticed\nnotting\nnumbered\nnyc\nobsessively\noccupied\noff-center\noffset\noften-hilarious\noh-so-important\nokay\nolives\nomitted\nomnibus\none-room\none-sidedness\noo\nooze\nopenly\nopportunism\nopting\noptions\norbit\norganized\norlean\noscars\noutline\noutrageousness\noveracted\noverboard\noverrides\noverstated\noverwhelms\np.t.\npa\npabulum\npage-turning\npageant\npalestinian\npamela\npander\npap\nparadigm\nparmentier\npartner\npartners\npartnerships\nparton\npaulette\npauline\npaved\npeanut\nperalta\nperil\nperiod-perfect\nperpetual\nperplexing\npersistence\npersistent\npersonally\npersonas\nperversely\npessimistic\nphantasms\nphantom\nphocion\nphotographic\npicnic\npiercingly\npig\npinheads\npinks\npinochet\nplasma\nplaystation\nplea\nplutonium\npoint-to-point\npointedly\npoke-mania\npolicy\npollak\npollyana\npomposity\npondering\npoof\npooper-scoopers\npopulating\nportent\npossession\nposter\nposter-boy\nposthumously\npouty-lipped\npowered\npraying\npre-teen\npre-wwii\npreteen\npretence\nprints\nproceed\nprod\nproduces\nproducing\nprone\nproperty\nprostituted\nprotestors\nprovocateur\npseudo-philosophic\npsychedelic\npublished\npunched\npunitive\npurgatory\npurportedly\npursued\npursuers\npère-fils\nq.\nquashed\nquasi-improvised\nquasi-shakespearean\nquick-cut\nquicksand\nquintet\nradiates\nrafael\nragbag\nraging\nrails\nramifications\nrandolph\nrape\nrapidly\nrapids\nrated\nraving\nraw-nerved\nre-hash\nre-invents\nreap\nrecently\nrecessive\nreclaiming\nrecognizably\nrecreation\nreenacting\nreflected\nregister\nrelating\nrelied\nrelish\nremove\nrender\nreplace\nresonances\nrestate\nresume\nresurrection\nresuscitate\nrethink\nretooled\nretrograde\nretrospective\nreunion\nreunions\nreversal\nreverse\nrevision\nrevisionism\nreward\nreworking\nrhapsodize\nrhino\nriding\nrigidly\nrigors\nringu\nrinzler\nrip-roaring\nripper\nrisky\nrivals\nroads\nroars\nrockumentary\nrodan\nroland\nroller\nromantics\nromeo\nromoli\nrooms\nrough-trade\nrouses\nroute\nroutes\nroy\nrubbish\nrudd\nruffle\nruggero\nruin\nruse\nrusted-out\nruthless\nrye\nsacrificed\nsamurai\nsanitised\nsat\nsatisfactorily\nscam\nscar\nschepisi\nschiffer\nschneidermeister\nschrader\nscoob\nscorcher\nscored\nscratches\nscreeching-metal\nseated\nsecondary\nselby\nseldom\nself-determination\nself-expression\nself-pitying\nself-reflexive\nself-styled\nsemi-stable\nseptember\nserbs\nserial\nserious-minded\nserry\nservicable\nsetpiece\nsetup\nseventy-minute\nseverely\nsexiness\nshaken\nshatters\nshaw\nshimmeringly\nshoulder\nsigning\nsirk\nsixth\nsixties\nsixties-style\nskid-row\nskilled\nskinny\nskyscraper\nskyscraper-trapeze\nslaps\nsleight-of-hand\nslickest\nslickness\nslide\nslivers\nslot\nslow-motion\nslow-moving\nsmokers\nsmuggling\nsnapping\nsniffle\nsnoozer\nsoaked\nsoapy\nsolomonic\nsommers\nsong-and-dance-man\nsooner\nspecial-interest\nspin-off\nsplatterfests\nspookily\nspot-on\nspouting\nspringboard\nspringing\nsquaddie\nsquirts\nstadium-seat\nstalker\nstalking\nstallone\nstandardized\nstanzas\nstartled\nstayed\nsteadfast\nstep-printing\nstereotype\nstiletto-stomps\nstimulus\nstoner\nstorytellers\nstraddle\nstraight-faced\nstraight-shooting\nstreetwise\nstrenuous\nstrikingly\nstrip-mined\nstroked\nstrutting\nstultifying\nstumble\nsubmerging\nsugar-coated\nsuggestive\nsullivan\nsummons\nsunbaked\nsuper-sized\nsuperman\nsupremes\nsurest\nsurge\nsurveys\nsusceptible\nsuspended\nswathe\nsweet-and-sour\nsweetest\nswims\nsydow\nt-shirt\ntad\ntaiwanese\ntakashi\ntalkiness\ntambién\ntangents\ntap-dancing\ntautou\ntechno-tripe\nteenaged\ntelanovela\ntelephone\ntemptingly\ntendencies\ntendentious\ntenderly\ntestosterone\nthank\ntheorist\ntherapeutic\ntherefore\nthewlis\nthievery\nthinks-it-is\nthousand-times\nthreatens\nthuds\ntick-tock\ntidal\ntie\ntim\ntipped\ntiring\ntitans\ntitle-bout\ntogetherness\ntolerable-to-adults\ntonal\ntoo-hot-for-tv\ntornatore\ntouchingly\ntoughest\ntradition-bound\ntrailer-trash\ntrained\ntrainspotting\ntranquil\ntransition\ntraveled\ntreacly\ntree\ntrembling\ntrial\ntrickster\ntrimmed\ntriviality\ntrivialize\ntron\ntroopers\ntropic\ntroubadour\ntrusted\ntruths\ntu\ntufano\ntv-insider\ntwelve\ntwenty-some\ntwentysomething\ntwinkly-eyed\nuk\nultra-cheesy\nunaccountable\nunafraid\nunbelievably\nunder-7\nunderdeveloped\nundermined\nunderutilized\nunderventilated\nundiminished\nunexplained\nunhappiness\nunhurried\nunleashed\nunorthodox\nunromantic\nunscathed\nunslick\nunspeakably\nunstable\nunsuccessfully\nunthinkable\nunwavering\nuseful\nushered\nusurp\nvacuous\nvardalos\nveggies\nvenality\nvenice/venice\nverdict\nverismo\nviolated\nvirgin\nvocalized\nvoting\nwai\nwalter\nwarned\nwarner\nwatched\nwaterboy\nwazoo\nwearisome\nweighed\nweightless\nwell-meant\nwell-structured\nwell-trod\nwheels\nwhet\nwhine\nwhiney\nwhirling\nwholesale\nwhoopee-cushion\nwidely\nwildcard\nwillie\nwillingly\nwince\nwire\nwireless\nwiser\nwives\nwobbly\nwondered\nwonderland\nwonton\nwrecks\nwrestler\nwrite\nwriter-producer-director\nwrites\nwyman\ny\nya-yas\nyawner\nyeah\nyorkers\nz-boys\nzap\nzemeckis\nzoe\n'53\n'n\n170\n1899\n1950\n1980\n1986\n4/5ths\n50-year-old\n50s\n52\n60-second\n72\n96\nabbass\nabridged\naccentuating\naccommodate\naccomodates\naccumulate\nacolytes\naction/thriller\nactorish\naddams\naddiction\nado\nadopts\nadrian\nadrien\nadvert\naffect\nafghan\nafterschool\nagendas\nagony\naid\naided\nal.\nalexander\nalias\nalienated\nallison\naltered\nalternatives\naltman\naltman-esque\namari\namaro\nambitiously\nambivalent\namc\name\namidst\namini\namir\namuses\nana\nanalgesic\nanarchy\nanchor\nangela\nanimaton\nanonymity\nanswered\nanti-war\nanyplace\nappetizer\naragorn\naranda\narch\narchetypal\narchitecture\nargentinean\narkansas\narwen\nasquith\nassess\nassign\nassignment\nastronauts\nattentive\nattracts\naudacious-impossible\naugustine\naurelie\naustralian\nauteil\nautocritique\naversion\naward-winning\nayres\nayurveda\nbabies\nbabysitter\nbackwater\nbagatelle\nbaird\nballerina\nballet\nbang-the-drum\nbarbs\nbarriers\nbathos\nbattles\nbe-all-end-all\nbe-bop\nbeast-within\nbeasts\nbeating\nbecker\nbeckons\nbed\nbedevilling\nbehan\nbeijing\nbelieves\nbelinsky\nbeloved-major\nbenevolent\nbergman\nberkeley\nberlin\nbetty\nbibbidy-bobbidi-bland\nblandly\nblarney\nblasting\nbless\nblimp\nbluer\nblush\nboarders\nbollywood/hollywood\nbombards\nbonding\nbooths\nboston\nbots\nbottom-rung\nboxes\nboy-meets-girl\nbrainy\nbreasts\nbreitbart\nbrent\nbristles\nbrittle\nbroadside\nbrody\nbubbles\nbuff\nbuffeted\nbug\nbug-eye\nbullwinkle\nburies\nburlesque\nbusby\nbuscemi\nbustling\nbypassing\ncabins\ncam\ncameras\ncapitalism\ncapitalize\ncapitalizes\ncaptions\ncaptive\ncaptives\ncaptors\ncarlito\ncarousel\ncascade\ncavorting\ncenters\nchai\nchapter\ncharacterize\nchilled\nchimney\nchimps\nchokes\nchristelle\ncimarron\ncirca\ncircles\ncivil\ncivilization\nclarissa\nclearasil\ncliffhanger\nclimbing\nclosure\nclothing\ncloudy\ncolin\ncolleagues\ncolorfully\ncom\ncommune\ncompelled\ncomplaints\ncomponents\ncompositions\ncomputerized\nconcealment\nconfessional\nconfirms\nconflicted\nconjuring\nconscience\nconsequence\nconsists\nconstricted\nconstrictive\nconsume\ncontain\ncontinually\ncontorting\ncontribute\nconveys\ncopious\ncorbett\ncorpus\ncorrectly\ncouch\ncounting\ncousin\ncovered\ncrassness\ncrazier\ncreepy-crawly\ncremaster\ncrimen\ncross-cultural\ncrushing\ncryin\ncuaron\ncub\ncurlers\ncurve\nd.\ndaddy\ndalloway\ndampened\ndanis\ndaredevils\ndazed\ndead-eye\ndearly\ndeblois\ndebuts\ndecasia\ndecisively\ndecoder\ndefeats\ndefiant\ndefuses\ndegenerates\ndeleting\ndeliberateness\ndenmark\ndense\ndependence\ndepending\ndepicted\nderanged\nderivativeness\ndesecrations\ndesplat\ndetachment\ndetention\ndeteriorates\ndevelopers\ndeviant\ndeviously\ndicey\ndichotomy\ndick\ndictator-madman\ndifferently\ndignified\ndimensional\ndisassociation\ndiscloses\ndisconcertingly\ndisconnection\ndiscursive\ndiscuss\ndiscussed\ndishes\ndismally\ndisney-style\ndisobedience\ndispatching\ndispel\ndisplacement\ndisposible\ndisrobed\ndistill\ndistinguishable\ndistorts\ndistracts\ndisturb\ndisturbed\ndiva\ndiverges\ndoctor\ndocu-drama\ndocumentary-like\ndodger\ndogma\ndognini\ndolby\ndominated\ndomineering\ndonna\ndoris\ndorm\ndoshas\ndot\ndotted\ndouble\ndoug\ndr.\ndrek\ndrinker\ndrumming\ndrung\ndry-eyed\nduking\ndumbo\ne-graveyard\neardrum-dicing\nearnhart\nedged\nelderly\nelect\nelectoral\nelectronic\neleven\nelicited\nelves\nelysian\nemergency\nenamored\nendgame\nendings\nenemies\nengineering\nentering\nentries\nenvelope\nenvelops\nenvironments\nepiphanies\neponymous\nequate\nerupt\nesoteric\nessayist\nestablish\nestablishing\nestablishment\nestela\netched\nethnography\nevenly\neverlyn\nexceeding\nexceptions\nexhibit\nexpand\nexplain\nexplodes\nexploits\nexpressively\nexpressly\nexquisitely\nextant\nextrusion\neye-boggling\neye-catching\nf\nf.\nfabian\nface-to-face\nfactory\nfacts\nfairies\nfamed\nfantasized\nfarenheit\nfeardotcom\nfetid\nfields\nfiery\nfifth\nfilm-culture\nfinch\nfinishing\nfirmer\nflashback\nflashbulb\nfleder\nfleet-footed\nfloyd\nforewarned\nforgoes\nfork\nforty\nforwards\nfosters\nfounders\nfrankenstein-monster\nfrankie\nfreaking\nfreewheeling\nfreight\nfrench-produced\nfrenzy\nfrighten\nfuses\ngaghan\ngaitskill\ngalvanize\ngambles\ngamely\ngandalf\ngaunt\ngaï\ngeeked\ngeneva\ngeorgia\ngesturing\ngheorghiu\ngidget\ngiggling\ngirlfriends\nglitz\ngobble\ngood-looking\ngoombah\ngorgeousness\ngovernance\ngrabowsky\ngreat-grandson\ngreengrass\ngrenoble\ngrip\ngrizzled\ngrouchy\ngrunge-pirate\ngrungy\ngurus\nhack-and-slash\nhades\nhagiographic\nhairdo\nhairier\nhalf-assed\nhalf-hour\nhalle\nhammer\nhammering\nhammers\nhammily\nhands-on\nhanley\nhaphazardness\nhappily-ever\nhard-core\nhardwood\nharlem\nharmed\nharps\nhart\nhaute\nheadbanger\nhealth\nheartland\nheartstrings\nhermitage\nherring\nhierarchy\nhighlander\nhighways\nhints\nhippopotamus\nhitler-study\nhjelje\nhobnail\nholistic\nholland\nholy\nhomeric\nhomosexuality\nhookers\nhorde\nhorrid\nhos\nhouseholds\nhowling\nhush\nhypertime\nhélène\ni-heard-a-joke\niben\nidiom\nignites\nimmortal\nimmune\nimparting\nimpatiently\nimpresses\ninauspicious\ninclusiveness\nindependence\nindispensable\nindividuals\nineffable\ninert\ninertia\ninfants\ninfinite\ninfirmity\ningeniously\ninsanity\ninsiders\ninsular\ninter-family\ninter-racial\ninterlocked\ninterminably\nintervention\ninterviewees\nintoxicatingly\nintractable\ninvested\nireland\nirreparably\nirreversible\nirrevocable\nirrigates\niteration\nitinerant\nj.r.r.\njackal\njacobson\njammies\njangle\njeremy\njesus\njia\njudgment\njumbled\njune\nkane\nkang\nkathryn\nkeener\nkiddie-oriented\nkidnapper\nknitting\nkubrick-meets-spielberg\nkuras\nkurds\nlabors\nlaced\nlacey\nladles\nlagaan\nlapaglia\nlasker\nlate-night\nlau\nleanest\nleaning\nlector\nled\nleers\nlending\nles\nless-than-thrilling\nlethargically\nletterman\nliana\nlimps\nlingered\nlionize\nlived-in\nliven\nliving-room\nloaf\nlocusts\nlohman\nlongley\nlouts\nlovingly\nlucid\nlucks\nluke\nlushly\nlynch-like\nmachinations\nmade-for-tv\nmafia\nmagi\nmaguire\nmaiden\nmail\nmaladjusted\nmalle\nmalls\nmanically\nmarch\nmarcus\nmargaritas\nmarker\nmarkers\nmaryam\nmassacres\nmctiernan\nmeanest\nmeatier\nmedia-constructed\nmethod\nmethodically\nmetro\nmiddle-earth\nmighty\nmind-bender\nmind-bending\nmisbegotten\nmischievous\nmisleading\nmobius\nmock\nmodern-office\nmolehill\nmonkeys\nmoon\nmora\nmorrissette\nmosque\nmovie-biz\nmrs.\nmuckraking\nmud\nmumbles\nmurderous\nmusset\nmuster\nnanook\nnaqoyqatsi\nnationalism\nnature/nurture\nnecessity\nneeding\nneedles\nnegated\nnegativity\nneo-augustinian\nneo-hitchcockianism\nneo-noir\nnerve-raked\nnewfoundland\nniblet\nnicks\nno-holds-barred\nnon-bondish\nnonbelievers\nnonethnic\nnotations\nnumbness\nobligations\nobserve\noccasion\nodd-couple\noddest\noff-puttingly\noff-screen\noh\noh-those-wacky-brits\nolympia\nonion\noodles\noozes\nopen-minded\nopinion\norleans\northodox\notar\nourside\noutnumber\noutward\noutweigh\nover-familiarity\novernight\noverwhelm\nowe\nownership\np.c.\npadre\npageants\npalm\npan-american\npandora\nparachutes\nparanormal\nparaphrase\nparrot\nparty-hearty\npasach\npassionately\npastel\npatent\npatting\npax\npay-off\npayback\npeaked\npedro\npeep\npercolating\npersecuted\npessimists\npete\npetite\nphifer\nplaintiveness\nplan\nplate\nplato\nplay-doh\nplayboy\nplaywright\npledge\nplod\nplods\nplunges\npluto\npolice-procedural\npoo\npopulist\npork\nportrayals\npost-camp\npost-production\npost-war\npotter\npotty-mouthed\npractical\npractitioners\npray\npre-9\npredators\npreferable\nprepared\npreposterousness\npresses\nprima\nprincesses\npro-serbian\nprofessor\nprofits\nprogression\npropensity\npros\nprosaic\nprotecting\nprozac\npseudo-rock-video\npsychodrama\npublicist\npulchritude\npunch-drunk\npunches\npunchline\npuritanical\npurports\npyschological\nqatsi\nquasi-documentary\nquinn\nrainbow\nrancid\nrape-payback\nrapturous\nre-fried\nre-imagining\nre-voiced\nreact\nreadings\nreaffirms\nrealization\nreassembled\nrecalling\nreconceptualize\nrecreating\nrecycles\nredeem\nredefinition\nrediscovers\nreel/real\nreferential\nreginald\nregrets\nreid\nreigen\nreinvention\nrejection\nrejects\nrekindles\nrelocated\nremained\nrendition\nrenewal\nreporters\nrepresent\nrequiem\nrequires\nresidences\nresidents\nrespectively\nresponses\nrestless\nrestored\nrests\nresumes\nretaliatory\nretrospectively\nreverberates\nreverence\nreyes\nriddle\nright-hand\nrisk\nrolls\nrom\nromance-novel\nromantic-comedy\nromanticized\nromero\nrosario\nrosemary\nrothman\nrubber-face\ns&m\nsafely\nsailor\nsalacious\nsalvaged\nsam\nsampi\nsandeman\nsanders\nsascha\nsatan\nsavaged\nscarpia\nschindler\nschools\nschtick\nscouse\nscrap\nscrewy\nscribe\nscripted\nscripters\nsecretions\nsecretly\nseeds\nseldahl\nself-flagellation\nself-inflicted\nself-sacrifice\nself-serious\nseller\nselling\nsemi-surrealist\nseparates\nsept.\nserenely\nserviceability\nshag\nshamefully\nshatner\nshattering\nshimizu\nshoe-loving\nshoestring\nshopping\nshort-story\nshrewdly\nshum\nsimpson\nsinuously\nsiren\nsituates\nskipped\nslather\nsleepwalk\nslides\nslights\nslop\nsloughs\nslowness\nsmashups\nsnapshot\nsnatch\nsniping\nsnobbery\nsober-minded\nsociopathy\nsodden\nsofia\nsolely\nsongbird\nsorcerer\nsorely\nspace-based\nspanish\nsparked\nspawned\nspecialized\nspecifics\nspied\nspinoff\nsplatter\nspouses\nspout\nspreading\nsquabbling\nstabbing\nstacks\nstacy\nstaggers\nstallion\nsteadily\nstepmom\nsteps\nstereo\nstinging\nstink\nstockings\nstomps\nstoppard\nstraddles\nstrangest\nstrategies\nstress\nstripe\nstupefying\nstyle-free\nsubsequent\nsubsided\nsubtitled\nsuicide\nsunk\nsuppression\nsurfer\nsurround\nsurrounds\nsven\nswingers\nsy\nsylvie\nsymbols\nt.\ntara\ntarkovsky\ntease\nteasing\ntelenovela\ntemporal\ntemptations\nten-year-old\ntenth\ntexan\ntheology\nthinly-conceived\nthree-to-one\nthroes\nthump\ntighter\ntinseltown\ntissue-thin\ntit-for-tat\ntnt\ntome\ntommy\ntonally\ntoolbags\ntools\ntormented\ntourism\ntov\ntowers\ntracking\ntranslating\ntrivializing\ntrot\ntrue-blue\ntrumpet\ntrusts\ntruth-telling\nturpin\ntwinkie\nugly-duckling\nultra-provincial\nunambitious\nunbreakable\nuncinematic\nuncommitted\nunder-rehearsed\nundergoing\nunderway\nunderwear\nunderwhelming\nundogmatic\nuneasily\nunembarrassing\nunemployment\nunevenly\nunfairly\nunhinged\nunreachable\nunrealistic\nunseemly\nunsophisticated\nuntrained\nunwillingness\nupdates\nupstaged\nusher\nut\nutilizes\nvariable\nvaudeville\nver\nverite\nvicarious\nvicente\nvideo-viewing\nvideo/dvd\nvillainess\nvilleneuve\nvirtue\nviva\nviveka\nvivi\nwade\nwaged\nwaits\nwalks\nwar-torn\nwarlord\nwash.\nwatercolor\nwatstein\nwattage\nweaver\nwedgie\nwell-rounded\nwelles\nwending\nwhiffle-ball\nwidget\nwiel\nwintry\nwishful\nwistfully\nwizard\nwoe\nwoodman\nwoolf\nworship\nwounded\nwounding\nwrists\nwrought\nyawn-provoking\nyear-end\nyoda\nyong\nyvan\nzeitgeist\nzen\nzips\nzwick\n"
  },
  {
    "path": "examples/sst2/vocabulary.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"A vocabulary that represents the tokens in a dataset and maps them to indices.\"\"\"\n\nimport collections\nfrom typing import Optional\nfrom collections.abc import Iterable, Sequence\n\nfrom absl import logging\n\n\nclass Vocabulary:\n  \"\"\"Represents a vocabulary that can be built from a dataset.\"\"\"\n\n  def __init__(\n      self,\n      vocab_path: str | None = None,\n      tokenized_sequences: Iterable[Sequence[bytes]] | None = None,\n      min_freq: int = 1,\n      pad_token: bytes = b'<pad>',\n      unk_token: bytes = b'<unk>',\n      bos_token: bytes = b'<s>',\n      eos_token: bytes = b'</s>',\n  ):\n    \"\"\"Loads the vocab from disk (if `vocab_path` is given) or builds it from `tokenized_sequences`.\"\"\"\n    self.pad_token = pad_token\n    self.unk_token = unk_token\n    self.bos_token = bos_token\n    self.eos_token = eos_token\n    self.special_tokens = (pad_token, unk_token, bos_token, eos_token)\n\n    if vocab_path:\n      self.load(vocab_path)\n    elif tokenized_sequences is not None:\n      self.build(tokenized_sequences, min_freq=min_freq)\n    else:\n      raise ValueError(\n          (\n              'Vocabulary needs either `vocab_path` or `tokenized_sequences` to'\n              ' be provided, got %r and %r.'\n          )\n          % (vocab_path, tokenized_sequences)\n      )\n\n  def build(\n      self, tokenized_sequences: Iterable[Sequence[bytes]], min_freq: int = 1\n  ):\n    \"\"\"Builds a vocabulary over tokens with optional minimum frequency.\n\n    Args:\n      tokenized_sequences: Iterable of token sequences to build the vocabulary.\n      min_freq: The minimum frequency of each token to be included. Default: 1.\n    \"\"\"\n    # Count all the tokens.\n    counter = collections.Counter()\n    for token_sequence in tokenized_sequences:\n      counter.update(token_sequence)\n\n    # Add special tokens to the start of vocab.\n    vocab = collections.OrderedDict()\n    for token in self.special_tokens:\n      vocab[token] = len(vocab)\n\n    # Add all other tokens to the vocab if their frequency is >= min_freq.\n    for token, freq in sorted(\n        # Sort by frequency (from high to low), and then by token string.\n        # This makes sure high frequency tokens get a low token ID.\n        counter.items(),\n        key=lambda token_freq: (-token_freq[1], token_freq[0]),\n    ):\n      if freq >= min_freq:\n        vocab[token] = len(vocab)\n\n    logging.info('Number of unfiltered tokens: %d', len(counter))\n    logging.info('Vocabulary size: %d', len(vocab))\n    self.vocab = vocab\n\n  def _getitem__(self, key: str):\n    return self.vocab[key]\n\n  def keys(self):\n    return self.vocab.keys()\n\n  def values(self):\n    return self.vocab.values()\n\n  def __len__(self):\n    return len(self.vocab)\n\n  @property\n  def pad_idx(self):\n    \"\"\"The index of the padding token.\"\"\"\n    return self.vocab[self.pad_token]\n\n  @property\n  def unk_idx(self):\n    \"\"\"The index of the unknown word token.\"\"\"\n    return self.vocab[self.unk_token]\n\n  @property\n  def bos_idx(self):\n    \"\"\"The index of the beginning-of-sequence token.\"\"\"\n    return self.vocab[self.bos_token]\n\n  @property\n  def eos_idx(self):\n    \"\"\"The index of the end-of-sequence token.\"\"\"\n    return self.vocab[self.eos_token]\n\n  def load(self, path: str) -> None:\n    \"\"\"Loads a vocabulary (one token per line) from disk.\"\"\"\n    vocab = collections.OrderedDict()\n    with open(path, 'rb') as f:\n      for i, token in enumerate(f):\n        assert isinstance(token, bytes), 'Expected byte tokens.'\n        vocab[token.rstrip()] = i\n    logging.info('Loaded vocabulary, size: %d', len(vocab))\n    self.vocab = vocab\n\n  def save(self, path: str) -> None:\n    \"\"\"Saves the vocabulary to disk.\"\"\"\n    with open(path, 'wb') as f:\n      for token in self.vocab:\n        assert isinstance(token, bytes), 'Expected byte tokens.'\n        f.write(token + b'\\n')\n    logging.info('Saved vocabulary to %s.', path)\n"
  },
  {
    "path": "examples/vae/README.md",
    "content": "# Basic VAE Example\n\nThis is an implementation of the paper [Auto-Encoding with Variational Bayes](http://arxiv.org/abs/1312.6114) by D.P.Kingma and M.Welling.\nThis code follows [pytorch/examples/vae](https://github.com/pytorch/examples/blob/master/vae/README.md).\n\n```bash\npip install -r requirements.txt\npython main.py --workdir=/tmp/mnist --config=configs/default.py\n```\n\n## Overriding Hyperparameter configurations\n\nThis VAE example allows specifying a hyperparameter configuration by the means of\nsetting `--config` flag. Configuration flag is defined using\n[config_flags](https://github.com/google/ml_collections/tree/master#config-flags).\n`config_flags` allows overriding configuration fields. This can be done as\nfollows:\n\n```shell\npython main.py \\\n--workdir=/tmp/mnist --config=configs/default.py \\\n--config.learning_rate=0.01 --config.num_epochs=10\n```\n\n\n## Examples\n\nIf you run the code by above command, you can get some generated images:\n\n![generated_mnist](./sample.png)\n\nand reconstructions of test set digits:\n\n![reconstruction_mnist](./reconstruction.png)\n\nThe test set loss after 10 epochs should be around `104`.\n"
  },
  {
    "path": "examples/vae/configs/default.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Default Hyperparameter configuration.\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = ml_collections.ConfigDict()\n\n  config.learning_rate = 0.001\n  config.latents = 20\n  config.batch_size = 128\n  config.num_epochs = 30\n  return config\n"
  },
  {
    "path": "examples/vae/input_pipeline.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Input pipeline for VAE dataset.\"\"\"\n\nimport jax\nimport jax.numpy as jnp\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\n\ndef build_train_set(batch_size, ds_builder):\n  \"\"\"Builds train dataset.\"\"\"\n\n  train_ds = ds_builder.as_dataset(split=tfds.Split.TRAIN)\n  train_ds = train_ds.map(prepare_image)\n  train_ds = train_ds.cache()\n  train_ds = train_ds.repeat()\n  train_ds = train_ds.shuffle(50000)\n  train_ds = train_ds.batch(batch_size)\n  train_ds = iter(tfds.as_numpy(train_ds))\n  return train_ds\n\n\ndef build_test_set(ds_builder):\n  \"\"\"Builds train dataset.\"\"\"\n  test_ds = ds_builder.as_dataset(split=tfds.Split.TEST)\n  test_ds = test_ds.map(prepare_image).batch(10000)\n  test_ds = jnp.array(list(test_ds)[0])\n  test_ds = jax.device_put(test_ds)\n  return test_ds\n\n\ndef prepare_image(x):\n  x = tf.cast(x['image'], tf.float32)\n  x = tf.reshape(x, (-1,))\n  return x\n"
  },
  {
    "path": "examples/vae/main.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Main file for running the VAE example.\n\nThis file is intentionally kept short. The majority for logic is in libraries\nthat can be easily tested and imported in Colab.\n\"\"\"\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nfrom clu import platform\nimport jax\nfrom ml_collections import config_flags\nimport tensorflow as tf\n\nimport train\n\n\nFLAGS = flags.FLAGS\n\nflags.DEFINE_string('workdir', None, 'Directory to store model data.')\nconfig_flags.DEFINE_config_file(\n    'config',\n    None,\n    'File path to the training hyperparameter configuration.',\n    lock_config=True,\n)\nflags.mark_flags_as_required(['config', 'workdir'])\n\n\ndef main(argv):\n  if len(argv) > 1:\n    raise app.UsageError('Too many command-line arguments.')\n\n  # Make sure tf does not allocate gpu memory.\n  tf.config.experimental.set_visible_devices([], 'GPU')\n\n  logging.info('JAX process: %d / %d', jax.process_index(), jax.process_count())\n  logging.info('JAX local devices: %r', jax.local_devices())\n\n  # Add a note so that we can tell which task is which JAX host.\n  # (Depending on the platform task 0 is not guaranteed to be host 0)\n  platform.work_unit().set_task_status(\n      f'process_index: {jax.process_index()}, '\n      f'process_count: {jax.process_count()}'\n  )\n\n  train.train_and_evaluate(FLAGS.config, FLAGS.workdir)\n\n\nif __name__ == '__main__':\n  app.run(main)\n"
  },
  {
    "path": "examples/vae/models.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"VAE model definitions.\"\"\"\n\nfrom flax import linen as nn\nfrom jax import random\nimport jax.numpy as jnp\n\n\nclass Encoder(nn.Module):\n  \"\"\"VAE Encoder.\"\"\"\n\n  latents: int\n\n  @nn.compact\n  def __call__(self, x):\n    x = nn.Dense(500, name='fc1')(x)\n    x = nn.relu(x)\n    mean_x = nn.Dense(self.latents, name='fc2_mean')(x)\n    logvar_x = nn.Dense(self.latents, name='fc2_logvar')(x)\n    return mean_x, logvar_x\n\n\nclass Decoder(nn.Module):\n  \"\"\"VAE Decoder.\"\"\"\n\n  @nn.compact\n  def __call__(self, z):\n    z = nn.Dense(500, name='fc1')(z)\n    z = nn.relu(z)\n    z = nn.Dense(784, name='fc2')(z)\n    return z\n\n\nclass VAE(nn.Module):\n  \"\"\"Full VAE model.\"\"\"\n\n  latents: int = 20\n\n  def setup(self):\n    self.encoder = Encoder(self.latents)\n    self.decoder = Decoder()\n\n  def __call__(self, x, z_rng):\n    mean, logvar = self.encoder(x)\n    z = reparameterize(z_rng, mean, logvar)\n    recon_x = self.decoder(z)\n    return recon_x, mean, logvar\n\n  def generate(self, z):\n    return nn.sigmoid(self.decoder(z))\n\n\ndef reparameterize(rng, mean, logvar):\n  std = jnp.exp(0.5 * logvar)\n  eps = random.normal(rng, logvar.shape)\n  return mean + eps * std\n\n\ndef model(latents):\n  return VAE(latents=latents)\n"
  },
  {
    "path": "examples/vae/requirements.txt",
    "content": "absl-py==1.4.0\nflax==0.6.9\nnumpy==1.23.5\noptax==0.1.5\nPillow==10.2.0\ntensorflow==2.12.0\ntensorflow-datasets==4.9.2"
  },
  {
    "path": "examples/vae/results/.gitignore",
    "content": "*.png\n"
  },
  {
    "path": "examples/vae/train.py",
    "content": "# Copyright 2023 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Training and evaluation logic.\"\"\"\nfrom typing import Any\n\nfrom absl import logging\nfrom flax import linen as nn\nimport input_pipeline\nimport models\nimport utils as vae_utils\nfrom flax.training import train_state\nimport jax\nfrom jax import random\nimport jax.numpy as jnp\nimport ml_collections\nimport optax\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\n\n@jax.vmap\ndef kl_divergence(mean, logvar):\n  return -0.5 * jnp.sum(1 + logvar - jnp.square(mean) - jnp.exp(logvar))\n\n\n@jax.vmap\ndef binary_cross_entropy_with_logits(logits, labels):\n  logits = nn.log_sigmoid(logits)\n  return -jnp.sum(\n      labels * logits + (1.0 - labels) * jnp.log(-jnp.expm1(logits))\n  )\n\n\ndef compute_metrics(recon_x, x, mean, logvar):\n  bce_loss = binary_cross_entropy_with_logits(recon_x, x).mean()\n  kld_loss = kl_divergence(mean, logvar).mean()\n  return {'bce': bce_loss, 'kld': kld_loss, 'loss': bce_loss + kld_loss}\n\n\ndef train_step(state, batch, z_rng, latents):\n  \"\"\"Train step.\"\"\"\n  def loss_fn(params):\n    recon_x, mean, logvar = models.model(latents).apply(\n        {'params': params}, batch, z_rng\n    )\n\n    bce_loss = binary_cross_entropy_with_logits(recon_x, batch).mean()\n    kld_loss = kl_divergence(mean, logvar).mean()\n    loss = bce_loss + kld_loss\n    return loss\n\n  grads = jax.grad(loss_fn)(state.params)\n  return state.apply_gradients(grads=grads)\n\n\ndef eval_f(params, images, z, z_rng, latents):\n  \"\"\"Evaluation function.\"\"\"\n  def eval_model(vae):\n    recon_images, mean, logvar = vae(images, z_rng)\n    comparison = jnp.concatenate([\n        images[:8].reshape(-1, 28, 28, 1),\n        recon_images[:8].reshape(-1, 28, 28, 1),\n    ])\n\n    generate_images = vae.generate(z)\n    generate_images = generate_images.reshape(-1, 28, 28, 1)\n    metrics = compute_metrics(recon_images, images, mean, logvar)\n    return metrics, comparison, generate_images\n\n  return nn.apply(eval_model, models.model(latents))({'params': params})\n\n\ndef train_and_evaluate(config: ml_collections.ConfigDict, workdir: str):\n  \"\"\"Train and evaulate pipeline.\"\"\"\n  tf.io.gfile.makedirs(workdir)\n\n  rng = random.key(0)\n  rng, key = random.split(rng)\n\n  ds_builder = tfds.builder('binarized_mnist')\n  ds_builder.download_and_prepare()\n\n  logging.info('Initializing dataset.')\n  train_ds = input_pipeline.build_train_set(config.batch_size, ds_builder)\n  test_ds = input_pipeline.build_test_set(ds_builder)\n\n  logging.info('Initializing model.')\n  init_data = jnp.ones((config.batch_size, 784), jnp.float32)\n  params = models.model(config.latents).init(key, init_data, rng)['params']\n\n  state = train_state.TrainState.create(\n      apply_fn=models.model(config.latents).apply,\n      params=params,\n      tx=optax.adam(config.learning_rate),\n  )\n\n  rng, z_key, eval_rng = random.split(rng, 3)\n  z = random.normal(z_key, (64, config.latents))\n\n  steps_per_epoch = (\n      ds_builder.info.splits['train'].num_examples // config.batch_size\n  )\n\n  for epoch in range(config.num_epochs):\n    for _ in range(steps_per_epoch):\n      batch = next(train_ds)\n      rng, key = random.split(rng)\n      state = train_step(state, batch, key, config.latents)\n\n    metrics, comparison, sample = eval_f(\n        state.params, test_ds, z, eval_rng, config.latents\n    )\n    vae_utils.save_image(\n        comparison, f'{workdir}/reconstruction_{epoch}.png', nrow=8\n    )\n    vae_utils.save_image(\n        sample, f'{workdir}/sample_{epoch}.png', nrow=8\n    )\n\n    print(\n        'eval epoch: {}, loss: {:.4f}, BCE: {:.4f}, KLD: {:.4f}'.format(\n            epoch + 1, metrics['loss'], metrics['bce'], metrics['kld']\n        )\n    )\n"
  },
  {
    "path": "examples/vae/utils.py",
    "content": "# Copyright 2023 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"\nThis code is created with reference to torchvision/utils.py.\n\nModify: torch.tensor -> jax.numpy.DeviceArray\nIf you want to know about this file in detail, please visit the original code:\n    https://github.com/pytorch/vision/blob/master/torchvision/utils.py\n\"\"\"\nimport math\n\nimport jax.numpy as jnp\nimport numpy as np\nfrom PIL import Image\n\n\ndef save_image(ndarray, fp, nrow=8, padding=2, pad_value=0.0, format_img=None):\n  \"\"\"Make a grid of images and Save it into an image file.\n\n  Args:\n    ndarray (array_like): 4D mini-batch images of shape (B x H x W x C)\n    fp:  A filename(string) or file object\n    nrow (int, optional): Number of images displayed in each row of the grid.\n      The final grid size is ``(B / nrow, nrow)``. Default: ``8``.\n    padding (int, optional): amount of padding. Default: ``2``.\n    pad_value (float, optional): Value for the padded pixels. Default: ``0``.\n    format_img(Optional):  If omitted, the format to use is determined from the\n      filename extension. If a file object was used instead of a filename,\n      this parameter should always be used.\n  \"\"\"\n\n  if not (\n      isinstance(ndarray, jnp.ndarray)\n      or (\n          isinstance(ndarray, list)\n          and all(isinstance(t, jnp.ndarray) for t in ndarray)\n      )\n  ):\n    raise TypeError(f'array_like of tensors expected, got {type(ndarray)}')\n\n  ndarray = jnp.asarray(ndarray)\n\n  if ndarray.ndim == 4 and ndarray.shape[-1] == 1:  # single-channel images\n    ndarray = jnp.concatenate((ndarray, ndarray, ndarray), -1)\n\n  # make the mini-batch of images into a grid\n  nmaps = ndarray.shape[0]\n  xmaps = min(nrow, nmaps)\n  ymaps = int(math.ceil(float(nmaps) / xmaps))\n  height, width = (\n      int(ndarray.shape[1] + padding),\n      int(ndarray.shape[2] + padding),\n  )\n  num_channels = ndarray.shape[3]\n  grid = jnp.full(\n      (height * ymaps + padding, width * xmaps + padding, num_channels),\n      pad_value,\n  ).astype(jnp.float32)\n  k = 0\n  for y in range(ymaps):\n    for x in range(xmaps):\n      if k >= nmaps:\n        break\n      grid = grid.at[\n          y * height + padding : (y + 1) * height,\n          x * width + padding : (x + 1) * width,\n      ].set(ndarray[k])\n      k = k + 1\n\n  # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer\n  ndarr = np.array(jnp.clip(grid * 255.0 + 0.5, 0, 255).astype(jnp.uint8))\n  im = Image.fromarray(ndarr.copy())\n  im.save(fp, format=format_img)\n"
  },
  {
    "path": "examples/wmt/README.md",
    "content": "## Machine Translation\n\nTrains a Transformer-based model (Vaswani *et al.*, 2017) on the WMT Machine\nTranslation en-de dataset.\n\nThis example uses linear learning rate warmup and inverse square root learning\nrate schedule.\n\nTable of contents:\n\n- [Requirements](#requirements)\n- [Example runs](#example-runs)\n- [Running on Cloud](#running-on-cloud)\n  - [Preparing the dataset](#preparing-the-dataset)\n  - [Google Cloud TPU](#google-cloud-tpu)\n\n### Requirements\n\n*   TensorFlow datasets `wmt17_translate/de-en` and `wmt14_translate/de-en` need\n    to be downloaded and prepared. A sentencepiece tokenizer vocabulary will be\n    automatically generated and saved on each training run.\n*   This example additionally depends on the `sentencepiece` and\n    `tensorflow-text` packages.\n\n### Example runs\n\nYou should expect to get numbers similar to these:\n\n\nHardware | config  | Training time |      BLEU      |                             TensorBoard.dev                              |                                                          Workdir\n-------- | ------- | ------------- | -------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------\nTPU v3-8 | default | 24m<br>13h18m | 25.55<br>32.87 | [2021-08-04](https://tensorboard.dev/experiment/nnH7JNCxTgC1ROakWePTlg/) | [gs://flax_public/examples/wmt/default](https://console.cloud.google.com/storage/browser/flax_public/examples/wmt/default)\nTPU v3-32 | default | 3h1m | 32.45 | [2021-11-05](https://tensorboard.dev/experiment/7IKeXjoeRKiMtqysQlbqzw/) | [gs://flax_public/examples/wmt/default_v3-32](https://console.cloud.google.com/storage/browser/flax_public/examples/wmt/default_v3-32)\nGPU V100 x8 (Mixed Precision) | gpu_mixed_precision        | 1h 58m       | 25.69 | [2021-07-07](https://tensorboard.dev/experiment/9S2WuqNWRDemmBuQE8K6Ew/) | -\n\n\n### Running on Cloud\n\n#### Preparing the WMT Datasets\n\nWe recommend downloading and preparing the TFDS datasets beforehand. For Cloud\nTPUs, we recommend using a cheap standard instance and saving the prepared TFDS\ndata on a storage bucket, from where it can be loaded directly. Set the\n`TFDS_DATA_DIR` to your storage bucket path (`gs://<bucket name>`).\n\nYou can download and prepare any of the WMT datasets using TFDS directly:\n`python -m tensorflow_datasets.scripts.download_and_prepare\n--datasets=wmt17_translate/de-en`\n\nThe typical academic BLEU evaluation also uses the WMT 2014 Test set: `python -m\ntensorflow_datasets.scripts.download_and_prepare\n--datasets=wmt14_translate/de-en`\n\n#### Google Cloud TPU\n\nSee below for commands to set up a single VM with 8 TPUs attached\n(`--accelerator-type v3-8`), or for an entire TPU slice spanning multiple\nVMs (e.g. `--accelerator-type v3-32`). For more details about how to set up and\nuse TPUs, refer to Cloud docs for\n[single VM setup](https://cloud.google.com/tpu/docs/jax-quickstart-tpu-vm)\nand [pod slice setup](https://cloud.google.com/tpu/docs/jax-quickstart-tpu-vm).\n\nFirst create a single TPUv3-8 VM and connect to it:\n\n```\nZONE=us-central1-a\nTPU_TYPE=v3-8\nVM_NAME=wmt\n\ngcloud alpha compute tpus tpu-vm create $VM_NAME \\\n    --zone $ZONE \\\n    --accelerator-type $TPU_TYPE \\\n    --version v2-alpha\n\ngcloud alpha compute tpus tpu-vm ssh $VM_NAME --zone $ZONE -- \\\n    -L 6006:localhost:6006\n```\n\nWhen connected install JAX:\n\n```\npip install \"jax[tpu]>=0.2.21\" \\\n    -f https://storage.googleapis.com/jax-releases/libtpu_releases.html\n```\n\nThen install Flax + the example dependencies:\n\n```\ngit clone --depth=1 --branch=main https://github.com/google/flax\ncd flax\npip install -e .\ncd examples/wmt\npip install -r requirements.txt\n```\n\nAnd finally start the training:\n\n```\npython3 main.py --workdir=$HOME/logs/wmt_256 \\\n    --config.per_device_batch_size=32 \\\n    --jax_backend_target=\"grpc://192.168.0.2:8470\"\n```\n\nNote that you might want to set `TFDS_DATA_DIR` as explained above. You probably\nalso want to start the long-running command above in a `tmux` session and start\nsome monitoring in a separate pane (note that we forwarded port 6006 locally\nabove):\n\n```\ntensorboard --logdir=$HOME/logs\n```\n\nWhen running on pod slices, after creating the TPU VM, there are different ways\nof running the training in SPMD fashion on the hosts connected to the TPUs that\nmake up the slice. We simply send the same installation/execution shell commands\nto all hosts in parallel with the command below. If anything fails it's\nusually a good idea to connect to a single host and execute the commands\ninteractively.\n\nFor convenience, the TPU creation commands are inlined below. Please note that\nwe define `GCS_TFDS_BUCKET` to where your data stands in your cloud bucket.\nAlso `YOUR_BUCKET` is the work directory you are experimenting in. You should\nchoose ZONE based on where your TPU and work directory is. [Here](https://cloud.google.com/tpu/docs/types-zones)\nhas some useful information on which zones you can have different types of TPUs.\n\n```shell\nVM_NAME=wmt\nREPO=https://github.com/google/flax\nBRANCH=main\nWORKDIR=gs://$YOUR_BUCKET/flax/examples/wmt/$(date +%Y%m%d_%H%M)\n\ngcloud alpha compute tpus tpu-vm create $VM_NAME \\\n    --zone=$ZONE \\\n    --version v2-alpha --accelerator-type v3-32\nFLAGS=\"--config.num_train_steps=$(( 100 * 1000 * 8/32 ))\n--config.warmup_steps=$(( 1000 * 8/32 ))\n--config.checkpoint_every_steps=$(( 10 * 1000 * 8/32 ))\"\n\ngcloud alpha compute tpus tpu-vm ssh $VM_NAME --zone $ZONE \\\n--worker=all --command \"\nset -x\npip install 'jax[tpu]>=0.2.21' -f https://storage.googleapis.com/jax-releases/libtpu_releases.html &&\npip install --user git+$REPO.git &&\n(test -d flax || git clone --depth=1 -b $BRANCH $REPO) &&\ncd flax &&\npip install -e . &&\ncd examples/wmt &&\npip install -r requirements.txt &&\nexport TFDS_DATA_DIR=gs://$GCS_TFDS_BUCKET/datasets &&\npython3 main.py --workdir=$WORKDIR --config=configs/default.py $FLAGS\n\"\n```\nPlease don't forget to disconnect and delete your vm after you are done:\n\n```\ngcloud alpha compute tpus tpu-vm delete $VM_NAME \\\n  --zone $ZONE\n```\n"
  },
  {
    "path": "examples/wmt/bleu.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nr\"\"\"Parallel BLEU score calculation.\n\nThis version of BLEU calculation is derived from the MLPerf transformer reference.\nTries to match SacreBLEU metric reasonably well, but is not identical.\n\nRefs:\n    tokenizer at:\n    https://github.com/tensorflow/models/blob/master/official/transformer/utils/tokenizer.py\n    original preprocessing tokenizer:\n    https://github.com/moses-smt/mosesdecoder/blob/master/scripts/generic/mteval-v14.pl#L954-L983\n    original t2t code:\n    https://github.com/tensorflow/tensor2tensor/blob/master/tensor2tensor/utils/bleu_hook.py\n\nUsage:\n    refs = '''food bar brown cow\n    blee bloo dog sat\n    or please take me out\n    '''\n    hyps = '''foo bar brown cow\n    blee bloo dog sit\n    please do take me out\n    '''\n    bleu_local(refs.split(\"\\n\"), hyps.split(\"\\n\"))  # 39.65\n\"\"\"\n\nimport collections\nimport math\nimport re\nimport sys\nimport unicodedata\n\nimport numpy as np\n\n\nclass UnicodeRegex:\n  \"\"\"Ad-hoc hack to recognize all punctuation and symbols.\"\"\"\n\n  def __init__(self):\n    punctuation = self.property_chars(\"P\")\n    self.nondigit_punct_re = re.compile(r\"([^\\d])([\" + punctuation + r\"])\")\n    self.punct_nondigit_re = re.compile(r\"([\" + punctuation + r\"])([^\\d])\")\n    self.symbol_re = re.compile(\"([\" + self.property_chars(\"S\") + \"])\")\n\n  def property_chars(self, prefix):\n    return \"\".join(\n        chr(x)\n        for x in range(sys.maxunicode)\n        if unicodedata.category(chr(x)).startswith(prefix)\n    )\n\n\nuregex = UnicodeRegex()\n\n\ndef bleu_tokenize(string):\n  r\"\"\"Tokenize a string following the official BLEU implementation.\n\n  See https://github.com/moses-smt/mosesdecoder/'\n           'blob/master/scripts/generic/mteval-v14.pl#L954-L983\n  In our case, the input string is expected to be just one line\n  and no HTML entities de-escaping is needed.\n  So we just tokenize on punctuation and symbols,\n  except when a punctuation is preceded and followed by a digit\n  (e.g. a comma/dot as a thousand/decimal separator).\n\n  Note that a number (e.g. a year) followed by a dot at the end of sentence\n  is NOT tokenized, i.e. the dot stays with the number because\n  `s/(\\p{P})(\\P{N})/ $1 $2/g` does not match this case (unless we add a\n  space after each sentence). However, this error is already in the\n  original mteval-v14.pl and we want to be consistent with it.\n\n  Args:\n    string: the input string\n\n  Returns:\n    a list of tokens\n  \"\"\"\n  string = uregex.nondigit_punct_re.sub(r\"\\1 \\2 \", string)\n  string = uregex.punct_nondigit_re.sub(r\" \\1 \\2\", string)\n  string = uregex.symbol_re.sub(r\" \\1 \", string)\n  return string.split()\n\n\ndef _get_ngrams(segment, max_order):\n  \"\"\"Extracts all n-grams up to a given maximum order from an input segment.\n\n  Args:\n    segment: text segment from which n-grams will be extracted.\n    max_order: maximum length in tokens of the n-grams returned by this methods.\n\n  Returns:\n    The Counter containing all n-grams up to max_order in segment\n    with a count of how many times each n-gram occurred.\n  \"\"\"\n  ngram_counts = collections.Counter()\n  for order in range(1, max_order + 1):\n    for i in range(0, len(segment) - order + 1):\n      ngram = tuple(segment[i : i + order])\n      ngram_counts[ngram] += 1\n  return ngram_counts\n\n\ndef compute_bleu_matches(reference_corpus, translation_corpus, max_order=4):\n  \"\"\"Computes BLEU match stats of translations against one or more references.\n\n  Args:\n    reference_corpus: list of references for each translation. Each reference\n      should be tokenized into a list of tokens.\n    translation_corpus: list of translations to score. Each translation should\n      be tokenized into a list of tokens.\n    max_order: Maximum n-gram order to use when computing BLEU score.\n\n  Returns:\n    Aggregated n-gram stats for BLEU calculation.\n  \"\"\"\n  reference_length = 0\n  translation_length = 0\n  bp = 1.0\n  geo_mean = 0\n\n  matches_by_order = [0] * max_order\n  possible_matches_by_order = [0] * max_order\n  precisions = []\n\n  for references, translations in zip(reference_corpus, translation_corpus):\n    reference_length += len(references)\n    translation_length += len(translations)\n    ref_ngram_counts = _get_ngrams(references, max_order)\n    translation_ngram_counts = _get_ngrams(translations, max_order)\n\n    overlap = {\n        ngram: min(count, translation_ngram_counts[ngram])\n        for ngram, count in ref_ngram_counts.items()\n    }\n\n    for ngram in overlap:\n      matches_by_order[len(ngram) - 1] += overlap[ngram]\n    for ngram in translation_ngram_counts:\n      possible_matches_by_order[len(ngram) - 1] += translation_ngram_counts[\n          ngram\n      ]\n\n  return (\n      np.array(matches_by_order),\n      np.array(possible_matches_by_order),\n      np.array(reference_length),\n      np.array(translation_length),\n  )\n\n\ndef bleu_partial(ref_lines, hyp_lines, case_sensitive=False):\n  \"\"\"Compute n-gram statistics for two lists of references and translations.\"\"\"\n  if len(ref_lines) != len(hyp_lines):\n    raise ValueError(\n        \"Reference and translation lists have different numbers of lines.\"\n    )\n  if not case_sensitive:\n    ref_lines = [x.lower() for x in ref_lines]\n    hyp_lines = [x.lower() for x in hyp_lines]\n  ref_tokens = [bleu_tokenize(x) for x in ref_lines]\n  hyp_tokens = [bleu_tokenize(x) for x in hyp_lines]\n  return compute_bleu_matches(ref_tokens, hyp_tokens)\n\n\ndef complete_bleu(\n    matches_by_order,\n    possible_matches_by_order,\n    reference_length,\n    translation_length,\n    max_order=4,\n    use_bp=True,\n):\n  \"\"\"Compute BLEU score from aggregated n-gram statistics.\"\"\"\n  precisions = [0] * max_order\n  smooth = 1.0\n  geo_mean = 0.0\n  for i in range(0, max_order):\n    if possible_matches_by_order[i] > 0:\n      precisions[i] = matches_by_order[i] / possible_matches_by_order[i]\n      if matches_by_order[i] > 0:\n        precisions[i] = matches_by_order[i] / possible_matches_by_order[i]\n      else:\n        smooth *= 2\n        precisions[i] = 1.0 / (smooth * possible_matches_by_order[i])\n    else:\n      precisions[i] = 0.0\n\n  if max(precisions) > 0:\n    p_log_sum = sum(math.log(p) for p in precisions if p)\n    geo_mean = math.exp(p_log_sum / max_order)\n\n  if use_bp:\n    if not reference_length:\n      bp = 1.0\n    else:\n      ratio = translation_length / reference_length\n      if ratio <= 0.0:\n        bp = 0.0\n      elif ratio >= 1.0:\n        bp = 1.0\n      else:\n        bp = math.exp(1 - 1.0 / ratio)\n  bleu = geo_mean * bp\n  return float(bleu) * 100.0\n\n\ndef bleu_local(ref_lines, hyp_lines, case_sensitive=False):\n  \"\"\"Compute BLEU for two lists of reference and hypothesis translations.\"\"\"\n  stats = bleu_partial(ref_lines, hyp_lines, case_sensitive=case_sensitive)\n  return complete_bleu(*stats) * 100\n"
  },
  {
    "path": "examples/wmt/configs/default.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Default Hyperparameter configuration.\"\"\"\n\nimport ml_collections\n\n\ndef get_config():\n  \"\"\"Get the default hyperparameter configuration.\"\"\"\n  config = ml_collections.ConfigDict()\n\n  # Path to load or store sentencepiece vocab file.\n  config.vocab_path = None\n\n  # Vocabulary size if `vocab_path` is not given.\n  config.vocab_size = 32_000\n\n  config.max_corpus_chars = 10**7\n\n  # Name of TFDS translation dataset to use.\n  config.dataset_name = 'wmt17_translate/de-en'\n\n  # Optional name of TFDS translation dataset to use for evaluation.\n  config.eval_dataset_name = 'wmt14_translate/de-en'\n  config.eval_split = 'test'\n\n  # Reverse the direction of translation.\n  config.reverse_translation = False\n\n  # Per device batch size for training.\n  config.per_device_batch_size = 32\n\n  # Beam size for inference.\n  config.beam_size = 4\n\n  config.num_train_steps = 100_000\n\n  # Number of steps to take during evaluation.\n  config.num_eval_steps = 20\n  # Number of steps to generate predictions (used for BLEU score).\n  # -1 will use the whole eval dataset.\n  config.num_predict_steps = -1\n\n  # Base learning rate.\n  config.learning_rate = 0.002\n\n  # Linear learning rate warmup.\n  config.warmup_steps = 1000\n\n  # Cross entropy loss label smoothing.\n  config.label_smoothing = 0.1\n\n  # Decay factor for AdamW style weight decay.\n  config.weight_decay = 0.0\n\n  # Maximum length cutoff for training examples.\n  config.max_target_length = 256\n  # Maximum length cutoff for eval examples.\n  config.max_eval_target_length = 256\n  # Maximum length cutoff for predicted tokens.\n  config.max_predict_length = 256\n\n  # Inputs and targets share embedding.\n  config.share_embeddings = True\n\n  # Final logit transform uses embedding matrix transpose.\n  config.logits_via_embedding = True\n\n  # Number of transformer layers.\n  config.num_layers = 6\n\n  # Size of query/key/value for attention.\n  config.qkv_dim = 1024\n  # Size of embeddings.\n  config.emb_dim = 1024\n  # Size of the MLP.\n  config.mlp_dim = 4096\n\n  # Number of attention heads.\n  config.num_heads = 16\n\n  # Dropout rate.\n  config.dropout_rate = 0.1\n\n  # Attention dropout rate.\n  config.attention_dropout_rate = 0.1\n\n  # Whether to save model checkpoints.\n  config.save_checkpoints = True\n  # Whether to restore from existing model checkpoints.\n  config.restore_checkpoints = True\n\n  # Save a checkpoint every these number of steps.\n  config.checkpoint_every_steps = 10_000\n  # Frequency of eval during training, e.g. every 1000 steps.\n  config.eval_every_steps = 1_000\n\n  # Use float16/bfloat16 (GPU/TPU) mixed precision training instead of float32.\n  config.use_mixed_precision = True\n\n  # Integer for PRNG random seed.\n  config.seed = 0\n\n  return config\n\n\ndef metrics():\n  return [\n      'train_loss',\n      'eval_loss',\n      'bleu',\n      'eval_accuracy',\n      'train_accuracy',\n      'uptime',\n      'steps_per_sec',\n      'train_learning_rate',\n  ]\n"
  },
  {
    "path": "examples/wmt/decode.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Fast decoding routines for inference from a trained model.\"\"\"\n\nimport typing\n\nimport flax\nimport jax\nfrom jax import lax\nimport jax.numpy as jnp\nimport numpy as np\n\n\n# Constants\n# We assume the default End-of-Sentence token id is 2 (SentencePiece).\nEOS_ID = 2\n# \"Effective negative infinity\" constant for masking in beam search.\nNEG_INF = np.array(-1.0e7)\n\n\ndef brevity_penalty(alpha, length):\n  \"\"\"Brevity penalty function for beam search penalizing short sequences.\n\n  Args:\n    alpha: float: brevity-penalty scaling parameter.\n    length: int: length of considered sequence.\n\n  Returns:\n    Brevity penalty score as jax scalar.\n  \"\"\"\n  return jnp.power(((5.0 + length) / 6.0), alpha)\n\n\n# Beam handling utility functions:\n\n\ndef add_beam_dim(x, beam_size):\n  \"\"\"Creates new beam dimension in non-scalar array and tiles into it.\"\"\"\n  if x.ndim == 0:  # ignore scalars (e.g. cache index)\n    return x\n  x = jnp.expand_dims(x, axis=1)\n  tile_dims = [1] * x.ndim\n  tile_dims[1] = beam_size\n  return jnp.tile(x, tile_dims)\n\n\ndef flatten_beam_dim(x):\n  \"\"\"Flattens the first two dimensions of a non-scalar array.\"\"\"\n  if x.ndim == 0:  # ignore scalars (e.g. cache index)\n    return x\n  return x.reshape((x.shape[0] * x.shape[1],) + x.shape[2:])\n\n\ndef unflatten_beam_dim(x, batch_size, beam_size):\n  \"\"\"Unflattens the first, flat batch*beam dimension of a non-scalar array.\"\"\"\n  if x.ndim == 0:  # ignore scalars (e.g. cache index)\n    return x\n  assert batch_size * beam_size == x.shape[0]\n  return x.reshape((batch_size, beam_size) + x.shape[1:])\n\n\ndef flat_batch_beam_expand(x, beam_size):\n  \"\"\"Expands each batch item by beam_size in batch_dimension.\"\"\"\n  return flatten_beam_dim(add_beam_dim(x, beam_size))\n\n\ndef gather_beams(nested, beam_indices, batch_size, new_beam_size):\n  \"\"\"Gathers the beam slices indexed by beam_indices into new beam array.\n\n  Args:\n    nested: pytree of arrays or scalars (the latter ignored).\n    beam_indices: array of beam_indices\n    batch_size: int: size of batch.\n    new_beam_size: int: size of _new_ beam dimension.\n\n  Returns:\n    New pytree with new beam arrays.\n    [batch_size, old_beam_size, ...] --> [batch_size, new_beam_size, ...]\n  \"\"\"\n  batch_indices = jnp.reshape(\n      jnp.arange(batch_size * new_beam_size) // new_beam_size,\n      (batch_size, new_beam_size),\n  )\n\n  def gather_fn(x):\n    if x.ndim == 0:  # ignore scalars (e.g. cache index)\n      return x\n    else:\n      return x[batch_indices, beam_indices]\n\n  return jax.tree_util.tree_map(gather_fn, nested)\n\n\ndef gather_topk_beams(nested, score_or_log_prob, batch_size, new_beam_size):\n  \"\"\"Gathers the top-k beam slices given by score_or_log_prob array.\n\n  Args:\n    nested: pytree of arrays or scalars (the latter ignored).\n    score_or_log_prob: [batch_size, old_beam_size] array of values to sort by\n      for top-k selection of beam slices.\n    batch_size: int: size of batch.\n    new_beam_size: int: size of _new_ top-k selected beam dimension\n\n  Returns:\n    New pytree with new beam arrays containing top k new_beam_size slices.\n    [batch_size, old_beam_size, ...] --> [batch_size, new_beam_size, ...]\n  \"\"\"\n  _, topk_indices = lax.top_k(score_or_log_prob, k=new_beam_size)\n  topk_indices = jnp.flip(topk_indices, axis=1)\n  return gather_beams(nested, topk_indices, batch_size, new_beam_size)\n\n\n# Beam search state:\n\n\n@flax.struct.dataclass\nclass BeamState:\n  \"\"\"Holds beam search state data.\"\"\"\n\n  # The position of the decoding loop in the length dimension.\n  cur_index: jax.Array  # scalar int32: current decoded length index\n  # The active sequence log probabilities and finished sequence scores.\n  live_logprobs: jax.Array  # float32: [batch_size, beam_size]\n  finished_scores: jax.Array  # float32: [batch_size, beam_size]\n  # The current active-beam-searching and finished sequences.\n  live_seqs: jax.Array  # int32: [batch_size, beam_size, max_decode_len]\n  finished_seqs: jax.Array  # int32: [batch_size, beam_size,\n  #                                         max_decode_len]\n  # Records which of the 'finished_seqs' is occupied and not a filler slot.\n  finished_flags: jax.Array  # bool: [batch_size, beam_size]\n  # The current state of the autoregressive decoding caches.\n  cache: typing.Any  # Any pytree of arrays, e.g. flax attention Cache object\n\n\ndef beam_init(batch_size, beam_size, max_decode_len, cache):\n  \"\"\"Initializes the beam search state data structure.\"\"\"\n  cur_index0 = jnp.array(0)\n  live_logprobs0 = jnp.tile(\n      jnp.array([0.0] + [NEG_INF] * (beam_size - 1)), [batch_size, 1]\n  )\n  finished_scores0 = jnp.ones((batch_size, beam_size)) * NEG_INF\n  live_seqs0 = jnp.zeros((batch_size, beam_size, max_decode_len), jnp.int32)\n  finished_seqs0 = jnp.zeros((batch_size, beam_size, max_decode_len), jnp.int32)\n  finished_flags0 = jnp.zeros((batch_size, beam_size), jnp.bool_)\n  # add beam dimension to attention cache pytree elements\n  beam_cache0 = jax.tree_util.tree_map(\n      lambda x: add_beam_dim(x, beam_size), cache\n  )\n  return BeamState(\n      cur_index=cur_index0,\n      live_logprobs=live_logprobs0,\n      finished_scores=finished_scores0,\n      live_seqs=live_seqs0,\n      finished_seqs=finished_seqs0,\n      finished_flags=finished_flags0,\n      cache=beam_cache0,\n  )\n\n\n# Beam search routine:\n\n\ndef beam_search(\n    inputs,\n    cache,\n    tokens_to_logits,\n    beam_size=4,\n    alpha=0.6,\n    eos_id=EOS_ID,\n    max_decode_len=None,\n):\n  \"\"\"Beam search for transformer machine translation.\n\n  Args:\n    inputs: array: [batch_size, length] int32 sequence of tokens.\n    cache: flax attention cache.\n    tokens_to_logits: fast autoregressive decoder function taking single token\n      slices and cache and returning next-token logits and updated cache.\n    beam_size: int: number of beams to use in beam search.\n    alpha: float: scaling factor for brevity penalty.\n    eos_id: int: id of end-of-sentence token for target vocabulary.\n    max_decode_len: int: maximum length of decoded translations.\n\n  Returns:\n     Tuple of:\n       [batch_size, beam_size, max_decode_len] top-scoring sequences\n       [batch_size, beam_size] beam-search scores.\n  \"\"\"\n  # We liberally annotate shape information for clarity below.\n\n  batch_size = inputs.shape[0]\n  if max_decode_len is None:\n    max_decode_len = inputs.shape[1]\n  end_marker = jnp.array(eos_id)\n\n  # initialize beam search state\n  beam_search_init_state = beam_init(\n      batch_size, beam_size, max_decode_len, cache\n  )\n\n  def beam_search_loop_cond_fn(state):\n    \"\"\"Beam search loop termination condition.\"\"\"\n    # Have we reached max decoding length?\n    not_at_end = state.cur_index < max_decode_len - 1\n\n    # Is no further progress in the beam search possible?\n    # Get the best possible scores from alive sequences.\n    min_brevity_penalty = brevity_penalty(alpha, max_decode_len)\n    best_live_scores = state.live_logprobs[:, -1:] / min_brevity_penalty\n    # Get the worst scores from finished sequences.\n    worst_finished_scores = jnp.min(\n        state.finished_scores, axis=1, keepdims=True\n    )\n    # Mask out scores from slots without any actual finished sequences.\n    worst_finished_scores = jnp.where(\n        state.finished_flags, worst_finished_scores, NEG_INF\n    )\n    # If no best possible live score is better than current worst finished\n    # scores, the search cannot improve the finished set further.\n    search_terminated = jnp.all(worst_finished_scores > best_live_scores)\n\n    # If we're not at the max decode length, and the search hasn't terminated,\n    # continue looping.\n    return not_at_end & (~search_terminated)\n\n  def beam_search_loop_body_fn(state):\n    \"\"\"Beam search loop state update function.\"\"\"\n    # Collect the current position slice along length to feed the fast\n    # autoregressive decoder model.  Flatten the beam dimension into batch\n    # dimension for feeding into the model.\n    # --> [batch * beam, 1]\n    flat_ids = flatten_beam_dim(\n        lax.dynamic_slice(\n            state.live_seqs, (0, 0, state.cur_index), (batch_size, beam_size, 1)\n        )\n    )\n    # Flatten beam dimension into batch to be compatible with model.\n    # {[batch, beam, ...], ...} --> {[batch * beam, ...], ...}\n    flat_cache = jax.tree_util.tree_map(flatten_beam_dim, state.cache)\n\n    # Call fast-decoder model on current tokens to get next-position logits.\n    # --> [batch * beam, vocab]\n    flat_logits, new_flat_cache = tokens_to_logits(flat_ids, flat_cache)\n\n    # unflatten beam dimension\n    # [batch * beam, vocab] --> [batch, beam, vocab]\n    logits = unflatten_beam_dim(flat_logits, batch_size, beam_size)\n    # Unflatten beam dimension in attention cache arrays\n    # {[batch * beam, ...], ...} --> {[batch, beam, ...], ...}\n    new_cache = jax.tree_util.tree_map(\n        lambda x: unflatten_beam_dim(x, batch_size, beam_size), new_flat_cache\n    )\n\n    # Gather log probabilities from logits\n    candidate_log_probs = jax.nn.log_softmax(logits)\n    # Add new logprobs to existing prefix logprobs.\n    # --> [batch, beam, vocab]\n    log_probs = candidate_log_probs + jnp.expand_dims(\n        state.live_logprobs, axis=2\n    )\n\n    # We'll need the vocab size, gather it from the log probability dimension.\n    vocab_size = log_probs.shape[2]\n\n    # Each item in batch has beam_size * vocab_size candidate sequences.\n    # For each item, get the top 2*k candidates with the highest log-\n    # probabilities. We gather the top 2*K beams here so that even if the best\n    # K sequences reach EOS simultaneously, we have another K sequences\n    # remaining to continue the live beam search.\n    beams_to_keep = 2 * beam_size\n    # Flatten beam and vocab dimensions.\n    flat_log_probs = log_probs.reshape((batch_size, beam_size * vocab_size))\n    # Gather the top 2*K scores from _all_ beams.\n    # --> [batch, 2*beams], [batch, 2*beams]\n    topk_log_probs, topk_indices = lax.top_k(flat_log_probs, k=beams_to_keep)\n    # Recover the beam index by floor division.\n    topk_beam_indices = topk_indices // vocab_size\n    # Gather 2*k top beams.\n    # --> [batch, 2*beams, length]\n    topk_seq = gather_beams(\n        state.live_seqs, topk_beam_indices, batch_size, beams_to_keep\n    )\n\n    # Append the most probable 2*K token IDs to the top 2*K sequences\n    # Recover token id by modulo division and expand Id array for broadcasting.\n    # --> [batch, 2*beams, 1]\n    topk_ids = jnp.expand_dims(topk_indices % vocab_size, axis=2)\n    # Update sequences for the 2*K top-k new sequences.\n    # --> [batch, 2*beams, length]\n    topk_seq = lax.dynamic_update_slice(\n        topk_seq, topk_ids, (0, 0, state.cur_index + 1)\n    )\n\n    # Update LIVE (in-progress) sequences:\n    # Did any of these sequences reach an end marker?\n    # --> [batch, 2*beams]\n    newly_finished = topk_seq[:, :, state.cur_index + 1] == end_marker\n    # To prevent these newly finished sequences from being added to the LIVE\n    # set of active beam search sequences, set their log probs to a very large\n    # negative value.\n    new_log_probs = topk_log_probs + newly_finished * NEG_INF\n    # Determine the top k beam indices (from top 2*k beams) from log probs.\n    # --> [batch, beams]\n    _, new_topk_indices = lax.top_k(new_log_probs, k=beam_size)\n    new_topk_indices = jnp.flip(new_topk_indices, axis=1)\n    # Gather the top k beams (from top 2*k beams).\n    # --> [batch, beams, length], [batch, beams]\n    top_alive_seq, top_alive_log_probs = gather_beams(\n        [topk_seq, new_log_probs], new_topk_indices, batch_size, beam_size\n    )\n\n    # Determine the top k beam indices from the original set of all beams.\n    # --> [batch, beams]\n    top_alive_indices = gather_beams(\n        topk_beam_indices, new_topk_indices, batch_size, beam_size\n    )\n    # With these, gather the top k beam-associated caches.\n    # --> {[batch, beams, ...], ...}\n    top_alive_cache = gather_beams(\n        new_cache, top_alive_indices, batch_size, beam_size\n    )\n\n    # Update FINISHED (reached end of sentence) sequences:\n    # Calculate new seq scores from log probabilities.\n    new_scores = topk_log_probs / brevity_penalty(alpha, state.cur_index + 1)\n    # Mask out the still unfinished sequences by adding large negative value.\n    # --> [batch, 2*beams]\n    new_scores += (~newly_finished) * NEG_INF\n\n    # Combine sequences, scores, and flags along the beam dimension and compare\n    # new finished sequence scores to existing finished scores and select the\n    # best from the new set of beams.\n    finished_seqs = jnp.concatenate(  # --> [batch, 3*beams, length]\n        [state.finished_seqs, topk_seq], axis=1\n    )\n    finished_scores = jnp.concatenate(  # --> [batch, 3*beams]\n        [state.finished_scores, new_scores], axis=1\n    )\n    finished_flags = jnp.concatenate(  # --> [batch, 3*beams]\n        [state.finished_flags, newly_finished], axis=1\n    )\n    # --> [batch, beams, length], [batch, beams], [batch, beams]\n    (\n        top_finished_seq,\n        top_finished_scores,\n        top_finished_flags,\n    ) = gather_topk_beams(\n        [finished_seqs, finished_scores, finished_flags],\n        finished_scores,\n        batch_size,\n        beam_size,\n    )\n\n    return BeamState(\n        cur_index=state.cur_index + 1,\n        live_logprobs=top_alive_log_probs,\n        finished_scores=top_finished_scores,\n        live_seqs=top_alive_seq,\n        finished_seqs=top_finished_seq,\n        finished_flags=top_finished_flags,\n        cache=top_alive_cache,\n    )\n\n  # Run while loop and get final beam search state.\n  final_state = lax.while_loop(\n      beam_search_loop_cond_fn, beam_search_loop_body_fn, beam_search_init_state\n  )\n\n  # Account for the edge-case where there are no finished sequences for a\n  # particular batch item. If so, return live sequences for that batch item.\n  # --> [batch]\n  none_finished = jnp.any(final_state.finished_flags, axis=1)\n  # --> [batch, beams, length]\n  finished_seqs = jnp.where(\n      none_finished[:, None, None],\n      final_state.finished_seqs,\n      final_state.live_seqs,\n  )\n  # --> [batch, beams]\n  finished_scores = jnp.where(\n      none_finished[:, None],\n      final_state.finished_scores,\n      final_state.live_logprobs,\n  )\n\n  return finished_seqs, finished_scores\n"
  },
  {
    "path": "examples/wmt/input_pipeline.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Input pipeline for a WMT dataset.\"\"\"\n\nimport os\nfrom typing import Dict, Optional, List, Union\n\nfrom clu import deterministic_data\nimport ml_collections\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\nimport tokenizer\n\n\nAUTOTUNE = tf.data.AUTOTUNE\nFeatures = dict[str, tf.Tensor]\n\n\nclass NormalizeFeatureNamesOp:\n  \"\"\"Normalizes feature names to 'inputs' and 'targets'.\"\"\"\n\n  def __init__(self, ds_info: tfds.core.DatasetInfo, reverse_translation: bool):\n    self.input_lang, self.target_lang = ds_info.supervised_keys\n    if reverse_translation:\n      self.input_lang, self.target_lang = self.target_lang, self.input_lang\n\n  def __call__(self, features: Features) -> Features:\n    features['inputs'] = features.pop(self.input_lang)\n    features['targets'] = features.pop(self.target_lang)\n    return features\n\n\ndef get_raw_dataset(\n    dataset_builder: tfds.core.DatasetBuilder,\n    split: str,\n    *,\n    reverse_translation: bool = False,\n) -> tf.data.Dataset:\n  \"\"\"Loads a raw WMT dataset and normalizes feature keys.\n\n  Args:\n    dataset_builder: TFDS dataset builder that can build `slit`.\n    split: Split to use. This must be the full split. We shard the split across\n      multiple hosts and currently don't support sharding subsplits.\n    reverse_translation: bool: whether to reverse the translation direction.\n      e.g. for 'de-en' this translates from english to german.\n\n  Returns:\n    Dataset with source and target language features mapped to 'inputs' and\n    'targets'.\n  \"\"\"\n  num_examples = dataset_builder.info.splits[split].num_examples\n  per_host_split = deterministic_data.get_read_instruction_for_host(\n      split, num_examples, drop_remainder=False\n  )\n  ds = dataset_builder.as_dataset(split=per_host_split, shuffle_files=False)\n  ds = ds.map(\n      NormalizeFeatureNamesOp(\n          dataset_builder.info, reverse_translation=reverse_translation\n      ),\n      num_parallel_calls=AUTOTUNE,\n  )\n  return ds\n\n\ndef pack_dataset(\n    dataset: tf.data.Dataset,\n    key2length: int | dict[str, int],\n    keys: list[str] | None = None,\n) -> tf.data.Dataset:\n  \"\"\"Creates a 'packed' version of a dataset on-the-fly.\n\n  Adapted from the mesh-tf implementation.\n\n  This is meant to replace the irritation of having to create a separate\n  \"packed\" version of a dataset to train efficiently on TPU.\n  Each example in the output dataset represents several examples in the\n  input dataset.\n  For each key in the input dataset, two additional keys are created:\n  <key>_segmentation: an int32 tensor identifying the parts\n     representing the original example.\n  <key>_position: an int32 tensor identifying the position within the original\n     example.\n  Example:\n  Two input examples get combined to form an output example.\n  The input examples are:\n  {\"inputs\": [8, 7, 1, 0], \"targets\":[4, 1, 0]}\n  {\"inputs\": [2, 3, 4, 1], \"targets\":[5, 6, 1]}\n  The output example is:\n  {\n                 \"inputs\": [8, 7, 1, 2, 3, 4, 1, 0, 0, 0]\n    \"inputs_segmentation\": [1, 1, 1, 2, 2, 2, 2, 0, 0, 0]\n        \"inputs_position\": [0, 1, 2, 0, 1, 2, 3, 0, 0, 0]\n                \"targets\": [4, 1, 5, 6, 1, 0, 0, 0, 0, 0]\n   \"targets_segmentation\": [1, 1, 2, 2, 2, 0, 0, 0, 0, 0]\n       \"targets_position\": [0, 1, 0, 1, 2, 0, 0, 0, 0, 0]\n  }\n  0 represents padding in both the inputs and the outputs.\n  Sequences in the incoming examples are truncated to length \"length\", and the\n  sequences in the output examples all have fixed (padded) length \"length\".\n\n  Args:\n    dataset: a tf.data.Dataset\n    key2length: an integer, or a dict from feature-key to integer\n    keys: a list of strings (e.g. [\"inputs\", \"targets\"])\n\n  Returns:\n    a tf.data.Dataset\n  \"\"\"\n  shapes = tf.nest.map_structure(lambda spec: spec.shape, dataset.element_spec)\n  if keys is None:\n    keys = list(shapes.keys())\n  for k in keys:\n    if k not in shapes:\n      raise ValueError(\n          'Key %s not found in dataset.  Available keys are %s'\n          % (k, shapes.keys())\n      )\n    if not shapes[k].is_compatible_with(tf.TensorShape([None])):  # type: ignore[wrong-arg-types]\n      raise ValueError('Tensors to be packed must be one-dimensional.')\n  # make sure that the length dictionary contains all keys as well as the\n  # keys suffixed by \"_segmentation\" and \"_position\"\n  if isinstance(key2length, int):\n    key2length = {k: key2length for k in keys}\n  for k in keys:\n    for suffix in ['_segmentation', '_position']:\n      key2length[k + suffix] = key2length[k]\n\n  # trim to length\n  dataset = dataset.map(\n      lambda x: {k: x[k][: key2length[k]] for k in keys},\n      num_parallel_calls=AUTOTUNE,\n  )\n  # Setting batch_size=length ensures that the concatenated sequences (if they\n  # have length >=1) are sufficient to fill at least one packed example.\n  batch_size = max(key2length.values())\n  dataset = dataset.padded_batch(\n      batch_size, padded_shapes={k: [-1] for k in keys}\n  )\n  dataset = _pack_with_tf_ops(dataset, keys, key2length)\n\n  # Set the Tensor shapes correctly since they get lost in the process.\n  def my_fn(x):\n    return {k: tf.reshape(v, [key2length[k]]) for k, v in x.items()}\n\n  return dataset.map(my_fn, num_parallel_calls=AUTOTUNE)\n\n\ndef _pack_with_tf_ops(\n    dataset: tf.data.Dataset, keys: list[str], key2length: dict[str, int]\n) -> tf.data.Dataset:\n  \"\"\"Helper-function for packing a dataset which has already been batched.\n\n  Helper for pack_dataset()  Uses tf.while_loop.\n\n  Args:\n    dataset: a dataset containing padded batches of examples.\n    keys: a list of strings\n    key2length: an dict from feature-key to integer\n\n  Returns:\n    a dataset.\n  \"\"\"\n  empty_example = {}\n  for k in keys:\n    empty_example[k] = tf.zeros([0], dtype=tf.int32)\n    empty_example[k + '_position'] = tf.zeros([0], dtype=tf.int32)\n  keys_etc = empty_example.keys()\n\n  def write_packed_example(partial, outputs):\n    new_partial = empty_example.copy()\n    new_outputs = {}\n    for k in keys_etc:\n      new_outputs[k] = outputs[k].write(\n          outputs[k].size(),\n          tf.pad(partial[k], [[0, key2length[k] - tf.size(partial[k])]]),\n      )\n    return new_partial, new_outputs\n\n  def map_fn(x):\n    \"\"\"Internal function to flat_map over.\n\n    Consumes a batch of input examples and produces a variable number of output\n    examples.\n    Args:\n      x: a single example\n\n    Returns:\n      a tf.data.Dataset\n    \"\"\"\n    partial = empty_example.copy()\n    i = tf.zeros([], dtype=tf.int32)\n    dynamic_batch_size = tf.shape(x[keys[0]])[0]\n    outputs = {}\n    for k in keys:\n      outputs[k] = tf.TensorArray(\n          tf.int32, size=0, dynamic_size=True, element_shape=[key2length[k]]\n      )\n      outputs[k + '_position'] = tf.TensorArray(\n          tf.int32, size=0, dynamic_size=True, element_shape=[key2length[k]]\n      )\n\n    def body_fn(i, partial, outputs):\n      \"\"\"Body function for while_loop.\n\n      Args:\n        i: integer scalar\n        partial: dictionary of Tensor (partially-constructed example)\n        outputs: dictionary of TensorArray\n\n      Returns:\n        A triple containing the new values of the inputs.\n      \"\"\"\n      can_append = True\n      one_example = {}\n      for k in keys:\n        val = tf.cast(x[k][i], tf.int32)\n        val = val[: tf.reduce_sum(tf.cast(tf.not_equal(val, 0), tf.int32))]\n        one_example[k] = val\n      for k in keys:\n        can_append = tf.logical_and(\n            can_append,\n            tf.less_equal(\n                tf.size(partial[k]) + tf.size(one_example[k]), key2length[k]\n            ),\n        )\n\n      def false_fn():\n        return write_packed_example(partial, outputs)\n\n      def true_fn():\n        return partial, outputs\n\n      partial, outputs = tf.cond(can_append, true_fn, false_fn)\n      new_partial = {}\n      for k in keys:\n        new_seq = one_example[k][: key2length[k]]\n        new_seq_len = tf.size(new_seq)\n        new_partial[k] = tf.concat([partial[k], new_seq], 0)\n        new_partial[k + '_position'] = tf.concat(\n            [partial[k + '_position'], tf.range(new_seq_len)], 0\n        )\n      partial = new_partial\n      return i + 1, partial, outputs\n\n    # For loop over all examples in the batch.\n    i, partial, outputs = tf.while_loop(\n        cond=lambda *_: True,\n        body=body_fn,\n        loop_vars=(i, partial, outputs),\n        shape_invariants=(\n            tf.TensorShape([]),  # type: ignore[wrong-arg-types]\n            {k: tf.TensorShape([None]) for k in keys_etc},  # type: ignore[wrong-arg-types]\n            {k: tf.TensorShape(None) for k in keys_etc},  # type: ignore[wrong-arg-types]\n        ),\n        maximum_iterations=dynamic_batch_size,\n    )\n    _, outputs = write_packed_example(partial, outputs)\n    packed = {k: outputs[k].stack() for k in keys_etc}\n    for k in keys:\n      packed[k + '_segmentation'] = tf.cumsum(\n          tf.cast(tf.equal(packed[k + '_position'], 0), tf.int32), axis=1\n      ) * tf.cast(tf.not_equal(packed[k], 0), tf.int32)\n    return packed\n\n  dataset = dataset.map(map_fn, num_parallel_calls=AUTOTUNE)\n  return dataset.unbatch()\n\n\n# -----------------------------------------------------------------------------\n# Main dataset prep routines.\n# -----------------------------------------------------------------------------\ndef preprocess_wmt_data(\n    dataset,\n    shuffle: bool,\n    num_epochs: int | None = 1,\n    pack_examples: bool = True,\n    shuffle_buffer_size: int = 1024,\n    max_length: int = 512,\n    batch_size: int = 256,\n    drop_remainder: bool = True,\n    prefetch_size: int = AUTOTUNE,\n):\n  \"\"\"Shuffle and batch/pack the given dataset.\"\"\"\n\n  def length_filter(max_len):\n    def filter_fn(x):\n      source, target = x['inputs'], x['targets']\n      l = tf.maximum(tf.shape(source)[0], tf.shape(target)[0])\n      return tf.less(l, max_len + 1)\n\n    return filter_fn\n\n  if max_length > 0:\n    dataset = dataset.filter(length_filter(max_length))\n\n  if shuffle:\n    dataset = dataset.shuffle(shuffle_buffer_size)\n  dataset = dataset.repeat(num_epochs)\n\n  if pack_examples:\n    dataset = pack_dataset(dataset, max_length)\n    dataset = dataset.batch(batch_size, drop_remainder=drop_remainder)\n  else:  # simple (static-shape) padded batching\n    dataset = dataset.padded_batch(\n        batch_size,\n        padded_shapes={'inputs': max_length, 'targets': max_length},\n        padding_values={'inputs': 0, 'targets': 0},\n        drop_remainder=drop_remainder,\n    )\n\n  if prefetch_size:\n    dataset = dataset.prefetch(prefetch_size)\n\n  return dataset\n\n\ndef get_wmt_datasets(\n    config: ml_collections.ConfigDict,\n    *,\n    n_devices: int,\n    reverse_translation: bool = True,\n    vocab_path: str | None = None,\n):\n  \"\"\"Load and return dataset of batched examples for use during training.\"\"\"\n  if vocab_path is None:\n    vocab_path = os.path.expanduser('~/wmt_sentencepiece_model')\n\n  train_ds_builder = tfds.builder(config.dataset_name)\n  train_data = get_raw_dataset(\n      train_ds_builder, 'train', reverse_translation=reverse_translation\n  )\n\n  if config.eval_dataset_name:\n    eval_ds_builder = tfds.builder(config.eval_dataset_name)\n  else:\n    eval_ds_builder = train_ds_builder\n  eval_data = get_raw_dataset(\n      eval_ds_builder,\n      config.eval_split,\n      reverse_translation=reverse_translation,\n  )\n\n  # Tokenize data.\n  sp_tokenizer = tokenizer.load_or_train_tokenizer(\n      train_data,\n      vocab_path=vocab_path,\n      vocab_size=config.vocab_size,\n      max_corpus_chars=config.max_corpus_chars,\n  )\n  train_data = train_data.map(\n      tokenizer.TokenizeOp(sp_tokenizer), num_parallel_calls=AUTOTUNE\n  )\n  eval_data = eval_data.map(\n      tokenizer.TokenizeOp(sp_tokenizer), num_parallel_calls=AUTOTUNE\n  )\n\n  batch_size = config.per_device_batch_size * n_devices\n\n  train_ds = preprocess_wmt_data(\n      train_data,\n      shuffle=True,\n      num_epochs=None,\n      pack_examples=True,\n      batch_size=batch_size,\n      max_length=config.max_target_length,\n  )\n\n  eval_ds = preprocess_wmt_data(\n      eval_data,\n      shuffle=False,\n      pack_examples=False,\n      batch_size=batch_size,\n      max_length=config.max_eval_target_length,\n  )\n\n  predict_ds = preprocess_wmt_data(\n      eval_data,\n      shuffle=False,\n      pack_examples=False,\n      batch_size=batch_size,\n      max_length=config.max_predict_length,\n      drop_remainder=False,\n  )\n\n  return train_ds, eval_ds, predict_ds, sp_tokenizer\n"
  },
  {
    "path": "examples/wmt/input_pipeline_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport pathlib\nimport sys\nimport tempfile\n\nfrom absl.testing import absltest\nimport tensorflow_datasets as tfds\n\nfrom configs import default\nimport input_pipeline\n\n\n# We just use different values here to verify that the input pipeline uses the\n# the correct value for the 3 different datasets.\n_TARGET_LENGTH = 32\n_EVAL_TARGET_LENGTH = 48\n_PREDICT_TARGET_LENGTH = 64\n\n\nclass InputPipelineTest(absltest.TestCase):\n\n  def setUp(self):\n    super().setUp()\n    if sys.version_info >= (3, 13):\n      self.skipTest('Test (and tensorflow-text) does not suport Python 3.13+')\n    self.train_ds, self.eval_ds, self.predict_ds = self._get_datasets()\n\n  def _get_datasets(self):\n    config = default.get_config()\n    config.per_device_batch_size = 1\n    config.vocab_size = 32\n    config.max_corpus_chars = 1000\n    config.max_target_length = _TARGET_LENGTH\n    config.max_eval_target_length = _EVAL_TARGET_LENGTH\n    config.max_predict_length = _PREDICT_TARGET_LENGTH\n\n    vocab_path = os.path.join(tempfile.mkdtemp(), 'sentencepiece_model')\n\n    # Go two directories up to the root of the flax directory.\n    flax_root_dir = pathlib.Path(__file__).parents[2]\n    data_dir = str(flax_root_dir) + '/.tfds/metadata'  # pylint: disable=unused-variable\n\n    with tfds.testing.mock_data(num_examples=128, data_dir=data_dir):\n      train_ds, eval_ds, predict_ds, _ = input_pipeline.get_wmt_datasets(\n          n_devices=2, config=config, vocab_path=vocab_path\n      )\n    return train_ds, eval_ds, predict_ds\n\n  def test_train_ds(self):\n    expected_shape = [2, _TARGET_LENGTH]  # 2 devices.\n    # For training we pack multiple short examples in one example.\n    # *_position and *_segmentation indicate the boundaries.\n    for batch in self.train_ds.take(3):\n      self.assertEqual(\n          {k: v.shape.as_list() for k, v in batch.items()},\n          {\n              'inputs': expected_shape,\n              'inputs_position': expected_shape,\n              'inputs_segmentation': expected_shape,\n              'targets': expected_shape,\n              'targets_position': expected_shape,\n              'targets_segmentation': expected_shape,\n          },\n      )\n\n  def test_eval_ds(self):\n    expected_shape = [2, _EVAL_TARGET_LENGTH]  # 2 devices.\n    for batch in self.eval_ds.take(3):\n      self.assertEqual(\n          {k: v.shape.as_list() for k, v in batch.items()},\n          {\n              'inputs': expected_shape,\n              'targets': expected_shape,\n          },\n      )\n\n  def test_predict_ds(self):\n    expected_shape = [2, _PREDICT_TARGET_LENGTH]  # 2 devices.\n    for batch in self.predict_ds.take(3):\n      self.assertEqual(\n          {k: v.shape.as_list() for k, v in batch.items()},\n          {\n              'inputs': expected_shape,\n              'targets': expected_shape,\n          },\n      )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "examples/wmt/main.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Main file for running the WMT example.\n\nThis file is intentionally kept short. The majority for logic is in libraries\nthat can be easily tested and imported in Colab.\n\"\"\"\n\nfrom absl import app\nfrom absl import flags\nfrom absl import logging\nfrom clu import platform\nimport jax\nfrom ml_collections import config_flags\nimport tensorflow as tf\n\nimport train\n\n\nFLAGS = flags.FLAGS\n\nflags.DEFINE_string('workdir', None, 'Directory to store model data.')\nconfig_flags.DEFINE_config_file(\n    'config',\n    'configs/default.py',\n    'File path to the training hyperparameter configuration.',\n    lock_config=True,\n)\nflags.mark_flags_as_required(['config', 'workdir'])\n\n\ndef main(argv):\n  if len(argv) > 1:\n    raise app.UsageError('Too many command-line arguments.')\n\n  # Hide any GPUs from TensorFlow. Otherwise TF might reserve memory and make\n  # it unavailable to JAX.\n  tf.config.experimental.set_visible_devices([], 'GPU')\n\n  logging.info('JAX process: %d / %d', jax.process_index(), jax.process_count())\n  logging.info('JAX local devices: %r', jax.local_devices())\n\n  # Add a note so that we can tell which task is which JAX host.\n  # (Depending on the platform task 0 is not guaranteed to be host 0)\n  platform.work_unit().set_task_status(\n      f'process_index: {jax.process_index()}, '\n      f'process_count: {jax.process_count()}'\n  )\n  platform.work_unit().create_artifact(\n      platform.ArtifactType.DIRECTORY, FLAGS.workdir, 'workdir'\n  )\n\n  train.train_and_evaluate(FLAGS.config, FLAGS.workdir)\n\n\nif __name__ == '__main__':\n  jax.config.config_with_absl()\n  app.run(main)\n"
  },
  {
    "path": "examples/wmt/models.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Transformer-based machine translation model.\"\"\"\n\n# pylint: disable=attribute-defined-outside-init,g-bare-generic\n# See issue #620.\n# pytype: disable=wrong-arg-count\n# pytype: disable=wrong-keyword-args\n# pytype: disable=attribute-error\n\nfrom collections.abc import Callable\nfrom typing import Any\n\nfrom flax import linen as nn\nfrom flax import struct\nfrom jax import lax\nimport jax.numpy as jnp\nimport numpy as np\n\n\n@struct.dataclass\nclass TransformerConfig:\n  \"\"\"Global hyperparameters used to minimize obnoxious kwarg plumbing.\"\"\"\n\n  vocab_size: int\n  output_vocab_size: int\n  share_embeddings: bool = False\n  logits_via_embedding: bool = False\n  dtype: Any = jnp.float32\n  emb_dim: int = 512\n  num_heads: int = 8\n  num_layers: int = 6\n  qkv_dim: int = 512\n  mlp_dim: int = 2048\n  max_len: int = 2048\n  dropout_rate: float = 0.1\n  attention_dropout_rate: float = 0.1\n  deterministic: bool = False\n  decode: bool = False\n  kernel_init: Callable = nn.initializers.xavier_uniform()\n  bias_init: Callable = nn.initializers.normal(stddev=1e-6)\n  posemb_init: Callable | None = None\n\n\ndef shift_right(x, axis=1):\n  \"\"\"Shift the input to the right by padding on axis 1.\"\"\"\n  pad_widths = [(0, 0)] * len(x.shape)\n  pad_widths[axis] = (1, 0)\n  padded = jnp.pad(\n      x, pad_widths, mode='constant', constant_values=x.dtype.type(0)\n  )\n  return padded[:, :-1]\n\n\ndef sinusoidal_init(max_len=2048, min_scale=1.0, max_scale=10000.0):\n  \"\"\"1D Sinusoidal Position Embedding Initializer.\n\n  Args:\n      max_len: maximum possible length for the input.\n      min_scale: float: minimum frequency-scale in sine grating.\n      max_scale: float: maximum frequency-scale in sine grating.\n\n  Returns:\n      output: init function returning `(1, max_len, d_feature)`\n  \"\"\"\n\n  def init(key, shape, dtype=np.float32):\n    \"\"\"Sinusoidal init.\"\"\"\n    del key, dtype\n    d_feature = shape[-1]\n    pe = np.zeros((max_len, d_feature), dtype=np.float32)\n    position = np.arange(0, max_len)[:, np.newaxis]\n    scale_factor = -np.log(max_scale / min_scale) / (d_feature // 2 - 1)\n    div_term = min_scale * np.exp(np.arange(0, d_feature // 2) * scale_factor)\n    pe[:, : d_feature // 2] = np.sin(position * div_term)\n    pe[:, d_feature // 2 : 2 * (d_feature // 2)] = np.cos(position * div_term)\n    pe = pe[np.newaxis, :, :]  # [1, max_len, d_feature]\n    return jnp.array(pe)\n\n  return init\n\n\nclass AddPositionEmbs(nn.Module):\n  \"\"\"Adds (optionally learned) positional embeddings to the inputs.\n\n  Attributes:\n    config: TransformerConfig dataclass containing hyperparameters.\n    decode: whether to run in single-position autoregressive mode.\n  \"\"\"\n\n  config: TransformerConfig\n  decode: bool = False\n\n  @nn.compact\n  def __call__(self, inputs, inputs_positions=None):\n    \"\"\"Applies AddPositionEmbs module.\n\n    By default this layer uses a fixed sinusoidal embedding table. If a\n    learned position embedding is desired, pass an initializer to\n    posemb_init in the configuration.\n\n    Args:\n      inputs: input data.\n      inputs_positions: input position indices for packed sequences.\n\n    Returns:\n      output: `(bs, timesteps, in_dim)`\n    \"\"\"\n    config = self.config\n    # inputs.shape is (batch_size, seq_len, emb_dim)\n    assert inputs.ndim == 3, (\n        'Number of dimensions should be 3, but it is: %d' % inputs.ndim\n    )\n    length = inputs.shape[1]\n    pos_emb_shape = (1, config.max_len, inputs.shape[-1])\n    if config.posemb_init is None:\n      # Use a fixed (non-learned) sinusoidal position embedding.\n      pos_embedding = sinusoidal_init(max_len=config.max_len)(\n          None, pos_emb_shape, None\n      )\n    else:\n      pos_embedding = self.param(\n          'pos_embedding', config.posemb_init, pos_emb_shape\n      )\n    pe = pos_embedding[:, :length, :]\n\n    # We use a cache position index for tracking decoding position.\n    if self.decode:\n      is_initialized = self.has_variable('cache', 'cache_index')\n      cache_index = self.variable(\n          'cache', 'cache_index', lambda: jnp.array(0, dtype=jnp.uint32)\n      )\n      if is_initialized:\n        i = cache_index.value\n        cache_index.value = i + 1\n        _, _, df = pos_embedding.shape\n        pe = lax.dynamic_slice(pos_embedding, jnp.array((0, i, 0)), (1, 1, df))\n    if inputs_positions is None:\n      # normal unpacked case:\n      return inputs + pe\n    else:\n      # for packed data we need to use known position indices:\n      return inputs + jnp.take(pe[0], inputs_positions, axis=0)\n\n\nclass MlpBlock(nn.Module):\n  \"\"\"Transformer MLP / feed-forward block.\n\n  Attributes:\n    config: TransformerConfig dataclass containing hyperparameters.\n    out_dim: optionally specify out dimension.\n  \"\"\"\n\n  config: TransformerConfig\n  out_dim: int | None = None\n\n  @nn.compact\n  def __call__(self, inputs):\n    \"\"\"Applies Transformer MlpBlock module.\"\"\"\n    config = self.config\n    actual_out_dim = inputs.shape[-1] if self.out_dim is None else self.out_dim\n    x = nn.Dense(\n        config.mlp_dim,\n        dtype=config.dtype,\n        kernel_init=config.kernel_init,\n        bias_init=config.bias_init,\n    )(inputs)\n    x = nn.relu(x)\n    x = nn.Dropout(rate=config.dropout_rate)(\n        x, deterministic=config.deterministic\n    )\n    output = nn.Dense(\n        actual_out_dim,\n        dtype=config.dtype,\n        kernel_init=config.kernel_init,\n        bias_init=config.bias_init,\n    )(x)\n    output = nn.Dropout(rate=config.dropout_rate)(\n        output, deterministic=config.deterministic\n    )\n    return output\n\n\nclass Encoder1DBlock(nn.Module):\n  \"\"\"Transformer encoder layer.\n\n  Attributes:\n    config: TransformerConfig dataclass containing hyperparameters.\n  \"\"\"\n\n  config: TransformerConfig\n\n  @nn.compact\n  def __call__(self, inputs, encoder_mask=None):\n    \"\"\"Applies Encoder1DBlock module.\n\n    Args:\n      inputs: input data.\n      encoder_mask: encoder self-attention mask.\n\n    Returns:\n      output after transformer encoder block.\n    \"\"\"\n    config = self.config\n\n    # Attention block.\n    assert inputs.ndim == 3\n    x = nn.LayerNorm(dtype=config.dtype)(inputs)\n    x = nn.MultiHeadDotProductAttention(\n        num_heads=config.num_heads,\n        dtype=config.dtype,\n        qkv_features=config.qkv_dim,\n        kernel_init=config.kernel_init,\n        bias_init=config.bias_init,\n        use_bias=False,\n        broadcast_dropout=False,\n        dropout_rate=config.attention_dropout_rate,\n        deterministic=config.deterministic,\n    )(x, mask=encoder_mask)\n\n    x = nn.Dropout(rate=config.dropout_rate)(\n        x, deterministic=config.deterministic\n    )\n    x = x + inputs\n\n    # MLP block.\n    y = nn.LayerNorm(dtype=config.dtype)(x)\n    y = MlpBlock(config=config)(y)\n\n    return x + y\n\n\nclass EncoderDecoder1DBlock(nn.Module):\n  \"\"\"Transformer encoder-decoder layer.\n\n  Attributes:\n    config: TransformerConfig dataclass containing hyperparameters.\n  \"\"\"\n\n  config: TransformerConfig\n\n  @nn.compact\n  def __call__(\n      self, targets, encoded, decoder_mask=None, encoder_decoder_mask=None\n  ):\n    \"\"\"Applies EncoderDecoder1DBlock module.\n\n    Args:\n      targets: input data for decoder\n      encoded: input data from encoder\n      decoder_mask: decoder self-attention mask.\n      encoder_decoder_mask: encoder-decoder attention mask.\n\n    Returns:\n      output after transformer encoder-decoder block.\n    \"\"\"\n    config = self.config\n\n    # Decoder block.\n    assert targets.ndim == 3\n    x = nn.LayerNorm(dtype=config.dtype)(targets)\n    x = nn.MultiHeadDotProductAttention(\n        num_heads=config.num_heads,\n        dtype=config.dtype,\n        qkv_features=config.qkv_dim,\n        kernel_init=config.kernel_init,\n        bias_init=config.bias_init,\n        use_bias=False,\n        broadcast_dropout=False,\n        dropout_rate=config.attention_dropout_rate,\n        deterministic=config.deterministic,\n        decode=config.decode,\n    )(x, mask=decoder_mask)\n    x = nn.Dropout(rate=config.dropout_rate)(\n        x, deterministic=config.deterministic\n    )\n    x = x + targets\n\n    # Encoder-Decoder block.\n    y = nn.LayerNorm(dtype=config.dtype)(x)\n    y = nn.MultiHeadDotProductAttention(\n        num_heads=config.num_heads,\n        dtype=config.dtype,\n        qkv_features=config.qkv_dim,\n        kernel_init=config.kernel_init,\n        bias_init=config.bias_init,\n        use_bias=False,\n        broadcast_dropout=False,\n        dropout_rate=config.attention_dropout_rate,\n        deterministic=config.deterministic,\n    )(y, encoded, mask=encoder_decoder_mask)\n\n    y = nn.Dropout(rate=config.dropout_rate)(\n        y, deterministic=config.deterministic\n    )\n    y = y + x\n\n    # MLP block.\n    z = nn.LayerNorm(dtype=config.dtype)(y)\n    z = MlpBlock(config=config)(z)\n\n    return y + z\n\n\nclass Encoder(nn.Module):\n  \"\"\"Transformer Model Encoder for sequence to sequence translation.\n\n  Attributes:\n    config: TransformerConfig dataclass containing hyperparameters.\n    shared_embedding: a shared embedding layer to use.\n  \"\"\"\n\n  config: TransformerConfig\n  shared_embedding: Any = None\n\n  @nn.compact\n  def __call__(self, inputs, inputs_positions=None, encoder_mask=None):\n    \"\"\"Applies Transformer model on the inputs.\n\n    Args:\n      inputs: input data\n      inputs_positions: input subsequence positions for packed examples.\n      encoder_mask: decoder self-attention mask.\n\n    Returns:\n      output of a transformer encoder.\n    \"\"\"\n    config = self.config\n    assert inputs.ndim == 2  # (batch, len)\n\n    # Input Embedding\n    if self.shared_embedding is None:\n      input_embed = nn.Embed(\n          num_embeddings=config.vocab_size,\n          features=config.emb_dim,\n          embedding_init=nn.initializers.normal(stddev=1.0),\n      )\n    else:\n      input_embed = self.shared_embedding\n    x = inputs.astype('int32')\n    x = input_embed(x)\n    x = AddPositionEmbs(config=config, decode=False, name='posembed_input')(\n        x, inputs_positions=inputs_positions\n    )\n    x = nn.Dropout(rate=config.dropout_rate)(\n        x, deterministic=config.deterministic\n    )\n\n    x = x.astype(config.dtype)\n\n    # Input Encoder\n    for lyr in range(config.num_layers):\n      x = Encoder1DBlock(config=config, name=f'encoderblock_{lyr}')(\n          x, encoder_mask\n      )\n\n    encoded = nn.LayerNorm(dtype=config.dtype, name='encoder_norm')(x)\n\n    return encoded\n\n\nclass Decoder(nn.Module):\n  \"\"\"Transformer Model Decoder for sequence to sequence translation.\n\n  Attributes:\n    config: TransformerConfig dataclass containing hyperparameters.\n    shared_embedding: a shared embedding layer to use.\n  \"\"\"\n\n  config: TransformerConfig\n  shared_embedding: Any = None\n\n  @nn.compact\n  def __call__(\n      self,\n      encoded,\n      targets,\n      targets_positions=None,\n      decoder_mask=None,\n      encoder_decoder_mask=None,\n  ):\n    \"\"\"Applies Transformer model on the inputs.\n\n    Args:\n      encoded: encoded input data from encoder.\n      targets: target inputs.\n      targets_positions: input subsequence positions for packed examples.\n      decoder_mask: decoder self-attention mask.\n      encoder_decoder_mask: encoder-decoder attention mask.\n\n    Returns:\n      output of a transformer decoder.\n    \"\"\"\n    config = self.config\n\n    assert encoded.ndim == 3  # (batch, len, depth)\n    assert targets.ndim == 2  # (batch, len)\n\n    # Target Embedding\n    if self.shared_embedding is None:\n      output_embed = nn.Embed(\n          num_embeddings=config.output_vocab_size,\n          features=config.emb_dim,\n          embedding_init=nn.initializers.normal(stddev=1.0),\n      )\n    else:\n      output_embed = self.shared_embedding\n\n    y = targets.astype('int32')\n    if not config.decode:\n      y = shift_right(y)\n    y = output_embed(y)\n    y = AddPositionEmbs(\n        config=config, decode=config.decode, name='posembed_output'\n    )(y, inputs_positions=targets_positions)\n    y = nn.Dropout(rate=config.dropout_rate)(\n        y, deterministic=config.deterministic\n    )\n\n    y = y.astype(config.dtype)\n\n    # Target-Input Decoder\n    for lyr in range(config.num_layers):\n      y = EncoderDecoder1DBlock(\n          config=config, name=f'encoderdecoderblock_{lyr}'\n      )(\n          y,\n          encoded,\n          decoder_mask=decoder_mask,\n          encoder_decoder_mask=encoder_decoder_mask,\n      )\n    y = nn.LayerNorm(dtype=config.dtype, name='encoderdecoder_norm')(y)\n\n    # Decoded Logits\n    if config.logits_via_embedding:\n      # Use the transpose of embedding matrix for logit transform.\n      logits = output_embed.attend(y.astype(jnp.float32))\n      # Correctly normalize pre-softmax logits for this shared case.\n      logits = logits / jnp.sqrt(y.shape[-1])\n    else:\n      logits = nn.Dense(\n          config.output_vocab_size,\n          dtype=config.dtype,\n          kernel_init=config.kernel_init,\n          bias_init=config.bias_init,\n          name='logitdense',\n      )(y)\n    return logits\n\n\nclass Transformer(nn.Module):\n  \"\"\"Transformer Model for sequence to sequence translation.\n\n  Attributes:\n    config: TransformerConfig dataclass containing hyperparameters.\n  \"\"\"\n\n  config: TransformerConfig\n\n  def setup(self):\n    config = self.config\n\n    if config.share_embeddings:\n      if config.output_vocab_size is not None:\n        assert (\n            config.output_vocab_size == config.vocab_size\n        ), \"can't share embedding with different vocab sizes.\"\n      self.shared_embedding = nn.Embed(\n          num_embeddings=config.vocab_size,\n          features=config.emb_dim,\n          embedding_init=nn.initializers.normal(stddev=1.0),\n      )\n    else:\n      self.shared_embedding = None\n\n    self.encoder = Encoder(\n        config=config, shared_embedding=self.shared_embedding\n    )\n    self.decoder = Decoder(\n        config=config, shared_embedding=self.shared_embedding\n    )\n\n  def encode(self, inputs, inputs_positions=None, inputs_segmentation=None):\n    \"\"\"Applies Transformer encoder-branch on the inputs.\n\n    Args:\n      inputs: input data.\n      inputs_positions: input subsequence positions for packed examples.\n      inputs_segmentation: input segmentation info for packed examples.\n\n    Returns:\n      encoded feature array from the transformer encoder.\n    \"\"\"\n    config = self.config\n    # Make padding attention mask.\n    encoder_mask = nn.make_attention_mask(\n        inputs > 0, inputs > 0, dtype=config.dtype\n    )\n    # Add segmentation block-diagonal attention mask if using segmented data.\n    if inputs_segmentation is not None:\n      encoder_mask = nn.combine_masks(\n          encoder_mask,\n          nn.make_attention_mask(\n              inputs_segmentation,\n              inputs_segmentation,\n              jnp.equal,\n              dtype=config.dtype,\n          ),\n      )\n    return self.encoder(\n        inputs, inputs_positions=inputs_positions, encoder_mask=encoder_mask\n    )\n\n  def decode(\n      self,\n      encoded,\n      inputs,  # only needed for masks\n      targets,\n      targets_positions=None,\n      inputs_segmentation=None,\n      targets_segmentation=None,\n  ):\n    \"\"\"Applies Transformer decoder-branch on encoded-input and target.\n\n    Args:\n      encoded: encoded input data from encoder.\n      inputs: input data (only needed for masking).\n      targets: target data.\n      targets_positions: target subsequence positions for packed examples.\n      inputs_segmentation: input segmentation info for packed examples.\n      targets_segmentation: target segmentation info for packed examples.\n\n    Returns:\n      logits array from transformer decoder.\n    \"\"\"\n    config = self.config\n\n    # Make padding attention masks.\n    if config.decode:\n      # for fast autoregressive decoding only a special encoder-decoder mask is\n      # used\n      decoder_mask = None\n      encoder_decoder_mask = nn.make_attention_mask(\n          jnp.ones_like(targets) > 0, inputs > 0, dtype=config.dtype\n      )\n    else:\n      decoder_mask = nn.combine_masks(\n          nn.make_attention_mask(targets > 0, targets > 0, dtype=config.dtype),\n          nn.make_causal_mask(targets, dtype=config.dtype),\n      )\n      encoder_decoder_mask = nn.make_attention_mask(\n          targets > 0, inputs > 0, dtype=config.dtype\n      )\n\n    # Add segmentation block-diagonal attention masks if using segmented data.\n    if inputs_segmentation is not None:\n      decoder_mask = nn.combine_masks(\n          decoder_mask,\n          nn.make_attention_mask(\n              targets_segmentation,\n              targets_segmentation,\n              jnp.equal,\n              dtype=config.dtype,\n          ),\n      )\n      encoder_decoder_mask = nn.combine_masks(\n          encoder_decoder_mask,\n          nn.make_attention_mask(\n              targets_segmentation,\n              inputs_segmentation,\n              jnp.equal,\n              dtype=config.dtype,\n          ),\n      )\n    logits = self.decoder(\n        encoded,\n        targets,\n        targets_positions=targets_positions,\n        decoder_mask=decoder_mask,\n        encoder_decoder_mask=encoder_decoder_mask,\n    )\n    return logits.astype(self.config.dtype)\n\n  def __call__(\n      self,\n      inputs,\n      targets,\n      inputs_positions=None,\n      targets_positions=None,\n      inputs_segmentation=None,\n      targets_segmentation=None,\n  ):\n    \"\"\"Applies Transformer model on the inputs.\n\n    Args:\n      inputs: input data.\n      targets: target data.\n      inputs_positions: input subsequence positions for packed examples.\n      targets_positions: target subsequence positions for packed examples.\n      inputs_segmentation: input segmentation info for packed examples.\n      targets_segmentation: target segmentation info for packed examples.\n\n    Returns:\n      logits array from full transformer.\n    \"\"\"\n    encoded = self.encode(\n        inputs,\n        inputs_positions=inputs_positions,\n        inputs_segmentation=inputs_segmentation,\n    )\n\n    return self.decode(\n        encoded,\n        inputs,  # only used for masks\n        targets,\n        targets_positions=targets_positions,\n        inputs_segmentation=inputs_segmentation,\n        targets_segmentation=targets_segmentation,\n    )\n"
  },
  {
    "path": "examples/wmt/requirements.txt",
    "content": "absl-py==1.0.0\nclu==0.0.6\nflax==0.6.0\n-f https://storage.googleapis.com/jax-releases/libtpu_releases.html\n-f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html\njax[cuda11_cudnn805]>=0.3.16  # change to jax[tpu] if running on tpus\nml-collections==0.1.0\nnumpy==1.22.0\noptax==0.1.0\nsentencepiece==0.1.96\nsix==1.15.0\ntensorflow==2.11.1\ntensorflow-datasets==4.4.0\ntensorflow-text==2.8.1\n"
  },
  {
    "path": "examples/wmt/tokenizer.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Provides op for tokenizing a dataset.\"\"\"\n\nimport dataclasses\nimport os\nimport tempfile\nimport time\nfrom typing import Any, Dict, Tuple\nfrom collections.abc import Iterable\nimport sys\n\nfrom absl import logging\nimport jax\nimport tensorflow as tf\nif sys.version_info < (3, 13):\n  import tensorflow_text as tftxt\nfrom sentencepiece import SentencePieceTrainer\n\nFeatures = dict[str, tf.Tensor]\n\n\ndef _dump_chars_to_textfile(\n    dataset: tf.data.Dataset,\n    maxchars: int = int(1e7),\n    data_keys=('inputs', 'targets'),\n) -> tuple[str, int]:\n  \"\"\"Write part of a TFDS sentence dataset to lines in a text file.\n\n  Args:\n    dataset: tf.dataset containing string-data.\n    maxchars: int: approximate number of characters to save from dataset.\n    data_keys: Tuple[str]: what keys in dataset to dump from.\n\n  Returns:\n    name of temp file with dataset bytes, exact number of characters dumped.\n  \"\"\"\n  char_count = 0\n  ds_iter = dataset.as_numpy_iterator()\n  with tempfile.NamedTemporaryFile(\n      delete=False, prefix='/tmp/ds_chars'\n  ) as outfp:\n    while char_count < maxchars:\n      example = next(ds_iter)\n      for k in data_keys:\n        line = example[k] + b'\\n'\n        char_count += len(line)\n        outfp.write(line)\n  return outfp.name, char_count\n\n\ndef _train_sentencepiece(\n    dataset: tf.data.Dataset,\n    *,\n    vocab_size: int,\n    maxchars: int = int(1e7),\n    model_path: str,\n    model_type: str = 'unigram',\n    character_coverage: float = 1.0,\n    data_keys=('inputs', 'targets'),\n):\n  \"\"\"Train SentencePiece tokenizer from subset of tf dataset.\n\n  Args:\n    dataset: tf.dataset\n    vocab_size: int: size of vocab tokens to train.\n    maxchars: int: number of characters to use for sentencepiece training.\n    model_path: str: path of model file to save vocab model to.\n    model_type: str: type of sentencepiece vocab to train.\n    character_coverage: amount of characters covered by the model, good defaults\n      are 0.9995 for languages with rich character set like Japanese or Chinese\n      and 1.0 for other languages with small character set.\n    data_keys: Tuple[str]: keys of dataset to use for training.\n\n  Returns:\n    path to the trained sentencepiece vocabulary model.\n  \"\"\"\n  if model_path.startswith('gs://'):\n    abs_model_path = model_path\n  else:\n    abs_model_path = os.path.abspath(os.path.expanduser(model_path))\n  fname, _ = _dump_chars_to_textfile(\n      dataset, maxchars=maxchars, data_keys=data_keys\n  )\n  with tempfile.NamedTemporaryFile(\n      delete=False, prefix='/tmp/sp_tmp'\n  ) as model_fp:\n    pass  # we just want a prefix'd tmp-filename\n  argstr = ' '.join([\n      f'--input={fname}',\n      f'--vocab_size={vocab_size}',\n      f'--character_coverage={character_coverage}',\n      f'--model_prefix={model_fp.name}',\n      f'--model_type={model_type}',\n  ])\n  SentencePieceTrainer.Train(argstr)\n  if jax.process_index() == 0:\n    # Use an intermediate filename that is renamed to the target name to address\n    # create and fill delays.\n    copy_rename_path = abs_model_path + '.rntmp'\n    tf.io.gfile.copy(model_fp.name + '.model', copy_rename_path, overwrite=True)\n    tf.io.gfile.rename(copy_rename_path, abs_model_path, overwrite=True)\n    logging.info('copied %s to %s', model_fp.name + '.model', abs_model_path)\n  else:\n    while not tf.io.gfile.exists(abs_model_path):\n      time.sleep(1)\n    time.sleep(1)\n  return abs_model_path\n\n\ndef _load_sentencepiece_tokenizer(\n    model_path: str,\n    add_bos: bool = False,\n    add_eos: bool = True,\n    reverse: bool = False,\n):\n  \"\"\"Load a tf-text SentencePiece tokenizer from given model filepath.\"\"\"\n  with tf.io.gfile.GFile(model_path, 'rb') as model_fp:\n    sp_model = model_fp.read()\n  sp_tokenizer = tftxt.SentencepieceTokenizer(\n      model=sp_model, add_bos=add_bos, add_eos=add_eos, reverse=reverse\n  )\n  return sp_tokenizer\n\n\ndef load_or_train_tokenizer(\n    dataset: tf.data.Dataset,\n    *,\n    vocab_path: str,\n    vocab_size: int,\n    max_corpus_chars: int,\n    data_keys: tuple[str, str] = ('inputs', 'targets'),\n):\n  \"\"\"Loads the tokenizer at `vocab_path` or trains a one from `dataset`.\"\"\"\n  try:\n    return _load_sentencepiece_tokenizer(vocab_path)\n  except tf.errors.NotFoundError:\n    logging.info('SentencePiece vocab not found, building one from data.')\n    vocab_path = _train_sentencepiece(\n        dataset,\n        vocab_size=vocab_size,\n        maxchars=max_corpus_chars,\n        model_path=vocab_path,\n        data_keys=data_keys,\n    )\n    return _load_sentencepiece_tokenizer(vocab_path)\n\n\n@dataclasses.dataclass\nclass TokenizeOp:\n  sp_tokenizer: Any\n  data_keys: Iterable[str] = ('inputs', 'targets')\n\n  def __call__(self, features: Features) -> Features:\n    for k in self.data_keys:\n      features[k] = self.sp_tokenizer.tokenize(features[k])\n    return features\n"
  },
  {
    "path": "examples/wmt/train.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Machine Translation example.\n\nThis script trains a Transformer on a WMT dataset.\n\"\"\"\n\n# pytype: disable=wrong-arg-count\n# pytype: disable=attribute-error\n\nimport collections\nimport functools\nimport os\n\nfrom absl import logging\nfrom clu import metric_writers\nfrom clu import periodic_actions\nfrom flax import jax_utils\nfrom flax import linen as nn\nfrom flax.training import checkpoints\nfrom flax.training import common_utils\nfrom flax.training import dynamic_scale as dynamic_scale_lib\nfrom flax.training import train_state\nimport jax\nimport jax.numpy as jnp\nimport ml_collections\nimport numpy as np\nimport optax\nimport orbax.checkpoint as ocp\nimport tensorflow as tf\n\nimport bleu\nimport decode\nimport input_pipeline\nimport models\n\n\nclass TrainState(train_state.TrainState):\n  dynamic_scale: dynamic_scale_lib.DynamicScale\n\n\ndef rsqrt_schedule(\n    init_value: float,\n    shift: int = 0,\n):\n  \"\"\"Applies a reverse square-root schedule.\n\n  The reverse square root schedule is simply `lr = init_value / sqrt(step)`.\n\n  Args:\n    init_value: Base learning rate (before applying the rsqrt schedule).\n    shift: How many steps the rsqrt should be shifted. Shifting the rsqrt\n      schedule makes it less steep in the beginning (close to 0).\n\n  Returns:\n    A schedule `count -> learning_rate`.\n  \"\"\"\n\n  def schedule(count):\n    return init_value * (count + shift) ** -0.5 * shift**0.5\n\n  return schedule\n\n\ndef create_learning_rate_schedule(learning_rate: float, warmup_steps: int):\n  \"\"\"Creates a rsqrt schedule with linear warmup.\"\"\"\n  return optax.join_schedules(\n      [\n          optax.linear_schedule(\n              init_value=0,\n              end_value=learning_rate,\n              transition_steps=warmup_steps,\n          ),\n          rsqrt_schedule(init_value=learning_rate, shift=warmup_steps),\n      ],\n      boundaries=[warmup_steps],\n  )\n\n\ndef compute_weighted_cross_entropy(\n    logits, targets, weights=None, label_smoothing=0.0\n):\n  \"\"\"Compute weighted cross entropy and entropy for log probs and targets.\n\n  Args:\n   logits: [batch, length, num_classes] float array.\n   targets: categorical targets [batch, length] int array.\n   weights: None or array of shape [batch, length].\n   label_smoothing: label smoothing constant, used to determine the on and off\n     values.\n\n  Returns:\n    Tuple of scalar loss and batch normalizing factor.\n  \"\"\"\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        \"Incorrect shapes. Got shape %s logits and %s targets\"\n        % (str(logits.shape), str(targets.shape))\n    )\n  vocab_size = logits.shape[-1]\n  confidence = 1.0 - label_smoothing\n  low_confidence = (1.0 - confidence) / (vocab_size - 1)\n  normalizing_constant = -(\n      confidence * jnp.log(confidence)\n      + (vocab_size - 1) * low_confidence * jnp.log(low_confidence + 1e-20)\n  )\n  soft_targets = common_utils.onehot(\n      targets, vocab_size, on_value=confidence, off_value=low_confidence\n  )\n\n  loss = -jnp.sum(soft_targets * nn.log_softmax(logits), axis=-1)\n  loss = loss - normalizing_constant\n\n  normalizing_factor = np.prod(targets.shape)\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_weighted_accuracy(logits, targets, weights=None):\n  \"\"\"Compute weighted accuracy for log probs and targets.\n\n  Args:\n   logits: [batch, length, num_classes] float array.\n   targets: categorical targets [batch, length] int array.\n   weights: None or array of shape [batch, length]\n\n  Returns:\n    Tuple of scalar loss and batch normalizing factor.\n  \"\"\"\n  if logits.ndim != targets.ndim + 1:\n    raise ValueError(\n        \"Incorrect shapes. Got shape %s logits and %s targets\"\n        % (str(logits.shape), str(targets.shape))\n    )\n  loss = jnp.equal(jnp.argmax(logits, axis=-1), targets)\n  normalizing_factor = np.prod(logits.shape[:-1])\n  if weights is not None:\n    loss = loss * weights\n    normalizing_factor = weights.sum()\n\n  return loss.sum(), normalizing_factor\n\n\ndef compute_metrics(logits, labels, weights, label_smoothing=0.0):\n  \"\"\"Compute summary metrics.\"\"\"\n  loss, weight_sum = compute_weighted_cross_entropy(\n      logits, labels, weights, label_smoothing\n  )\n  acc, _ = compute_weighted_accuracy(logits, labels, weights)\n  metrics = {\n      \"loss\": loss,\n      \"accuracy\": acc,\n      \"denominator\": weight_sum,\n  }\n  metrics = jax.lax.psum(metrics, axis_name=\"batch\")\n  return metrics\n\n\n# Primary training / eval / decode step functions.\n# -----------------------------------------------------------------------------\n\n\ndef train_step(\n    state,\n    batch,\n    config,\n    learning_rate_fn,\n    label_smoothing=0.0,\n    dropout_rng=None,\n):\n  \"\"\"Perform a single training step.\"\"\"\n  # X_position and X_segmentation are needed only when using \"packed examples\"\n  # where multiple sequences are packed into the same example with this\n  # metadata.\n  # if such features are not present they are ignored and the example is treated\n  # like a normal, unpacked sequence example.\n  train_keys = [\n      \"inputs\",\n      \"targets\",\n      \"inputs_position\",\n      \"targets_position\",\n      \"inputs_segmentation\",\n      \"targets_segmentation\",\n  ]\n  (\n      inputs,\n      targets,\n      inputs_positions,\n      targets_positions,\n      inputs_segmentation,\n      targets_segmentation,\n  ) = (batch.get(k, None) for k in train_keys)\n\n  weights = jnp.where(targets > 0, 1, 0).astype(jnp.float32)\n\n  dropout_rng = jax.random.fold_in(dropout_rng, state.step)\n\n  def loss_fn(params):\n    \"\"\"loss function used for training.\"\"\"\n    logits = models.Transformer(config).apply(\n        {\"params\": params},\n        inputs,\n        targets,\n        inputs_positions=inputs_positions,\n        targets_positions=targets_positions,\n        inputs_segmentation=inputs_segmentation,\n        targets_segmentation=targets_segmentation,\n        rngs={\"dropout\": dropout_rng},\n    )\n\n    loss, weight_sum = compute_weighted_cross_entropy(\n        logits, targets, weights, label_smoothing\n    )\n    mean_loss = loss / weight_sum\n    return mean_loss, logits\n\n  step = state.step\n\n  if state.dynamic_scale:\n    # dynamic scale takes care of averaging gradients across replicas\n    grad_fn = state.dynamic_scale.value_and_grad(\n        loss_fn, has_aux=True, axis_name=\"batch\"\n    )\n    dynamic_scale, is_fin, (_, logits), grads = grad_fn(state.params)\n    state = state.replace(dynamic_scale=dynamic_scale)\n  else:\n    grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n    (_, logits), grads = grad_fn(state.params)\n    grads = jax.lax.pmean(grads, axis_name=\"batch\")\n\n  new_state = state.apply_gradients(grads=grads)\n  metrics = compute_metrics(logits, targets, weights)\n  metrics[\"learning_rate\"] = learning_rate_fn(step)\n\n  if state.dynamic_scale:\n    # if is_fin == False the gradients contain Inf/NaNs and optimizer state and\n    # params should be restored (= skip this step).\n    select_fn = functools.partial(jnp.where, is_fin)  # pylint: disable=undefined-variable\n    new_state = new_state.replace(\n        opt_state=jax.tree_util.tree_map(\n            select_fn, new_state.opt_state, state.opt_state\n        ),\n        params=jax.tree_util.tree_map(\n            select_fn, new_state.params, state.params\n        ),\n    )\n    metrics[\"loss_scale\"] = dynamic_scale.scale * metrics[\"denominator\"]  # pylint: disable=undefined-variable\n\n  return new_state, metrics\n\n\ndef eval_step(params, batch, config, label_smoothing=0.0):\n  \"\"\"Calculate evaluation metrics on a batch.\"\"\"\n  inputs, targets = batch[\"inputs\"], batch[\"targets\"]\n  weights = jnp.where(targets > 0, 1.0, 0.0)\n  logits = models.Transformer(config).apply({\"params\": params}, inputs, targets)\n\n  return compute_metrics(logits, targets, weights, label_smoothing)\n\n\ndef initialize_cache(inputs, max_decode_len, config):\n  \"\"\"Initialize a cache for a given input shape and max decode length.\"\"\"\n  target_shape = (inputs.shape[0], max_decode_len) + inputs.shape[2:]\n  initial_variables = models.Transformer(config).init(\n      jax.random.key(0),\n      jnp.ones(inputs.shape, config.dtype),\n      jnp.ones(target_shape, config.dtype),\n  )\n  return initial_variables[\"cache\"]\n\n\ndef predict_step(\n    inputs, params, cache, eos_id, max_decode_len, config, beam_size=4\n):\n  \"\"\"Predict translation with fast decoding beam search on a batch.\"\"\"\n  # Prepare transformer fast-decoder call for beam search: for beam search, we\n  # need to set up our decoder model to handle a batch size equal to\n  # batch_size * beam_size, where each batch item's data is expanded in-place\n  # rather than tiled.\n  # i.e. if we denote each batch element subtensor as el[n]:\n  # [el0, el1, el2] --> beamsize=2 --> [el0,el0,el1,el1,el2,el2]\n  encoded_inputs = decode.flat_batch_beam_expand(\n      models.Transformer(config).apply(\n          {\"params\": params}, inputs, method=models.Transformer.encode\n      ),\n      beam_size,\n  )\n  raw_inputs = decode.flat_batch_beam_expand(inputs, beam_size)\n\n  def tokens_ids_to_logits(flat_ids, flat_cache):\n    \"\"\"Token slice to logits from decoder model.\"\"\"\n    # --> [batch * beam, 1, vocab]\n    flat_logits, new_vars = models.Transformer(config).apply(\n        {\"params\": params, \"cache\": flat_cache},\n        encoded_inputs,\n        raw_inputs,  # only needed for input padding mask\n        flat_ids,\n        mutable=[\"cache\"],\n        method=models.Transformer.decode,\n    )\n    new_flat_cache = new_vars[\"cache\"]\n    # Remove singleton sequence-length dimension:\n    # [batch * beam, 1, vocab] --> [batch * beam, vocab]\n    flat_logits = flat_logits.squeeze(axis=1)\n    return flat_logits, new_flat_cache\n\n  # Using the above-defined single-step decoder function, run a\n  # beam search over possible sequences given input encoding.\n  beam_seqs, _ = decode.beam_search(\n      inputs,\n      cache,\n      tokens_ids_to_logits,\n      beam_size=beam_size,\n      alpha=0.6,\n      eos_id=eos_id,\n      max_decode_len=max_decode_len,\n  )\n\n  # Beam search returns [n_batch, n_beam, n_length + 1] with beam dimension\n  # sorted in increasing order of log-probability.\n  # Return the highest scoring beam sequence, drop first dummy 0 token.\n  return beam_seqs[:, -1, 1:]\n\n\n# Utils for prediction and BLEU calculation\n# -----------------------------------------------------------------------------\n\n\ndef pad_examples(x, desired_batch_size):\n  \"\"\"Expand batch to desired size by repeating last slice.\"\"\"\n  batch_pad = desired_batch_size - x.shape[0]\n  return np.concatenate([x, np.tile(x[-1], (batch_pad, 1))], axis=0)\n\n\ndef per_host_sum_pmap(in_tree):\n  \"\"\"Execute psum on in_tree\"s leaves over one device per host.\"\"\"\n  host2devices = collections.defaultdict(list)\n  for d in jax.devices():\n    host2devices[d.process_index].append(d)\n  devices = [host2devices[k][0] for k in host2devices]\n  host_psum = jax.pmap(lambda x: jax.lax.psum(x, \"i\"), \"i\", devices=devices)\n\n  def pre_pmap(xs):\n    return jax.tree_util.tree_map(\n        lambda x: jnp.broadcast_to(x, (1,) + x.shape), xs\n    )\n\n  def post_pmap(xs):\n    # Avoid degraded performance under the new jax.pmap. See\n    # https://docs.jax.dev/en/latest/migrate_pmap.html#int-indexing-into-sharded-arrays.\n    return jax.tree_util.tree_map(\n        lambda x: x.addressable_shards[0].data.squeeze(0), xs\n    )\n\n  return post_pmap(host_psum(pre_pmap(in_tree)))\n\n\ndef tohost(x):\n  \"\"\"Collect batches from all devices to host and flatten batch dimensions.\"\"\"\n  n_device, n_batch, *remaining_dims = x.shape\n  return np.array(x).reshape((n_device * n_batch,) + tuple(remaining_dims))\n\n\ndef evaluate(\n    *, p_eval_step, params, eval_ds: tf.data.Dataset, num_eval_steps: int\n):\n  \"\"\"Evaluate the params an return a dictionary with the metrics.\"\"\"\n  logging.info(\"Gathering evaluation metrics.\")\n  eval_metrics = []\n  eval_iter = iter(eval_ds)  # pytype: disable=wrong-arg-types\n  for _, eval_batch in zip(range(num_eval_steps), eval_iter):\n    eval_batch = jax.tree_util.tree_map(lambda x: x._numpy(), eval_batch)  # pylint: disable=protected-access\n    eval_batch = common_utils.shard(eval_batch)\n    metrics = p_eval_step(params, eval_batch)\n    eval_metrics.append(metrics)\n  eval_metrics = common_utils.get_metrics(eval_metrics)\n  eval_metrics_sums = jax.tree_util.tree_map(jnp.sum, eval_metrics)\n  eval_denominator = eval_metrics_sums.pop(\"denominator\")\n  eval_summary = jax.tree_util.tree_map(\n      lambda x: x / eval_denominator,  # pylint: disable=cell-var-from-loop\n      eval_metrics_sums,\n  )\n  return eval_summary\n\n\ndef translate_and_calculate_bleu(\n    *,\n    p_pred_step,\n    p_init_cache,\n    params,\n    predict_ds: tf.data.Dataset,\n    decode_tokens,\n    max_predict_length: int,\n):\n  \"\"\"Translates the `predict_ds` and calculates the BLEU score.\"\"\"\n  n_devices = jax.local_device_count()\n  logging.info(\"Translating evaluation dataset.\")\n  sources, references, predictions = [], [], []\n  for pred_batch in predict_ds:\n    pred_batch = jax.tree_util.tree_map(lambda x: x._numpy(), pred_batch)  # pylint: disable=protected-access\n    # Handle final odd-sized batch by padding instead of dropping it.\n    cur_pred_batch_size = pred_batch[\"inputs\"].shape[0]\n    if cur_pred_batch_size % n_devices:\n      padded_size = int(np.ceil(cur_pred_batch_size / n_devices) * n_devices)\n      pred_batch = jax.tree_util.tree_map(\n          lambda x: pad_examples(x, padded_size),  # pylint: disable=cell-var-from-loop\n          pred_batch,\n      )\n    pred_batch = common_utils.shard(pred_batch)\n    cache = p_init_cache(pred_batch[\"inputs\"])\n    predicted = p_pred_step(\n        pred_batch[\"inputs\"], params, cache, decode.EOS_ID, max_predict_length\n    )\n    predicted = tohost(predicted)\n    inputs = tohost(pred_batch[\"inputs\"])\n    targets = tohost(pred_batch[\"targets\"])\n    # Iterate through non-padding examples of batch.\n    for i, s in enumerate(predicted[:cur_pred_batch_size]):\n      sources.append(decode_tokens(inputs[i]))\n      references.append(decode_tokens(targets[i]))\n      predictions.append(decode_tokens(s))\n  logging.info(\n      \"Translation: %d predictions %d references %d sources.\",\n      len(predictions),\n      len(references),\n      len(sources),\n  )\n\n  # Calculate BLEU score for translated eval corpus against reference.\n  bleu_matches = bleu.bleu_partial(references, predictions)\n  all_bleu_matches = per_host_sum_pmap(bleu_matches)\n  bleu_score = bleu.complete_bleu(*all_bleu_matches)\n  # Save translation samples for tensorboard.\n  exemplars = \"\"\n  for n in np.random.choice(np.arange(len(predictions)), 8):\n    exemplars += f\"{sources[n]}\\n\\n{references[n]}\\n\\n{predictions[n]}\\n\\n\"\n  return exemplars, bleu_score\n\n\ndef preferred_dtype(config):\n  platform = jax.local_devices()[0].platform\n  if config.use_mixed_precision:\n    if platform == \"tpu\":\n      return jnp.bfloat16\n    elif platform == \"gpu\":\n      return jnp.float16\n  return jnp.float32\n\n\ndef train_and_evaluate(config: ml_collections.ConfigDict, workdir: str):\n  \"\"\"Runs a training and evaluation loop.\n\n  Args:\n    config: Configuration to use.\n    workdir: Working directory for checkpoints and TF summaries. If this\n      contains checkpoint training will be resumed from the latest checkpoint.\n  \"\"\"\n  tf.io.gfile.makedirs(workdir)\n\n  vocab_path = config.vocab_path\n  if vocab_path is None:\n    vocab_path = os.path.join(workdir, \"sentencepiece_model\")\n    config.vocab_path = vocab_path\n  tf.io.gfile.makedirs(os.path.split(vocab_path)[0])\n\n  # Load Dataset\n  # ---------------------------------------------------------------------------\n  logging.info(\"Initializing dataset.\")\n  train_ds, eval_ds, predict_ds, encoder = input_pipeline.get_wmt_datasets(\n      n_devices=jax.local_device_count(),\n      config=config,\n      reverse_translation=config.reverse_translation,\n      vocab_path=vocab_path,\n  )\n\n  train_iter = iter(train_ds)\n  vocab_size = int(encoder.vocab_size())\n  eos_id = decode.EOS_ID  # Default Sentencepiece EOS token.\n\n  def decode_tokens(toks):\n    valid_toks = toks[: np.argmax(toks == eos_id) + 1].astype(np.int32)\n    return encoder.detokenize(valid_toks).numpy().decode(\"utf-8\")\n\n  if config.num_predict_steps > 0:\n    predict_ds = predict_ds.take(config.num_predict_steps)\n\n  logging.info(\"Initializing model, optimizer, and step functions.\")\n\n  dtype = preferred_dtype(config)\n\n  # Build Model and Optimizer\n  # ---------------------------------------------------------------------------\n  train_config = models.TransformerConfig(\n      vocab_size=vocab_size,\n      output_vocab_size=vocab_size,\n      share_embeddings=config.share_embeddings,\n      logits_via_embedding=config.logits_via_embedding,\n      dtype=dtype,\n      emb_dim=config.emb_dim,\n      num_heads=config.num_heads,\n      num_layers=config.num_layers,\n      qkv_dim=config.qkv_dim,\n      mlp_dim=config.mlp_dim,\n      max_len=max(config.max_target_length, config.max_eval_target_length),\n      dropout_rate=config.dropout_rate,\n      attention_dropout_rate=config.attention_dropout_rate,\n      deterministic=False,\n      decode=False,\n      kernel_init=nn.initializers.xavier_uniform(),\n      bias_init=nn.initializers.normal(stddev=1e-6),\n  )\n  eval_config = train_config.replace(deterministic=True)\n  predict_config = train_config.replace(deterministic=True, decode=True)\n\n  start_step = 0\n  rng = jax.random.key(config.seed)\n  rng, init_rng = jax.random.split(rng)\n  input_shape = (config.per_device_batch_size, config.max_target_length)\n  target_shape = (config.per_device_batch_size, config.max_target_length)\n\n  m = models.Transformer(eval_config)\n  initial_variables = jax.jit(m.init)(\n      init_rng,\n      jnp.ones(input_shape, jnp.float32),\n      jnp.ones(target_shape, jnp.float32),\n  )\n\n  # Create train state with Adam optimizer and weight decay.\n  learning_rate_fn = create_learning_rate_schedule(\n      learning_rate=config.learning_rate, warmup_steps=config.warmup_steps\n  )\n  dynamic_scale = None\n  if dtype == jnp.float16:\n    dynamic_scale = dynamic_scale_lib.DynamicScale()\n  state = TrainState.create(\n      apply_fn=m.apply,\n      params=initial_variables[\"params\"],\n      tx=optax.adamw(\n          learning_rate=learning_rate_fn,\n          b1=0.9,\n          b2=0.98,\n          eps=1e-9,\n          weight_decay=config.weight_decay,\n      ),\n      dynamic_scale=dynamic_scale,\n  )\n\n  # We access model params only via state.params\n  del initial_variables\n\n  if config.restore_checkpoints:\n    # Restore unreplicated optimizer + model state from last checkpoint.\n    state = checkpoints.restore_checkpoint(workdir, state)\n    # Grab last step.\n    start_step = int(state.step)\n\n  writer = metric_writers.create_default_writer(\n      workdir, just_logging=jax.process_index() > 0\n  )\n  if start_step == 0:\n    writer.write_hparams(dict(config))\n\n  # Replicate state.\n  state = jax_utils.replicate(state)\n\n  # compile multidevice versions of train/eval/predict step and cache init fn.\n  p_train_step = jax.pmap(\n      functools.partial(\n          train_step,\n          config=train_config,\n          learning_rate_fn=learning_rate_fn,\n          label_smoothing=config.label_smoothing,\n      ),\n      axis_name=\"batch\",\n      donate_argnums=(0,),\n  )  # pytype: disable=wrong-arg-types\n  p_eval_step = jax.pmap(\n      functools.partial(eval_step, config=eval_config), axis_name=\"batch\"\n  )\n  p_init_cache = jax.pmap(\n      functools.partial(\n          initialize_cache,\n          max_decode_len=config.max_predict_length,\n          config=predict_config,\n      ),\n      axis_name=\"batch\",\n  )\n  p_pred_step = jax.pmap(\n      functools.partial(\n          predict_step, config=predict_config, beam_size=config.beam_size\n      ),\n      axis_name=\"batch\",\n      static_broadcasted_argnums=(3, 4),\n  )  # eos token, max_length are constant\n\n  # Main Train Loop\n  # ---------------------------------------------------------------------------\n\n  # We init the first set of dropout PRNG keys, but update it afterwards inside\n  # the main pmap\"d training update for performance.\n  dropout_rngs = jax.random.split(rng, jax.local_device_count())\n  del rng\n\n  logging.info(\"Starting training loop.\")\n  hooks = []\n  report_progress = periodic_actions.ReportProgress(\n      num_train_steps=config.num_train_steps, writer=writer\n  )\n  if jax.process_index() == 0:\n    hooks += [\n        report_progress,\n        periodic_actions.Profile(logdir=workdir, num_profile_steps=5),\n    ]\n  train_metrics = []\n  with metric_writers.ensure_flushes(writer):\n    for step in range(start_step, config.num_train_steps):\n      is_last_step = step == config.num_train_steps - 1\n\n      # Shard data to devices and do a training step.\n      with jax.profiler.StepTraceAnnotation(\"train\", step_num=step):\n        batch = common_utils.shard(\n            jax.tree_util.tree_map(np.asarray, next(train_iter))\n        )\n        state, metrics = p_train_step(state, batch, dropout_rng=dropout_rngs)\n        train_metrics.append(metrics)\n\n      # Quick indication that training is happening.\n      logging.log_first_n(logging.INFO, \"Finished training step %d.\", 5, step)\n      for h in hooks:\n        h(step)\n\n      # Periodic metric handling.\n      if step % config.eval_every_steps == 0 or is_last_step:\n        with report_progress.timed(\"training_metrics\"):\n          logging.info(\"Gathering training metrics.\")\n          train_metrics = common_utils.get_metrics(train_metrics)\n          lr = train_metrics.pop(\"learning_rate\").mean()\n          metrics_sums = jax.tree_util.tree_map(jnp.sum, train_metrics)\n          denominator = metrics_sums.pop(\"denominator\")\n          summary = jax.tree_util.tree_map(\n              lambda x: x / denominator, metrics_sums  # pylint: disable=cell-var-from-loop\n          )\n          summary[\"learning_rate\"] = lr\n          summary = {\"train_\" + k: v for k, v in summary.items()}\n          writer.write_scalars(step, summary)\n          train_metrics = []\n\n        with report_progress.timed(\"eval\"):\n          eval_results = evaluate(\n              p_eval_step=p_eval_step,\n              params=state.params,\n              eval_ds=eval_ds,\n              num_eval_steps=config.num_eval_steps,\n          )\n          writer.write_scalars(\n              step, {\"eval_\" + k: v for k, v in eval_results.items()}\n          )\n\n        with report_progress.timed(\"translate_and_bleu\"):\n          exemplars, bleu_score = translate_and_calculate_bleu(\n              p_pred_step=p_pred_step,\n              p_init_cache=p_init_cache,\n              params=state.params,\n              predict_ds=predict_ds,\n              decode_tokens=decode_tokens,\n              max_predict_length=config.max_predict_length,\n          )\n          writer.write_scalars(step, {\"bleu\": bleu_score})\n          writer.write_texts(step, {\"samples\": exemplars})\n\n      # Save a checkpoint on one host after every checkpoint_freq steps.\n      save_checkpoint = (\n          step % config.checkpoint_every_steps == 0 or is_last_step\n      )\n      if config.save_checkpoints and save_checkpoint:\n        logging.info(\"Saving checkpoint step %d.\", step)\n\n        # Orbax can not handle host local arrays from pmap.\n        replicated_state = jax.tree_util.tree_map(\n            ocp.utils.fully_replicated_host_local_array_to_global_array,\n            state,\n        )\n        with report_progress.timed(\"checkpoint\"):\n          checkpoints.save_checkpoint_multiprocess(\n              workdir, replicated_state, step\n          )\n"
  },
  {
    "path": "examples/wmt/train_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport pathlib\nimport tempfile\nimport sys\n\nfrom absl import logging\nfrom absl.testing import absltest\nimport jax\nimport tensorflow as tf\nimport tensorflow_datasets as tfds\n\nfrom configs import default\nimport train\n\n\njax.config.update('jax_disable_most_optimizations', True)\n\n\nclass TrainTest(absltest.TestCase):\n  \"\"\"Test cases for WMT library.\"\"\"\n\n  def setUp(self):\n    if sys.version_info >= (3, 13):\n      self.skipTest('Test (and tensorflow-text) does not suport Python 3.13+')\n    super().setUp()\n    tf.config.experimental.set_visible_devices([], 'GPU')\n\n  def test_train_and_evaluate(self):\n    config = default.get_config()\n    config.max_corpus_chars = 1000\n    config.vocab_size = 32\n    config.per_device_batch_size = 1\n    config.num_train_steps = 1\n    config.num_eval_steps = 1\n    config.num_predict_steps = 1\n\n    config.num_layers = 1\n    config.qkv_dim = 128\n    config.emb_dim = 128\n    config.mlp_dim = 512\n    config.num_heads = 2\n\n    config.max_target_length = 32\n    config.max_eval_target_length = 32\n    config.max_predict_length = 32\n\n    workdir = tempfile.mkdtemp()\n\n    # Go two directories up to the root of the flax directory.\n    flax_root_dir = pathlib.Path(__file__).parents[2]\n    data_dir = str(flax_root_dir) + '/.tfds/metadata'  # pylint: disable=unused-variable\n\n    with tfds.testing.mock_data(num_examples=128, data_dir=data_dir):\n      train.train_and_evaluate(config, workdir)\n    logging.info('workdir content: %s', tf.io.gfile.listdir(workdir))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "flax/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Flax API.\"\"\"\n\n# pylint: disable=g-import-not-at-top\n# pyformat: disable\n\nfrom flax import configurations\nconfig: configurations.Config = configurations.config\ndel configurations\n\nfrom flax import core\nfrom flax import jax_utils\nfrom flax import linen\nfrom flax import serialization\nfrom flax import traverse_util\n\nfrom flax import version\n__version__: str = version.__version__\ndel version\n\n# DO NOT REMOVE - Marker for internal deprecated API.\n\n# DO NOT REMOVE - Marker for internal logging.\n\n# pyformat: enable\n# pylint: enable=g-import-not-at-top\n"
  },
  {
    "path": "flax/configurations.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Global configuration flags for Flax.\"\"\"\n\nimport os\nfrom contextlib import contextmanager\nfrom typing import Any, Generic, NoReturn, TypeVar, overload\n\n_T = TypeVar('_T')\n\n\nclass Config:\n  flax_use_flaxlib: bool\n  flax_array_ref: bool\n  flax_pytree_module: bool\n  flax_max_repr_depth: int | None\n  flax_always_shard_variable: bool\n  flax_hijax_variable: bool\n  nnx_graph_mode: bool\n  nnx_graph_updates: bool\n  # See https://google.github.io/pytype/faq.html.\n  _HAS_DYNAMIC_ATTRIBUTES = True\n\n  def __init__(self):\n    self._values = {}\n\n  def _add_option(self, name, default):\n    if name in self._values:\n      raise RuntimeError(f'Config option {name} already defined')\n    self._values[name] = default\n\n  def _read(self, name):\n    try:\n      return self._values[name]\n    except KeyError:\n      raise LookupError(f'Unrecognized config option: {name}')\n\n  @overload\n  def update(self, name: str, value: Any, /) -> None:\n    ...\n\n  @overload\n  def update(self, holder: 'FlagHolder[_T]', value: _T, /) -> None:\n    ...\n\n  def update(self, name_or_holder, value, /):\n    \"\"\"Modify the value of a given flag.\n\n    Args:\n      name_or_holder: the name of the flag to modify or the corresponding\n        flag holder object.\n      value: new value to set.\n    \"\"\"\n    name = name_or_holder\n    if isinstance(name_or_holder, FlagHolder):\n      name = name_or_holder.name\n    if name not in self._values:\n      raise LookupError(f'Unrecognized config option: {name}')\n    self._values[name] = value\n\n  def __repr__(self):\n    values_repr = ', '.join(f'\\n  {k}={v!r}' for k, v in self._values.items())\n    return f'Config({values_repr}\\n)'\n\n  @contextmanager\n  def temp_flip_flag(self, var_name: str, var_value: bool):\n    \"\"\"Context manager to temporarily flip feature flags for test functions.\n\n    Args:\n      var_name: the config variable name (without the 'flax_' prefix)\n      var_value: the boolean value to set var_name to temporarily\n    \"\"\"\n    old_value = getattr(self, f'flax_{var_name}')\n    try:\n      self.update(f'flax_{var_name}', var_value)\n      yield\n    finally:\n      self.update(f'flax_{var_name}', old_value)\n\n\nconfig = Config()\n\n# Config parsing utils\n\n\nclass FlagHolder(Generic[_T]):\n  def __init__(self, name, help):\n    self.name = name\n    self.__name__ = name[4:] if name.startswith('flax_') else name\n    self.__doc__ = f'Flag holder for `{name}`.\\n\\n{help}'\n\n  def __bool__(self) -> NoReturn:\n    raise TypeError(\n      \"bool() not supported for instances of type '{0}' \"\n      \"(did you mean to use '{0}.value' instead?)\".format(type(self).__name__)\n    )\n\n  @property\n  def value(self) -> _T:\n    return config._read(self.name)\n\n\ndef bool_flag(name: str, *, default: bool, help: str) -> FlagHolder[bool]:\n  \"\"\"Set up a boolean flag.\n\n  Example::\n\n    enable_foo = bool_flag(\n        name='flax_enable_foo',\n        default=False,\n        help='Enable foo.',\n    )\n\n  Now the ``FLAX_ENABLE_FOO`` shell environment variable can be used to\n  control the process-level value of the flag, in addition to using e.g.\n  ``config.update(\"flax_enable_foo\", True)`` directly.\n\n  Args:\n    name: converted to lowercase to define the name of the flag. It is\n      converted to uppercase to define the corresponding shell environment\n      variable.\n    default: a default value for the flag.\n    help: used to populate the docstring of the returned flag holder object.\n\n  Returns:\n    A flag holder object for accessing the value of the flag.\n  \"\"\"\n  name = name.lower()\n  config._add_option(name, static_bool_env(name.upper(), default))\n  fh = FlagHolder[bool](name, help)\n  setattr(Config, name, property(lambda _: fh.value, doc=help))\n  return fh\n\n\ndef int_flag(name: str, *, default: int | None, help: str) -> FlagHolder[int]:\n  \"\"\"Set up an integer flag.\n\n  Example::\n\n    num_foo = int_flag(\n        name='flax_num_foo',\n        default=42,\n        help='Number of foo.',\n    )\n\n  Now the ``FLAX_NUM_FOO`` shell environment variable can be used to\n  control the process-level value of the flag, in addition to using e.g.\n  ``config.update(\"flax_num_foo\", 42)`` directly.\n\n  Args:\n    name: converted to lowercase to define the name of the flag. It is\n      converted to uppercase to define the corresponding shell environment\n      variable.\n    default: a default value for the flag.\n    help: used to populate the docstring of the returned flag holder object.\n\n  Returns:\n    A flag holder object for accessing the value of the flag.\n  \"\"\"\n  name = name.lower()\n  config._add_option(name, static_int_env(name.upper(), default))\n  fh = FlagHolder[int](name, help)\n  setattr(Config, name, property(lambda _: fh.value, doc=help))\n  return fh\n\n\ndef static_bool_env(varname: str, default: bool) -> bool:\n  \"\"\"Read an environment variable and interpret it as a boolean.\n\n  This is deprecated. Please use bool_flag() unless your flag\n  will be used in a static method and does not require runtime updates.\n\n  True values are (case insensitive): 'y', 'yes', 't', 'true', 'on', and '1';\n  false values are 'n', 'no', 'f', 'false', 'off', and '0'.\n  Args:\n    varname: the name of the variable\n    default: the default boolean value\n  Returns:\n    boolean return value derived from defaults and environment.\n  Raises: ValueError if the environment variable is anything else.\n  \"\"\"\n  val = os.getenv(varname, str(default))\n  val = val.lower()\n  if val in ('y', 'yes', 't', 'true', 'on', '1'):\n    return True\n  elif val in ('n', 'no', 'f', 'false', 'off', '0'):\n    return False\n  else:\n    raise ValueError(\n      f'invalid truth value {val!r} for environment {varname!r}'\n    )\n\n\ndef static_int_env(varname: str, default: int | None) -> int | None:\n  \"\"\"Read an environment variable and interpret it as an integer.\n\n  Args:\n    varname: the name of the variable\n    default: the default integer value\n  Returns:\n    integer return value derived from defaults and environment.\n  Raises: ValueError if the environment variable is not an integer.\n  \"\"\"\n  val = os.getenv(varname)\n  if val is None:\n    return default\n  try:\n    return int(val)\n  except ValueError:\n    raise ValueError(\n      f'invalid integer value {val!r} for environment {varname!r}'\n    ) from None\n\n\n# Flax Global Configuration Variables:\n\nflax_filter_frames = bool_flag(\n  name='flax_filter_frames',\n  default=True,\n  help='Whether to hide flax-internal stack frames from tracebacks.',\n)\n\nflax_profile = bool_flag(\n  name='flax_profile',\n  default=True,\n  help='Whether to run Module methods under jax.named_scope for profiles.',\n)\n\nflax_use_orbax_checkpointing = bool_flag(\n  name='flax_use_orbax_checkpointing',\n  default=True,\n  help='Whether to use Orbax to save checkpoints.',\n)\n\nflax_preserve_adopted_names = bool_flag(\n  name='flax_preserve_adopted_names',\n  default=False,\n  help=\"When adopting outside modules, don't clobber existing names.\",\n)\n\n# TODO(marcuschiam): remove this feature flag once regular dict migration is complete\nflax_return_frozendict = bool_flag(\n  name='flax_return_frozendict',\n  default=False,\n  help='Whether to return FrozenDicts when calling init or apply.',\n)\n\nflax_fix_rng = bool_flag(\n  name='flax_fix_rng_separator',\n  default=False,\n  help=(\n    'Whether to add separator characters when folding in static data into'\n    ' PRNG keys.'\n  ),\n)\n\nflax_use_flaxlib = bool_flag(\n  name='flax_use_flaxlib',\n  default=False,\n  help='Whether to use flaxlib for C++ acceleration.',\n)\nflax_array_ref = bool_flag(\n  name='flax_array_ref',\n  default=False,\n  help='Whether to use array refs.',\n)\nflax_pytree_module = bool_flag(\n  name='flax_pytree_module',\n  default=True,\n  help='Whether Modules are pytrees by default or not.',\n)\n\nflax_max_repr_depth = int_flag(\n  name='flax_max_repr_depth',\n  default=None,\n  help='Maximum depth of reprs for nested flax objects. Default is None (no limit).',\n)\n\nflax_always_shard_variable = bool_flag(\n  name='flax_always_shard_variable',\n  default=True,\n  help='Whether a `nnx.Variable` should always automatically be sharded if it contains sharding annotations.',\n)\nflax_hijax_variable = bool_flag(\n  name='flax_hijax_variable',\n  default=False,\n  help='Whether to enable HiJAX support for `nnx.Variable`.',\n)\nnnx_graph_mode = bool_flag(\n  name='nnx_graph_mode',\n  default=True,\n  help='Whether NNX APIs default to graph-mode (True) or tree-mode (False).',\n)\nnnx_graph_updates = bool_flag(\n  name='nnx_graph_updates',\n  default=True,\n  help='Whether graph-mode uses dynamic (True) or simple (False) graph traversal.',\n)"
  },
  {
    "path": "flax/core/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom .axes_scan import broadcast as broadcast\nfrom .frozen_dict import (\n    FrozenDict as FrozenDict,\n    copy as copy,\n    freeze as freeze,\n    pop as pop,\n    pretty_repr as pretty_repr,\n    unfreeze as unfreeze,\n)\nfrom .lift import (\n    custom_vjp as custom_vjp,\n    jit as jit,\n    jvp as jvp,\n    remat_scan as remat_scan,\n    remat as remat,\n    scan as scan,\n    vjp as vjp,\n    vmap as vmap,\n    while_loop as while_loop,\n)\nfrom .meta import (\n    AxisMetadata as AxisMetadata,\n    map_axis_meta as map_axis_meta,\n    unbox as unbox,\n)\nfrom .scope import (\n    DenyList as DenyList,\n    Scope as Scope,\n    apply as apply,\n    bind as bind,\n    init as init,\n    lazy_init as lazy_init,\n)\nfrom .tracers import (\n    check_trace_level as check_trace_level,\n    current_trace as current_trace,\n)\n\nfrom flax.typing import (\n    Array as Array,\n)\n"
  },
  {
    "path": "flax/core/axes_scan.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Wrapper around jax.lax.scan with in_axes/out_axes API.\"\"\"\nfrom collections.abc import Callable\nimport functools\nfrom typing import Any, Optional\n\nimport jax\nfrom jax import core\nfrom jax import lax\nfrom jax.extend import linear_util as lu\nfrom jax.interpreters import partial_eval as pe\nimport jax.numpy as jnp\nimport numpy as np\n\nScanAxis = Optional[int]\n\n\nclass _Broadcast:\n  pass\n\n\nbroadcast = _Broadcast()\n\n\ndef build_shaped_array(x, batch_dim: bool = False) -> core.ShapedArray:\n  \"\"\"Builds ShapedArray preserving as much information from x as possible.\"\"\"\n  shape = jnp.shape(x)\n  sharding = x.aval.sharding if hasattr(x, \"aval\") else None\n  if batch_dim:\n    shape = shape[1:]\n    if sharding is not None:\n      if sharding.spec[0] is not None:\n        raise ValueError(\n            \"Batch dimension in scan `xs` cannot be sharded.\"\n        )\n      sharding = sharding.update(\n          spec=jax.sharding.PartitionSpec(*sharding.spec[1:]))\n  return core.ShapedArray(\n      shape=shape,\n      dtype=jnp.result_type(x),\n      sharding=sharding,\n      **{k: getattr(x, k) for k in [\"weak_type\", \"manual_type\"]\n         if hasattr(x, k)},\n  )\n\n\ndef scan(\n    fn: Callable[..., Any],\n    in_axes: Any,\n    out_axes: Any,\n    length: int | None = None,\n    reverse: bool = False,\n    unroll: int = 1,\n    _split_transpose: bool = False,\n    check_constancy_invariants: bool = True,\n):\n  \"\"\"A wrapper around `jax.lax.scan` with in_axes/out_axes api.\n\n  Example::\n    def body_fn(b, c, x):\n      return b + 2, c + 1, 2 * x\n\n    loop = scan(body_fn, in_axes=0, out_axes=0)\n    broadcast_in = 1\n    carry = 2\n    xs = jnp.arange(3)\n    broadcast_out, carry, ys = loop(broadcast_in, carry, xs)\n    print(broadcast_out)  # prints: 3\n    print(carry)  # prints: 5\n    print(ys)  # prints: [0, 2, 4]\n\n\n  Args:\n    fn: the body function of the scan loop of the form\n      `(broadcast_in, carry, *args) -> (broadcast_out, carry, scan_out)`.\n      the broadcast argument allows for loop independent inputs/outputs to\n      be computed inside `fn`. `fn` will be called once to compute\n      `broadcast_out`. The actual loop will receive `broadcast_out` as the new\n      `broadcast_in`. This is useful for initializing values inside the loop.\n    in_axes: specifies the axis along which arguments are scanned.\n      Use `broadcast` to use the same value across iterations.\n    out_axes: specifies the axis along which outputs are concatenated.\n      Use `broadcast` if a return value should not be concatenated and\n      is independent of the loop body.\n    length: number of iterations. Only needs to be specified if there\n      is no scan axis from which it can be derived.\n    reverse: scan in reverse order from end to start.\n    unroll: how many scan iterations to unroll within a single\n      iteration of a loop (default: 1).\n    _split_transpose: An experimental feature to split the transpose of scan\n       into a scan and a map, backed by an experimental Jax lax.scan() feature.\n    check_constancy_invariants: If true, the scan will verify that the\n      broadcast constants are true loop invariants, and further supports\n      broadcast function (non-carry) outputs.  This requires an extra jax\n      tracing step however, so setting to false can reduce trace time on larger\n      models.\n  Returns:\n     the function that performs the scan of the form:\n     (broadcast_in, carry_in, *args) -> (broadcast_out, carry_out, scan_out).\n  \"\"\"\n\n  def transpose_to_front(ax, xs):\n    if ax is broadcast:\n      return ()\n    if ax == 0:\n      return xs\n\n    def trans(x):\n      perm = tuple(range(x.ndim))\n      perm = (ax,) + tuple(np.delete(perm, ax))\n      return jnp.transpose(x, perm)\n\n    return jax.tree_util.tree_map(trans, xs)\n\n  def transpose_from_front(ax, xs):\n    if ax is broadcast:\n      return ()\n    if ax == 0:\n      return xs\n\n    def trans(x):\n      if ax < 0:\n        pax = x.ndim + ax\n      else:\n        pax = ax\n      assert pax < x.ndim\n      perm = tuple(range(1, pax + 1)) + (0,) + tuple(range(pax + 1, x.ndim))\n      return jnp.transpose(x, perm)\n\n    return jax.tree_util.tree_map(trans, xs)\n\n  def scan_fn(broadcast_in, init, *args):\n    # Requires one extra tracing operation to test invariants:\n    # Verifies that broadcast constants are true loop invariants, and further\n    # supports broadcast function (non-carry) outputs.\n\n    xs = jax.tree_util.tree_map(transpose_to_front, in_axes, args)\n\n    def body_fn(c, xs, init_mode=False):\n      # inject constants\n      xs = jax.tree_util.tree_map(\n          lambda ax, arg, x: (arg if ax is broadcast else x), in_axes, args, xs\n      )\n      broadcast_out, c, ys = fn(broadcast_in, c, *xs)\n\n      if init_mode:\n        ys = jax.tree_util.tree_map(\n            lambda ax, y: (y if ax is broadcast else ()), out_axes, ys\n        )\n        return broadcast_out, ys\n      else:\n        ys = jax.tree_util.tree_map(\n            lambda ax, y: (() if ax is broadcast else y), out_axes, ys\n        )\n        return c, ys\n\n    broadcast_body = functools.partial(body_fn, init_mode=True)\n\n    init_flat, carry_tree = jax.tree.flatten(init)\n    xs_flat, scan_tree = jax.tree.flatten(xs)\n    carry_avals = [build_shaped_array(x) for x in init_flat]\n    scan_avals = [build_shaped_array(x, batch_dim=True) for x in  xs_flat]\n    in_avals = [*carry_avals, *scan_avals]\n    in_tree = jax.tree_util.treedef_tuple((carry_tree, scan_tree))\n    assert all(isinstance(a, core.AbstractValue) for a in in_avals), in_avals\n\n    debug_info = jax.api_util.debug_info(\"flax scan\", broadcast_body,\n                                         (in_tree,), {})\n    f_flat, out_tree = jax.api_util.flatten_fun_nokwargs(\n        lu.wrap_init(broadcast_body, debug_info=debug_info), in_tree\n    )\n    in_pvals = list(map(pe.PartialVal.unknown, in_avals))\n    _, out_pvals, _ = pe.trace_to_jaxpr_nounits(f_flat, in_pvals)\n\n    out_flat = []\n    for pv, const in out_pvals:\n      if pv is not None:\n        raise ValueError(\n            'broadcasted variable has a data dependency on the scan body.'\n        )\n      out_flat.append(const)\n    broadcast_in, constants_out = jax.tree_util.tree_unflatten(\n        out_tree(), out_flat\n    )\n\n    if jax.version.__version_info__ > (0, 4, 25):\n      c, ys = lax.scan(\n          body_fn, init, xs, length=length, reverse=reverse, unroll=unroll,\n          _split_transpose=_split_transpose\n      )\n    else:\n      c, ys = lax.scan(\n          body_fn, init, xs, length=length, reverse=reverse, unroll=unroll\n      )\n    ys = jax.tree_util.tree_map(transpose_from_front, out_axes, ys)\n    ys = jax.tree_util.tree_map(\n        lambda ax, const, y: (const if ax is broadcast else y),\n        out_axes,\n        constants_out,\n        ys,\n    )\n    return broadcast_in, c, ys\n\n  def simple_scan_fn(broadcast_in, init, *args):\n    # Saves an extra tracing operation.\n    # No verification of constancy, and no support for non-carry broadcast\n    # function outputs.\n    xs = jax.tree_util.tree_map(transpose_to_front, in_axes, args)\n\n    if broadcast in jax.tree_util.tree_leaves(out_axes):\n      raise ValueError(f\"nn.scan run with check_constancy_invariants=False \"\n                       f\"does not support broadcast non-carry function \"\n                       f\"outputs.  out_axes was given as {out_axes}\")\n\n    def body_fn(c, xs):\n      # inject constants\n      xs = jax.tree_util.tree_map(\n          lambda ax, arg, x: (arg if ax is broadcast else x), in_axes, args, xs\n      )\n      _, c, ys = fn(broadcast_in, c, *xs)\n      return c, ys\n\n    if jax.version.__version_info__ > (0, 4, 25):\n      c, ys = lax.scan(\n          body_fn, init, xs, length=length, reverse=reverse, unroll=unroll,\n          _split_transpose=_split_transpose\n      )\n    else:\n      c, ys = lax.scan(\n          body_fn, init, xs, length=length, reverse=reverse, unroll=unroll\n      )\n    ys = jax.tree_util.tree_map(transpose_from_front, out_axes, ys)\n    return broadcast_in, c, ys\n\n  if check_constancy_invariants:\n    return scan_fn\n  else:\n    return simple_scan_fn\n"
  },
  {
    "path": "flax/core/flax_functional_engine.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"colab_type\": \"code\"\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import functools\\n\",\n    \"import jax\\n\",\n    \"from jax import numpy as jnp, random, lax\\n\",\n    \"import numpy as np\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {\n    \"colab_type\": \"code\"\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from flax import linen as nn, struct\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {\n    \"colab_type\": \"code\"\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from flax.core import Scope, init, apply, Array, lift, unfreeze\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {\n    \"colab_type\": \"code\",\n    \"outputId\": \"2558605e-e485-407e-b062-74d31cc49f1e\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"FrozenDict({'params': {'kernel': DeviceArray([[ 0.15374057, -0.6807397 , -1.3350962 ],\\n\",\n      \"             [ 0.59940743, -0.69430196, -0.7663768 ]], dtype=float32), 'bias': DeviceArray([0., 0., 0.], dtype=float32)}})\\n\"\n     ]\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"(DeviceArray([[0.17045607]], dtype=float32),\\n\",\n       \" FrozenDict({'params': {'hidden': {'bias': DeviceArray([0., 0., 0.], dtype=float32), 'kernel': DeviceArray([[-0.22119394,  0.22075175, -0.0925657 ],\\n\",\n       \"              [ 0.40571952,  0.27750877,  1.0542233 ]], dtype=float32)}, 'out': {'kernel': DeviceArray([[ 0.21448377],\\n\",\n       \"              [-0.01530595],\\n\",\n       \"              [ 0.14402702]], dtype=float32), 'bias': DeviceArray([0.], dtype=float32)}}}))\"\n      ]\n     },\n     \"execution_count\": 4,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"def dense(\\n\",\n    \"    scope: Scope,\\n\",\n    \"    inputs: Array,\\n\",\n    \"    features: int,\\n\",\n    \"    bias: bool = True,\\n\",\n    \"    kernel_init=nn.linear.default_kernel_init,\\n\",\n    \"    bias_init=nn.initializers.zeros_init(),\\n\",\n    \"):\\n\",\n    \"  kernel = scope.param('kernel', kernel_init, (inputs.shape[-1], features))\\n\",\n    \"  y = jnp.dot(inputs, kernel)\\n\",\n    \"  if bias:\\n\",\n    \"    y += scope.param('bias', bias_init, (features,))\\n\",\n    \"  return y\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"model_fn = functools.partial(dense, features=3)\\n\",\n    \"\\n\",\n    \"x = jnp.ones((1, 2))\\n\",\n    \"y, params = init(model_fn)(random.key(0), x)\\n\",\n    \"print(params)\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"def mlp(scope: Scope, inputs: Array, features: int):\\n\",\n    \"  hidden = scope.child(dense, 'hidden')(inputs, features)\\n\",\n    \"  hidden = nn.relu(hidden)\\n\",\n    \"  return dense(scope.push('out'), hidden, 1)\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"init(mlp)(random.key(0), x, features=3)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {\n    \"colab_type\": \"code\",\n    \"outputId\": \"5790b763-df4f-47c8-9f4e-53fd1e1eb1fd\",\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"[[ 0.11575121 -0.51936364 -1.113899  ]\\n\",\n      \" [ 0.45569834 -0.5300623  -0.5873911 ]]\\n\",\n      \"[ 0.45569834 -0.5300623  -0.5873911 ]\\n\",\n      \"[[-1.5175114 -0.6617551]]\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"@struct.dataclass\\n\",\n    \"class Embedding:\\n\",\n    \"  table: np.ndarray\\n\",\n    \"\\n\",\n    \"  def lookup(self, indices):\\n\",\n    \"    return self.table[indices]\\n\",\n    \"\\n\",\n    \"  def attend(self, query):\\n\",\n    \"    return jnp.dot(query, self.table.T)\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"# all the embedding module does is provide a convenient initializers\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"def embedding(\\n\",\n    \"    scope: Scope,\\n\",\n    \"    num_embeddings: int,\\n\",\n    \"    features: int,\\n\",\n    \"    init_fn=nn.linear.default_embed_init,\\n\",\n    \") -> Embedding:\\n\",\n    \"  table = scope.param('table', init_fn, (num_embeddings, features))\\n\",\n    \"  return Embedding(table)\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"embedding, _ = init(embedding)(random.key(0), num_embeddings=2, features=3)\\n\",\n    \"print(embedding.table)\\n\",\n    \"print(embedding.lookup(1))\\n\",\n    \"print(\\n\",\n    \"    embedding.attend(\\n\",\n    \"        jnp.ones((\\n\",\n    \"            1,\\n\",\n    \"            3,\\n\",\n    \"        ))\\n\",\n    \"    )\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {\n    \"colab_type\": \"code\",\n    \"outputId\": \"dd9c5079-10e7-4944-e09a-e9f65573a733\"\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"((((1, 3), (1, 3)), (1, 3)),\\n\",\n       \" FrozenDict({'params': {'hf': {'bias': (3,), 'kernel': (3, 3)}, 'hg': {'bias': (3,), 'kernel': (3, 3)}, 'hi': {'bias': (3,), 'kernel': (3, 3)}, 'ho': {'bias': (3,), 'kernel': (3, 3)}, 'if': {'kernel': (2, 3)}, 'ig': {'kernel': (2, 3)}, 'ii': {'kernel': (2, 3)}, 'io': {'kernel': (2, 3)}}}))\"\n      ]\n     },\n     \"execution_count\": 6,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"def lstm(\\n\",\n    \"    scope,\\n\",\n    \"    carry,\\n\",\n    \"    inputs,\\n\",\n    \"    gate_fn=nn.activation.sigmoid,\\n\",\n    \"    activation_fn=nn.activation.tanh,\\n\",\n    \"    kernel_init=nn.linear.default_kernel_init,\\n\",\n    \"    recurrent_kernel_init=nn.initializers.orthogonal(),\\n\",\n    \"    bias_init=nn.initializers.zeros_init(),\\n\",\n    \"):\\n\",\n    \"  r\\\"\\\"\\\"A long short-term memory (LSTM) cell.\\n\",\n    \"\\n\",\n    \"  the mathematical definition of the cell is as follows\\n\",\n    \"  .. math::\\n\",\n    \"      \\\\begin{array}{ll}\\n\",\n    \"      i = \\\\sigma(W_{ii} x + W_{hi} h + b_{hi}) \\\\\\\\\\n\",\n    \"      f = \\\\sigma(W_{if} x + W_{hf} h + b_{hf}) \\\\\\\\\\n\",\n    \"      g = \\\\tanh(W_{ig} x + W_{hg} h + b_{hg}) \\\\\\\\\\n\",\n    \"      o = \\\\sigma(W_{io} x + W_{ho} h + b_{ho}) \\\\\\\\\\n\",\n    \"      c' = f * c + i * g \\\\\\\\\\n\",\n    \"      h' = o * \\\\tanh(c') \\\\\\\\\\n\",\n    \"      \\\\end{array}\\n\",\n    \"  where x is the input, h is the output of the previous time step, and c is\\n\",\n    \"  the memory.\\n\",\n    \"\\n\",\n    \"  Args:\\n\",\n    \"    carry: the hidden state of the LSTM cell,\\n\",\n    \"      initialized using `LSTMCell.initialize_carry`.\\n\",\n    \"    inputs: an ndarray with the input for the current time step.\\n\",\n    \"      All dimensions except the final are considered batch dimensions.\\n\",\n    \"    gate_fn: activation function used for gates (default: sigmoid)\\n\",\n    \"    activation_fn: activation function used for output and memory update\\n\",\n    \"      (default: tanh).\\n\",\n    \"    kernel_init: initializer function for the kernels that transform\\n\",\n    \"      the input (default: lecun_normal).\\n\",\n    \"    recurrent_kernel_init: initializer function for the kernels that transform\\n\",\n    \"      the hidden state (default: orthogonal).\\n\",\n    \"    bias_init: initializer for the bias parameters (default: zeros_init())\\n\",\n    \"  Returns:\\n\",\n    \"    A tuple with the new carry and the output.\\n\",\n    \"  \\\"\\\"\\\"\\n\",\n    \"  c, h = carry\\n\",\n    \"  hidden_features = h.shape[-1]\\n\",\n    \"  # input and recurrent layers are summed so only one needs a bias.\\n\",\n    \"  dense_h = lambda name: scope.child(dense, name)(\\n\",\n    \"      h,\\n\",\n    \"      features=hidden_features,\\n\",\n    \"      bias=True,\\n\",\n    \"      kernel_init=recurrent_kernel_init,\\n\",\n    \"      bias_init=bias_init,\\n\",\n    \"  )\\n\",\n    \"  dense_i = lambda name: scope.child(dense, name)(\\n\",\n    \"      inputs, features=hidden_features, bias=False, kernel_init=kernel_init\\n\",\n    \"  )\\n\",\n    \"  i = gate_fn(dense_i(name='ii') + dense_h(name='hi'))\\n\",\n    \"  f = gate_fn(dense_i(name='if') + dense_h(name='hf'))\\n\",\n    \"  g = activation_fn(dense_i(name='ig') + dense_h(name='hg'))\\n\",\n    \"  o = gate_fn(dense_i(name='io') + dense_h(name='ho'))\\n\",\n    \"  new_c = f * c + i * g\\n\",\n    \"  new_h = o * activation_fn(new_c)\\n\",\n    \"  return (new_c, new_h), new_h\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"def lstm_init_carry(batch_dims, size, init_fn=jnp.zeros):\\n\",\n    \"  shape = batch_dims + (size,)\\n\",\n    \"  return init_fn(shape), init_fn(shape)\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"x = jnp.ones((1, 2))\\n\",\n    \"carry = lstm_init_carry((1,), 3)\\n\",\n    \"y, variables = init(lstm)(random.key(0), carry, x)\\n\",\n    \"jax.tree_util.tree_map(np.shape, (y, variables))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"initialized parameter shapes:\\n\",\n      \" {'params': {'hf': {'bias': (2,), 'kernel': (2, 2)}, 'hg': {'bias': (2,), 'kernel': (2, 2)}, 'hi': {'bias': (2,), 'kernel': (2, 2)}, 'ho': {'bias': (2,), 'kernel': (2, 2)}, 'if': {'kernel': (2, 2)}, 'ig': {'kernel': (2, 2)}, 'ii': {'kernel': (2, 2)}, 'io': {'kernel': (2, 2)}}}\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"def simple_scan(scope: Scope, xs):\\n\",\n    \"  init_carry = lstm_init_carry(xs.shape[:1], xs.shape[-1])\\n\",\n    \"  #   cell = scope.child(lstm, 'cell')\\n\",\n    \"  #   ys = []\\n\",\n    \"  #   for i in range(xs.shape[1]):\\n\",\n    \"  #       x = xs[:, i]\\n\",\n    \"  #       init_carry, y = cell(init_carry, x)\\n\",\n    \"  #       ys.append(y)\\n\",\n    \"  #   return init_carry, ys\\n\",\n    \"  lstm_scan = lift.scan(\\n\",\n    \"      lstm,\\n\",\n    \"      in_axes=1,\\n\",\n    \"      out_axes=1,\\n\",\n    \"      variable_broadcast='params',\\n\",\n    \"      split_rngs={'params': False},\\n\",\n    \"  )\\n\",\n    \"  return lstm_scan(scope, init_carry, xs)\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"key1, key2 = random.split(random.key(0), 2)\\n\",\n    \"xs = random.uniform(key1, (1, 5, 2))\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"y, init_variables = init(simple_scan)(key2, xs)\\n\",\n    \"\\n\",\n    \"print(\\n\",\n    \"    'initialized parameter shapes:\\\\n',\\n\",\n    \"    jax.tree_util.tree_map(jnp.shape, unfreeze(init_variables)),\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {\n    \"tags\": []\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"output:\\n\",\n      \" (DeviceArray([[-0.35626447,  0.25178757]], dtype=float32), DeviceArray([[-0.17885922,  0.13063088]], dtype=float32))\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"y = apply(simple_scan)(init_variables, xs)[0]\\n\",\n    \"print('output:\\\\n', y)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {},\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "flax/core/frozen_dict.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Frozen Dictionary.\"\"\"\n\nimport collections\nfrom types import MappingProxyType\nfrom typing import Any, TypeVar\nfrom collections.abc import Hashable, Mapping\n\nimport jax\n\nfrom flax import serialization\n\n\nclass FrozenKeysView(collections.abc.KeysView):\n  \"\"\"A wrapper for a more useful repr of the keys in a frozen dict.\"\"\"\n\n  def __repr__(self):\n    return f'frozen_dict_keys({list(self)})'\n\n\nclass FrozenValuesView(collections.abc.ValuesView):\n  \"\"\"A wrapper for a more useful repr of the values in a frozen dict.\"\"\"\n\n  def __repr__(self):\n    return f'frozen_dict_values({list(self)})'\n\n\nK = TypeVar('K')\nV = TypeVar('V')\n\n\ndef _indent(x, num_spaces):\n  indent_str = ' ' * num_spaces\n  lines = x.split('\\n')\n  assert not lines[-1]\n  # skip the final line because it's empty and should not be indented.\n  return '\\n'.join(indent_str + line for line in lines[:-1]) + '\\n'\n\n\n@jax.tree_util.register_pytree_with_keys_class\nclass FrozenDict(Mapping[K, V]):\n  \"\"\"An immutable variant of the Python dict.\"\"\"\n\n  __slots__ = ('_dict', '_hash')\n\n  def __init__(self, *args, __unsafe_skip_copy__=False, **kwargs):  # pylint: disable=invalid-name\n    # make sure the dict is as\n    xs = dict(*args, **kwargs)\n    if __unsafe_skip_copy__:\n      self._dict = xs\n    else:\n      self._dict = _prepare_freeze(xs)\n\n    self._hash = None\n\n  def __getitem__(self, key):\n    v = self._dict[key]\n    if isinstance(v, dict):\n      return FrozenDict(v)\n    return v\n\n  def __setitem__(self, key, value):\n    raise ValueError('FrozenDict is immutable.')\n\n  def __contains__(self, key):\n    return key in self._dict\n\n  def __iter__(self):\n    return iter(self._dict)\n\n  def __len__(self):\n    return len(self._dict)\n\n  def __repr__(self):\n    return self.pretty_repr()\n\n  def __reduce__(self):\n    return FrozenDict, (self.unfreeze(),)\n\n  def get(self, key, default=None):\n    \"\"\"Get an item from the FrozenDict.\"\"\"\n    if key in self._dict:\n      return self[key]\n    return default\n\n  def pretty_repr(self, num_spaces=4):\n    \"\"\"Returns an indented representation of the nested dictionary.\"\"\"\n\n    def pretty_dict(x):\n      if not isinstance(x, dict):\n        return repr(x)\n      rep = ''\n      for key, val in x.items():\n        rep += f'{key}: {pretty_dict(val)},\\n'\n      if rep:\n        return '{\\n' + _indent(rep, num_spaces) + '}'\n      else:\n        return '{}'\n\n    return f'FrozenDict({pretty_dict(self._dict)})'\n\n  def __hash__(self):\n    if self._hash is None:\n      h = 0\n      for key, value in self.items():\n        h ^= hash((key, value))\n      self._hash = h\n    return self._hash\n\n  def copy(\n    self, add_or_replace: Mapping[K, V] = MappingProxyType({})\n  ) -> 'FrozenDict[K, V]':\n    \"\"\"Create a new FrozenDict with additional or replaced entries.\"\"\"\n    return type(self)({**self, **unfreeze(add_or_replace)})  # type: ignore[arg-type]\n\n  def keys(self):\n    return FrozenKeysView(self)\n\n  def values(self):\n    return FrozenValuesView(self)\n\n  def items(self):\n    for key in self._dict:\n      yield (key, self[key])\n\n  def pop(self, key: K) -> tuple['FrozenDict[K, V]', V]:\n    \"\"\"Create a new FrozenDict where one entry is removed.\n\n    Example::\n\n      >>> from flax.core import FrozenDict\n      >>> variables = FrozenDict({'params': {...}, 'batch_stats': {...}})\n      >>> new_variables, params = variables.pop('params')\n\n    Args:\n      key: the key to remove from the dict\n    Returns:\n      A pair with the new FrozenDict and the removed value.\n    \"\"\"\n    value = self[key]\n    new_dict = dict(self._dict)\n    new_dict.pop(key)\n    new_self = type(self)(new_dict)\n    return new_self, value\n\n  def unfreeze(self) -> dict[K, V]:\n    \"\"\"Unfreeze this FrozenDict.\n\n    Returns:\n      An unfrozen version of this FrozenDict instance.\n    \"\"\"\n    return unfreeze(self)\n\n  def tree_flatten_with_keys(self) -> tuple[tuple[Any, ...], Hashable]:\n    \"\"\"Flattens this FrozenDict.\n\n    Returns:\n      A flattened version of this FrozenDict instance.\n    \"\"\"\n    sorted_keys = sorted(self._dict)\n    return tuple(\n      [(jax.tree_util.DictKey(k), self._dict[k]) for k in sorted_keys]\n    ), tuple(sorted_keys)\n\n  @classmethod\n  def tree_unflatten(cls, keys, values):\n    # data is already deep copied due to tree map mechanism\n    # we can skip the deep copy in the constructor\n    return cls({k: v for k, v in zip(keys, values)}, __unsafe_skip_copy__=True)\n\n\ndef _prepare_freeze(xs: Any) -> Any:\n  \"\"\"Deep copy unfrozen dicts to make the dictionary FrozenDict safe.\"\"\"\n  if isinstance(xs, FrozenDict):\n    # we can safely ref share the internal state of a FrozenDict\n    # because it is immutable.\n    return xs._dict  # pylint: disable=protected-access\n  if not isinstance(xs, dict):\n    # return a leaf as is.\n    return xs\n  # recursively copy dictionary to avoid ref sharing\n  return {key: _prepare_freeze(val) for key, val in xs.items()}\n\n\ndef freeze(xs: Mapping[Any, Any]) -> FrozenDict[Any, Any]:\n  \"\"\"Freeze a nested dict.\n\n  Makes a nested ``dict`` immutable by transforming it into ``FrozenDict``.\n\n  Args:\n    xs: Dictionary to freeze (a regualr Python dict).\n  Returns:\n    The frozen dictionary.\n  \"\"\"\n  return FrozenDict(xs)\n\n\ndef unfreeze(x: FrozenDict | dict[str, Any]) -> dict[Any, Any]:\n  \"\"\"Unfreeze a FrozenDict.\n\n  Makes a mutable copy of a ``FrozenDict`` mutable by transforming\n  it into (nested) dict.\n\n  Args:\n    x: Frozen dictionary to unfreeze.\n  Returns:\n    The unfrozen dictionary (a regular Python dict).\n  \"\"\"\n  if isinstance(x, FrozenDict):\n    # deep copy internal state of a FrozenDict\n    # the dict branch would also work here but\n    # it is much less performant because jax.tree_util.tree_map\n    # uses an optimized C implementation.\n    return jax.tree_util.tree_map(lambda y: y, x._dict)  # type: ignore\n  elif isinstance(x, dict):\n    ys = {}\n    for key, value in x.items():\n      ys[key] = unfreeze(value)\n    return ys\n  else:\n    return x\n\n\ndef copy(\n  x: FrozenDict | dict[str, Any],\n  add_or_replace: FrozenDict[str, Any] | dict[str, Any] = FrozenDict({}),\n) -> FrozenDict | dict[str, Any]:\n  \"\"\"Create a new dict with additional and/or replaced entries. This is a utility\n  function that can act on either a FrozenDict or regular dict and mimics the\n  behavior of ``FrozenDict.copy``.\n\n  Example::\n\n    >>> from flax.core import FrozenDict, copy\n    >>> variables = FrozenDict({'params': {...}, 'batch_stats': {...}})\n    >>> new_variables = copy(variables, {'additional_entries': 1})\n\n  Args:\n    x: the dictionary to be copied and updated\n    add_or_replace: dictionary of key-value pairs to add or replace in the dict x\n  Returns:\n    A new dict with the additional and/or replaced entries.\n  \"\"\"\n\n  if isinstance(x, FrozenDict):\n    return x.copy(add_or_replace)\n  elif isinstance(x, dict):\n    new_dict = jax.tree_util.tree_map(\n        lambda x: x, x\n    )  # make a deep copy of dict x\n    new_dict.update(add_or_replace)\n    return new_dict\n  raise TypeError(f'Expected FrozenDict or dict, got {type(x)}')\n\n\ndef pop(\n  x: FrozenDict | dict[str, Any], key: str\n) -> tuple[FrozenDict | dict[str, Any], Any]:\n  \"\"\"Create a new dict where one entry is removed. This is a utility\n  function that can act on either a FrozenDict or regular dict and\n  mimics the behavior of ``FrozenDict.pop``.\n\n  Example::\n\n    >>> from flax.core import FrozenDict, pop\n    >>> variables = FrozenDict({'params': {...}, 'batch_stats': {...}})\n    >>> new_variables, params = pop(variables, 'params')\n\n  Args:\n    x: the dictionary to remove the entry from\n    key: the key to remove from the dict\n  Returns:\n    A pair with the new dict and the removed value.\n  \"\"\"\n\n  if isinstance(x, FrozenDict):\n    return x.pop(key)\n  elif isinstance(x, dict):\n    new_dict = jax.tree_util.tree_map(\n        lambda x: x, x\n    )  # make a deep copy of dict x\n    value = new_dict.pop(key)\n    return new_dict, value\n  raise TypeError(f'Expected FrozenDict or dict, got {type(x)}')\n\n\ndef pretty_repr(x: Any, num_spaces: int = 4) -> str:\n  \"\"\"Returns an indented representation of the nested dictionary.\n  This is a utility function that can act on either a FrozenDict or\n  regular dict and mimics the behavior of ``FrozenDict.pretty_repr``.\n  If x is any other dtype, this function will return ``repr(x)``.\n\n  Args:\n    x: the dictionary to be represented\n    num_spaces: the number of space characters in each indentation level\n  Returns:\n    An indented string representation of the nested dictionary.\n  \"\"\"\n\n  if isinstance(x, FrozenDict):\n    return x.pretty_repr()\n  else:\n\n    def pretty_dict(x):\n      if not isinstance(x, dict):\n        return repr(x)\n      rep = ''\n      for key, val in x.items():\n        rep += f'{key}: {pretty_dict(val)},\\n'\n      if rep:\n        return '{\\n' + _indent(rep, num_spaces) + '}'\n      else:\n        return '{}'\n\n    return pretty_dict(x)\n\n\ndef _frozen_dict_state_dict(xs):\n  str_keys = {str(k) for k in xs.keys()}\n  if len(str_keys) != len(xs):\n    raise ValueError(\n      'Dict keys do not have a unique string representation: '\n      f'{str_keys} vs given: {xs}'\n    )\n  return {str(key): serialization.to_state_dict(value) for key, value in xs.items()}\n\n\ndef _restore_frozen_dict(xs, states):\n  diff = set(map(str, xs.keys())).difference(map(str, states.keys()))\n  if diff:\n    raise ValueError(\n      'The target dict keys and state dict keys do not match, target dict'\n      f' contains keys {diff} which are not present in state dict at path'\n      f' {serialization.current_path()}'\n    )\n\n  return FrozenDict(\n    {\n      key: serialization.from_state_dict(value, states[str(key)], name=key)\n      for key, value in xs.items()\n    }\n  )\n\n\nserialization.register_serialization_state(\n  FrozenDict, _frozen_dict_state_dict, _restore_frozen_dict\n)\n"
  },
  {
    "path": "flax/core/lift.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Jax transform lifting.\"\"\"\n\nimport collections\nfrom collections.abc import Callable, Iterable, Mapping, Sequence\nimport contextlib\nimport dataclasses\nimport functools\nimport threading\nfrom typing import Any, Generic, TypeVar\nimport warnings\n\nfrom flax import traceback_util\nfrom flax import traverse_util\nfrom flax.typing import (\n    In,\n    InOutAxis,\n    InOutScanAxis,\n    Out,\n)\nimport jax\nfrom jax import random\n\nfrom . import axes_scan, meta\nfrom .frozen_dict import freeze, unfreeze\nfrom .scope import (\n    CollectionFilter,\n    DenyList,  # pylint: disable=g-multiple-import\n    Filter,\n    LazyRng,\n    PRNGSequenceFilter,\n    Scope,\n    group_collections,\n    in_filter,\n    intersect_filters,\n    is_filter_empty,\n    subtract_filters,\n    union_filters,\n)\n\ntraceback_util.register_exclusion(__file__)\n\nA = TypeVar('A')\n\n\n@dataclasses.dataclass\nclass TransformContext(Generic[A], threading.local):\n  \"\"\"Context for a transform.\"\"\"\n\n  stack: list[A] = dataclasses.field(default_factory=list)\n\n  @contextlib.contextmanager\n  def push(self, a: A):\n    self.stack.append(a)\n    try:\n      yield\n    finally:\n      self.stack.pop()\n\n  def get(self) -> A:\n    return self.stack[-1]\n\n\ndef tree_map_rngs(fn, tree):\n  \"\"\"Needed for mapping JAX random.* functions over PRNGKey leaves.\"\"\"\n  return jax.tree_util.tree_map(\n    fn,\n    tree,\n    is_leaf=lambda x: hasattr(x, 'dtype')\n    and jax.dtypes.issubdtype(x.dtype, jax.dtypes.prng_key),\n  )\n\n\ndef _dedup_scopes(scopes):\n  \"\"\"Deduplicated scopes.\"\"\"\n  paths = []\n  # must preseve insertion order for duplication to work correctly\n  minimal_set = collections.OrderedDict((s, ()) for s in scopes)\n  for leaf in scopes:\n    scope = leaf.parent\n    max_parent = leaf\n    max_parent_path = ()\n    path = [leaf.name]\n    while scope is not None:\n      if scope in minimal_set:\n        max_parent = scope\n        max_parent_path = tuple(reversed(path))\n      path.append(scope.name)\n      scope = scope.parent\n    if max_parent is not leaf and leaf in minimal_set:\n      del minimal_set[leaf]\n    paths.append((max_parent, max_parent_path))\n  return tuple(minimal_set), tuple(paths)\n\n\ndef _dup_scopes(orig_scopes, scopes, paths):\n  \"\"\"Duplicated scopes.\"\"\"\n  mapping = dict(zip(orig_scopes, scopes))\n  scopes = []\n  for root, path in paths:\n    scope = mapping[root]\n    for name in path:\n      scope = scope.push(name, reuse=True)\n    scopes.append(scope)\n  return scopes\n\n\ndef _transpose(xs):\n  return tuple(zip(*xs))\n\n\ndef _partial_pack(\n    scope_tree: Scope,\n    in_variable_filters: Sequence[CollectionFilter],\n    out_variable_filters: Sequence[CollectionFilter],\n    rng_filters: Sequence[PRNGSequenceFilter],\n    name=None,\n) -> tuple[Callable[..., Any], Callable[..., Any], Any, Any, Callable[..., Any]]:\n  \"\"\"Pack variables and rngs for functional transformations.\n\n  The _partial_pack function is the building block for all other lifted transformations.\n\n  Args:\n    fn: The function to pack. `fn` has the signature\n    in_variable_filters: Input variable filters.\n    out_variable_filters: Output variable filters.\n    rng_filters: RNG filters.\n    name: The name of the packed scope.\n    enable_kwargs: Whether to enable kwargs or not.\n  Returns:\n    `(scope_fn, repack_fn, variable_groups, rng_groups, publish_results_fn)`\n  \"\"\"\n  # pylint: disable=protected-access\n  scopes, treedef = jax.tree_util.tree_flatten(scope_tree)\n  scopes, paths = _dedup_scopes(scopes)\n\n  variable_groups_xs = []\n\n  for scope in scopes:\n    scope._validate_trace_level()\n    scope._populate_collections()\n    variable_groups_xs.append(\n        group_collections(scope._variables, in_variable_filters)\n    )\n  variable_groups_xs_t = _transpose(variable_groups_xs)\n\n  # Make sure that in-only variable collections are frozen\n  for variable_group_xs in variable_groups_xs_t:\n    for variable_group in variable_group_xs:\n      for col_name, collection in variable_group.items():\n        col_in_out = any(\n            in_filter(col_filter, col_name)\n            for col_filter in out_variable_filters\n        )\n        if not col_in_out:\n          variable_group[col_name] = freeze(collection)\n  rng_groups_xs = []\n  inner_rng_counters = []\n  for scope in scopes:\n    rng_counters = scope.rng_counters\n    rng_groups = group_collections(scope.rngs, rng_filters)\n    rng_groups_xs.append(rng_groups)\n    inner_rng_counters.append(rng_counters)\n  rng_groups_xs_t = _transpose(rng_groups_xs)\n\n  def scope_fn(\n      variable_groups_xs_t,\n      rng_groups_xs_t,\n      mutable_filter: CollectionFilter = True,\n  ):\n    inner_scopes = []\n    mutable: Filter = False\n    for out_filter in out_variable_filters:\n      mutable = union_filters(mutable, out_filter)\n    # could be () in the edge case where no rngs or variable_groups are lifted\n    # in this case fallback to ((),) * len(scopes) to make sure the zip has\n    # something to iterate over for each scope.\n    variable_groups_xs = _transpose(variable_groups_xs_t) or ((),) * len(\n        scopes\n    )\n    rng_groups_xs = _transpose(rng_groups_xs_t) or ((),) * len(scopes)\n    assert len(variable_groups_xs) == len(scopes)\n    assert len(rng_groups_xs) == len(scopes)\n    for variable_groups, rng_groups, scope, rng_counters in zip(\n        variable_groups_xs, rng_groups_xs, scopes, inner_rng_counters\n    ):\n      variables = {}\n      rngs = {}\n      for variable_group in variable_groups:\n        variables.update(variable_group)\n      for rng_group in rng_groups:\n        rngs.update(rng_group)\n      # make sure variable dicts are cloned and can't be manipulated by ref\n      # sharing.\n      variables = jax.tree_util.tree_map(lambda x: x, variables)\n      scope_mutable = intersect_filters(\n          intersect_filters(scope.mutable, mutable), mutable_filter\n      )\n      new_debug_path = scope.debug_path\n      if name:\n        if new_debug_path:\n          new_debug_path = new_debug_path[:-1] + (\n              f'{name}({new_debug_path[-1]})',\n          )\n        else:\n          new_debug_path = (f'{name}()',)\n      inner_scope = Scope(\n          variables,\n          name=scope.name,\n          rngs=rngs,\n          mutable=scope_mutable,\n          parent=None,\n          path=scope.path,\n          debug_path=new_debug_path,\n          flags=scope.flags,\n      )\n      inner_scope.rng_counters = rng_counters\n      inner_scopes.append(inner_scope)\n    inner_scopes = _dup_scopes(scopes, inner_scopes, paths)\n    return treedef.unflatten(inner_scopes)\n\n  def repack_fn(inner_scope_tree):\n    inner_scopes = treedef.flatten_up_to(inner_scope_tree)\n    inner_scopes, inner_paths = _dedup_scopes(inner_scopes)\n    inner_scopes = list(inner_scopes)\n    assert [p for _, p in paths] == [p for _, p in inner_paths]\n    out_variable_groups_xs = []\n    for inner_scope in inner_scopes:\n      inner_scope.invalidate()\n      inner_scope._validate_trace_level()\n      mutable_variables = {\n          key: val\n          for key, val in inner_scope._variables.items()\n          if in_filter(inner_scope.mutable, key)\n      }\n      out_variable_groups = group_collections(\n          mutable_variables, tuple(out_variable_filters) + (True,)\n      )\n      remainder = tuple(out_variable_groups[-1].keys())\n      if remainder:\n        raise ValueError(f'unmapped output variables: {remainder}')\n      out_variable_groups_xs.append(out_variable_groups[:-1])\n\n    return _transpose(out_variable_groups_xs)\n\n  def publish_results_fn(out_variable_groups_xs_t):\n    out_variable_groups_xs = _transpose(out_variable_groups_xs_t)\n    for scope, out_variable_groups, rng_counters in zip(\n        scopes, out_variable_groups_xs, inner_rng_counters\n    ):\n      for out_variable_group in out_variable_groups:\n        for col_name, collection in out_variable_group.items():\n          if not scope.is_mutable_collection(col_name):\n            # Some lifted transforms like scan return redundant variables.\n            continue\n          for var_name, value in collection.items():\n            scope.put_variable(col_name, var_name, value)\n\n  return (\n      scope_fn,\n      repack_fn,\n      variable_groups_xs_t,\n      rng_groups_xs_t,\n      publish_results_fn,\n    )\n\n\ndef pack(\n    fn: Callable[..., Any],\n    in_variable_filters: Sequence[CollectionFilter],\n    out_variable_filters: Sequence[CollectionFilter],\n    rng_filters: Sequence[PRNGSequenceFilter],\n    name=None,\n    enable_kwargs=False,\n) -> Callable[..., Any]:\n  \"\"\"Pack variables and rngs for functional transformations.\n\n  The pack function is the building block for all other lifted transformations.\n\n  Args:\n    fn: The function to pack. `fn` has the signature\n      `(scope_fn, repack_fn, variable_groups, rng_groups, *args) ->\n      (output, packed_variables)`.\n    in_variable_filters: Input variable filters.\n    out_variable_filters: Output variable filters.\n    rng_filters: RNG filters.\n    name: The name of the packed scope.\n    enable_kwargs: Whether to enable kwargs or not.\n  Returns:\n    A callable which expects a scope as the first argument.\n  \"\"\"\n\n  @functools.wraps(fn)\n  def wrapper(scope_tree: Scope, *args, **kwargs):\n    if not enable_kwargs and kwargs:\n      msg = 'kwargs are not supported in {}, so \"{}\" is(are) ignored'\n      warnings.warn(msg.format(name, ', '.join(kwargs.keys())), RuntimeWarning)\n    (\n        scope_fn,\n        repack_fn,\n        variable_groups_xs_t,\n        rng_groups_xs_t,\n        publish_results_fn,\n    ) = _partial_pack(scope_tree, in_variable_filters, out_variable_filters, rng_filters, name)\n    if enable_kwargs:\n      y, out_variable_groups_xs_t = fn(\n          scope_fn,\n          repack_fn,\n          variable_groups_xs_t,\n          rng_groups_xs_t,\n          *args,\n          **kwargs,\n      )\n    else:\n      y, out_variable_groups_xs_t = fn(\n          scope_fn, repack_fn, variable_groups_xs_t, rng_groups_xs_t, *args\n      )\n    publish_results_fn(out_variable_groups_xs_t)\n    return y\n\n  return wrapper\n\n\nid_fn = lambda x: x\n\n\ndef map_variables(\n  fn: Callable[..., Any],\n  mapped_collections: CollectionFilter,\n  map_in_fn: Callable[..., Any] = id_fn,\n  map_out_fn: Callable[..., Any] = id_fn,\n  init: bool = False,\n  mutable: bool = False,\n  rngs: PRNGSequenceFilter = True,\n  variables: CollectionFilter = True,\n) -> Callable[..., Any]:\n  \"\"\"Map Variables inside a scope.\n\n  Args:\n    fn: the function to be transformed.\n    mapped_collections: the collection(s) to be transformed.\n    map_in_fn: creates a view of the target variables.\n    map_out_fn: transforms the updated variables in the view after mutation.\n    init: If True, variables are initialized before transformation.\n    mutable: If True, the mapped variable collections will be mutable.\n    rngs: PRNGSequences added to the transformed scope (default: all).\n    variables: Additional Variable collections added to the transformed scope.\n      Besides those specified by `target` (default: all).\n\n  Returns:\n    A callable expecting a scope as the first argument.\n  \"\"\"\n  is_target_out = mutable or init\n\n  def wrapper(scope_fn, repack, variable_groups, rng_groups, *args, **kwargs):\n    target, variables = variable_groups\n    if init:\n      scopes = scope_fn((target, variables), rng_groups)\n      has_mutable_cols = any(\n        not is_filter_empty(scope.mutable)\n        for scope in jax.tree_util.tree_leaves(scopes)\n      )\n      if has_mutable_cols:\n        fn(scopes, *args, **kwargs)\n        target, _ = repack(scopes)\n        target = tuple(map_out_fn(x) for x in target)\n    target = tuple(map_in_fn(unfreeze(x)) for x in target)\n    mfilter = True\n    if not is_target_out:\n      # mapped collections should not be mutable\n      # unless the mapping supports it (by init=True or mutable=True)\n      mfilter = subtract_filters(mfilter, mapped_collections)\n    scopes = scope_fn((target, variables), rng_groups, mutable_filter=mfilter)\n    y = fn(scopes, *args, **kwargs)\n    out_target, out_vars = repack(scopes)\n    if is_target_out:\n      out_target = tuple(map_out_fn(x) for x in out_target)\n    return y, (out_target, out_vars)\n\n  in_vars = (mapped_collections, variables)\n  out_vars = (\n    in_vars\n    if is_target_out\n    else (False, subtract_filters(variables, mapped_collections))\n  )\n  return pack(\n    wrapper,\n    in_vars,\n    out_vars,\n    (rngs,),\n    enable_kwargs=True,\n    name='map_variables',\n  )\n\n\ndef swap_collection(fn: Callable[..., Any], col_a: str, col_b: str):\n  \"\"\"Swap two collections.\"\"\"\n\n  def swap(target):\n    a = target[col_a] if col_a in target else {}\n    b = target[col_b] if col_b in target else {}\n    target[col_b], target[col_a] = a, b\n    return target\n\n  return map_variables(fn, (col_a, col_b), swap, swap, mutable=True)\n\n\ndef _split_in_out_axes(xs: Mapping[CollectionFilter, Any]):\n  unpack = lambda v: v.axis if isinstance(v, (In, Out)) else v\n  in_axes = {k: unpack(v) for k, v in xs.items() if not isinstance(v, Out)}\n  out_axes = {k: unpack(v) for k, v in xs.items() if not isinstance(v, In)}\n  return in_axes, out_axes\n\n\ndef _bwd_wrapper(treedef, bwd_fn, tangent):\n  vars_grad, *inputs_grad = bwd_fn(tangent)\n  vars_grad = treedef.unflatten(vars_grad)\n  return (vars_grad, *inputs_grad)\n\n\ndef vjp(\n  fn: Callable[..., Any],\n  scope: Scope,\n  *primals,\n  has_aux: bool = False,\n  reduce_axes=(),\n  vjp_variables: CollectionFilter = 'params',\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n) -> tuple[Any, Callable[..., Any]] | tuple[Any, Callable[..., Any], Any]:\n  \"\"\"A lifted version of ``jax.vjp``.\n\n  See ``jax.vjp`` for the unlifted vector-Jacobian product (backward gradient).\n\n  Note that a gradient is returned for all variables in the collections\n  specified by `vjp_variables`. However, the backward function only expects\n  a cotangent for the return value of `fn`. If variables require a co-tangent\n  as well they can be returned from `fn` using `scope.variables()`.\n\n  Example::\n\n    def learn_scale(scope, x, y):\n      p = scope.param('scale', nn.initializers.zeros_init(), ())\n      return p * x * y\n    def f(scope, x, y):\n      z, bwd = lift.vjp(learn_scale, scope, x, y)\n      params_grad, x_grad, y_grad = bwd(jnp.ones(z.shape))\n      return z, params_grad, x_grad, y_grad\n\n  Args:\n    fn: Function to be differentiated. Its arguments should be arrays, scalars,\n      or standard Python containers of arrays or scalars. It should return an\n      array, scalar, or standard Python container of arrays or scalars. It will\n      receive the scope and primals as arguments.\n    scope: The scope of which the variables will be differentiated.\n    *primals: A sequence of primal values at which the Jacobian of ``fn``\n      should be evaluated. The length of ``primals`` should be equal to the\n      number of positional parameters to ``fn``. Each primal value should be a\n      tuple of arrays, scalar, or standard Python containers thereof.\n    has_aux: Optional, bool. Indicates whether ``fn`` returns a pair where the\n     first element is considered the output of the mathematical function to be\n     differentiated and the second element is auxiliary data. Default ``False``.\n    vjp_variables: The vjpfun will return a cotangent vector for all\n      variable collections specified by this filter.\n    variables: other variables collections that are available inside `fn` but\n      do not receive a cotangent.\n    rngs: the prngs that are available inside `fn`.\n\n  Returns:\n    If ``has_aux`` is ``False``, returns a ``(primals_out, vjpfun)`` pair, where\n    ``primals_out`` is ``fn(*primals)``.\n    ``vjpfun`` is a function from a cotangent vector with the same shape as\n    ``primals_out`` to a tuple of cotangent vectors with the same shape as\n    ``primals``, representing the vector-Jacobian product of ``fn`` evaluated at\n    ``primals``. If ``has_aux`` is ``True``, returns a\n    ``(primals_out, vjpfun, aux)`` tuple where ``aux`` is the auxiliary data\n    returned by ``fn``.\n  \"\"\"\n  if reduce_axes:\n    raise NotImplementedError('reduce_axes argument to vjp is deprecated')\n  del reduce_axes\n\n  def inner(scope_fn, repack_fn, variable_groups, rng_groups, *args):\n    vjp_vars, other_vars = variable_groups\n\n    @functools.wraps(fn)\n    def wrapper(vjp_vars, *args):\n      variable_groups = (vjp_vars, other_vars)\n      scope = scope_fn(variable_groups, rng_groups)\n      if has_aux:\n        y, aux = fn(scope, *args)\n      else:\n        y = fn(scope, *args)\n        aux = ()\n      return y, (aux, repack_fn(scope))\n\n    y, bwd, (aux, out_vars) = jax.vjp(\n      wrapper, vjp_vars, *args, has_aux=True\n    )\n    treedef = jax.tree_util.tree_structure(scope)\n    bwd = jax.tree_util.Partial(functools.partial(_bwd_wrapper, treedef), bwd)\n    if has_aux:\n      return (y, bwd, aux), out_vars\n    else:\n      return (y, bwd), out_vars\n\n  return pack(\n    inner,\n    (vjp_variables, variables),\n    (variables,),\n    (rngs,),\n    name='vjp',\n    enable_kwargs=False,\n  )(scope, *primals)\n\n\ndef value_and_grad(\n  fn: Callable[..., Any],\n  scope: Scope,\n  *primals,\n  has_aux: bool = False,\n  reduce_axes=(),\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n) -> tuple[Any, Callable[..., Any]] | tuple[Any, Callable[..., Any], Any]:\n  \"\"\"A limited lifted version of ``jax.value_and_grad``.\n\n  See ``jax.value_and_grad`` for the unlifted reverse mode gradient.\n\n  Note that for this convenience function, gradients are only calculated for\n  the function inputs (all function inputs), and not with respect to any scope\n  variables. The target function must return a scalar-valued output.\n\n  Example::\n\n    def learn_scale(scope, x, y):\n      p = scope.param('scale', nn.initializers.zeros_init(), ())\n      return p * x * y\n    def f(scope, x, y):\n      z, x_grad, y_grad = lift.value_and_grad(learn_scale, scope, x, y)\n      return z, x_grad, y_grad\n\n  Args:\n    fn: Function to be differentiated. Its arguments should be arrays, scalars,\n      or standard Python containers of arrays or scalars. It should return an\n      array, scalar, or standard Python container of arrays or scalars. It will\n      receive the scope and primals as arguments.\n    scope: The scope of which the variables will be differentiated.\n    *primals: A sequence of primal values at which the Jacobian of ``fn``\n      should be evaluated. The length of ``primals`` should be equal to the\n      number of positional parameters to ``fn``. Each primal value should be a\n      tuple of arrays, scalar, or standard Python containers thereof.\n    has_aux: Optional, bool. Indicates whether ``fn`` returns a pair where the\n     first element is considered the output of the mathematical function to be\n     differentiated and the second element is auxiliary data. Default ``False``.\n    variables: other variables collections that are available inside `fn` but\n      do not receive a cotangent.\n    rngs: the prngs that are available inside `fn`.\n\n  Returns:\n    If ``has_aux`` is ``False``, returns a ``(primals_out, grads)`` pair, where\n    ``primals_out`` is ``fn(*primals)``.\n    If ``has_aux`` is ``True``, returns a\n    ``(primals_out, aux, grads)`` tuple where ``aux`` is the auxiliary data\n    returned by ``fn``.\n  \"\"\"\n  if reduce_axes:\n    raise NotImplementedError(\n        'reduce_axes argument to value_and_grad is deprecated')\n  del reduce_axes\n\n  def inner(scope_fn, repack_fn, variable_groups, rng_groups, *args):\n    @functools.wraps(fn)\n    def wrapper(*args):\n      scope = scope_fn(variable_groups, rng_groups)\n      if has_aux:\n        y, aux = fn(scope, *args)\n      else:\n        y = fn(scope, *args)\n        aux = ()\n      return y, (aux, repack_fn(scope))\n\n    y, bwd, (aux, out_vars) = jax.vjp(\n      wrapper,\n      *args,\n      has_aux=True,\n    )\n\n    inputs_grad = bwd(jax.numpy.ones_like(y))\n\n    if has_aux:\n      return (y, aux, inputs_grad), out_vars\n    else:\n      return (y, inputs_grad), out_vars\n\n  return pack(\n    inner,\n    (variables,),\n    (variables,),\n    (rngs,),\n    name='value_and_grad',\n    enable_kwargs=False,\n  )(scope, *primals)\n\n\ndef jvp(\n  fn: Callable[..., Any],\n  scope: Scope,\n  primals,\n  tangents,\n  variable_tangents,\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n) -> tuple[Any, Any]:\n  \"\"\"A lifted version of ``jax.jvp``.\n\n  See ``jax.jvp`` for the unlifted Jacobian-vector product (forward gradient).\n\n  Note that no tangents are returned for variables. When variable tangents\n  are required their value should be returned explicitly by `fn`\n  using `scope.variables()`.\n\n  Example::\n\n    def learn_scale(scope, x):\n      p = scope.param('scale', nn.initializers.zeros_init(), ())\n      return p * x\n\n    def f(scope, x):\n      vars_t = jax.tree_util.tree_map(jnp.ones_like,\n                                      scope.variables().get('params', {}))\n      x, out_t = lift.jvp(\n          learn_scale, scope, (x,), (jnp.zeros_like(x),),\n          variable_tangents={'params': vars_t})\n      return out_t\n\n  Args:\n    fn: The function to be transformed.\n    scope: The scope(s) which should be lifted into the transform.\n    primals: The primal values at which the Jacobian of ``fun`` should be\n      evaluated. Should be either a tuple or a list of arguments,\n      and its length should be equal to the number of positional parameters of\n      ``fun``.\n    tangents: The tangent vector for which the Jacobian-vector product should be\n      evaluated. Should be either a tuple or a list of tangents, with the same\n      tree structure and array shapes as ``primals``.\n    variable_tangents: A dict or PyTree fo dicts with the same structure as\n      scopes. Each entry in the dict specifies the tangents for a variable\n      collection. Not specifying a collection in variable_tangents is\n      equivalent to passing a zero vector as the tangent.\n    variables: other variables collections that are available inside `fn` but\n      do not receive a tangent.\n    rngs: the prngs that are available inside `fn`.\n\n  Returns:\n    A ``(primals_out, tangents_out)`` pair, where ``primals_out`` is\n    ``fun(*primals)``, and ``tangents_out`` is the Jacobian-vector product of\n    ``function`` evaluated at ``primals`` with ``tangents``. The\n    ``tangents_out`` value has the same Python tree structure and shapes as\n    ``primals_out``.\n  \"\"\"\n\n  def inner(scope_fn, repack_fn, variable_groups, rng_groups, *args):\n    jvp_vars, other_vars = variable_groups\n\n    @functools.wraps(fn)\n    def wrapper(vars_primals, args):\n      variable_groups = (vars_primals, other_vars)\n      scope = scope_fn(variable_groups, rng_groups)\n      y = fn(scope, *args)\n      return y, repack_fn(scope)\n\n    (y, out_vars), out_tangents = jax.jvp(\n      wrapper, (jvp_vars, args), (variable_tangents, tangents)\n    )\n    return (y, out_tangents[0]), out_vars\n\n  # filter out empty tangent collections because JAX will error on non-equal\n  # tree structure for example: {\"params\": {}} != {}.\n  treedef = jax.tree_util.tree_structure(scope)\n\n  variable_tangents = tuple(\n    {k: v for k, v in vt.items() if v}  # pylint: disable=g-complex-comprehension\n    for vt in treedef.flatten_up_to(variable_tangents)\n  )\n  target = tuple(variable_tangents[0].keys())\n  return pack(\n    inner,\n    (target, variables),\n    (variables,),\n    (rngs,),\n    name='jvp',\n    enable_kwargs=False,\n  )(scope, *primals)\n\n\ndef vmap(\n  fn: Callable[..., Any],\n  variable_axes: Mapping[CollectionFilter, InOutAxis],\n  split_rngs: Mapping[PRNGSequenceFilter, bool],\n  in_axes=0,\n  out_axes=0,\n  axis_size: int | None = None,\n  axis_name: str | None = None,\n  spmd_axis_name: str | None = None,\n  metadata_params: dict[Any, Any] = {},\n) -> Callable[..., Any]:\n  \"\"\"A lifted version of ``jax.vmap``.\n\n  See ``jax.vmap`` for the unlifted batch transform in Jax.\n\n  ``vmap`` can be used to add a batch axis to a scope function.\n  For example we could create a version of ``dense`` with\n  a batch axis that does not share parameters::\n\n    batch_dense = lift.vmap(\n        nn.dense,\n        in_axes=(0, None),\n        variable_axes={'params': 0},\n        split_rngs={'params': True})\n\n  By using ``variable_axes={'params': 0}``, we indicate that the\n  parameters themselves are mapped over and therefore not shared along\n  the mapped axis. Consequently, we also split the 'params' RNG,\n  otherwise the parameters would be initialized identically along\n  the mapped axis.\n\n  Similarly, ``vmap`` could be use to add a batch axis with parameter\n  sharing::\n\n    batch_foo = lift.vmap(\n        foo,\n        in_axes=0, out_axes=0,\n        variable_axes={'params': None},\n        split_rngs={'params': False})\n\n  Here we use ``variable_axes={'params': None}`` to indicate the parameter\n  variables are shared along the mapped axis. Consequently, the 'params'\n  RNG must also be shared.\n\n  Args:\n    fn: the function to be transformed.\n    variable_axes: the variable collections that are lifted into the batching\n      transformation. Use `None` to indicate a broadcasted collection or an\n      integer to map over an axis.\n    split_rngs: Split PRNG sequences will be different for each index of the\n      batch dimension. Unsplit PRNGs will be broadcasted.\n    in_axes: Specifies the mapping of the input arguments (see `jax.vmap).\n    out_axes: Specifies the mapping of the return value (see `jax.vmap).\n    axis_size: Specifies the size of the batch axis. This only needs to be\n      specified if it cannot be derived from the input arguments.\n    axis_name: Specifies a name for the batch axis. Can be used together with\n      parallel reduction primitives (e.g. `jax.lax.pmean`, `jax.lax.ppermute`,\n      etc.). Note, this is only used for pmap and shmap. For SPMD jit, you do\n      not need to manually synchronize. Just make sure that the axes are\n      correctly annotated and XLA:SPMD will insert the necessary collectives.\n    spmd_axis_name: Axis name added to any pjit sharding constraints appearing\n      in `fn`. See also\n      https://github.com/google/flax/blob/main/flax/linen/partitioning.py.\n    metadata_params: arguments dict passed to AxisMetadata instances in the\n      variable tree.\n\n  Returns:\n    A vectorized version of the input scope function.\n  \"\"\"\n  variable_in_axes, variable_out_axes = _split_in_out_axes(variable_axes)\n  variable_in_groups, variable_in_axes = _unzip2(variable_in_axes.items())\n  variable_out_groups, variable_out_axes = _unzip2(variable_out_axes.items())\n  rng_groups, rng_splits = _unzip2(split_rngs.items())\n  rng_axes = tuple(0 if rng_split else None for rng_split in rng_splits)\n\n  def inner(scope_fn, repack_fn, variable_groups, rng_groups, *args):\n    # optional user-defined variable transform on the way in\n    new_variable_groups = []\n    for var_group, axis in zip(variable_groups, variable_in_axes):\n      if axis is not None:\n        new_variable_groups.append(\n            meta.remove_axis(var_group, axis, metadata_params)\n        )\n      else:\n        new_variable_groups.append(var_group)\n    variable_groups = tuple(new_variable_groups)\n\n    # split rngs\n    def find_axis_size(axis, x):\n      if axis is not None:\n        leaves = jax.tree_util.tree_leaves(x)\n        if leaves:\n          return leaves[0].shape[axis]\n      return ()\n\n    axis_sizes = jax.tree_util.tree_map(\n        find_axis_size, (variable_in_axes, in_axes), (variable_groups, args),\n        is_leaf=lambda x: x is None\n    )\n    axis_sizes = set(jax.tree_util.tree_leaves(axis_sizes))\n    if axis_size is None and len(axis_sizes) == 1:\n      (d_axis_size,) = axis_sizes\n    elif len(axis_sizes) > 1:\n      raise ValueError(f'Inconsistent batch axis sizes: {axis_sizes}')\n    elif axis_size is None:\n      raise ValueError('axis_size should be specified manually.')\n    else:\n      d_axis_size = axis_size\n\n    def split_fn(rng):\n      # random.clone is only available on Jax versions 0.4.26 or newer. See:\n      # https://jax.readthedocs.io/en/latest/jax.experimental.key_reuse.htmls\n      if hasattr(random, 'clone'):\n        rng = random.clone(rng)\n      rngs = random.split(rng, d_axis_size)\n      if spmd_axis_name is not None:\n        args_flat, _ = jax.tree.flatten(args)\n        axes_flat = _broadcast_prefix_tree(in_axes, args)\n        any_vmapped_axis_sharded = any(\n            jax.typeof(x).sharding.spec[i] == spmd_axis_name\n            for x, i in zip(args_flat, axes_flat)\n            if i is not None\n        )\n        if any_vmapped_axis_sharded:\n          rngs = jax.sharding.reshard(rngs, jax.P(spmd_axis_name))\n      return rngs\n\n    rng_groups = tuple(\n        tree_map_rngs(split_fn, rng_group) if split else rng_group\n        for rng_group, split in zip(rng_groups, rng_splits)\n    )\n\n    @functools.partial(\n        jax.vmap,\n        in_axes=(variable_in_axes, rng_axes, in_axes),\n        out_axes=(out_axes, variable_out_axes),\n        axis_name=axis_name,\n        axis_size=axis_size,\n        spmd_axis_name=spmd_axis_name,\n    )\n    @functools.wraps(fn)\n    def mapped(variable_groups, rng_groups, args):\n      scope = scope_fn(variable_groups, rng_groups)\n      y = fn(scope, *args)\n      return y, repack_fn(scope)\n\n    # optional user-defined variable transform on the way out\n    y, vars_out = mapped(variable_groups, rng_groups, args)\n    new_vars_out = []\n    for var_group, axis in zip(vars_out, variable_out_axes):\n      if axis is not None:\n        new_vars_out.append(meta.add_axis(var_group, axis, metadata_params))\n      else:\n        new_vars_out.append(var_group)\n    vars_out = tuple(new_vars_out)\n    return y, vars_out\n\n  return pack(\n    inner, variable_in_groups, variable_out_groups, rng_groups, name='vmap'\n  )\n\n\ndef scan(\n  fn: Callable[..., Any],\n  variable_axes: Mapping[CollectionFilter, InOutScanAxis] = {},\n  variable_broadcast: CollectionFilter = False,\n  variable_carry: CollectionFilter = False,\n  split_rngs: Mapping[PRNGSequenceFilter, bool] = {},\n  in_axes=0,\n  out_axes=0,\n  length: int | None = None,\n  reverse: bool = False,\n  unroll: int = 1,\n  _split_transpose: bool = False,\n  data_transform: Callable[..., Any] | None = None,\n  metadata_params: dict[Any, Any] = {},\n  check_constancy_invariants: bool = True,\n) -> Callable[..., Any]:\n  \"\"\"A lifted version of ``jax.lax.scan``.\n\n  See ``jax.lax.scan`` for the unlifted scan in Jax.\n\n  To improve consistency with ``vmap``, this version of scan\n  uses ``in_axes`` and ``out_axes`` to determine which arguments\n  are scanned over and along which axis.\n\n  ``scan`` distinguishes between 3 different types of values inside the loop:\n\n  1. **scan**: a value that is iterated over in a loop. All scan values must\n    have the same size in the axis they are scanned over. Scanned outputs\n    will be stacked along the scan axis.\n  2. **carry**: A carried value is updated at each loop iteration. It must\n    have the same shape and dtype throughout the loop.\n  3. **broadcast**: a value that is closed over by the loop. When a variable\n    is broadcasted they are typically initialized inside the loop body but\n    independent of the loop variables.\n\n  The loop body should have the signature\n  ``(scope, body, carry, *xs) -> (carry, ys)``, where ``xs`` and ``ys``\n  are the scan values that go in and out of the loop.\n\n  Example::\n\n    scope.variable('counter', 'i', jnp.zeros, ())\n    def body_fn(scope, c, x):\n      counter = scope.variable('counter', 'i', jnp.zeros, ())\n      counter.value += 1\n      x = scope.child(nn.dense)(x, 1)\n      return c, x\n\n    _, ys = lift.scan(\n        body_fn,\n        variable_carry='counter',\n        variable_broadcast='params',\n        split_rngs={'params': False})(scope, (), xs)\n\n  Args:\n    fn: the function to be transformed.\n    variable_axes: the variable collections that are scanned over.\n    variable_broadcast: Specifies the broadcasted variable collections.\n      A broadcasted variable should not depend on any computation that cannot b\n      lifted out of the loop. This is typically used to define shared parameters\n      inside the fn.\n    variable_carry: Specifies the variable collections that are carried through\n      the loop. Mutations to these variables are carried to the next iteration\n      and will be preserved when the scan finishes.\n    split_rngs: Split PRNG sequences will be different for each loop iterations.\n      If split is False the PRNGs will be the same across iterations.\n    in_axes: Specifies the axis to scan over for the arguments. Should be a\n      prefix tree of the arguments. Use `flax.core.broadcast` to feed an entire\n      input to each iteration of the scan body.\n    out_axes: Specifies the axis to scan over for the return value. Should be a\n      prefix tree of the return value.\n    length: Specifies the number of loop iterations. This only needs\n      to be specified if it cannot be derived from the scan arguments.\n    reverse: If true, scan from end to start in reverse order.\n    unroll: how many scan iterations to unroll within a single\n      iteration of a loop (default: 1).\n    _split_transpose: An experimental feature to split the transpose of a scan\n       into a scan and a map, backed by an experimental Jax lax.scan() feature.\n    data_transform: optional function to transform raw variable and rng groups,\n      intended for inline SPMD annotations.\n    metadata_params: arguments dict passed to AxisMetadata instances in the\n      variable tree.\n    check_constancy_invariants: If true, the scan will verify that the\n      broadcast constants are true loop invariants, and further supports\n      broadcast function (non-carry) outputs.  This requires an extra jax\n      tracing step however, so setting to false can reduce trace time on larger\n      models.\n\n  Returns:\n    The scan function with the signature\n    ``(scope, carry, *xxs) -> (carry, yys)``, where ``xxs`` and ``yys`` are the\n    scan values that go in and out of the loop.\n  \"\"\"\n  variable_in_axes, variable_out_axes = _split_in_out_axes(variable_axes)\n  variable_in_groups, variable_in_axes = _unzip2(variable_in_axes.items())\n  variable_out_groups, variable_out_axes = _unzip2(variable_out_axes.items())\n  assert all(isinstance(ax, int) for ax in variable_in_axes)\n  assert all(isinstance(ax, int) for ax in variable_out_axes)\n  rng_groups, rng_splits = _unzip2(split_rngs.items())\n  rng_axes = tuple(\n    0 if rng_split else axes_scan.broadcast for rng_split in rng_splits\n  )\n\n  def inner(scope_fn, repack_fn, variable_groups, rng_groups, init, *args):\n    def find_length(axis, x):\n      if axis is not axes_scan.broadcast:\n        leaves = jax.tree_util.tree_leaves(x)\n        if leaves:\n          return leaves[0].shape[axis]\n      return ()\n\n    # split rngs\n    lengths = jax.tree_util.tree_map(find_length, in_axes, args)\n    lengths = set(jax.tree_util.tree_leaves(lengths))\n    if length is None and len(lengths) == 1:\n      (d_length,) = lengths\n    elif len(lengths) > 1:\n      raise ValueError(f'Inconsistent scan lengths: {lengths}')\n    elif length is None:\n      raise ValueError('length should be specified manually.')\n    else:\n      d_length = length\n    # random.clone is only available on Jax versions 0.4.26 or newer\n    # see: https://jax.readthedocs.io/en/latest/jax.experimental.key_reuse.html\n    if hasattr(random, 'clone'):\n      split_fn = lambda rng: random.split(random.clone(rng), d_length)\n    else:\n      split_fn = lambda rng: random.split(rng, d_length)\n\n    rng_groups = tuple(\n        tree_map_rngs(split_fn, rng_group) if split else rng_group\n        for rng_group, split in zip(rng_groups, rng_splits)\n    )\n\n    @functools.partial(\n        axes_scan.scan,\n        in_axes=(variable_in_axes, rng_axes, in_axes),\n        out_axes=(out_axes, variable_out_axes),\n        length=length,\n        reverse=reverse,\n        unroll=unroll,\n        _split_transpose=_split_transpose,\n        check_constancy_invariants=check_constancy_invariants,\n    )\n    def scanned(broadcast_vars, carry, scan_variable_groups, rng_groups, args):\n      carry_vars, c = carry\n      variable_groups = (broadcast_vars, carry_vars) + scan_variable_groups\n      if data_transform is not None:\n        variable_groups, rng_groups = data_transform(\n            variable_groups, rng_groups\n        )\n      scope = scope_fn(variable_groups, rng_groups)\n      c, y = fn(scope, c, *args)\n      out_vars = repack_fn(scope)\n      broadcast_vars_out = out_vars[0]\n      carry_vars = out_vars[1]\n      scan_vars = out_vars[2:]\n      # add immutable broadcast vars back to broadcast output\n      # otherwise they won't be fed to the actual scan body\n      for in_group, out_group in zip(broadcast_vars, broadcast_vars_out):\n        for col in in_group:\n          if col not in out_group:\n            out_group[col] = in_group[col]\n      return broadcast_vars_out, (carry_vars, c), (y, scan_vars)\n\n    broadcast_vars = variable_groups[0]\n    carry_vars = variable_groups[1]\n    scan_vars = variable_groups[2:]\n    new_scan_vars = []\n    for scan_group, axis in zip(scan_vars, variable_in_axes):\n      new_scan_vars.append(meta.remove_axis(scan_group, axis, metadata_params))\n    broadcast_vars, (carry_vars, c), (ys, scan_vars) = scanned(\n      broadcast_vars,\n      (carry_vars, init),\n      tuple(new_scan_vars),\n      rng_groups,\n      args,\n    )\n    new_scan_vars = []\n    for scan_group, axis in zip(scan_vars, variable_out_axes):\n      new_scan_vars.append(meta.add_axis(scan_group, axis, metadata_params))\n    scan_vars = tuple(new_scan_vars)\n    out_vars = (\n      broadcast_vars,\n      carry_vars,\n    ) + scan_vars\n    return (c, ys), out_vars\n\n  return pack(\n    inner,\n    (variable_broadcast, variable_carry) + variable_in_groups,\n    (variable_broadcast, variable_carry) + variable_out_groups,\n    rng_groups,\n    name='scan',\n  )\n\n\nC = TypeVar('C')\n\n\ndef while_loop(\n  cond_fn: Callable[[Scope, C], bool],\n  body_fn: Callable[[Scope, C], C],\n  scope: Scope,\n  init: C,\n  carry_variables: CollectionFilter = False,\n  broadcast_variables: CollectionFilter = True,\n  split_rngs: Mapping[PRNGSequenceFilter, bool] = {},\n) -> C:\n  \"\"\"Lifted version of jax.lax.while_loop.\n\n  The lifted scope is passed to `cond_fn` and `body_fn`.\n  Broadcasted variables are immutable. The carry variable are\n  mutable but cannot change shape and dtype.\n  This also means you cannot initialize variables inside\n  the body. Consider calling `body_fn` once manually before\n  calling `while_loop` if variable initialization is required.\n\n  Example::\n\n    def f(scope, x):\n      def cond_fn(scope, c):\n        return scope.get_variable('state', 'acc') < 10\n      def body_fn(scope, c):\n        acc = scope.variable('state', 'acc')\n        acc += 1\n        y = scope.child(nn.dense)(c, c.shape[-1])\n        return y\n\n      c = x\n      c = body_fn(scope, c)\n      return lift.while_loop(cond_fn, body_fn, scope, (),\n                             carry_variables='state')\n\n  Args:\n    cond_fn: Should return True as long as the loop should continue.\n    body_fn: The body of the while loop.\n    scope: The scope(s) which should be lifted into the loop.\n    init: The initial state passed to the loop\n    carry_variables: collections that are carried through the loop\n      and are therefore mutable (default: none).\n    broadcast_variables: collections that are closed over and are\n      therefore read-only (default: all collections)\n    split_rngs: Split PRNG sequences will be different for each loop iterations.\n      If split is False the PRNGs will be the same across iterations.\n  Returns:\n    The final state after executing the while loop.\n  \"\"\"\n  rng_groups, rng_splits = _unzip2(split_rngs.items())\n\n  def inner(scope_fn, repack_fn, variable_groups, rng_groups):\n    carry_variables, broadcast_variables = variable_groups\n\n    def make_loop_rngs(i):\n      local_rng_groups = []\n      for rng_group, rng_split in zip(rng_groups, rng_splits):\n        if rng_split:\n          rng_group = tree_map_rngs(\n            lambda rng: random.fold_in(rng, i), rng_group\n          )\n        local_rng_groups.append(rng_group)\n      return local_rng_groups\n\n    def cond_wrapper(c):\n      i, carry_variables, carry = c\n      scope = scope_fn(\n        (carry_variables, broadcast_variables),\n        make_loop_rngs(-i),\n        mutable_filter=False,\n      )\n      return cond_fn(scope, carry)\n\n    def body_wrapper(c):\n      i, carry_variables, carry = c\n      scope = scope_fn(\n        (carry_variables, broadcast_variables), make_loop_rngs(i)\n      )\n      carry = body_fn(scope, carry)\n      (carry_variables,) = repack_fn(scope)\n      return (i + 1, carry_variables, carry)\n\n    c = (0, carry_variables, init)\n    _, carry_variables, carry = jax.lax.while_loop(\n      cond_wrapper, body_wrapper, c\n    )\n    return carry, (carry_variables,)\n\n  return pack(\n    inner,\n    (carry_variables, broadcast_variables),\n    (carry_variables,),\n    rng_groups,\n    name='while_loop',\n  )(scope)\n\n\ndef cond(\n  pred: Any,\n  true_fun: Callable[..., C],\n  false_fun: Callable[..., C],\n  scope: Scope,\n  *operands,\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n) -> C:\n  \"\"\"Lifted version of ``jax.lax.cond``.\n\n  The returned values from ``true_fun`` and ``false_fun``\n  must have the same Pytree structure, shapes, and dtypes.\n  The variables created or updated inside the\n  branches must also have the same structure.\n  Note that this constraint is violated when\n  creating variables or submodules in only one branch.\n  Because initializing variables in just one branch\n  causes the parameter structure to be different.\n\n  Example::\n\n    def cond_example(scope, x, pred):\n      scope.variable('state', 'true_count', lambda: 0)\n      scope.variable('state', 'false_count', lambda: 0)\n      def true_fn(scope, x):\n        scope.variable('state', 'true_count').value += 1\n        return scope.child(nn.dense)(x, 2)\n      def false_fn(scope, x):\n        scope.variable('state', 'false_count').value += 1\n        return -scope.child(nn.dense)(x, 2)\n      return lift.cond(pred, true_fn, false_fn, scope, x)\n\n\n  Args:\n    pred: determines if true_fun or false_fun is evaluated.\n    true_fun: The function evalauted when ``pred`` is `True`.\n      The signature is (Scope, *operands) -> T.\n    false_fun: The function evalauted when ``pred`` is `False`.\n      The signature is (Scope, *operands) -> T.\n    scope: A Scope or Pytree of scopes to pass\n    *operands: The arguments passed to ``true_fun`` and ``false_fun``\n    variables: The variable collections passed to the conditional\n      branches (default: all)\n    rngs: The PRNG sequences passed to the conditionals (default: all)\n  Returns:\n    The result of the evaluated branch (``true_fun`` or ``false_fun``).\n  \"\"\"\n  branches = [true_fun, false_fun]\n\n  def inner(scope_fn, repack_fn, variable_groups, rng_groups):\n    def branch_wrapper(branch_fn, *operands):\n      scope = scope_fn(variable_groups, rng_groups)\n      y = branch_fn(scope, *operands)\n      return y, repack_fn(scope)\n\n    pure_branches = [\n      functools.partial(branch_wrapper, branch_fn) for branch_fn in branches\n    ]\n    return jax.lax.cond(pred, pure_branches[0], pure_branches[1], *operands)\n\n  return pack(inner, (variables,), (variables,), (rngs,), name='cond')(scope)\n\n\ndef switch(\n  index: Any,\n  branches: Sequence[Callable[..., C]],\n  scope: Scope,\n  *operands,\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n) -> C:\n  \"\"\"Lifted version of ``jax.lax.switch``.\n\n  The returned values from ``branches``\n  must have the same Pytree structure, shapes, and dtypes.\n  The variables created or updated inside the\n  branches must also have the same structure.\n  Note that this constraint is violated when\n  creating variables or submodules in only one branch.\n  Because initializing variables in just one branch\n  causes the parameter structure to be different.\n\n  Example::\n\n    def switch_example(scope, x, index):\n      scope.variable('state', 'a_count', lambda: 0)\n      scope.variable('state', 'b_count', lambda: 0)\n      scope.variable('state', 'c_count', lambda: 0)\n      def a_fn(scope, x):\n        scope.variable('state', 'a_count').value += 1\n        return scope.child(nn.dense)(x, 2)\n      def b_fn(scope, x):\n        scope.variable('state', 'b_count').value += 1\n        return -scope.child(nn.dense)(x, 2)\n      def c_fn(scope, x):\n        scope.variable('state', 'c_count').value += 1\n        return scope.child(nn.dense)(x, 2)\n      return lift.switch(index, [a_fn, b_fn, c_fn], scope, x)\n\n  If you want to have a different parameter structure for each branch\n  you should run all branch on initialization before calling switch::\n\n    def multihead_switch_example(scope, x, index):\n      def a_fn(scope, x):\n        x = scope.child(nn.dense)(x, 10)\n        x = scope.child(nn.dense)(x, 7)\n        return scope.child(nn.dense)(x, 5)\n      def b_fn(scope, x):\n        x = scope.child(nn.dense)(x, 11)\n        return scope.child(nn.dense)(x, 5)\n      def c_fn(scope, x):\n        return scope.child(nn.dense)(x, 5)\n\n      branches = [a_fn, b_fn, c_fn]\n\n      # run all branches on init\n      if scope.is_mutable_collection('params'):\n        for branch in branches:\n          _ = branch(scope, x)\n\n      return lift.switch(index, branches, scope, x)\n\n  Args:\n    index: Integer scalar type, indicating which branch function to apply.\n    branches: Sequence of functions to be applied based on index.\n      The signature of each function is (Scope, *operands) -> T.\n    scope: A Scope or Pytree of scopes to pass\n    *operands: The arguments passed to ``true_fun`` and ``false_fun``\n    variables: The variable collections passed to the conditional\n      branches (default: all)\n    rngs: The PRNG sequences passed to the conditionals (default: all)\n  Returns:\n    The result of the evaluated branch.\n  \"\"\"\n\n  def inner(scope_fn, repack_fn, variable_groups, rng_groups):\n    def branch_wrapper(branch_fn, *operands):\n      scope = scope_fn(variable_groups, rng_groups)\n      y = branch_fn(scope, *operands)\n      return y, repack_fn(scope)\n\n    pure_branches = [\n      functools.partial(branch_wrapper, branch_fn) for branch_fn in branches\n    ]\n    return jax.lax.switch(index, pure_branches, *operands)\n\n  return pack(inner, (variables,), (variables,), (rngs,), name='switch')(scope)\n\n\ndef custom_vjp(\n  fn: Callable[..., Any],\n  forward_fn: Callable[..., Any],\n  backward_fn: Callable[..., Any],\n  grad_vars: CollectionFilter = 'params',\n  nondiff_argnums=(),\n):\n  \"\"\"Lifted version of `jax.custom_vjp`.\n\n  `forward_fn` and `backward_fn` together define a custom vjp for `fn`.\n  The original `fn` will run in case a vjp (backward gradient) is not computed.\n\n  The `forward_fn` receives the same arguments as `fn` but is expected to return\n  a tuple containing the output of `fn(scope, *args)` and the residuals that are\n  passed to `backward_fn`.\n\n  The `backward_fn` receives the nondiff arguments, residuals, and the output\n  tangents. It should return a tuple containing the variable and input tangents.\n\n  Note that the vjp function returned by `lift.vjp` can be passed as residual\n  and used in the `backward_fn`. The scope is unavailable during the backward\n  pass. If the scope is required in `backward_fn`, a snapshot of the variables\n  can be taken and returned as a residual in the `forward_fn`.\n\n  Example::\n\n    f = nn.dense\n\n    def fwd(scope, x, features):\n      y, vjp_fn = lift.vjp(partial(f, features=features), scope, x)\n      return y, vjp_fn\n\n    def bwd(features, vjp_fn, y_t):\n      params_t, *inputs_t = vjp_fn(y_t)\n      params_t = jax.tree_util.tree_map(jnp.sign, params_t)\n      return (params_t, *inputs_t)\n\n    dense_sign_grad = lift.custom_vjp(\n        f, forward_fn=fwd, backward_fn=bwd, nondiff_argnums=(2,))\n\n  Args:\n    fn: The function to define a custom_vjp for. The first argument\n      should be a ``Module`` instance.\n    forward_fn: A function with the same arguments as `fn` returning an tuple\n      with the original output and the residuals that will be passed to\n      `backward_fn`.\n    backward_fn: arguments are passed as (*nondiff_args, residuals, tangents)\n      The function should return a tuple containing the tangents for the\n      variable in the collections specified by `grad_vars` and the input\n      arguments (except the scope and nondiff args).\n    grad_vars: The collections for which a vjp will be computed\n      (default: \"params\").\n    nondiff_argnums: arguments for which no vjp is computed.\n  Returns:\n    A function with the same signature as `fn` with the custom vjp.\n  \"\"\"\n\n  def inner(scope_fn, repack_fn, variable_groups, rng_groups, *args):\n    grad_variables, other_variables = variable_groups\n    scopes_treedef = None\n\n    def f(grad_variables, *args):\n      scope = scope_fn((grad_variables, other_variables), rng_groups)\n      y = fn(scope, *args)\n      vars_out = repack_fn(scope)\n      return y, vars_out\n\n    f = jax.custom_vjp(f, nondiff_argnums=nondiff_argnums)\n\n    def f_fwd(grad_variables, *args):\n      nonlocal scopes_treedef\n      scopes = scope_fn((grad_variables, other_variables), rng_groups)\n      scopes_treedef = jax.tree_util.tree_structure(scopes)\n      y, res = forward_fn(scopes, *args)\n      vars_out = repack_fn(scopes)\n      return (y, vars_out), res\n\n    def f_bwd(*args):\n      # the backward function does not pass a lifted scope to the user.\n      # Currently, there is no way to have side effects flow out of backward\n      # pass. Even without mutation variables would be ill-defined. For example,\n      # would we take a snapshot of the variables before or after calling\n      # `forward_fn`?\n      nondiff_args = args[:-2]\n      res, g = args[-2:]  # pylint: disable=unbalanced-tuple-unpacking\n      g_y, _ = g\n      var_t, *inputs_t = backward_fn(*nondiff_args, res, g_y)\n      assert scopes_treedef is not None, 'backward called before forward?!'\n      var_t = tuple(scopes_treedef.flatten_up_to(var_t))\n      return (var_t, *inputs_t)\n\n    f.defvjp(f_fwd, f_bwd)\n\n    return f(grad_variables, *args)\n\n  variable_in_groups = (grad_vars, True)\n  variable_out_groups = (grad_vars, True)\n  rng_groups = (True,)\n  return pack(\n    inner,\n    variable_in_groups,\n    variable_out_groups,\n    rng_groups,\n    name='custom_vjp',\n  )\n\n\ndef checkpoint(\n  fn: Callable[..., Any],\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n  concrete: bool = False,\n  prevent_cse: bool = True,\n  static_argnums: int | tuple[int, ...] = (),\n  policy: Callable[..., bool] | None = None,\n) -> Callable[..., Any]:\n  \"\"\"Lifted version of ``jax.checkpoint``.\n\n  This function is aliased to ``lift.remat`` just like ``jax.remat``.\n\n  Args:\n    fn: scope function for which intermediate computations should be re-computed\n      when computing gradients.\n    variables: The variable collections that are lifted. By default all\n      collections are lifted.\n    rngs: The PRNG sequences that are lifted. By default all PRNG sequences are\n      lifted.\n    concrete: Optional, boolean indicating whether ``fun`` may involve\n      value-dependent Python control flow (default ``False``). Support for such\n      control flow is optional, and disabled by default, because in some\n      edge-case compositions with :func:`jax.jit` it can lead to some extra\n      computation.\n    prevent_cse: Optional, boolean indicating whether to prevent common\n      subexpression elimination (CSE) optimizations in the HLO generated from\n      differentiation. This CSE prevention has costs because it can foil other\n      optimizations, and because it can incur high overheads on some backends,\n      especially GPU. The default is True because otherwise, under a ``jit`` or\n      ``pmap``, CSE can defeat the purpose of this decorator. But in some\n      settings, like when used inside a ``scan``, this CSE prevention mechanism\n      is unnecessary, in which case ``prevent_cse`` can be set to False.\n    static_argnums: Optional, int or sequence of ints, indicates which argument\n      values on which to specialize for tracing and caching purposes. Specifying\n      arguments as static can avoid ConcretizationTypeErrors when tracing, but\n      at the cost of more retracing overheads.\n    policy: Experimental checkpoint policy, see ``jax.checkpoint``.\n\n  Returns:\n    A wrapped version of ``fn``. When computing gradients intermediate\n    computations will be re-computed when computing gradients.\n  \"\"\"\n\n  def inner(scope_fn, repack_fn, variable_groups, rng_groups, *args, **kwargs):\n    # add 2 to each static_argnums because we add two initial arguments to rematted\n    static_argnums_ = jax.tree_util.tree_map(lambda x: x + 2, static_argnums)\n\n    # After JAX v0.3.16, concrete=False is a no-op and concrete=True raises\n    # NotImplementedError. Starting in JAX v0.8.2, the concrete argument is\n    # deprecated and will be removed in the future.\n    if concrete:\n      raise NotImplementedError(\n          \"The concrete argument is deprecated. Use static_argnums instead.\"\n          \" for more information, see\"\n          \" https://docs.jax.dev/en/latest/jep/11830-new-remat-checkpoint.html\"\n      )\n\n    @functools.partial(\n      jax.remat,\n      static_argnums=static_argnums_,\n      prevent_cse=prevent_cse,\n      policy=policy,\n    )\n    @functools.wraps(fn)\n    def rematted(variable_groups, rng_groups, *args, **kwargs):\n      scope = scope_fn(variable_groups, rng_groups)\n      y = fn(scope, *args, **kwargs)\n      return y, repack_fn(scope)\n\n    return rematted(variable_groups, rng_groups, *args, **kwargs)\n\n  return pack(\n    inner,\n    (variables,),\n    (variables,),\n    (rngs,),\n    name='remat',\n    enable_kwargs=True,\n  )\n\n\nremat = checkpoint\n\n\ndef _hashable_filter(x):\n  \"\"\"Hashable version of CollectionFilter.\"\"\"\n  if isinstance(x, str):\n    return (x,)\n  if isinstance(x, Iterable):\n    return tuple(x)  # convert un-hashable list & sets to tuple\n  if isinstance(x, DenyList):\n    return DenyList(\n      _hashable_filter(x.deny)\n    )  # convert inner filter recursively\n  return x\n\n\nclass CountsHolder:\n\n  def __init__(self, flat_d):\n    self.flat_d = flat_d\n\n  @classmethod\n  def make(cls, d):\n    flat_d = traverse_util.flatten_dict(d)\n    flat_d = {k: v for k, v in flat_d.items()}\n    return cls(flat_d)\n\n  def sub(self, other):\n    delta_flat_d = {}\n    new_flat_d = collections.defaultdict(int, self.flat_d)\n    old_flat_d = collections.defaultdict(int, other.flat_d)\n    for k in new_flat_d:\n      delta_flat_d[k] = new_flat_d[k] - old_flat_d[k]\n    return CountsHolder(delta_flat_d)\n\n  def add(self, other):\n    delta_flat_d = {}\n    new_flat_d = collections.defaultdict(int, self.flat_d)\n    old_flat_d = collections.defaultdict(int, other.flat_d)\n    for k in new_flat_d:\n      delta_flat_d[k] = new_flat_d[k] + old_flat_d[k]\n    return CountsHolder(delta_flat_d)\n\n  def unflat(self):\n    return traverse_util.unflatten_dict(self.flat_d)\n\n\ndef set_from_dict(original, updates):\n  for k in updates:\n    if k not in original:\n      original[k] = updates[k]\n    else:\n      if isinstance(updates[k], dict):\n        set_from_dict(original[k], updates[k])\n      else:\n        original[k] = updates[k]\n\n\nclass _SideEffectCache(threading.local):\n\n  def __init__(self):\n    self.cache = {}\n\n\n_side_effect_cache = _SideEffectCache()\n\n\ndef _restore_rng_counters(scopes, fingerprint, capture_old_counts):\n  if fingerprint not in _side_effect_cache.cache:\n    capture_new_counts = jax.tree.map(\n        lambda s: CountsHolder.make(s.rng_counters), scopes\n    )\n    capture_delta_counts = jax.tree.map(\n        lambda old, new: new.sub(old),\n        capture_old_counts,\n        capture_new_counts,\n    )\n    _side_effect_cache.cache[fingerprint] = capture_delta_counts\n  else:\n    updated_counts = jax.tree.map(\n        lambda x, y: x.add(y).unflat(),\n        _side_effect_cache.cache[fingerprint],\n        capture_old_counts,\n    )\n    jax.tree.map(\n        lambda s, u: set_from_dict(s.rng_counters, u),\n        scopes,\n        updated_counts,\n    )\n\n\ndef jit(\n    fn: Callable[..., Any],\n    variables: CollectionFilter = True,\n    rngs: PRNGSequenceFilter = True,\n    static_argnums: int | Iterable[int] = (),\n    static_argnames: str | Iterable[str] = (),\n    donate_argnums: int | Iterable[int] = (),\n    device=None,\n    backend: str | None = None,\n) -> Callable[..., Any]:\n  \"\"\"Lifted version of ``jax.jit``.\n\n  Args:\n    fn: Scope function to be jitted.\n    variables: The variable collections that are lifted. By default all\n      collections are lifted.\n    rngs: The PRNG sequences that are lifted. By default all PRNG sequences\n      are lifted.\n    static_argnums: An int or collection of ints specifying which positional\n      arguments to treat as static (compile-time constant). Operations that only\n      depend on static arguments will be constant-folded in Python (during\n      tracing), and so the corresponding argument values can be any Python\n      object. Static arguments should be hashable, meaning both ``__hash__`` and\n      ``__eq__`` are implemented, and immutable. Calling the jitted function\n      with different values for these constants will trigger recompilation. If\n      the jitted function is called with fewer positional arguments than\n      indicated by ``static_argnums`` then an error is raised. Arguments that\n      are not arrays or containers thereof must be marked as static.\n      Defaults to ().\n    static_argnames: An optional string or collection of strings specifying\n      which named arguments to treat as static (compile-time constant). See the\n      comment on ``static_argnums`` for details. If not\n      provided but ``static_argnums`` is set, the default is based on calling\n      ``inspect.signature(fun)`` to find corresponding named arguments.\n    donate_argnums: Specify which arguments are \"donated\" to the computation.\n      It is safe to donate arguments if you no longer need them once the\n      computation has finished. In some cases XLA can make use of donated\n      buffers to reduce the amount of memory needed to perform a computation,\n      for example recycling one of your input buffers to store a result. You\n      should not reuse buffers that you donate to a computation, JAX will raise\n      an error if you try to.\n    device: This is an experimental feature and the API is likely to change.\n      Optional, the Device the jitted function will run on. (Available devices\n      can be retrieved via :py:func:`jax.devices`.) The default is inherited\n      from XLA's DeviceAssignment logic and is usually to use\n      ``jax.devices()[0]``.\n    backend: a string representing the XLA backend: ``'cpu'``, ``'gpu'``, or\n      ``'tpu'``.\n\n  Returns:\n    A wrapped version of ``fn``, set up for just-in-time compilation.\n  \"\"\"\n  if not isinstance(static_argnums, Iterable):\n    static_argnums = (static_argnums,)\n  if not isinstance(donate_argnums, Iterable):\n    donate_argnums = (donate_argnums,)\n  # offset argnums by two because first argument in the original function is the\n  # scope while jitted has 3 functions before the user arguments.\n  static_argnums = (0,) + tuple(i + 2 for i in static_argnums if i > 0)\n  donate_argnums = tuple(i + 2 for i in donate_argnums if i > 0)\n\n  # Close over scope_fn & repack_fn to avoid recompilation\n  # this is impure but we use the fingerprint arg to differentiate between cases\n  # where scope_fn or repack_fn actually produce non-identical results.\n  jit_context = TransformContext[tuple[Callable, Callable]]()\n\n  @functools.partial(\n      jax.jit,\n      static_argnums=static_argnums,\n      static_argnames=static_argnames,\n      donate_argnums=donate_argnums,\n      device=device,\n      backend=backend,\n  )\n  @functools.wraps(fn)\n  def jitted(fingerprint, variable_groups, rng_groups, *args, **kwargs):\n    scope_fn, repack_fn = jit_context.get()\n    hash_key = fingerprint[1]\n    # fingerprint is only used to differentiate the cache signature\n    # del fingerprint\n    scope = scope_fn(variable_groups, rng_groups)  # pylint: disable=not-callable\n    y = fn(scope, hash_key, *args, **kwargs)\n    return y, repack_fn(scope)  # pylint: disable=not-callable\n\n  def inner(\n      scope_fn,\n      repack_fn,\n      variable_groups,\n      rng_groups,\n      module_hash_key,\n      *args,\n      **kwargs,\n  ):\n    with jit_context.push((scope_fn, repack_fn)):\n      scopes: list[Scope] = jax.tree_util.tree_leaves(\n          scope_fn(variable_groups, rng_groups)\n      )\n      mutable = tuple(_hashable_filter(scope.mutable) for scope in scopes)\n\n      rng_groups = jax.tree.map(\n          lambda x: x.clear_suffix() if isinstance(x, LazyRng) else x,\n          rng_groups,\n          is_leaf=lambda x: isinstance(x, LazyRng),\n      )\n\n      fingerprint = (mutable, module_hash_key)\n      capture_old_counts = jax.tree.map(\n          lambda s: CountsHolder.make(s.rng_counters), scopes\n      )\n      res = jitted(fingerprint, variable_groups, rng_groups, *args, **kwargs)\n      _restore_rng_counters(scopes, fingerprint, capture_old_counts)\n      return res\n\n  return pack(\n      inner, (variables,), (variables,), (rngs,), name='jit', enable_kwargs=True\n  )\n\n\ndef remat_scan(\n  body_fn: Callable[..., Any],\n  lengths: Sequence[int],\n  policy: Callable[..., bool] | None = None,\n  variable_broadcast: CollectionFilter = False,\n  variable_carry: CollectionFilter = False,\n  variable_axes: Mapping[CollectionFilter, InOutScanAxis] = {True: 0},\n  split_rngs: Mapping[PRNGSequenceFilter, bool] = {True: True},\n) -> Callable[..., Any]:\n  \"\"\"Combines `lift.remat` and `lift.scan` for memory efficiency and constant time compilation.\n\n  ``remat_scan`` allows for constant compile times and sublinear\n  memory usage with respect to model depth. At a small constant\n  penalty. This is typically beneficial for very deep models.\n\n  Example::\n\n    def body_fn(scope, x):\n      return nn.dense(scope, x, features=x.shape[-1])\n    # 100x dense with O(sqrt(N)) memory for gradient computation\n    y = lift.remat_scan(body_fn, lengths=(10, 10))(scope, x)\n\n  Args:\n    body_fn: Scope function to be repeated using a (nested scan)\n    lengths: number of loop iterations at the given level. The total number of\n      iterations `n = prod(lengths)`. each loop is rematerialized. This way the\n      memory consumption is proportional to `n^(1 / d)` where `d =\n      len(lengths)`. Minimal memory consumptions requires tuning the lengths\n      such that the same amount of memory is consumed at each level of the\n      nested loop.\n    policy: Experimental checkpoint policy, see ``jax.checkpoint``.\n    variable_broadcast: Specifies the broadcasted variable collections. A\n      broadcasted variable should not depend on any computation that cannot be\n      lifted out of the loop. This is typically used to define shared parameters\n      inside the fn.\n    variable_carry: Specifies the variable collections that are carried through\n      the loop. Mutations to these variables are carried to the next iteration\n      and will be preserved when the scan finishes.\n    variable_axes: the variable collections that are scanned over.\n    split_rngs: Split PRNG sequences will be different for each loop iterations.\n      If split is False the PRNGs will be the same across iterations.\n  Returns:\n    A wrapped version of ``body_fn`` that repeats itself prod(lengths) times.\n  \"\"\"\n  # TODO(jheek) should remat scan have scan inputs/outputs?\n  scan_fn = functools.partial(\n    scan,\n    variable_broadcast=variable_broadcast,\n    variable_carry=variable_carry,\n    variable_axes=variable_axes,\n    split_rngs=split_rngs,\n  )\n  if len(lengths) == 1:\n\n    def wrapper(scope, carry):\n      return body_fn(scope, carry), ()\n\n    fn = lambda scope, c: scan_fn(wrapper, length=lengths[0])(scope, c)[0]\n  else:\n\n    @functools.partial(remat, policy=policy, prevent_cse=False)\n    def inner_loop(scope, carry):\n      carry = remat_scan(\n        body_fn,\n        lengths[1:],\n        policy,\n        variable_broadcast,\n        variable_carry,\n        variable_axes,\n        split_rngs,\n      )(scope, carry)\n      return carry, ()\n\n    fn = lambda scope, c: scan_fn(inner_loop, length=lengths[0])(scope, c)[0]\n  return fn\n\n\ndef _unzip2(xs):\n  ys = tuple(zip(*xs))\n  return ys if ys else ((), ())\n\n\ndef _broadcast_prefix_tree(prefix_tree: Any, full_tree: Any) -> list[Any]:\n  bcast_flat = []\n  num_leaves_fn = lambda t: jax.tree.flatten(t)[1].num_leaves\n  jax.tree.map(\n      lambda x, subtree: bcast_flat.extend([x] * num_leaves_fn(subtree)),\n      prefix_tree,\n      full_tree,\n      is_leaf=lambda x: x is None,\n  )\n  return bcast_flat\n\n\ndef fold_rngs(\n    fn: Callable[..., Any],\n    variables: CollectionFilter = True,\n    rngs: PRNGSequenceFilter = True,\n) -> Callable[..., Any]:\n  # Close over scope_fn & repack_fn to avoid recompilation\n  # this is impure but we use the fingerprint arg to differentiate between cases\n  # where scope_fn or repack_fn actually produce non-identical results.\n  fold_rngs_context = TransformContext[tuple[Callable, Callable]]()\n\n  @functools.wraps(fn)\n  def wrapped_fold_rngs(fingerprint, variable_groups, rng_groups, *args, **kwargs):\n    scope_fn, repack_fn = fold_rngs_context.get()\n    hash_key = fingerprint[1]\n    # fingerprint is only used to differentiate the cache signature\n    # del fingerprint\n    scope = scope_fn(variable_groups, rng_groups)  # pylint: disable=not-callable\n    y = fn(scope, hash_key, *args, **kwargs)\n    return y, repack_fn(scope)  # pylint: disable=not-callable\n\n  def inner_fold_rngs(\n      scope_fn,\n      repack_fn,\n      variable_groups,\n      rng_groups,\n      module_hash_key,\n      *args,\n      **kwargs,\n  ):\n    with fold_rngs_context.push((scope_fn, repack_fn)):\n      scopes: list[Scope] = jax.tree_util.tree_leaves(\n          scope_fn(variable_groups, rng_groups)\n      )\n      mutable = tuple(_hashable_filter(scope.mutable) for scope in scopes)\n\n      rng_groups = jax.tree.map(\n          lambda x: x.clear_suffix() if isinstance(x, LazyRng) else x,\n          rng_groups,\n          is_leaf=lambda x: isinstance(x, LazyRng),\n      )\n\n      fingerprint = (mutable, module_hash_key)\n      capture_old_counts = jax.tree.map(\n          lambda s: CountsHolder.make(s.rng_counters), scopes\n      )\n      res = wrapped_fold_rngs(\n          fingerprint, variable_groups, rng_groups, *args, **kwargs\n      )\n      _restore_rng_counters(scopes, fingerprint, capture_old_counts)\n      return res\n\n  return pack(\n      inner_fold_rngs,\n      (variables,),\n      (variables,),\n      (rngs,),\n      name='fold_rngs',\n      enable_kwargs=True,\n  )\n"
  },
  {
    "path": "flax/core/meta.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Boxed Metadata API\n\nBoxed metadata enables tracking arbitrary metadata for linen variables\nthat is compatible with lifted transformations.\n\nSee ``Partitioned`` for a practical example on how to use this metadata\nto keep track of how variables should be partitioned with ``jax.pjit``.\n\"\"\"\n\nimport abc\nimport dataclasses\nimport functools\nfrom typing import Any, Generic, TypeVar\nfrom collections.abc import Callable\n\nfrom flax import errors, struct\nfrom flax.typing import LogicalNames\nimport jax\n\nA = TypeVar('A')\nB = TypeVar('B')\nTAxisMetadata = TypeVar('TAxisMetadata', bound='AxisMetadata[Any]')\n\n\nclass AxisMetadata(Generic[A], metaclass=abc.ABCMeta):\n  \"\"\"Abstract base class for boxed Metadata.\n\n  ``AxisMetadata`` enables arbitrary, per axis metadata for variables.\n  By using ``unbox`` the metadata is stripped away to obtain the original\n  variables. By using unboxing, most code handling variables does not need\n  to handle ``AxisMetadata`` specifically, but can directly operate on the JAX\n  arrays that they wrap.\n\n  Additionally, ``AxisMetadata`` supports updating metadata whenever an axis\n  is added or removed by a functional transformation\n  (e.g.: ``nn.scan`` or ``nn.vmap``) using the ``add_axis`` and ``remove_axis``\n  methods.\n\n  By extending ``AxisMetadata``, custom metadata can be stored. See\n  ``Partitioned`` for a specific implementation.\n  \"\"\"\n\n  @abc.abstractmethod\n  def unbox(self) -> A:\n    \"\"\"Returns the content of the AxisMetadata box.\n\n    Note that unlike ``meta.unbox`` the unbox call should not recursively unbox\n    metadata. It should simply return value that it wraps directly even\n    if that value itself is an instance of AxisMetadata.\n\n    In practise, AxisMetadata subclasses should be registered as PyTree nodes to\n    support passing instances to JAX and Flax APIs. The leaves returned for this\n    node should correspond to the value returned by unbox.\n\n    Returns:\n      The unboxed value.\n    \"\"\"\n    pass\n\n  @abc.abstractmethod\n  def replace_boxed(self, val: B) -> 'AxisMetadata[B]':\n    \"\"\"Replaces the boxed value with the provided value.\n\n    Args:\n      val: The new value to be boxed by this AxisMetadata wrapper\n\n    Returns:\n      A new instance of the same type as self with `val` as the new ``unbox``\n      content\n    \"\"\"\n    pass\n\n  @abc.abstractmethod\n  def add_axis(\n      self: TAxisMetadata, index: int, params: dict[Any, Any]\n  ) -> TAxisMetadata:\n    \"\"\"Adds a new axis to the axis metadata.\n\n    Note that add_axis and remove_axis should act as each other's inverse\n    (meaning: ``x.add_axis(i, p).remove_axis(i, p) == x``)\n\n    Args:\n      index: The position at which the new axis will be inserted\n      params: An arbitrary dictionary of parameters passed by the transformation\n        that introduces the new axis (e.g.: ``nn.scan`` or ``nn.vmap``). The\n        user passes this dictionary as the `metadata_param` argument to the\n        transformation.\n\n    Returns:\n      A new instance of the same type as self and with the same ``unbox``\n      content with updated axis metadata.\n    \"\"\"\n    pass\n\n  @abc.abstractmethod\n  def remove_axis(\n      self: TAxisMetadata, index: int, params: dict[Any, Any]\n  ) -> TAxisMetadata:\n    \"\"\"Removes an axis from the axis metadata.\n\n    Note that add_axis and remove_axis should act as each other's inverse\n    (meaning: ``x.remove_axis(i, p).add_axis(i, p) == x``)\n\n    Args:\n      index: The position of the axis that is to be removed\n      params: An arbitrary dictionary of parameters passed by the transformation\n        that introduced the axis (e.g.: ``nn.scan`` or ``nn.vmap``). The user\n        passes this dictionary as the `metadata_param` argument to the\n        transformation.\n\n    Returns:\n      A new instance of the same type as self and with the same ``unbox``\n      content with updated axis metadata.\n    \"\"\"\n    pass\n\n\ndef is_axis_metadata(val: Any) -> bool:\n  \"\"\"Returns whether the argument is an instance of AxisMetadata.\"\"\"\n  return isinstance(val, AxisMetadata)\n\n\ndef map_axis_meta(fn: Callable[[AxisMetadata[Any]], Any], tree: Any) -> Any:\n  \"\"\"Maps over all PyTree nodes that are AxisMetadata instances.\"\"\"\n\n  def wrapper(x):\n    if isinstance(x, AxisMetadata):\n      return fn(x)\n    else:\n      return x\n\n  return jax.tree_util.tree_map(wrapper, tree, is_leaf=is_axis_metadata)\n\n\ndef add_axis(tree: Any, index: int, params: dict[Any, Any]) -> Any:\n  \"\"\"Add an axis to each AxisMetadata node in a PyTree.\"\"\"\n  return map_axis_meta(lambda x: x.add_axis(index, params), tree)\n\n\ndef remove_axis(tree: Any, index: int, params: dict[Any, Any]) -> Any:\n  \"\"\"Remove an axis from each AxisMetadata node in a PyTree.\"\"\"\n  return map_axis_meta(lambda x: x.remove_axis(index, params), tree)\n\n\ndef unbox(tree: Any) -> Any:\n  \"\"\"Strips all AxisMetadata boxes from a PyTree.\"\"\"\n  return map_axis_meta(lambda x: unbox(x.unbox()), tree)\n\n\ndef replace_boxed(tree: Any, updates: Any) -> Any:\n  \"\"\"Updates all AxisMetadata boxes with the values in updates.\"\"\"\n\n  def inner_update(c, v):\n    if isinstance(c, AxisMetadata):\n      return c.replace_boxed(replace_boxed(c.unbox(), v))\n    else:\n      return v\n\n  return jax.tree_util.tree_map(\n      inner_update, tree, updates, is_leaf=is_axis_metadata\n  )\n\n\nPARTITION_NAME = 'partition_name'\n\n\ndef get_global_mesh() -> jax.sharding.AbstractMesh | jax.sharding.Mesh | None:\n  mesh = jax.sharding.get_abstract_mesh()\n  if mesh.empty:\n    return None\n  return mesh\n\n\ndef global_mesh_defined() -> bool:\n  \"\"\"Checks if global mesh resource environment is defined.\"\"\"\n  mesh = get_global_mesh()\n  return mesh is not None\n\n\nclass Partitioned(struct.PyTreeNode, AxisMetadata[A]):\n  \"\"\"Wrapper for partitioning metadata.\n\n  ``Partitioned`` is used to extend variables with partitioning information\n  required for ``jax.experimental.pjit``.\n\n  The easiest way to define Partitioned variables is by using the\n  ``with_partitioning`` wrapper around the variable initializer.\n\n  Example::\n\n    class MLP(nn.Module):\n      hidden_size: int\n      @nn.compact\n      def __call__(self, x):\n        ki = nn.linear.default_kernel_init\n        h = nn.Dense(\n            self.hidden_size,\n            kernel_init=nn.with_partitioning(ki, ('data', 'model')))(x)\n        h = nn.relu(h)\n        return nn.Dense(\n            x.shape[-1],\n            kernel_init=nn.with_partitioning(ki, ('model', 'data')))(h)\n    mlp = MLP(4096)\n    x = jnp.ones((8 * 1024, 1024))\n    # use eval_shape to get the Partitioned instances for the variables.\n    # this way we can determine the PartitionSpecs for the init variables\n    # before we call the init fn.\n    var_spec = nn.get_partition_spec(\n        jax.eval_shape(mlp.init, random.key(0), x))\n    init_fn = mesh(pjit(mlp.init,\n                        (None, PartitionSpec(\"data\", \"model\")), var_spec))\n    variables = init_fn(random.key(0), x)\n    apply_fn = mesh(pjit(\n        mlp.apply,\n        (var_spec, PartitionSpec(\"data\", \"model\")),\n         PartitionSpec(\"data\", \"model\")))\n    apply_fn(variables, x)\n\n\n  ``Partitioned`` values can gain additional axes when using transformations\n  like ``nn.vmap`` and ``nn.scan``. In this case you can specify the name of\n  the new axis with the `metadata_params` args in vmap/scan::\n\n    class Model(nn.Module):\n    @nn.compact\n    def __call__(self, x):\n      def body(mdl, c):\n        c = MLP(4096)(c)\n        return c, ()\n      c, _ = nn.scan(\n          body, variable_axes={\"params\": 0}, split_rngs={\"params\": 0}, length=8,\n          metadata_params={nn.meta.PARTITION_NAME: \"layers\"})(self, x)\n      return c\n  \"\"\"\n\n  value: Any\n  names: LogicalNames = struct.field(pytree_node=False)\n  mesh: jax.sharding.Mesh | None = struct.field(\n      default=None, pytree_node=False\n  )\n\n  def unbox(self, apply_constraint=True) -> A:\n    \"\"\"Returns the wrapped value with the partitioning applied as a sharding constraint.\"\"\"\n    if apply_constraint and (global_mesh_defined() or self.mesh is not None):\n      axis_resource = self.get_partition_spec()\n      if self.mesh is not None:\n        sharding = jax.sharding.NamedSharding(self.mesh, axis_resource)\n        return jax.lax.with_sharding_constraint(self.value, sharding)\n      return jax.lax.with_sharding_constraint(self.value, axis_resource)\n    else:\n      return self.value\n\n  def replace_boxed(self, val: B) -> 'Partitioned[B]':\n    return self.replace(value=val)  # type: ignore\n\n  def _get_partition_name(self, params: dict[Any, Any]) -> str:\n    if PARTITION_NAME not in params:\n      raise errors.PartitioningUnspecifiedError(self)\n    return params[PARTITION_NAME]\n\n  def add_axis(self, index: int, params: dict[Any, Any]) -> 'Partitioned[A]':\n    axis_name = self._get_partition_name(params)\n    names = list(self.names)\n    while len(names) < index:\n      names.append(None)  # type: ignore\n    names.insert(index, axis_name)  # type: ignore\n    return self.replace(names=tuple(names))\n\n  def remove_axis(self, index: int, params: dict[Any, Any]) -> 'Partitioned[A]':\n    axis_name = self._get_partition_name(params)\n    names = list(self.names)\n    assert names.pop(index) == axis_name\n    return self.replace(names=tuple(names))\n\n  def get_partition_spec(self) -> jax.sharding.PartitionSpec:\n    \"\"\"Returns the ``Partitionspec`` for this partitioned value.\"\"\"\n    return jax.sharding.PartitionSpec(*self.names)\n\n  def get_sharding(self, mesh: jax.sharding.Mesh) -> jax.sharding.Sharding:\n    \"\"\"Returns the ``NamedSharding`` for this partitioned value.\"\"\"\n    return jax.sharding.NamedSharding(mesh, self.get_partition_spec())\n\n  def to_nnx_metadata(self) -> dict[str, Any]:\n    \"\"\"Return a dict of metadata that can translate into an `nnx.Variable`.\"\"\"\n    metadata = dict(vars(self))\n    metadata['out_sharding'] = metadata.pop('names')\n    return metadata\n\n  @classmethod\n  def from_nnx_metadata(cls, metadata: dict[str, Any]):\n    \"\"\"Given a dict of `nnx.Variable` format metadata, create a `nn.Partitioned`.\"\"\"\n    metadata['names'] = metadata.pop('out_sharding')\n    fields = {x.name for x in dataclasses.fields(cls)}\n    return cls(**{k: v for k, v in metadata.items() if k in fields})\n\n\ndef with_partitioning(\n    fn: Callable[..., Any],\n    names: LogicalNames,\n    mesh: jax.sharding.Mesh | None = None,\n) -> Callable[..., Partitioned[Any]]:\n  \"\"\"Wraps a function's return value with Partitioned.\n\n  Example::\n\n    >>> import flax.linen as nn\n    >>> kernel_init = nn.with_partitioning(\n    ...     nn.initializers.lecun_normal(), (None, \"data\"))\n    >>> partitioned_dense = nn.Dense(features=3, kernel_init=kernel_init)\n\n  Args:\n    fn: The function to be wrapped. Typically this is an initializer.\n    names: The logical axis passed to ``Partitioned``.\n    mesh: The mesh to use for the partitioning. If None, the global mesh\n      resource is used if available.\n\n  Returns:\n    A function wrapping ``fn`` that will return an instance of ``Partitioned``.\n  \"\"\"\n\n  @functools.wraps(fn)\n  def wrapper(*args, **kwargs):\n    return Partitioned(fn(*args, **kwargs), names, mesh=mesh)\n\n  return wrapper\n\n\ndef _get_leaf_pspec(x: Any) -> jax.sharding.PartitionSpec | None:\n  if hasattr(x, 'get_partition_spec'):\n    return x.get_partition_spec()\n  # Unboxed arrays, which should be replicated across all devices\n  elif hasattr(x, 'shape'):\n    return jax.sharding.PartitionSpec()\n  else:\n    return None\n\n\ndef get_partition_spec(tree: Any) -> Any:\n  \"\"\"Extracts a PartitionSpec tree from a PyTree containing ``Partitioned`` values.\"\"\"\n  return jax.tree_util.tree_map(\n      _get_leaf_pspec, tree, is_leaf=lambda x: isinstance(x, AxisMetadata)\n  )\n\n\ndef get_sharding(tree: Any, mesh: jax.sharding.Mesh) -> Any:\n  \"\"\"Extracts a jax.sharding tree from a PyTree containing ``Partitioned`` values and a mesh.\"\"\"\n  def f(x: Any) -> jax.sharding.Sharding | None:\n    if hasattr(x, 'get_sharding'):\n      return x.get_sharding(mesh)\n    pspec = _get_leaf_pspec(x)\n    if pspec is None:\n      return None\n    return jax.sharding.NamedSharding(mesh, pspec)\n\n  return jax.tree_util.tree_map(\n      f, tree, is_leaf=lambda x: isinstance(x, AxisMetadata)\n  )\n"
  },
  {
    "path": "flax/core/nn/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Flax Neural Network api.\"\"\"\n\n# pylint: disable=g-multiple-import\n# re-export commonly used modules and functions\nfrom flax.linen import activation as activation\nfrom flax.linen import initializers as initializers\nfrom flax.linen.activation import (\n    celu as celu,\n    elu as elu,\n    gelu as gelu,\n    glu as glu,\n    leaky_relu as leaky_relu,\n    log_sigmoid as log_sigmoid,\n    log_softmax as log_softmax,\n    relu as relu,\n    sigmoid as sigmoid,\n    silu as silu,\n    soft_sign as soft_sign,\n    softmax as softmax,\n    softplus as softplus,\n    swish as swish,\n    tanh as tanh,\n)\nfrom flax.linen.pooling import (avg_pool as avg_pool, max_pool as max_pool)\nfrom .attention import (\n    dot_product_attention as dot_product_attention,\n    multi_head_dot_product_attention as multi_head_dot_product_attention,\n)\nfrom .linear import (\n    Embedding as Embedding,\n    conv_transpose as conv_transpose,\n    conv as conv,\n    dense_general as dense_general,\n    dense as dense,\n    embedding as embedding,\n)\nfrom .normalization import (\n    batch_norm as batch_norm,\n    group_norm as group_norm,\n    layer_norm as layer_norm,\n)\nfrom .stochastic import dropout as dropout\n\n# pylint: enable=g-multiple-import\n"
  },
  {
    "path": "flax/core/nn/attention.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Attention core modules for Flax.\"\"\"\n\nimport functools\nfrom collections.abc import Iterable  # pylint: disable=g-importing-member\nfrom typing import Any\nfrom collections.abc import Callable\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom jax import lax, random\n\nfrom flax import struct\nfrom flax.core import Scope\nfrom flax.linen import initializers\n\nfrom .linear import default_kernel_init, dense_general\n\n\ndef dot_product_attention(\n  scope,\n  query,\n  key,\n  value,\n  dtype=jnp.float32,\n  bias=None,\n  axis=None,\n  broadcast_dropout=True,\n  dropout_rng=None,\n  dropout_rate=0.0,\n  deterministic=False,\n  precision=None,\n):\n  \"\"\"Computes dot-product attention given query, key, and value.\n\n  This is the core function for applying attention based on\n  https://arxiv.org/abs/1706.03762. It calculates the attention weights given\n  query and key and combines the values using the attention weights. This\n  function supports multi-dimensional inputs.\n\n\n  Args:\n    query: queries for calculating attention with shape of `[batch_size, dim1,\n      dim2, ..., dimN, num_heads, mem_channels]`.\n    key: keys for calculating attention with shape of `[batch_size, dim1, dim2,\n      ..., dimN, num_heads, mem_channels]`.\n    value: values to be used in attention with shape of `[batch_size, dim1,\n      dim2,..., dimN, num_heads, value_channels]`.\n    dtype: the dtype of the computation (default: float32)\n    bias: bias for the attention weights. This can be used for incorporating\n      autoregressive mask, padding mask, proximity bias.\n    axis: axises over which the attention is applied.\n    broadcast_dropout: bool: use a broadcasted dropout along batch dims.\n    dropout_rng: JAX PRNGKey: to be used for dropout\n    dropout_rate: dropout rate\n    deterministic: bool, deterministic or not (to apply dropout)\n    precision: numerical precision of the computation see `jax.lax.Precision`\n      for details.\n\n  Returns:\n    Output of shape `[bs, dim1, dim2, ..., dimN,, num_heads, value_channels]`.\n  \"\"\"\n  assert key.shape[:-1] == value.shape[:-1]\n  assert query.shape[0:1] == key.shape[0:1] and query.shape[-1] == key.shape[-1]\n\n  if axis is None:\n    axis = tuple(range(1, key.ndim - 2))\n  if not isinstance(axis, Iterable):\n    axis = (axis,)\n  assert key.ndim == query.ndim\n  assert key.ndim == value.ndim\n  for ax in axis:\n    if not (query.ndim >= 3 and 1 <= ax < query.ndim - 2):\n      raise ValueError(\n        'Attention axis must be between the batch axis and the last-two axes.'\n      )\n  depth = query.shape[-1]\n  n = key.ndim\n  # batch_dims is  <bs, <non-attention dims>, num_heads>\n  batch_dims = tuple(np.delete(range(n), axis + (n - 1,)))\n  # q & k -> (bs, <non-attention dims>, num_heads, <attention dims>, channels)\n  qk_perm = batch_dims + axis + (n - 1,)\n  key = key.transpose(qk_perm)\n  query = query.transpose(qk_perm)\n  # v -> (bs, <non-attention dims>, num_heads, channels, <attention dims>)\n  v_perm = batch_dims + (n - 1,) + axis\n  value = value.transpose(v_perm)\n\n  query = query / jnp.sqrt(depth).astype(dtype)\n  batch_dims_t = tuple(range(len(batch_dims)))\n  attn_weights = lax.dot_general(\n    query,\n    key,\n    (((n - 1,), (n - 1,)), (batch_dims_t, batch_dims_t)),\n    precision=precision,\n  )\n\n  # apply attention bias: masking, droput, proximity bias, ect.\n  if bias is not None:\n    attn_weights = attn_weights + bias\n\n  # normalize the attention weights\n  norm_dims = tuple(range(attn_weights.ndim - len(axis), attn_weights.ndim))\n  attn_weights = lax.exp(\n    attn_weights\n    - jax.scipy.special.logsumexp(attn_weights, axis=norm_dims, keepdims=True)\n  )\n  attn_weights = attn_weights.astype(dtype)\n\n  # apply dropout\n  if not deterministic and dropout_rate > 0.0:\n    if dropout_rng is None:\n      dropout_rng = scope.make_rng('dropout')\n    keep_prob = 1.0 - dropout_rate\n    if broadcast_dropout:\n      # dropout is broadcast across the batch+head+non-attention dimension\n      dropout_dims = attn_weights.shape[-(2 * len(axis)) :]\n      dropout_shape = tuple([1] * len(batch_dims_t)) + dropout_dims\n      keep = random.bernoulli(dropout_rng, keep_prob, dropout_shape)\n    else:\n      keep = random.bernoulli(dropout_rng, keep_prob, attn_weights.shape)\n    multiplier = keep.astype(attn_weights.dtype) / jnp.asarray(\n      keep_prob, dtype=dtype\n    )\n    attn_weights = attn_weights * multiplier\n\n  # compute the new values given the attention weights\n  wv_contracting_dims = (norm_dims, range(value.ndim - len(axis), value.ndim))\n  y = lax.dot_general(\n    attn_weights,\n    value,\n    (wv_contracting_dims, (batch_dims_t, batch_dims_t)),\n    precision=precision,\n  )\n\n  # back to (bs, dim1, dim2, ..., dimN, num_heads, channels)\n  perm_inv = _invert_perm(qk_perm)\n  y = y.transpose(perm_inv)\n  return y\n\n\ndef _invert_perm(perm):\n  perm_inv = [0] * len(perm)\n  for i, j in enumerate(perm):\n    perm_inv[j] = i\n  return tuple(perm_inv)\n\n\nclass CacheEntry(struct.PyTreeNode):\n  key: np.ndarray\n  value: np.ndarray\n  i: np.ndarray\n\n\ndef multi_head_dot_product_attention(\n  scope: Scope,\n  inputs_q,\n  inputs_kv,\n  num_heads,\n  dtype=jnp.float32,\n  qkv_features=None,\n  out_features=None,\n  attention_axis=None,\n  causal_mask=False,\n  padding_mask=None,\n  key_padding_mask=None,\n  segmentation=None,\n  key_segmentation=None,\n  cache=False,\n  broadcast_dropout=True,\n  dropout_rng=None,\n  dropout_rate=0.0,\n  deterministic=False,\n  precision=None,\n  kernel_init=default_kernel_init,\n  bias_init=initializers.zeros_init(),\n  bias=True,\n  attention_fn=dot_product_attention,\n):\n  \"\"\"Applies multi-head dot product attention on the input data.\n\n  Projects the inputs into multi-headed query, key, and value vectors,\n  applies dot-product attention and project the results to an output vector.\n\n  This can be used for encoder-decoder attention by specifying both `inputs_q`\n  and `inputs_kv` orfor self-attention by only specifying `inputs_q` and\n  setting `inputs_kv` to None.\n\n  Args:\n    inputs_q: input queries of shape `[bs, dim1, dim2, ..., dimN, features]`.\n    inputs_kv: key/values of shape `[bs, dim1, dim2, ..., dimN, features]`\n      or None for self-attention, inn which case key/values will be derived\n      from inputs_q.\n    num_heads: number of attention heads. Features (i.e. inputs_q.shape[-1])\n      should be divisible by the number of heads.\n    dtype: the dtype of the computation (default: float32)\n    qkv_features: dimension of the key, query, and value.\n    out_features: dimension of the last projection\n    attention_axis: axes over which the attention is applied ( 'None' means\n      attention over all axes, but batch, heads, and features).\n    causal_mask: boolean specifying whether to apply a causal mask on the\n      attention weights. If True, the output at timestep `t` will not depend\n      on inputs at timesteps strictly greater than `t`.\n    padding_mask: boolean specifying query tokens that are pad token.\n    key_padding_mask: boolean specifying key-value tokens that are pad token.\n    segmentation: segment indices for packed inputs_q data.\n    key_segmentation: segment indices for packed inputs_kv data.\n    cache: an instance of `flax.deprecated.nn.attention.Cache` used for\n      efficient autoregressive decoding.\n    broadcast_dropout: bool: use a broadcasted dropout along batch dims.\n    dropout_rng: JAX PRNGKey: to be used for dropout\n    dropout_rate: dropout rate\n    deterministic: bool, deterministic or not (to apply dropout)\n    precision: numerical precision of the computation see `jax.lax.Precision`\n      for details.\n    kernel_init: initializer for the kernel of the Dense layers.\n    bias_init: initializer for the bias of the Dense layers.\n    bias: bool: whether pointwise QKVO dense transforms use bias.\n    attention_fn: dot_product_attention or compatible function. Accepts\n    query, key, value, and returns output of shape\n    `[bs, dim1, dim2, ..., dimN,, num_heads, value_channels]``\n\n  Returns:\n    output of shape `[bs, dim1, dim2, ..., dimN, features]`.\n  \"\"\"\n\n  assert (\n    causal_mask or not cache\n  ), 'Caching is only support for causal attention.'\n\n  if inputs_kv is None:\n    inputs_kv = inputs_q\n\n  if attention_axis is None:\n    attention_axis = tuple(range(1, inputs_q.ndim - 1))\n\n  features = out_features or inputs_q.shape[-1]\n  qkv_features = qkv_features or inputs_q.shape[-1]\n\n  assert (\n    qkv_features % num_heads == 0\n  ), 'Memory dimension must be divisible by number of heads.'\n  head_dim = qkv_features // num_heads\n\n  dense = functools.partial(\n    dense_general,\n    axis=-1,\n    dtype=dtype,\n    features=(num_heads, head_dim),\n    kernel_init=kernel_init,\n    bias_init=bias_init,\n    bias=bias,\n    precision=precision,\n  )\n  # project inputs_q to multi-headed q/k/v\n  # dimensions are then [bs, dims..., n_heads, n_features_per_head]\n  query = scope.child(dense, 'query')(inputs_q)\n  key = scope.child(dense, 'key')(inputs_kv)\n  value = scope.child(dense, 'value')(inputs_kv)\n\n  if cache:\n    cache_entry: Callable[[Any], CacheEntry] | CacheEntry\n    if not scope.has_variable('cache', 'entry'):\n      ndim, tail_shape = (key.ndim, key.shape[-2:])\n\n      def init_fn(shape, dtype=jnp.float32):\n        full_shape = shape + tail_shape\n        if len(full_shape) != ndim:\n          raise ValueError(\n            'Shape should be a tuple with the shape of the batch'\n            'and attention dims.'\n          )\n        return CacheEntry(\n          key=jnp.zeros(full_shape, dtype),\n          value=jnp.zeros(full_shape, dtype),\n          i=jnp.zeros((), jnp.uint32),\n        )\n\n      cache_entry = init_fn\n    else:\n      cache_entry = scope.get_variable('cache', 'entry')\n      if not isinstance(cache_entry, CacheEntry):\n        raise ValueError('Cache is not initialized.')\n\n      expected_shape = list(cache_entry.key.shape[:-2])\n      for attn_dim in attention_axis:\n        expected_shape[attn_dim] = 1\n      expected_shape = tuple(expected_shape) + inputs_q.shape[-1:]\n      if expected_shape != inputs_q.shape:\n        raise ValueError(\n          'Invalid shape provided, expected shape %s instead got %s.'\n          % (expected_shape, inputs_q.shape)\n        )\n\n      cshape = cache_entry.key.shape\n      indices = [0] * len(cshape)\n      i = cache_entry.i\n      attn_size = np.prod(np.take(cshape, attention_axis))\n      for attn_dim in attention_axis:\n        attn_size //= cshape[attn_dim]\n        indices[attn_dim] = i // attn_size\n        i = i % attn_size\n\n      key = lax.dynamic_update_slice(cache_entry.key, key, indices)  # type: ignore\n      value = lax.dynamic_update_slice(cache_entry.value, value, indices)  # type: ignore\n      one = jnp.array(1, jnp.uint32)\n      cache_entry = cache_entry.replace(\n        i=cache_entry.i + one, key=key, value=value\n      )\n\n      # TODO(levskaya): verify this is still needed in translation decoding.\n      key_padding_mask = jnp.broadcast_to(\n        (jnp.arange(cshape[1]) < cache_entry.i), cshape[:2]\n      )\n      key_padding_mask = key_padding_mask.astype(jnp.float32)[..., None]\n    scope.put_variable('cache', 'entry', cache_entry)\n\n  # create attention masks\n  mask_components = []\n\n  if causal_mask:\n    if cache and isinstance(cache_entry, CacheEntry):\n      bias_pre_shape = (1,) * (key.ndim - 1)\n      attn_shape = tuple(np.take(key.shape, attention_axis))\n      attn_size = np.prod(attn_shape)\n      ii = jnp.arange(attn_size, dtype=jnp.uint32)\n      mask = ii < cache_entry.i\n      mask_components.append(mask.reshape(bias_pre_shape + attn_shape))\n    else:\n      mask_components.append(_make_causal_mask(key, attention_axis))\n\n  if padding_mask is not None:\n    if key_padding_mask is None:\n      key_padding_mask = padding_mask\n    padding_mask = make_padding_mask(\n      padding_mask_query=padding_mask,\n      padding_mask_key=key_padding_mask,\n      query_shape=query.shape,\n      key_shape=key.shape,\n      attention_axis=attention_axis,\n    )\n    mask_components.append(padding_mask)\n\n  if segmentation is not None:\n    if key_segmentation is None:\n      key_segmentation = segmentation\n    segmentation_mask = make_padding_mask(\n      padding_mask_query=segmentation,\n      padding_mask_key=key_segmentation,\n      query_shape=query.shape,\n      key_shape=key.shape,\n      attention_axis=attention_axis,\n      segmentation_mask=True,\n    )\n    mask_components.append(segmentation_mask)\n\n  if mask_components:\n    attention_mask = mask_components[0]\n    for component in mask_components[1:]:\n      attention_mask = jnp.logical_and(attention_mask, component)\n\n    # attention mask in the form of attention bias\n    attention_bias = lax.select(\n      attention_mask > 0,\n      jnp.full(attention_mask.shape, 0.0).astype(dtype),\n      jnp.full(attention_mask.shape, -1e10).astype(dtype),\n    )\n  else:\n    attention_bias = None\n\n  # apply attention\n  x = scope.child(attention_fn)(\n    query,\n    key,\n    value,\n    dtype=dtype,\n    axis=attention_axis,\n    bias=attention_bias,\n    precision=precision,\n    dropout_rng=dropout_rng,\n    dropout_rate=dropout_rate,\n    broadcast_dropout=broadcast_dropout,\n    deterministic=deterministic,\n  )\n\n  # back to the original inputs dimensions\n  out = scope.child(dense_general, name='out')(\n    x,\n    features=features,\n    axis=(-2, -1),\n    kernel_init=kernel_init,\n    bias_init=bias_init,\n    bias=bias,\n    dtype=dtype,\n    precision=precision,\n  )\n\n  return out\n\n\n# TODO(flax-dev): Consider refactoring MultiHeadDotProductAttention and moving\n# causal_mask and cache support into this class instead.\n# SelfAttention = MultiHeadDotProductAttention.partial(inputs_kv=None)\n\n\ndef make_padding_mask(\n  padding_mask_query,\n  padding_mask_key,\n  query_shape,\n  key_shape,\n  attention_axis=None,\n  segmentation_mask=False,\n):\n  \"\"\"Makes padding mask for attention weights.\n\n  In case of 1d inputs (i.e., `[bs, len, features]`, the attention weights will\n  be `[bs, len, len]` and this function makes a square matrix [len, len].\n\n  Args:\n    padding_mask_query: padding mask of query <bs, qdim1,.., qdimn>\n    padding_mask_key: padding mask of query <bs, key1,.., keyn>\n    query_shape: shape of the query\n    key_shape: shape of the key, which is equal to the shape of value.\n    attention_axis: axis over which attention is applied.\n    segmentation_mask: bool: if true use equality on cartesian product rather\n      than outer product for constructing segmentation masks.\n  Returns:\n    The padding mask for attention weights.\n  \"\"\"\n  assert query_shape[0] == key_shape[0]\n  assert len(query_shape) == len(key_shape)\n\n  ndim = len(key_shape)\n  if attention_axis is None:\n    attention_axis = tuple(range(1, ndim - 2))\n  assert isinstance(attention_axis, tuple)\n  for ax in attention_axis:\n    if not (ndim >= 3 and 1 <= ax < ndim - 2):\n      raise ValueError(\n        'Attention axis must be between the batch axis and the last-two axes.'\n      )\n\n  mask_shape_final = (query_shape[0], 1)  #  batch_size, 1 (for all heads)s\n  for ax in attention_axis:\n    mask_shape_final += (query_shape[ax],)\n  for ax in attention_axis:\n    mask_shape_final += (key_shape[ax],)\n\n  padding_mask_query = padding_mask_query[..., None]\n  padding_mask_key = padding_mask_key[..., None]\n  perm = (0,) + tuple(np.flip(np.arange(padding_mask_key.ndim)))[:-1]\n  if segmentation_mask:\n    mask = jnp.equal(padding_mask_query, padding_mask_key.transpose(perm))\n  else:\n    mask = jnp.multiply(padding_mask_query, padding_mask_key.transpose(perm))\n\n  mask = mask.reshape(mask_shape_final)\n  mask = jax.lax.convert_element_type(mask, jnp.float32)\n  return mask\n\n\ndef _make_causal_mask(key, attention_axis=None, self_mask=False):\n  \"\"\"Makes a causal mask, to be used for masking out the future for attention.\n\n  In case of 1d inputs (i.e., `[bs, len, features]`, the attention weights will\n  be `[bs, len, len]` and this function makes a square matrix [len, len] with\n  zeros in upper triangle and ones in lower triangle.\n\n  Args:\n    key: shape of the key, which is equal to the shape of value and is\n      assumed to be equal to the shape of the query (since this is used in\n      self-attention when decoding).\n    attention_axis: axis over which attention is applied.\n    self_mask: if mask out the diagonal or not.\n\n  Returns:\n    A causal mask to be used to mask out future positions.\n  \"\"\"\n  if attention_axis is None:\n    attention_axis = tuple(range(1, key.ndim - 2))\n  assert isinstance(attention_axis, tuple)\n  for ax in attention_axis:\n    if not (key.ndim >= 3 and 1 <= ax < key.ndim - 2):\n      raise ValueError(\n        'Attention axis must be between the batch axis and the last-two axes.'\n      )\n\n  mask_shape = tuple([1] * (key.ndim - len(attention_axis) - 1))\n  mask_shape_final = mask_shape\n  for _ in range(2):\n    flatten_dim = 1\n    for ax in attention_axis:\n      mask_shape_final += (key.shape[ax],)\n      flatten_dim *= key.shape[ax]\n    mask_shape += (flatten_dim,)\n\n  def tri(n, m, k=0):\n    # Tie in the key to avoid the mask becoming a constant.\n    # This way XLA can construct the mask during computation and fuse it\n    # with the attention ops.\n    x = jnp.arange(n, dtype=jnp.int32)\n    y = jnp.arange(m, dtype=jnp.int32)\n    mask = lax.ge(\n      (lax.broadcast_in_dim(x, shape=(n, m), broadcast_dimensions=(0,))) + k,\n      lax.broadcast(y, [n]),\n    )\n    return mask\n\n  k = -1 if self_mask else 0\n  mask = tri(*mask_shape[-2:], k=k).reshape(mask_shape_final)\n  return mask\n"
  },
  {
    "path": "flax/core/nn/linear.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Linear modules.\"\"\"\n\nfrom collections.abc import Iterable  # pylint: disable=g-importing-member\n\nimport jax.numpy as jnp\nimport numpy as np\nfrom jax import lax\n\nfrom flax import struct\nfrom flax.core import Scope\nfrom flax.linen import initializers\n\ndefault_kernel_init = initializers.lecun_normal()\n\n\ndef _normalize_axes(axes, ndim):\n  # A tuple by convention. len(axes_tuple) then also gives the rank efficiently.\n  return tuple(ax if ax >= 0 else ndim + ax for ax in axes)\n\n\ndef dense_general(\n  scope,\n  inputs,\n  features,\n  axis=-1,\n  batch_dims=(),\n  bias=True,\n  dtype=jnp.float32,\n  kernel_init=default_kernel_init,\n  bias_init=initializers.zeros_init(),\n  precision=None,\n):\n  \"\"\"Applies a linear transformation to the inputs along multiple dimensions.\n\n  Args:\n    inputs: The nd-array to be transformed.\n    features: tuple with numbers of output features.\n    axis: tuple with axes to apply the transformation on.\n    batch_dims: tuple with batch axes.\n    bias: whether to add a bias to the output (default: True).\n    dtype: the dtype of the computation (default: float32).\n    kernel_init: initializer function for the weight matrix.\n    bias_init: initializer function for the bias.\n    precision: numerical precision of the computation see `jax.lax.Precision`\n      for details.\n  Returns:\n    The transformed input.\n  \"\"\"\n  inputs = jnp.asarray(inputs, dtype)\n\n  if not isinstance(features, Iterable):\n    features = (features,)\n  if not isinstance(axis, Iterable):\n    axis = (axis,)\n  if not isinstance(batch_dims, Iterable):\n    batch_dims = (batch_dims,)\n  features, axis, batch_dims = tuple(features), tuple(axis), tuple(batch_dims)\n\n  if batch_dims:\n    max_dim = np.max(batch_dims)\n    if set(batch_dims) != set(range(max_dim + 1)):\n      raise ValueError(\n        'batch_dims %s must be consecutive leading '\n        'dimensions starting from 0.' % str(batch_dims)\n      )\n\n  ndim = inputs.ndim\n  n_batch_dims = len(batch_dims)\n  axis = _normalize_axes(axis, ndim)\n  batch_dims = _normalize_axes(batch_dims, ndim)\n  n_axis, n_features = len(axis), len(features)\n\n  def kernel_init_wrap(rng, shape, dtype=jnp.float32):\n    size_batch_dims = np.prod(shape[:n_batch_dims], dtype=np.int32)\n    flat_shape = (\n      np.prod(shape[n_batch_dims : n_axis + n_batch_dims]),\n      np.prod(shape[-n_features:]),\n    )\n    kernel = jnp.concatenate(\n      [kernel_init(rng, flat_shape, dtype) for _ in range(size_batch_dims)],\n      axis=0,\n    )\n    return jnp.reshape(kernel, shape)\n\n  batch_shape = tuple(inputs.shape[ax] for ax in batch_dims)\n  kernel_shape = tuple(inputs.shape[ax] for ax in axis) + features\n  kernel = scope.param('kernel', kernel_init_wrap, batch_shape + kernel_shape)\n  kernel = jnp.asarray(kernel, dtype)\n\n  batch_ind = tuple(range(n_batch_dims))\n  contract_ind = tuple(range(n_batch_dims, n_axis + n_batch_dims))\n  out = lax.dot_general(\n    inputs,\n    kernel,\n    ((axis, contract_ind), (batch_dims, batch_ind)),\n    precision=precision,\n  )\n  if bias:\n\n    def bias_init_wrap(rng, shape, dtype=jnp.float32):\n      size_batch_dims = np.prod(shape[:n_batch_dims], dtype=np.int32)\n      flat_shape = (np.prod(shape[-n_features:]),)\n      bias = jnp.concatenate(\n        [bias_init(rng, flat_shape, dtype) for _ in range(size_batch_dims)],\n        axis=0,\n      )\n      return jnp.reshape(bias, shape)\n\n    bias = scope.param('bias', bias_init_wrap, batch_shape + features)\n\n    # Reshape bias for broadcast.\n    expand_dims = sorted(set(range(inputs.ndim)) - set(axis) - set(batch_dims))\n    for ax in expand_dims:\n      bias = jnp.expand_dims(bias, ax)\n    bias = jnp.asarray(bias, dtype)\n    out = out + bias\n  return out\n\n\ndef dense(\n  scope,\n  inputs,\n  features,\n  bias=True,\n  dtype=jnp.float32,\n  precision=None,\n  kernel_init=default_kernel_init,\n  bias_init=initializers.zeros_init(),\n):\n  \"\"\"Applies a linear transformation to the inputs along the last dimension.\n\n  Args:\n    inputs: The nd-array to be transformed.\n    features: the number of output features.\n    bias: whether to add a bias to the output (default: True).\n    dtype: the dtype of the computation (default: float32).\n    precision: numerical precision of the computation see `jax.lax.Precision`\n      for details.\n    kernel_init: initializer function for the weight matrix.\n    bias_init: initializer function for the bias.\n  Returns:\n    The transformed input.\n  \"\"\"\n  inputs = jnp.asarray(inputs, dtype)\n  kernel = scope.param('kernel', kernel_init, (inputs.shape[-1], features))\n  kernel = jnp.asarray(kernel, dtype)\n  y = lax.dot_general(\n    inputs,\n    kernel,\n    (((inputs.ndim - 1,), (0,)), ((), ())),\n    precision=precision,\n  )\n  if bias:\n    bias = scope.param('bias', bias_init, (features,))\n    bias = jnp.asarray(bias, dtype)\n    y += jnp.reshape(bias, (1,) * (y.ndim - 1) + (-1,))\n  return y\n\n\ndef _conv_dimension_numbers(input_shape):\n  \"\"\"Computes the dimension numbers based on the input shape.\"\"\"\n  ndim = len(input_shape)\n  lhs_spec = (0, ndim - 1) + tuple(range(1, ndim - 1))\n  rhs_spec = (ndim - 1, ndim - 2) + tuple(range(0, ndim - 2))\n  out_spec = lhs_spec\n  return lax.ConvDimensionNumbers(lhs_spec, rhs_spec, out_spec)\n\n\ndef conv(\n  scope,\n  inputs,\n  features,\n  kernel_size,\n  strides=None,\n  padding='SAME',\n  input_dilation=None,\n  kernel_dilation=None,\n  feature_group_count=1,\n  bias=True,\n  dtype=jnp.float32,\n  precision=None,\n  kernel_init=default_kernel_init,\n  bias_init=initializers.zeros_init(),\n):\n  \"\"\"Applies a convolution to the inputs.\n\n  Args:\n    inputs: input data with dimensions (batch, spatial_dims..., features).\n    features: number of convolution filters.\n    kernel_size: shape of the convolutional kernel.\n    strides: a sequence of `n` integers, representing the inter-window\n      strides.\n    padding: either the string `'SAME'`, the string `'VALID'`, or a sequence\n      of `n` `(low, high)` integer pairs that give the padding to apply before\n      and after each spatial dimension.\n    input_dilation: `None`, or a sequence of `n` integers, giving the\n      dilation factor to apply in each spatial dimension of `inputs`.\n      Convolution with input dilation `d` is equivalent to transposed\n      convolution with stride `d`.\n    kernel_dilation: `None`, or a sequence of `n` integers, giving the\n      dilation factor to apply in each spatial dimension of the convolution\n      kernel. Convolution with kernel dilation is also known as 'atrous\n      convolution'.\n    feature_group_count: integer, default 1. If specified divides the input\n      features into groups.\n    bias: whether to add a bias to the output (default: True).\n    dtype: the dtype of the computation (default: float32).\n    precision: numerical precision of the computation see `jax.lax.Precision`\n      for details.\n    kernel_init: initializer for the convolutional kernel.\n    bias_init: initializer for the bias.\n  Returns:\n    The convolved data.\n  \"\"\"\n\n  inputs = jnp.asarray(inputs, dtype)\n\n  if strides is None:\n    strides = (1,) * (inputs.ndim - 2)\n\n  in_features = inputs.shape[-1]\n  assert in_features % feature_group_count == 0\n  kernel_shape = kernel_size + (in_features // feature_group_count, features)\n  kernel = scope.param('kernel', kernel_init, kernel_shape)\n  kernel = jnp.asarray(kernel, dtype)\n\n  dimension_numbers = _conv_dimension_numbers(inputs.shape)\n  y = lax.conv_general_dilated(\n    inputs,\n    kernel,\n    strides,\n    padding,\n    lhs_dilation=input_dilation,\n    rhs_dilation=kernel_dilation,\n    dimension_numbers=dimension_numbers,\n    feature_group_count=feature_group_count,\n    precision=precision,\n  )\n\n  if bias:\n    bias = scope.param('bias', bias_init, (features,))\n    bias = jnp.asarray(bias, dtype)\n    y += jnp.reshape(bias, (1,) * (y.ndim - 1) + (-1,))\n  return y\n\n\ndef conv_transpose(\n  scope,\n  inputs,\n  features,\n  kernel_size,\n  strides=None,\n  padding='SAME',\n  kernel_dilation=None,\n  bias=True,\n  dtype=jnp.float32,\n  precision=None,\n  kernel_init=default_kernel_init,\n  bias_init=initializers.zeros_init(),\n):\n  \"\"\"Applies a transposed convolution to the inputs. Behaviour mirrors that of\n  `jax.lax.conv_transpose`.\n\n  Args:\n    scope: functional scope.\n    inputs: input data with dimensions (batch, spatial_dims..., features).\n    features: number of convolution filters.\n    kernel_size: shape of the convolutional kernel.\n    strides: a sequence of `n` integers, representing the inter-window\n      strides.\n    padding: either the string `'SAME'`, the string `'VALID'`, or a sequence\n      of `n` `(low, high)` integer pairs that give the padding to apply before\n      and after each spatial dimension.\n    kernel_dilation: `None`, or a sequence of `n` integers, giving the\n      dilation factor to apply in each spatial dimension of the convolution\n      kernel. Convolution with kernel dilation is also known as 'atrous\n      convolution'.\n    bias: whether to add a bias to the output (default: True).\n    dtype: the dtype of the computation (default: float32).\n    precision: numerical precision of the computation see `jax.lax.Precision`\n      for details.\n    kernel_init: initializer for the convolutional kernel.\n    bias_init: initializer for the bias.\n  Returns:\n    The convolved data.\n  \"\"\"\n  inputs = jnp.asarray(inputs, dtype)\n  strides = strides or (1,) * (inputs.ndim - 2)\n\n  in_features = inputs.shape[-1]\n  kernel_shape = kernel_size + (in_features, features)\n  kernel = scope.param('kernel', kernel_init, kernel_shape)\n  kernel = jnp.asarray(kernel, dtype)\n\n  y = lax.conv_transpose(\n    inputs,\n    kernel,\n    strides,\n    padding,\n    rhs_dilation=kernel_dilation,\n    precision=precision,\n  )\n\n  if bias:\n    bias = scope.param('bias', bias_init, (features,))\n    bias = jnp.asarray(bias, dtype)\n    y += jnp.reshape(bias, (1,) * (y.ndim - 1) + (-1,))\n  return y\n\n\ndefault_embed_init = initializers.variance_scaling(\n  1.0, 'fan_in', 'normal', out_axis=0\n)\n\n\n@struct.dataclass\nclass Embedding:\n  table: np.ndarray\n\n  def lookup(self, indices):\n    \"\"\"Embeds the inputs along the last dimension.\n\n    Args:\n      indices: input data, all dimensions are considered batch dimensions.\n\n    Returns:\n      Output which is embedded input data.  The output shape follows the input,\n      with an additional `features` dimension appended.\n    \"\"\"\n    if indices.dtype not in [jnp.int32, jnp.int64, jnp.uint32, jnp.uint64]:\n      raise ValueError('Input type must be an integer or unsigned integer.')\n    return self.table[indices]\n\n  def attend(self, query):\n    \"\"\"Attend over the embedding using a query array.\n\n    Args:\n      query: array with last dimension equal the feature depth `features` of the\n        embedding.\n\n    Returns:\n      An array with final dim `num_embeddings` corresponding to the batched\n      inner-product of the array of query vectors against each embedding.\n      Commonly used for weight-sharing between embeddings and logit transform\n      in NLP models.\n    \"\"\"\n    return jnp.dot(query, self.table.T)\n\n\ndef embedding(\n  scope: Scope, num_embeddings: int, features: int, init_fn=default_embed_init\n) -> Embedding:\n  \"\"\"Creates embedding dataclass.\n\n  Args:\n    num_embeddings: number of embeddings.\n    features: Number of feature dimensions for each embedding.\n    embedding_init: embedding initializer.\n\n  Returns:\n    Embedding dataclass with lookup and attend methods.\n  \"\"\"\n  table = scope.param('table', init_fn, (num_embeddings, features))\n  return Embedding(table)  # type: ignore\n"
  },
  {
    "path": "flax/core/nn/normalization.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Normalization modules for Flax.\"\"\"\n\nimport jax.numpy as jnp\nfrom jax import lax\n\nfrom flax.core import Scope\nfrom flax.linen import initializers\n\n\ndef _absolute_dims(ndim, dims):\n  return tuple(ndim + dim if dim < 0 else dim for dim in dims)\n\n\ndef batch_norm(\n  scope: Scope,\n  x,\n  use_running_average=False,\n  axis=-1,\n  momentum=0.99,\n  epsilon=1e-5,\n  dtype=jnp.float32,\n  bias=True,\n  scale=True,\n  bias_init=initializers.zeros_init(),\n  scale_init=initializers.ones_init(),\n  axis_name=None,\n  axis_index_groups=None,\n  kind='batch_stats',\n):\n  x = jnp.asarray(x, jnp.float32)\n  axis = axis if isinstance(axis, tuple) else (axis,)\n  axis = _absolute_dims(x.ndim, axis)\n  redux = tuple(i for i in range(x.ndim) if i not in axis)\n\n  def pmean(x):\n    m = jnp.mean(x, redux, keepdims=True)\n    if axis_name is not None:\n      m = lax.pmean(m, axis_name=axis_name, axis_index_groups=axis_index_groups)\n    return m\n\n  mean = pmean(x)\n  squeeze_shape = jnp.squeeze(mean).shape\n  mean2 = pmean(jnp.square(x))\n  var = mean2 - jnp.square(mean)\n\n  is_init = not scope.has_variable(kind, 'mean')\n  ra_mean = scope.variable(kind, 'mean', jnp.zeros, squeeze_shape)\n  ra_var = scope.variable(kind, 'var', jnp.ones, squeeze_shape)\n\n  if use_running_average:\n    # if ra_mean is not None:\n    #   raise ValueError('batch_stats should be provided if use_running_averages=True')\n    mean = jnp.reshape(ra_mean.value, mean.shape)\n    var = jnp.reshape(ra_var.value, var.shape)\n  else:\n    if not is_init:\n      beta = 1.0 - momentum\n      ra_mean.value += beta * (jnp.squeeze(mean) - ra_mean.value)\n      ra_var.value += beta * (jnp.squeeze(var) - ra_var.value)\n  y = x - mean\n  mul = lax.rsqrt(var + epsilon)\n  if scale:\n    mul = mul * scope.param('scale', scale_init, squeeze_shape).reshape(\n      mean.shape\n    )\n  y = y * mul\n  if bias:\n    y = y + scope.param('bias', bias_init, squeeze_shape).reshape(mean.shape)\n  return jnp.asarray(y, dtype)\n\n\ndef layer_norm(\n  scope: Scope,\n  x,\n  epsilon=1e-6,\n  dtype=jnp.float32,\n  bias=True,\n  scale=True,\n  bias_init=initializers.zeros_init(),\n  scale_init=initializers.ones_init(),\n):\n  \"\"\"Applies layer normalization on the input.\n  It normalizes the activations of the layer for each given example in a\n  batch independently, rather than across a batch like Batch Normalization.\n  i.e. applies a transformation that maintains the mean activation within\n  each example close to 0 and the activation standard deviation close to 1.\n  Args:\n    x: the inputs\n    epsilon: A small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the computation (default: float32).\n    bias:  If True, bias (beta) is added.\n    scale: If True, multiply by scale (gamma). When the next layer is linear\n      (also e.g. nn.relu), this can be disabled since the scaling will be done\n      by the next layer.\n    bias_init: Initializer for bias, by default, zero.\n    scale_init: Initializer for scale, by default, one.\n  Returns:\n    Normalized inputs (the same shape as inputs).\n  \"\"\"\n  features = x.shape[-1]\n  mean = jnp.mean(x, axis=-1, keepdims=True)\n  mean2 = jnp.mean(lax.square(x), axis=-1, keepdims=True)\n  var = mean2 - lax.square(mean)\n  mul = lax.rsqrt(var + epsilon)\n  if scale:\n    mul = mul * jnp.asarray(\n      scope.param('scale', scale_init, (features,)), dtype\n    )\n  y = (x - mean) * mul\n  if bias:\n    y = y + jnp.asarray(scope.param('bias', bias_init, (features,)), dtype)\n  return y\n\n\ndef group_norm(\n  scope,\n  x,\n  num_groups=32,\n  group_size=None,\n  epsilon=1e-6,\n  dtype=jnp.float32,\n  bias=True,\n  scale=True,\n  bias_init=initializers.zeros_init(),\n  scale_init=initializers.ones_init(),\n):\n  \"\"\"Applies group normalization to the input (arxiv.org/abs/1803.08494).\n  This op is similar to batch normalization, but statistics are shared across\n  equally-sized groups of channels and not shared across batch dimension.\n  Thus, group normalization does not depend on the batch composition and does\n  not require maintaining internal state for storing statistics.\n  The user should either specify the total number of channel groups or the\n  number of channels per group.\n  Args:\n    x: the input of shape N...C, where N is a batch dimension and C is a\n      channels dimensions. `...` represents an arbitrary number of extra\n      dimensions that are used to accumulate statistics over.\n    num_groups: the total number of channel groups. The default value of 32 is\n      proposed by the original group normalization paper.\n    group_size: the number of channels in a group.\n    epsilon: A small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the computation (default: float32).\n    bias:  If True, bias (beta) is added.\n    scale: If True, multiply by scale (gamma). When the next layer is linear\n      (also e.g. nn.relu), this can be disabled since the scaling will be done\n      by the next layer.\n    bias_init: Initializer for bias, by default, zero.\n    scale_init: Initializer for scale, by default, one.\n  Returns:\n    Normalized inputs (the same shape as inputs).\n  \"\"\"\n  x = jnp.asarray(x, jnp.float32)\n  if (num_groups is None and group_size is None) or (\n    num_groups is not None and group_size is not None\n  ):\n    raise ValueError(\n      'Either `num_groups` or `group_size` should be '\n      'specified, but not both of them.'\n    )\n\n  if group_size is not None:\n    channels = x.shape[-1]\n    if channels % group_size != 0:\n      raise ValueError(\n        'Number of channels ({}) is not multiple of the '\n        'group size ({}).'.format(channels, group_size)\n      )\n    num_groups = channels // group_size\n\n  input_shape = x.shape\n  group_shape = x.shape[:-1] + (num_groups, x.shape[-1] // num_groups)\n\n  x = x.reshape(group_shape)\n\n  reduction_axis = list(range(1, x.ndim - 2)) + [x.ndim - 1]\n\n  mean = jnp.mean(x, axis=reduction_axis, keepdims=True)\n  mean_of_squares = jnp.mean(jnp.square(x), axis=reduction_axis, keepdims=True)\n  var = mean_of_squares - jnp.square(mean)\n\n  x = (x - mean) * lax.rsqrt(var + epsilon)\n\n  x = x.reshape(input_shape)\n\n  feature_shape = tuple([1 for d in input_shape[:-1]] + [input_shape[-1]])\n  if scale:\n    x = x * scope.param('scale', scale_init, feature_shape)\n  if bias:\n    x = x + scope.param('bias', bias_init, feature_shape)\n\n  return x.astype(dtype)\n"
  },
  {
    "path": "flax/core/nn/stochastic.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Stochastic modules.\"\"\"\n\nimport jax.numpy as jnp\nfrom jax import lax, random\n\n\ndef dropout(scope, inputs, rate, deterministic=False, rng=None):\n  \"\"\"Applies a random dropout mask to the input.\n  Args:\n    inputs: the inputs that should be randomly masked.\n    rate: the probablity of masking out a value.\n    deterministic: if false the inputs are scaled by `1 / (1 - rate)` and\n      masked, whereas if true, no mask is applied and the inputs are returned as\n      is.\n    rng: an optional `jax.random.PRNGKey`. By default `nn.make_rng()` will\n      be used.\n  Returns:\n    The masked inputs.\n  \"\"\"\n  if rate == 0.0:\n    return inputs\n  keep_prob = 1.0 - rate\n\n  if deterministic:\n    return inputs\n  else:\n    if rng is None:\n      rng = scope.make_rng('dropout')\n    mask = random.bernoulli(rng, p=keep_prob, shape=inputs.shape)\n    return lax.select(mask, inputs / keep_prob, jnp.zeros_like(inputs))\n"
  },
  {
    "path": "flax/core/partial_eval.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport functools\nfrom typing import Any\n\nimport jax\nfrom jax import core\nfrom jax.extend import linear_util as lu\nfrom jax.interpreters import partial_eval as pe\n\nfrom flax import errors\n\n\ndef _maybe_unknown(x: Any) -> pe.PartialVal:\n  if isinstance(x, jax.ShapeDtypeStruct):\n    return pe.PartialVal.unknown(core.ShapedArray(x.shape, x.dtype))\n  else:\n    return pe.PartialVal.known(x)\n\n\ndef lazy_init(fn):\n  \"\"\"Lazily evaluates a function by using the shapes of the inputs.\n\n  The returned function accepts a combination of JAX values and\n  ``jax.ShapeDtypeStruct`` instances for the inputs for which we\n  don't need concrete values (only the shape and dtype).\n\n  This API is used by ``core.lazy_init`` or ``Module.lazy_init``\n  to initialize variables without doing any actual computation on the\n  inputs.\n\n  Args:\n    fn: the function to be lazily evaluated.\n  Returns:\n    A new function that accepts a mix of concrete values and\n    ``jax.ShapeDtypeStruct`` instances.\n  \"\"\"\n\n  @functools.wraps(fn)\n  def wrapper(*args, **kwargs):\n    # TODO(mattjj,jheek): use a public JAX API\n    # flatten fn and prepare for internal JAX transform\n    inputs_flat, in_tree = jax.tree_util.tree_flatten((args, kwargs))\n    debug_info = jax.api_util.debug_info(\"lazy_init\", fn, (in_tree,), {})\n    f_flat, out_tree = jax.api_util.flatten_fun(\n      lu.wrap_init(fn, debug_info=debug_info), in_tree)\n    # map inputs to PartialVal known/unknown\n    # only the computations depending on knowns will be executed\n    in_pvals = [_maybe_unknown(x) for x in inputs_flat]\n    _, out_pvals, _ = pe.trace_to_jaxpr_nounits(f_flat, in_pvals)\n    # all outputs should be knowns. If this fails\n    # the user is creating variables that depend on a\n    # argument that was passed as a ShapeDtypeStruct.\n    out_flat = []\n    for pv, const in out_pvals:\n      if pv is None:\n        # const is the actual value of the known output\n        out_flat.append(const)\n      else:\n        raise errors.LazyInitError(pv)\n    return jax.tree_util.tree_unflatten(out_tree(), out_flat)\n\n  return wrapper\n"
  },
  {
    "path": "flax/core/scope.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Flax functional core: Scopes.\"\"\"\n\nimport collections\nimport contextlib\nimport dataclasses\nimport functools\nimport hashlib\nimport typing\nfrom typing import (\n  Any,\n  Generic,\n  Literal,\n  Optional,\n  TypeVar,\n  Union,\n  cast,\n  overload,\n)\nfrom collections.abc import Callable, Iterable, Mapping, Sequence\n\nimport jax\nimport numpy as np\nfrom jax import numpy as jnp\nfrom jax import random, tree_util\n\nfrom flax import config as config\nfrom flax import errors, struct, traceback_util\nfrom flax.ids import uuid\nfrom flax.typing import (\n  PRNGKey,\n  Array,\n  RNGSequences,\n  Collection,\n  MutableCollection,\n  VariableDict,\n  FrozenVariableDict as FrozenVariableDict,\n  MutableVariableDict,\n  PRNGFoldable,\n)\n\nfrom . import meta, partial_eval, tracers\nfrom .frozen_dict import FrozenDict, freeze, unfreeze\n\ntraceback_util.register_exclusion(__file__)\n\nT = TypeVar('T')\n\n\nFilter = Union[bool, str, typing.Collection[str], 'DenyList']\n\n# When conditioning on filters we require explicit boolean comparisons.\n# pylint: disable=g-bool-id-comparison\n\n\n@dataclasses.dataclass(frozen=True, eq=True)\nclass DenyList:\n  \"\"\"DenyList represents an opt-out based mutability filter.\n  DenyList can be used to make every collection mutable except the ones\n  defined in the given filter.\n  To for example make everything but the params collection mutable::\n    nn.apply(fn, mutable=nn.DenyList([\"params\"]))\n  Attributes:\n    deny: The filter representing the collections that are not mutable.\n  \"\"\"\n\n  deny: Filter\n\n  def __lt__(self, other):\n    if isinstance(other, str):\n      return False\n    if isinstance(other, DenyList):\n      return str(self.deny) < str(other.deny)\n    return NotImplemented\n\n  def __gt__(self, other):\n    if isinstance(other, str):\n      return True\n    if isinstance(other, DenyList):\n      return str(self.deny) > str(other.deny)\n    return NotImplemented\n\n\nCollectionFilter = Filter\nPRNGSequenceFilter = Filter\n\n\nclass LazyRng(struct.PyTreeNode):\n  \"\"\"Wrapper around JAX PRNGKey that lazily maintains a tuple of static data to be folded into the rng.\"\"\"\n\n  rng: PRNGKey\n  suffix: tuple[PRNGFoldable, ...] = struct.field(pytree_node=False)\n\n  def as_jax_rng(self) -> PRNGKey:\n    return _fold_in_static(self.rng, self.suffix)\n\n  @staticmethod\n  def create(\n    rng: Union['LazyRng', PRNGKey], *suffix: PRNGFoldable\n  ) -> 'LazyRng':\n    if isinstance(rng, LazyRng):\n      return LazyRng(rng.rng, rng.suffix + suffix)\n    else:\n      return LazyRng(rng, suffix)\n\n  def clear_suffix(self):\n    key = self.rng\n    return LazyRng(key, ())\n\n\ndef _fold_in_static(\n  rng: PRNGKey, data: typing.Collection[PRNGFoldable]\n) -> PRNGKey:\n  \"\"\"Folds static data (strings & ints) into a jax.random.PRNGKey using its SHA-1 hash.\n\n  This is faster than splitting an PRNGKey because it allows generating new PRNG\n  keys in parallel that are independent of each other.\n\n  Args:\n   rng: the rng to fold the string into.\n   data: the string to be folded in.\n\n  Returns:\n   The newly generated PRNG key.\n  \"\"\"\n  if not data:\n    return rng\n  m = hashlib.sha1()\n  for x in data:\n    if config.flax_fix_rng_separator:\n      # encode seperate to avoid collisions like for example: (\"ab\", \"c\") and (\"a\", \"bc\")\n      m.update(b'\\00')\n    if isinstance(x, str):\n      m.update(x.encode('utf-8'))\n    elif isinstance(x, int):\n      m.update(x.to_bytes((x.bit_length() + 7) // 8, byteorder='big'))\n    else:\n      raise ValueError(f'Expected int or string, got: {x}')\n  d = m.digest()\n  hash_int = int.from_bytes(d[:4], byteorder='big')\n  return random.fold_in(rng, jnp.uint32(hash_int))  # type: ignore\n\n\ndef is_filter_empty(filter_like: Filter) -> bool:\n  \"\"\"Returns True if `filter_like` is an empty filter.\n\n  Args:\n    filter_like: The filter to test.\n\n  Returns:\n    A filter is empty when it is an empty collection, it is a bool with value\n    False, ir it is a DenyList that matches everything. A string filter is never\n    empty.\n  \"\"\"\n  if isinstance(filter_like, str):\n    return False\n  if isinstance(filter_like, typing.Collection):\n    return not filter_like\n  if isinstance(filter_like, bool):\n    return not filter_like\n  if isinstance(filter_like, DenyList):\n    # if any arbitrary collection is in the denylist it matches everything so\n    # the filter is empty. This is checked with a stub.\n    return in_filter(filter_like.deny, '__flax_internal_stub__')\n  raise errors.InvalidFilterError(filter_like)\n\n\ndef in_filter(filter_like: Filter, col: str) -> bool:\n  \"\"\"Checks whether a filter can be applied to a collection.\n\n  Used for both collections and rng sequence filters.\n\n  Args:\n    filter_like: a filter (either a boolean, a string, or a list of strings) for\n      a collection.\n    col: a collection, which is a string identifying a dictionary of data, for\n      instance \"params\" or \"batch_stats\".\n\n  Returns:\n    True if either `filter_like` is True, equal to `col`, or a sequence\n    containing `col`.\n  \"\"\"\n  if isinstance(filter_like, str):\n    return col == filter_like\n  if isinstance(filter_like, typing.Collection):\n    return col in filter_like\n  if isinstance(filter_like, bool):\n    return filter_like\n  if isinstance(filter_like, DenyList):\n    return not in_filter(filter_like.deny, col)\n  raise errors.InvalidFilterError(filter_like)\n\n\ndef filter_to_set(x: Filter) -> set[str]:\n  \"\"\"Converts a Filter into a set of collections, fails on the infinite set.\n\n  Args:\n    x: a filter (boolean, string, or list of strings).\n\n  Returns:\n    The input filter represented as a set of strings.\n  \"\"\"\n  assert x is not True and not isinstance(x, DenyList), 'Infinite set'\n  if x is False:\n    return set()\n  if isinstance(x, str):\n    return {x}\n  if isinstance(x, typing.Collection):\n    return set(x)\n  raise errors.InvalidFilterError(x)\n\n\ndef union_filters(a: Filter, b: Filter) -> Filter:\n  \"\"\"Takes the union of two filters (similar to a logical or).\n\n  Args:\n    a: a filter.\n    b: a filter.\n\n  Returns:\n    The union of the two input filters. For instance,\n    `union_filters('f1', ['f2']) = {'f1', 'f2'}`.\n  \"\"\"\n  if a is True or b is True:\n    return True\n  if isinstance(a, DenyList) and isinstance(b, DenyList):\n    return DenyList(intersect_filters(a.deny, b.deny))\n  if isinstance(b, DenyList):\n    a, b = b, a\n  if isinstance(a, DenyList):\n    return DenyList(subtract_filters(a.deny, b))\n\n  a = filter_to_set(a)\n  b = filter_to_set(b)\n  return a.union(b)\n\n\ndef subtract_filters(a: Filter, b: Filter) -> Filter:\n  \"\"\"Returns the subtraction of b from a.\n\n  Args:\n    a: a filter.\n    b: a filter.\n\n  Returns:\n    A filter matching with values in a that are not in b.\n  \"\"\"\n  if b is True:\n    return False\n  if a is True:\n    return DenyList(b)\n  if isinstance(a, DenyList) and isinstance(b, DenyList):\n    return subtract_filters(b.deny, a.deny)\n  if isinstance(a, DenyList):\n    return DenyList(union_filters(a.deny, b))\n  if isinstance(b, DenyList):\n    return intersect_filters(a, b.deny)\n  a = filter_to_set(a)\n  b = filter_to_set(b)\n  return a - b\n\n\ndef intersect_filters(a: Filter, b: Filter) -> Filter:\n  \"\"\"Take the intersection of two filters (similar to a logical and).\n\n  Args:\n    a: a filter.\n    b: a filter.\n\n  Returns:\n    The intersection of the two input filters. For instance,\n    `intersect_filters('f1', ['f1', 'f2']) = {'f1'}`.\n  \"\"\"\n  if a is True:\n    return b\n  if b is True:\n    return a\n  if isinstance(a, DenyList) and isinstance(b, DenyList):\n    return DenyList(union_filters(b.deny, a.deny))\n  if isinstance(b, DenyList):\n    b, a = a, b\n  if isinstance(a, DenyList):\n    return subtract_filters(b, a.deny)\n  a = filter_to_set(a)\n  b = filter_to_set(b)\n  return a.intersection(b)\n\n\ndef group_collections(\n  xs: VariableDict, col_filters: Sequence[CollectionFilter]\n) -> Sequence[MutableVariableDict]:\n  \"\"\"Groups variables by collection filters.\n\n  Iteratively applies the filters in `col_filters` to `xs`, and adds the result\n  of applying each filter to the output sequence. Each key in `xs` is only added\n  to the output once.\n\n  Args:\n    xs: a dictionary of variables, keyed by collections (strings).\n    col_filters: a list of collection filters.\n\n  Returns:\n    A sequence S with `len(S) == len(col_filters)`. Each `S[i]` is the result of\n    applying filter `col_filters[i]` to the remaining keys in `xs`.\n  \"\"\"\n  cols: Iterable[str]\n  cols = xs.keys()\n  groups = []\n  for col_filter in col_filters:\n    remaining_cols = []\n    group = {}\n    for col in cols:\n      if in_filter(col_filter, col):\n        group[col] = jax.tree_util.tree_map(lambda x: x, xs[col])\n      else:\n        remaining_cols.append(col)\n    cols = remaining_cols\n    groups.append(group)\n  return tuple(groups)\n\n\nclass Variable(Generic[T]):\n  \"\"\"A Variable object allows mutable access to a variable in a VariableDict.\n\n  Variables are identified by a collection (e.g., \"batch_stats\") and a name\n  (e.g., \"moving_mean\"). The value property gives access to the variable's\n  content and can be assigned to for mutation.\n  \"\"\"\n\n  def __init__(self, scope: 'Scope', collection: str, name: str, unbox: bool):\n    \"\"\"Initializes a variable.\n\n    Args:\n      scope: The scope in which the variable is stored.\n      collection: The collection of the variable (e.g., \"params\").\n      name: The name of the variable (e.g., \"dense\").\n      unbox: Whether to unbox boxed values with metadata.\n    \"\"\"\n    self._id = uuid()\n    self.scope = scope\n    self.collection = collection\n    self.name = name\n    self.unbox = unbox\n\n  @property\n  def value(self) -> T:\n    \"\"\"Returns the value of this Variable.\"\"\"\n    v = self.scope.get_variable(self.collection, self.name)\n    return meta.unbox(v) if self.unbox else v\n\n  @value.setter\n  def value(self, value: T):\n    \"\"\"Updates the value of this Variable.\"\"\"\n    if self.unbox:\n      cur = self.scope.get_variable(self.collection, self.name)\n      cur_struct = tree_util.tree_structure(cur, is_leaf=meta.is_axis_metadata)\n      value_struct = tree_util.tree_structure(\n        value, is_leaf=meta.is_axis_metadata\n      )\n      has_meta = any(map(meta.is_axis_metadata, cur_struct.flatten_up_to(cur)))\n      if cur_struct == value_struct and has_meta: # type: ignore[operator]\n        value = meta.replace_boxed(cur, value)\n\n    self.scope.put_variable(self.collection, self.name, value)\n\n  def is_mutable(self) -> bool:\n    \"\"\"Checks if this Variable is mutable.\"\"\"\n    return self.scope.is_mutable_collection(self.collection)\n\n\nclass _ChildRNGSentinel:\n  pass\n\n\n# used to identify that an rng counter is meant for a child scope\nchild_rng_token = _ChildRNGSentinel()\n\n\nclass _DefaultSentinel:\n  pass\n\n\n# used to denote no default flag value on scope\nno_flag = _DefaultSentinel()\n\n\n# Make sure reference sharing of child variable dictionaries isn't broken.\n# See https://github.com/google/flax/issues/2022 for more details.\ndef _put_variable(target, key, val):\n  if (\n      key in target\n      and isinstance(target[key], dict)\n      and isinstance(val, Mapping)\n  ):\n    for k, v in val.items():\n      _put_variable(target[key], k, v)\n  else:\n    target[key] = val\n\n\nclass Scope:\n  \"\"\"A Scope allows easy access to variables and manages RNGS of a neural network layer.\n\n  Scopes are purely functional and encapsulated in\n  :class:`flax.linen.module.Module`, so users writing neural network code\n  usually generally do not interact with ``Scopes`` directly.\n\n  See `core design tests\n  <https://github.com/google/flax/tree/main/tests/core/design>`_\n  for a number of examples using ``Scopes``.\n  \"\"\"\n\n  reservations: dict[str, set[str | None]]\n\n  def __init__(\n    self,\n    variables: MutableVariableDict,\n    rngs: RNGSequences | dict[str, LazyRng] | None = None,\n    name: str | None = None,\n    mutable: CollectionFilter = False,\n    parent: Optional['Scope'] = None,\n    path: Iterable[str] = (),\n    debug_path: Iterable[str] = (),\n    flags: Mapping | None = None,\n  ):\n    \"\"\"Initializes a Scope.\n\n    Args:\n      variables: VariableDict to initialize the Scope with.\n      rngs: RNGs used in this scope or one of the child scopes.\n      name: name of this scope.\n      mutable: A CollectionFilter determining which variables are mutable.\n      parent: The parent scope.\n      path: The path in the variable tree from the root scope to this scope. It\n        exactly matches the module path.\n      debug_path: Similar to path but could contain transformation decorators.\n      flags: internal flags.\n    \"\"\"\n    rngs = {k: LazyRng.create(v) for k, v in rngs.items()} if rngs else {}\n    self._variables = variables\n    self.parent = parent\n    self.name = name\n    self.path = tuple(path)\n    self.debug_path = tuple(debug_path) or self.path\n    self.rngs = rngs\n    self.mutable = mutable\n    self.flags = freeze({} if flags is None else flags)\n\n    self._root = parent.root if parent else None\n    self.trace_level = tracers.current_trace()\n\n    self.rng_counters = {key: 0 for key in self.rngs}\n    self.reservations = collections.defaultdict(set)\n\n    self._invalid = False\n\n  def __eq__(self, other: Any) -> bool:\n    # If the root variable dict and path are the same, then two scopes behave\n    # identically. Effectively, a scope is nothing more than a cursor into a\n    # variable dict and an rng counter dict.\n    if not isinstance(other, Scope):\n      return False\n    if self is other:\n      return True\n    return (\n      self.root._variables is other.root._variables\n      and self.path == other.path\n      and self.rng_counters is other.rng_counters\n    )\n\n  def __hash__(self) -> int:\n    # see __eq__\n    return hash((id(self.root._variables), self.path, id(self.rng_counters)))\n\n  @property\n  def root(self) -> 'Scope':\n    return self._root or self\n\n  @property\n  def path_text(self) -> str:\n    \"\"\"Returns the debug path as a human readable string.\"\"\"\n    return '/' + '/'.join(self.debug_path)\n\n  @property\n  def invalid(self) -> bool:\n    \"\"\"Returns true if this scope is invalidated as a result of `Scope.temporary`.\"\"\"\n    return self._invalid\n\n  def _check_valid(self):\n    if self._invalid:\n      raise errors.InvalidScopeError(self.name)\n\n  @contextlib.contextmanager\n  def temporary(self):\n    \"\"\"Returns a context manager that will invalidate this Scope when leaving the context.\"\"\"\n    try:\n      yield self\n    finally:\n      self.invalidate()\n\n  def invalidate(self):\n    \"\"\"Invalidates the Scope.\"\"\"\n    self._invalid = True\n\n  def mutable_variables(self) -> VariableDict | dict[str, Any]:\n    \"\"\"Returns an immutable copy of the mutable variables belonging to this Scope.\"\"\"\n    self._populate_collections()\n    xs = {\n      k: v for k, v in self._variables.items() if in_filter(self.mutable, k)\n    }\n    if config.flax_return_frozendict:\n      return freeze(xs)\n    return xs\n\n  def variables(self) -> VariableDict | dict[str, Any]:\n    \"\"\"Returns an immutable copy of the variables belonging to this Scope.\"\"\"\n    self._populate_collections()\n    if config.flax_return_frozendict:\n      return freeze(self._variables)\n    return self._variables\n\n  def _validate_trace_level(self):\n    tracers.check_trace_level(self.trace_level)\n\n  def rewound(self, rewind_rngs: bool = False) -> 'Scope':\n    \"\"\"Returns a rewound version of this Scope.\n\n    Args:\n      rewind_rngs: if true, reset the RNG counter of this scope.\n\n    Returns:\n      A rewound version of this scope, which means reservations are\n      emptied, and the rng counter is optionally rewound.\n    \"\"\"\n    self._check_valid()\n    scope = Scope(\n      self._variables,\n      self.rngs,\n      self.name,\n      self.mutable,\n      self.parent,\n      path=self.path,\n      debug_path=self.debug_path,\n      flags=self.flags,\n    )\n    if not rewind_rngs:\n      scope.rng_counters = self.rng_counters\n    return scope\n\n  def name_reserved(self, name: str, col: str | None = None) -> bool:\n    \"\"\"Checks whether a name for a child Scope or Variable is taken.\n\n    Args:\n      name: the name to check for collision.\n      col: if a variable, the collection used.\n    \"\"\"\n    if name in self.reservations:\n      # allow the same name for two variables in\n      # different collections, otherwise raise error.\n      if (\n        None in self.reservations[name]\n        or col is None\n        or col in self.reservations[name]\n      ):\n        return True\n    return False\n\n  def reserve(self, name: str, col: str | None = None):\n    \"\"\"Reserves a name for a child Scope or Variable.\n\n    Throws an error if the name exists already.\n\n    Args:\n      name: the name to reserve.\n      col: if a variable, the collection used.\n    \"\"\"\n    if not isinstance(name, str):\n      raise TypeError(\n        'The type of scope \"{name}\" should be string but ' f'it is {type(name)}'\n      )\n    if self.name_reserved(name, col):\n      raise ValueError(f'Duplicate use of scope name: \"{name}\"')\n    self.reservations[name].add(col)\n\n  def default_name(self, prefix: str) -> str:\n    \"\"\"Generates an unreserved name with the given prefix.\n\n    Args:\n      prefix: prefix to use for generating an unreserved name.\n\n    Returns:\n      The generated name.\n    \"\"\"\n    i = 0\n    while True:\n      name = f'{prefix}{i}'\n      if name not in self.reservations:\n        return name\n      i += 1\n\n  def push(\n    self, name: str | None = None, prefix: str = '', reuse=False\n  ) -> 'Scope':\n    \"\"\"Creates a child Scope.\n\n    Args:\n      name: optional name of the child.\n      prefix: prefix used for generating the name if `name` is `None`.\n      reuse: if True will return a pre-existing child scope with the given name\n        instead of throwing an error.\n\n    Returns:\n      The child scope.\n    \"\"\"\n    self._check_valid()\n    self._validate_trace_level()\n    if name is None:\n      name = self.default_name(prefix)\n    if not reuse or name not in self.reservations:\n      self.reserve(name)\n    rngs = {key: LazyRng.create(rng, name) for key, rng in self.rngs.items()}\n    rng_key = (child_rng_token, name)\n    if rng_key in self.rng_counters:\n      rng_counters = self.rng_counters.get(rng_key)  # type: ignore\n    else:\n      rng_counters = {key: 0 for key in rngs}\n      self.rng_counters[rng_key] = rng_counters  # type: ignore\n    scope = Scope(\n      {},\n      name=name,\n      rngs=rngs,\n      parent=self,\n      mutable=self.mutable,\n      path=self.path + (name,),\n      debug_path=self.debug_path + (name,),\n      flags=self.flags,\n    )\n    scope.rng_counters = rng_counters\n    return scope\n\n  def child(\n    self,\n    fn: Callable[..., Any],\n    name: str | None = None,\n    prefix: str | None = None,\n    named_call: bool = True,\n    **partial_kwargs,\n  ) -> Callable[..., Any]:\n    \"\"\"Partially applies a child scope to fn.\n\n    When calling the returned function multiple times variables will be reused.\n\n    Args:\n      fn: the function to partially apply the child Scope to.\n      name: optional name of the child.\n      prefix: prefix used for generating name if it is `None`.\n      named_call: if true, `fn` will be run under `jax.named_scope`. The XLA\n        profiler will use this to name tag the computation.\n      **partial_kwargs: additional kwargs partially applied to `fn`.\n\n    Returns:\n      The function with a partially applied scope.\n    \"\"\"\n    if name is None:\n      if prefix is None:\n        prefix = fn.__name__ + '_' if hasattr(fn, '__name__') else ''\n      name = self.default_name(prefix)\n    scope = self.push(name)\n\n    @functools.wraps(fn)\n    def wrapper(*args, **kwargs):\n      kwargs = dict(partial_kwargs, **kwargs)\n      if named_call:\n        with jax.named_scope(name):\n          res = fn(scope.rewound(), *args, **kwargs)\n      else:\n        res = fn(scope.rewound(), *args, **kwargs)\n      return res\n\n    return wrapper\n\n  def is_mutable_collection(self, col: str) -> bool:\n    \"\"\"Returns true if the collection `col` is mutable.\"\"\"\n    return in_filter(self.mutable, col)\n\n  def is_collection_empty(self, col: str) -> bool:\n    \"\"\"Returns true if the collection is empty.\"\"\"\n    if col in self.root._variables:  # pylint: disable=protected-access\n      return not self.root._variables[col]  # pylint: disable=protected-access\n    return True\n\n  def _mutable_collection(self, col: str) -> MutableCollection:\n    \"\"\"Returns the collection `col` as a mutable object.\"\"\"\n    assert self.is_mutable_collection(col), f'Collection {col} is not mutable'\n\n    # The actual variable dict is stored in the root scope only, and subscopes\n    # hold references to subtrees relevant to them. This function ensures that\n    # the collections are created in the top-level Scope and we return the\n    # correct reference.\n    if col not in self._variables:\n      if not self.parent:\n        # If this is the top-level Scope, just add an empty collection.\n        self._variables[col] = {}\n      else:\n        assert self.name is not None  # Only top-level Scope have name None.\n        # Populate the parent collections recursively and obtain a reference to\n        # the direct parent (which, by transitivity, is be a reference to a\n        # dict in the root Scope).\n        parent_col = self.parent._mutable_collection(col)  # pylint: disable=protected-access\n        if self.name not in parent_col:\n          # If this Scope's name does not occur in the parent collection, add it\n          # to the parent scope (updating the parent's variable dict).\n          parent_col[self.name] = {}\n        # Store a reference to the parent's scope collection for in this scope's\n        # variable dict.\n        self._variables[col] = parent_col[self.name]\n\n    return self._variables[col]\n\n  def _collection(self, col: str) -> Collection:\n    \"\"\"Returns a collection of variables of collection `col`.\"\"\"\n    if col not in self._variables:\n      if self.parent:\n        assert self.name is not None\n        parent_col = self.parent._collection(col)  # pylint: disable=protected-access\n        if self.name not in parent_col:\n          return FrozenDict()\n        self._variables[col] = parent_col[self.name]\n      else:\n        return FrozenDict()\n    return self._variables[col]\n\n  def has_rng(self, name: str) -> bool:\n    \"\"\"Returns true if a PRNGSequence with name `name` exists.\"\"\"\n    return name in self.rngs\n\n  def make_rng(self, name: str = 'params') -> PRNGKey:\n    \"\"\"Generates A PRNGKey from a PRNGSequence with name `name`.\"\"\"\n    if not self.has_rng(name):\n      if self.has_rng('params'):\n        name = 'params'\n      else:\n        raise errors.InvalidRngError(f'{self.name} needs PRNG for \"{name}\"')\n    self._check_valid()\n    self._validate_trace_level()\n    self.rng_counters[name] += 1\n    return LazyRng.create(self.rngs[name], self.rng_counters[name]).as_jax_rng()\n\n  def get_variable(self, col: str, name: str, default: Any = None) -> Any:\n    \"\"\"Retrieves the value of a Variable.\n\n    Args:\n      col: the variable collection.\n      name: the name of the variable.\n      default: the default value to return if the variable does not exist in\n        this scope.\n\n    Returns:\n      The value of the input variable, of the default value if the variable\n      doesn't exist in this scope.\n    \"\"\"\n    variables = self._collection(col)\n    if name in variables:\n      return variables[name]\n    else:\n      return default\n\n  def has_variable(self, col: str, name: str) -> bool:\n    \"\"\"Returns true if the given variable exists in this scope.\n\n    Args:\n      col: the collection of the variable.\n      name: the name of the variable.\n    \"\"\"\n    variables = self._collection(col)\n    return name in variables\n\n  def put_variable(self, col: str, name: str, value: Any):\n    \"\"\"Updates the value of the given variable if it is mutable, or an error otherwise.\n\n    Args:\n      col: the collection of the variable.\n      name: the name of the variable.\n      value: the new value of the given variable.\n    \"\"\"\n    self._check_valid()\n    self._validate_trace_level()\n    if not self.is_mutable_collection(col):\n      raise errors.ModifyScopeVariableError(col, name, self.path_text)\n    variables = self._mutable_collection(col)\n\n    _put_variable(variables, name, value)\n\n  @overload\n  def variable(\n    self,\n    col: str,\n    name: str,\n    init_fn: Callable[..., T] | None = None,\n    *init_args,\n  ) -> Variable[T]:\n    ...\n\n  @overload\n  def variable(\n    self,\n    col: str,\n    name: str,\n    init_fn: Callable[..., T] | None = None,\n    *init_args,\n    unbox: Literal[True],\n    **init_kwargs,\n  ) -> Variable[T]:\n    ...\n\n  @overload\n  def variable(\n    self,\n    col: str,\n    name: str,\n    init_fn: Callable[..., T] | None = None,\n    *init_args,\n    unbox: Literal[False],\n    **init_kwargs,\n  ) -> Variable[meta.AxisMetadata[T]]:\n    ...\n\n  @overload\n  def variable(\n    self,\n    col: str,\n    name: str,\n    init_fn: Callable[..., T] | None = None,\n    *init_args,\n    unbox: bool = True,\n    **init_kwargs,\n  ) -> Variable[T] | Variable[meta.AxisMetadata[T]]:\n    ...\n\n  def variable(\n    self,\n    col: str,\n    name: str,  # pylint: disable=keyword-arg-before-vararg\n    init_fn: Callable[..., T] | None = None,\n    *init_args,\n    unbox: bool = True,\n    **init_kwargs,\n  ) -> Variable[T] | Variable[meta.AxisMetadata[T]]:\n    \"\"\"Creates a variable if it doesn't exist yet in this scope and returns it.\n\n    Args:\n      col: the collection of the variable.\n      name: the name of the variable.\n      init_fn: a function taking a PRNGKey plus any other number of positional\n        arguments. If None, the variable must already be initialized otherwise\n        an error is raised.\n      *init_args: the positional arguments to evaluate init_fn on lazily.\n      unbox: If True, ``AxisMetadata`` instances are replaced by their unboxed\n        value, see ``flax.nn.meta.unbox`` (default: True).\n      **init_kwargs: the key-word arguments to evaluate init_fn on lazily.\n\n    Returns:\n      The variable.  Throws an error if the variable exists already.\n    \"\"\"\n    self.reserve(name, col)\n    if not self.has_variable(col, name):\n      if not self.is_mutable_collection(col) or init_fn is None:\n        if self.is_collection_empty(col):\n          raise errors.ScopeCollectionNotFound(col, name, self.path_text)\n        raise errors.ScopeVariableNotFoundError(name, col, self.path_text)\n      init_value = init_fn(*init_args, **init_kwargs)\n      self.put_variable(col, name, init_value)\n    # cast to make static analyzers happy\n    return cast(\n      Union[Variable[T], Variable[meta.AxisMetadata[T]]],\n      Variable(self, col, name, unbox=unbox),\n    )\n\n  @overload\n  def param(\n    self, name: str, init_fn: Callable[..., T], *init_args,\n  ) -> T:\n    ...\n\n  @overload\n  def param(\n      self,\n      name: str,\n      init_fn: Callable[..., meta.AxisMetadata[T]] | Callable[..., T],\n      *init_args,\n      unbox: Literal[True],\n      **init_kwargs,\n  ) -> T:\n    ...\n\n  @overload\n  def param(\n      self,\n      name: str,\n      init_fn: Callable[..., T],\n      *init_args,\n      unbox: Literal[False],\n      **init_kwargs,\n  ) -> T:\n    ...\n\n  @overload\n  def param(\n    self,\n    name: str,\n    init_fn: Callable[..., T | meta.AxisMetadata[T]],\n    *init_args,\n    unbox: bool,\n    **init_kwargs,\n  ) -> T | meta.AxisMetadata[T]:\n    ...\n\n  def param(\n    self,\n    name: str,\n    init_fn: Callable[..., T],\n    *init_args,\n    unbox: bool = True,\n    **init_kwargs,\n  ) -> T | meta.AxisMetadata[T]:\n    \"\"\"Creates a parameter if it doesn't exist yet in this scope and returns it.\n\n    If the parameter exists already, the existing value is simply returned.\n\n    Args:\n      name: the name of the parameter.\n      init_fn: a function taking a PRNGKey plus any other number of positional\n        arguments.\n      *init_args: the positional arguments to evaluate init_fn on lazily.\n      unbox: If True, ``AxisMetadata`` instances are replaced by their unboxed\n        value, see ``flax.nn.meta.unbox`` (default: True).\n      **init_kwargs: the key-word arguments to evaluate init_fn on lazily.\n\n    Returns:\n      The parameters. Throws an error if the params exist already.\n    \"\"\"\n    self.reserve(name, 'params')\n    if self.has_variable('params', name):\n      value = self.get_variable('params', name)\n      if unbox:\n        value = meta.unbox(value)\n      # Validate that the shape of the init_fn output is the same as the shape\n      # of the existing parameter. This is to make sure that the hparams set up\n      # in a Flax Module match the shapes coming in during apply, and if not,\n      # catch it with an error message.\n      # NOTE: We could consider moving this to `self.`\n      abs_value = jax.eval_shape(\n          lambda: init_fn(random.key(0), *init_args, **init_kwargs)\n      )\n      abs_value_flat = jax.tree_util.tree_leaves(abs_value)\n      value_flat = jax.tree_util.tree_leaves(value)\n      for val, abs_val in zip(value_flat, abs_value_flat):\n        # NOTE: We could check dtype consistency here as well but its usefulness\n        # is less obvious. We might intentionally change the dtype for inference\n        # to a half float type for example.\n        if np.shape(val) != np.shape(abs_val):\n          raise errors.ScopeParamShapeError(\n              name, self.path_text, np.shape(val), np.shape(abs_val)\n          )\n    else:\n      if not self.is_mutable_collection('params'):\n        if self.is_collection_empty('params'):\n          raise errors.ScopeCollectionNotFound('params', name, self.path_text)\n        raise errors.ScopeParamNotFoundError(name, self.path_text)\n      value = init_fn(self.make_rng('params'), *init_args, **init_kwargs)\n      self.put_variable('params', name, value)\n      if unbox:\n        value = meta.unbox(value)\n    return value\n\n  def _populate_collections(self):\n    collections = self.root._variables.keys()  # pylint: disable=protected-access\n    for col in collections:\n      self._collection(col)\n\n  def has_flag(self, key) -> bool:\n    return key in self.flags\n\n  def get_flag(self, key, default=no_flag) -> Any:\n    if key not in self.flags and default is no_flag:\n      return ValueError(f'Flag {key} not present on scope.')\n    return self.flags.get(key, default)\n\n\ndef _unfreeze_variables(variables, mutable):\n  new_variables = {}\n  for key, value in variables.items():\n    if in_filter(mutable, key):\n      new_variables[key] = unfreeze(value)\n    else:\n      new_variables[key] = value\n  return new_variables\n\n\ndef bind(\n  variables: VariableDict,\n  rngs: RNGSequences | None = None,\n  mutable: CollectionFilter = False,\n  flags: Mapping | None = None,\n):\n  \"\"\"Binds variables and rngs to a new ``Scope``.\n\n  bind provides a ``Scope`` instance without transforming a function with\n  ``apply``. This is particularly useful for debugging and interactive use cases\n  like notebooks where a function would limit the ability split up code into\n  different cells.\n\n  a ``Scope`` instance is a stateful object. Note that idiomatic JAX is\n  functional and therefore a ``Scope` does not mix well well with vanilla JAX\n  APIs. Therefore, we recommend using ``apply`` when code should be reusable and\n  compatible across the JAX software ecosystem.\n\n  Args:\n    variables: Variable dictionary to bind.\n    rngs: RNGs to bind.\n    mutable: Which variable collections to treat as mutable.\n    flags: internal flags.\n\n  Returns:\n    A new scope with the variables and rngs bound to it.\n  \"\"\"\n  if not _is_valid_variables(variables):\n    raise errors.ApplyScopeInvalidVariablesTypeError()\n  if rngs is not None and not _is_valid_rngs(rngs):\n    raise errors.InvalidRngError(\n      'rngs should be a dictionary mapping strings to `jax.PRNGKey`.'\n    )\n  new_variables = _unfreeze_variables(variables, mutable)\n  return Scope(new_variables, rngs=rngs, mutable=mutable, flags=flags)\n\n\ndef apply(\n  fn: Callable[..., Any],\n  mutable: CollectionFilter = False,\n  flags: Mapping | None = None,\n) -> Callable[..., Any]:\n  \"\"\"Functionalize a `Scope` function.\n\n  Args:\n    fn: a function taking a `Scope` as its first argument.\n    mutable: the filter determining which variable collections are mutable.\n    flags: internal flags.\n\n  Returns:\n    `fn` with the scope partially applied.\n  \"\"\"\n\n  @functools.wraps(fn)\n  def wrapper(\n    variables: VariableDict,\n    *args,\n    rngs: PRNGKey | RNGSequences | None = None,\n    **kwargs,\n  ) -> Any | tuple[Any, VariableDict | dict[str, Any]]:\n    if rngs is not None:\n      if not _is_valid_rng(rngs) and not _is_valid_rngs(rngs):\n        raise ValueError(\n          'The ``rngs`` argument passed to an apply function should be a '\n          '``jax.PRNGKey`` or a dictionary mapping strings to '\n          '``jax.PRNGKey``.'\n        )\n      if not isinstance(rngs, (dict, FrozenDict)):\n        rngs = {'params': rngs}\n\n    # Try to detect if user accidentally passed {'params': {'params': ...}.\n    if (\n      'params' in variables\n      and isinstance(variables['params'], (dict, FrozenDict))\n      and 'params' in variables['params']\n    ):\n      raise errors.ApplyScopeInvalidVariablesStructureError(variables)\n\n    with bind(\n      variables, rngs=rngs, mutable=mutable, flags=flags\n    ).temporary() as root:\n      y = fn(root, *args, **kwargs)\n    if mutable is not False:\n      return y, root.mutable_variables()\n    else:\n      return y\n\n  return wrapper\n\n\ndef init(\n  fn: Callable[..., Any],\n  mutable: CollectionFilter = True,\n  flags: Mapping | None = None,\n) -> Callable[..., Any]:\n  \"\"\"Functionalize a `Scope` function for initialization.\n\n  Args:\n    fn: a function taking a `Scope` as its first argument.\n    mutable: the filter determining which variable collections are mutable.\n    flags: internal flags.\n\n  Returns:\n    `fn` with the scope partially applied.\n  \"\"\"\n\n  @functools.wraps(fn)\n  def wrapper(rngs, *args, **kwargs) -> tuple[Any, VariableDict]:\n    if not _is_valid_rng(rngs) and not _is_valid_rngs(rngs):\n      raise ValueError(\n        'First argument passed to an init function should be a '\n        '``jax.PRNGKey`` or a dictionary mapping strings to '\n        '``jax.PRNGKey``.'\n      )\n    if not isinstance(rngs, (dict, FrozenDict)):\n      rngs = {'params': rngs}\n    init_flags = {**(flags if flags is not None else {}), 'initializing': True}\n    return apply(fn, mutable=mutable, flags=init_flags)(\n      {}, *args, rngs=rngs, **kwargs\n    )\n\n  return wrapper\n\n\ndef lazy_init(\n  fn: Callable[..., Any],\n  mutable: CollectionFilter = True,\n  flags: Mapping | None = None,\n) -> Callable[..., Any]:\n  \"\"\"Functionalizes a `Scope` function for lazy initialization.\n\n  Similar to ``init`` except that the init function now accepts\n  ``jax.ShapeDtypeStruct`` instances for arguments that do not\n  affect the variable initialization (typically this is all the input data).\n\n  Example::\n\n    def f(scope, x):\n        # the kernel init only uses the shape of x so we don't actually\n        # need a value for x and can pass it as a ShapeDtypeStruct in lazy_init.\n        k = scope.param(\"kernel\", nn.initializers.lecun_normal(), (x.shape[-1], x.shape[-1]))\n        return x @ k\n    init_fn = lazy_init(f)\n    variables = init_fn(random.key(0), jax.ShapeDtypeStruct((1, 128), jnp.float32))\n\n\n  Args:\n    fn: a function taking a `Scope` as its first argument.\n    mutable: the filter determining which variable collections are mutable.\n    flags: internal flags.\n\n  Returns:\n    `fn` with the scope partially applied. Unlike ``init`` which returns a tuple of function\n    output and variables, the lazy init function only returns the variables.\n  \"\"\"\n  return partial_eval.lazy_init(\n    lambda *args, **kwargs: init(fn, mutable, flags)(*args, **kwargs)[1]\n  )\n\n\ndef _is_valid_collection(col: VariableDict):\n  if not isinstance(col, (FrozenDict, dict)):\n    return False\n  for name in col.keys():\n    # Any value can be stored in a collection so only keys can be verified.\n    if not isinstance(name, str):\n      return False\n  return True\n\n\ndef _is_valid_variables(variables: VariableDict) -> bool:\n  \"\"\"Checks whether the given variable dict is valid.\n\n  Args:\n    variables: A variable dict.\n\n  Returns:\n    True if `variables` is a valid variable dict.\n  \"\"\"\n  for name, col in variables.items():\n    if not isinstance(name, str):\n      return False\n    if not _is_valid_collection(col):\n      return False\n  return True\n\n\ndef _is_valid_rng(rng: Array):\n  \"\"\"Checks whether rng is a valid JAX PRNGKey, also handling custom prngs.\"\"\"\n  # Allow for user-provided LazyRng - useful for compatibility when refactoring.\n  if isinstance(rng, LazyRng):\n    return True\n\n  # This check is valid for either new-style or old-style PRNG keys\n  if not isinstance(rng, (np.ndarray, jnp.ndarray)):\n    return False\n\n  # Handle new-style typed PRNG keys\n  if jax.dtypes.issubdtype(rng.dtype, jax.dtypes.prng_key):\n    return rng.shape == ()\n\n  # Handle old-style raw PRNG keys\n  expected_rng = jax.eval_shape(\n    lambda s: jax.random.key_data(jax.random.key(s)), 0\n  )\n  if (rng.shape, rng.dtype) != (expected_rng.shape, expected_rng.dtype):\n    return False\n  return True\n\n\ndef _is_valid_rngs(rngs: PRNGKey | RNGSequences):\n  if not isinstance(rngs, (FrozenDict, dict)):\n    return False\n  for key, val in rngs.items():\n    if not isinstance(key, str):\n      return False\n    if not _is_valid_rng(val):\n      return False\n  return True\n"
  },
  {
    "path": "flax/core/spmd.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport contextlib\nimport dataclasses\nimport threading\n\nimport jax\nfrom jax.sharding import PartitionSpec, NamedSharding\nfrom flax.core import meta\nfrom jax.experimental.layout import Format\nfrom flax.typing import (\n    LogicalRules,\n    Sharding,\n)\n\ndef get_pspec(sharding, sharding_rules = None) -> PartitionSpec:\n  \"\"\"Given an `nnx.Variable`, return its `PartitionSpec`.\"\"\"\n  return apply_rules(sharding, sharding_rules)   # type: ignore\n\ndef map_sharding(f, sharding):\n  if isinstance(sharding, PartitionSpec) or isinstance(sharding, tuple):\n    return PartitionSpec(*map(f, sharding))\n  elif isinstance(sharding, NamedSharding):\n    return NamedSharding(sharding.mesh, map_sharding(f, sharding.spec)) # type: ignore\n  elif isinstance(sharding, Format):\n    return Format(sharding.layout, map_sharding(f, sharding.sharding))  # type: ignore\n\ndef get_mesh(sharding):\n  if isinstance(sharding, PartitionSpec) or isinstance(sharding, tuple):\n    return None\n  elif isinstance(sharding, NamedSharding):\n    return sharding.mesh\n  elif isinstance(sharding, Format):\n    return get_mesh(sharding.sharding)\n\ndef apply_rules(sharding, sharding_rules):\n  \"\"\"Rename the axes of a sharding specification (which can include `PartitionSpec`, `NamedSharding` or `Format` objects).\"\"\"\n  if get_logical_axis_rules() or sharding_rules:\n    context_rules = get_logical_axis_rules()\n    rules = {alias: on_mesh for (alias, on_mesh) in composite_rules(context_rules, sharding_rules)}\n  else:\n    rules = {}\n  return map_sharding(lambda a: rules.get(a, a), sharding)\n\ndef _apply_sharding(value, sharding, mesh):\n  if isinstance(sharding, Format):\n    return jax.lax.with_sharding_constraint(value, sharding)\n  if mesh.are_all_axes_explicit:\n    return jax.sharding.reshard(value, sharding)\n  elif mesh.are_all_axes_auto:\n    return jax.lax.with_sharding_constraint(value, sharding)\n  else:\n    raise ValueError(\n        'Mesh must have all axes as Explicit or all axes as Auto. '\n        f'Got mixed axis types: {mesh.axis_types}')\n\ndef shard_value(value, out_sharding, sharding_rules, mesh):\n  if not out_sharding:\n    return value\n\n  if mesh is None:\n    mesh = meta.get_global_mesh()\n\n  out_sharding = apply_rules(out_sharding, sharding_rules)\n\n  sharding_mesh = get_mesh(out_sharding)\n\n  if sharding_mesh:\n    if mesh:\n      assert mesh == out_sharding.mesh\n    mesh = sharding_mesh\n\n  if mesh is None:\n    raise ValueError(\n      'An auto mesh context or metadata is required if creating a variable'\n      f' with annotation {out_sharding=}. '\n      'For more guidance, see https://flax.readthedocs.io/en/latest/flip/4844-var-eager-sharding.html.')\n\n  if isinstance(out_sharding, PartitionSpec):\n    out_sharding = NamedSharding(mesh, out_sharding)\n\n  return _apply_sharding(value, out_sharding, mesh)\n\n\n\n\n# Dynamic Axis Mapping Context\n# ------------------------------------------------------------------------------\n\n\n@dataclasses.dataclass\nclass _AxisRules(threading.local):\n  \"\"\"Dynamic logical axis to mesh axis binding context.\"\"\"\n\n  rules: LogicalRules = ()\n\n\n# Global axis binding context.\n_axis_rules = _AxisRules()\n\n\ndef set_logical_axis_rules(rules: LogicalRules):\n  \"\"\"Sets the global logical axis to mesh axis binding.\"\"\"\n  _axis_rules.rules = rules\n\n\ndef get_logical_axis_rules() -> LogicalRules:\n  \"\"\"Returns the global logical axis to mesh axis binding.\"\"\"\n  return _axis_rules.rules\n\n\n@contextlib.contextmanager\ndef logical_axis_rules(rules: LogicalRules):\n  \"\"\"Context manager for setting the logical to mesh axis bindings.\"\"\"\n  old_rules = _axis_rules.rules\n  try:\n    _axis_rules.rules = rules\n    yield\n  finally:\n    _axis_rules.rules = old_rules\n\n\ndef composite_rules(rule1, rule2):\n  if not rule1 and not rule2:\n    return ()\n  if rule1 and not rule2:\n    return rule1\n  if rule2 and not rule1:\n    return rule2\n  rules = {alias: value for alias, value in rule1}\n  for alias, value in rule2:\n    if alias in rules and rules[alias] != value:\n      raise ValueError(\n          f'Inconsistent logical axis annotations for {alias}: '\n          f'{rules[alias]} vs {value}'\n      )\n    rules[alias] = value\n  return tuple(rules.items())\n\n\ndef from_sharding_rules(\n    sharding: Sharding, sharding_rules: LogicalRules\n) -> Sharding:\n  rules = {alias: on_mesh for (alias, on_mesh) in sharding_rules}\n  return tuple(\n      rules[str(s)] if (s and str(s) in rules) else s for s in sharding\n  )\n"
  },
  {
    "path": "flax/core/tracers.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Functionality for inspecting jax tracers.\"\"\"\n\nimport jax\nimport jax.core\n\n\ndef current_trace():\n  \"\"\"Returns the current JAX state tracer.\"\"\"\n  if jax.__version_info__ <= (0, 4, 33):\n    top = jax.core.find_top_trace(())\n    if top:\n      return top.level\n    else:\n      return float('-inf')\n\n  return jax.core.get_opaque_trace_state(convention=\"flax\")\n\ndef check_trace_level(base_level):\n  # TODO(cgarciae): skipping for now as it breaks\n  # too many internal tests.\n  # level = current_trace()\n  # if level != base_level:\n  #   raise errors.JaxTransformError()\n  pass\n"
  },
  {
    "path": "flax/core/variables.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"A variable dict is a normal Python dictionary, which is a container for one\nor more \"variable collections\", each of which are nested dictionaries whose\nleaves are ``jax.numpy`` arrays.\n\nThe different variable collections share the same nested tree structure.\n\nFor example, consider the following variable dictionary::\n\n  {\n    \"params\": {\n      \"Conv1\": { \"weight\": ..., \"bias\": ... },\n      \"BatchNorm1\": { \"scale\": ..., \"mean\": ... },\n      \"Conv2\": {...}\n    },\n    \"batch_stats\": {\n      \"BatchNorm1\": { \"moving_mean\": ..., \"moving_average\": ...}\n    }\n  }\n\nIn this case, the ``\"BatchNorm1\"`` key lives in both the ``\"params\"`` and\n```\"batch_stats\"\"`` collections. This reflects the fact that the submodule\nnamed ``\"\"BatchNorm1\"\"`` has both trainable parameters (the ``\"params\"`` collection),\nas well as other non-trainable variables (the ``\"batch_stats\"`` collection)\n\nTODO: Make \"variable dict\" design note, and link to it from here.\n\"\"\"\n\nfrom .scope import Variable\n"
  },
  {
    "path": "flax/cursor.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport dataclasses\nimport enum\nfrom typing import (\n  Any,\n  Generic,\n  Protocol,\n  TypeVar,\n  runtime_checkable,\n)\nfrom collections.abc import Callable, Generator, Mapping\n\nfrom flax.core import FrozenDict\nfrom flax.errors import CursorFindError, TraverseTreeError\n\nA = TypeVar('A')\nKey = Any\n\n\n@runtime_checkable\nclass Indexable(Protocol):\n  def __getitem__(self, key) -> Any:\n    ...\n\n\nclass AccessType(enum.Enum):\n  ITEM = enum.auto()\n  ATTR = enum.auto()\n\n\n@dataclasses.dataclass\nclass ParentKey(Generic[A]):\n  parent: 'Cursor[A]'\n  key: Key\n  access_type: AccessType\n\n\ndef is_named_tuple(obj):\n  return (\n    isinstance(obj, tuple)\n    and hasattr(obj, '_fields')\n    and hasattr(obj, '_asdict')\n    and hasattr(obj, '_replace')\n  )\n\n\ndef _traverse_tree(path, obj, *, update_fn=None, cond_fn=None):\n  \"\"\"Helper function for ``Cursor.apply_update`` and ``Cursor.find_all``.\n  Exactly one of ``update_fn`` and ``cond_fn`` must be not None.\n\n  - If ``update_fn`` is not None, then ``Cursor.apply_update`` is calling\n    this function and ``_traverse_tree`` will return a generator where\n    each generated element is of type Tuple[Tuple[Union[str, int], AccessType], Any].\n    The first element is a tuple of the key path and access type where the\n    change was applied from the ``update_fn``, and the second element is\n    the newly modified value. If the generator is non-empty, then the\n    tuple key path will always be non-empty as well.\n  - If ``cond_fn`` is not None, then ``Cursor.find_all`` is calling this\n    function and ``_traverse_tree`` will return a generator where each\n    generated element is of type Tuple[Union[str, int], AccessType]. The\n    tuple contains the key path and access type where the object was found\n    that fulfilled the conditions of the ``cond_fn``.\n  \"\"\"\n  if not (bool(update_fn) ^ bool(cond_fn)):\n    raise TraverseTreeError(update_fn, cond_fn)\n\n  if path:\n    str_path = '/'.join(str(key) for key, _ in path)\n    if update_fn:\n      new_obj = update_fn(str_path, obj)\n      if new_obj is not obj:\n        yield path, new_obj\n        return\n    elif cond_fn(str_path, obj):  # type: ignore\n      yield path\n      return\n\n  if isinstance(obj, (FrozenDict, dict)):\n    items = obj.items()\n    access_type = AccessType.ITEM\n  elif is_named_tuple(obj):\n    items = ((name, getattr(obj, name)) for name in obj._fields)  # type: ignore\n    access_type = AccessType.ATTR\n  elif isinstance(obj, (list, tuple)):\n    items = enumerate(obj)\n    access_type = AccessType.ITEM\n  elif dataclasses.is_dataclass(obj):\n    items = (\n      (f.name, getattr(obj, f.name)) for f in dataclasses.fields(obj) if f.init\n    )\n    access_type = AccessType.ATTR\n  else:\n    return\n\n  if update_fn:\n    for key, value in items:\n      yield from _traverse_tree(\n        path + ((key, access_type),), value, update_fn=update_fn\n      )\n  else:\n    for key, value in items:\n      yield from _traverse_tree(\n        path + ((key, access_type),), value, cond_fn=cond_fn\n      )\n\n\nclass Cursor(Generic[A]):\n  _obj: A\n  _parent_key: ParentKey[A] | None\n  _changes: dict[Any, 'Cursor[A]']\n\n  def __init__(self, obj: A, parent_key: ParentKey[A] | None):\n    # NOTE: we use `vars` here to avoid calling `__setattr__`\n    # vars(self) = self.__dict__\n    vars(self)['_obj'] = obj\n    vars(self)['_parent_key'] = parent_key\n    vars(self)['_changes'] = {}\n\n  @property\n  def _root(self) -> 'Cursor[A]':\n    if self._parent_key is None:\n      return self\n    else:\n      return self._parent_key.parent._root  # type: ignore\n\n  @property\n  def _path(self) -> str:\n    if self._parent_key is None:\n      return ''\n    if self._parent_key.access_type == AccessType.ITEM:  # type: ignore\n      if isinstance(self._parent_key.key, str):  # type: ignore\n        key = \"'\" + self._parent_key.key + \"'\"  # type: ignore\n      else:\n        key = str(self._parent_key.key)  # type: ignore\n      return self._parent_key.parent._path + '[' + key + ']'  # type: ignore\n    # self.parent_key.access_type == AccessType.ATTR:\n    return self._parent_key.parent._path + '.' + self._parent_key.key  # type: ignore\n\n  def __getitem__(self, key) -> 'Cursor[A]':\n    if key in self._changes:\n      return self._changes[key]\n\n    if not isinstance(self._obj, Indexable):\n      raise TypeError(f'Cannot index into {self._obj}')\n\n    if isinstance(self._obj, Mapping) and key not in self._obj:\n      raise KeyError(f'Key {key} not found in {self._obj}')\n\n    if is_named_tuple(self._obj):\n      return getattr(self, self._obj._fields[key])  # type: ignore\n\n    child = Cursor(self._obj[key], ParentKey(self, key, AccessType.ITEM))\n    self._changes[key] = child\n    return child\n\n  def __getattr__(self, name) -> 'Cursor[A]':\n    if name in self._changes:\n      return self._changes[name]\n\n    if not hasattr(self._obj, name):\n      raise AttributeError(f'Attribute {name} not found in {self._obj}')\n\n    child = Cursor(\n      getattr(self._obj, name), ParentKey(self, name, AccessType.ATTR)\n    )\n    self._changes[name] = child\n    return child\n\n  def __setitem__(self, key, value):\n    if is_named_tuple(self._obj):\n      return setattr(self, self._obj._fields[key], value)  # type: ignore\n    self._changes[key] = Cursor(value, ParentKey(self, key, AccessType.ITEM))\n\n  def __setattr__(self, name, value):\n    self._changes[name] = Cursor(value, ParentKey(self, name, AccessType.ATTR))\n\n  def set(self, value) -> A:\n    \"\"\"Set a new value for an attribute, property, element or entry\n    in the Cursor object and return a copy of the original object,\n    containing the new set value.\n\n    Example::\n\n      >>> from flax.cursor import cursor\n      >>> from flax.training import train_state\n      >>> import optax\n\n      >>> dict_obj = {'a': 1, 'b': (2, 3), 'c': [4, 5]}\n      >>> modified_dict_obj = cursor(dict_obj)['b'][0].set(10)\n      >>> assert modified_dict_obj == {'a': 1, 'b': (10, 3), 'c': [4, 5]}\n\n      >>> state = train_state.TrainState.create(\n      ...     apply_fn=lambda x: x,\n      ...     params=dict_obj,\n      ...     tx=optax.adam(1e-3),\n      ... )\n      >>> modified_state = cursor(state).params['b'][1].set(10)\n      >>> assert modified_state.params == {'a': 1, 'b': (2, 10), 'c': [4, 5]}\n\n    Args:\n      value: the value used to set an attribute, property, element or entry in the Cursor object\n    Returns:\n      A copy of the original object with the new set value.\n    \"\"\"\n    if self._parent_key is None:\n      return value\n    parent, key = self._parent_key.parent, self._parent_key.key  # type: ignore\n    parent._changes[key] = Cursor(value, self._parent_key)\n    return parent._root.build()\n\n  def build(self) -> A:\n    \"\"\"Create and return a copy of the original object with accumulated changes.\n    This method is to be called after making changes to the Cursor object.\n\n    .. note::\n      The new object is built bottom-up, the changes will be first applied\n      to the leaf nodes, and then its parent, all the way up to the root.\n\n    Example::\n\n      >>> from flax.cursor import cursor\n      >>> from flax.training import train_state\n      >>> import optax\n\n      >>> dict_obj = {'a': 1, 'b': (2, 3), 'c': [4, 5]}\n      >>> c = cursor(dict_obj)\n      >>> c['b'][0] = 10\n      >>> c['a'] = (100, 200)\n      >>> modified_dict_obj = c.build()\n      >>> assert modified_dict_obj == {'a': (100, 200), 'b': (10, 3), 'c': [4, 5]}\n\n      >>> state = train_state.TrainState.create(\n      ...     apply_fn=lambda x: x,\n      ...     params=dict_obj,\n      ...     tx=optax.adam(1e-3),\n      ... )\n      >>> new_fn = lambda x: x + 1\n      >>> c = cursor(state)\n      >>> c.params['b'][1] = 10\n      >>> c.apply_fn = new_fn\n      >>> modified_state = c.build()\n      >>> assert modified_state.params == {'a': 1, 'b': (2, 10), 'c': [4, 5]}\n      >>> assert modified_state.apply_fn == new_fn\n\n    Returns:\n      A copy of the original object with the accumulated changes.\n    \"\"\"\n    changes = {\n      key: child.build() if isinstance(child, Cursor) else child\n      for key, child in self._changes.items()\n    }\n    if isinstance(self._obj, FrozenDict):\n      obj = self._obj.copy(changes)  # type: ignore\n    elif isinstance(self._obj, (dict, list)):\n      obj = self._obj.copy()  # type: ignore\n      for key, value in changes.items():\n        obj[key] = value\n    elif is_named_tuple(self._obj):\n      obj = self._obj._replace(**changes)  # type: ignore\n    elif isinstance(self._obj, tuple):\n      obj = list(self._obj)  # type: ignore\n      for key, value in changes.items():\n        obj[key] = value\n      obj = tuple(obj)  # type: ignore\n    elif dataclasses.is_dataclass(self._obj):\n      obj = dataclasses.replace(self._obj, **changes)  # type: ignore\n    else:\n      obj = self._obj  # type: ignore\n    return obj  # type: ignore\n\n  def apply_update(\n    self,\n    update_fn: Callable[[str, Any], Any],\n  ) -> 'Cursor[A]':\n    \"\"\"Traverse the Cursor object and record conditional changes recursively via an ``update_fn``.\n    The changes are recorded in the Cursor object's ``._changes`` dictionary. To generate a copy\n    of the original object with the accumulated changes, call the ``.build`` method after calling\n    ``.apply_update``.\n\n    The ``update_fn`` has a function signature of ``(str, Any) -> Any``:\n\n    - The input arguments are the current key path (in the form of a string delimited\n      by ``'/'``) and value at that current key path\n    - The output is the new value (either modified by the ``update_fn`` or same as the\n      input value if the condition wasn't fulfilled)\n\n    .. note::\n      - If the ``update_fn`` returns a modified value, this method will not recurse any further\n        down that branch to record changes. For example, if we intend to replace an attribute that points\n        to a dictionary with an int, we don't need to look for further changes inside the dictionary,\n        since the dictionary will be replaced anyways.\n      - The ``is`` operator is used to determine whether the return value is modified (by comparing it\n        to the input value). Therefore if the ``update_fn`` modifies a mutable container (e.g. lists,\n        dicts, etc.) and returns the same container, ``.apply_update`` will treat the returned value as\n        unmodified as it contains the same ``id``. To avoid this, return a copy of the modified value.\n      - ``.apply_update`` WILL NOT call the ``update_fn`` to the value at the top-most level of\n        the pytree (i.e. the root node). The ``update_fn`` will first be called on the root node's\n        children, and then the pytree traversal will continue recursively from there.\n\n    Example::\n\n      >>> import flax.linen as nn\n      >>> from flax.cursor import cursor\n      >>> import jax, jax.numpy as jnp\n\n      >>> class Model(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     x = nn.Dense(3)(x)\n      ...     x = nn.relu(x)\n      ...     x = nn.Dense(3)(x)\n      ...     x = nn.relu(x)\n      ...     x = nn.Dense(3)(x)\n      ...     x = nn.relu(x)\n      ...     return x\n\n      >>> params = Model().init(jax.random.key(0), jnp.empty((1, 2)))['params']\n\n      >>> def update_fn(path, value):\n      ...   '''Multiply all dense kernel params by 2 and add 1.\n      ...   Subtract the Dense_1 bias param by 1.'''\n      ...   if 'kernel' in path:\n      ...     return value * 2 + 1\n      ...   elif 'Dense_1' in path and 'bias' in path:\n      ...     return value - 1\n      ...   return value\n\n      >>> c = cursor(params)\n      >>> new_params = c.apply_update(update_fn).build()\n      >>> for layer in ('Dense_0', 'Dense_1', 'Dense_2'):\n      ...   assert (new_params[layer]['kernel'] == 2 * params[layer]['kernel'] + 1).all()\n      ...   if layer == 'Dense_1':\n      ...     assert (new_params[layer]['bias'] == params[layer]['bias'] - 1).all()\n      ...   else:\n      ...     assert (new_params[layer]['bias'] == params[layer]['bias']).all()\n\n      >>> assert jax.tree_util.tree_all(\n      ...       jax.tree_util.tree_map(\n      ...           lambda x, y: (x == y).all(),\n      ...           params,\n      ...           Model().init(jax.random.key(0), jnp.empty((1, 2)))[\n      ...               'params'\n      ...           ],\n      ...       )\n      ...   ) # make sure original params are unchanged\n\n    Args:\n      update_fn: the function that will conditionally record changes to the Cursor object\n    Returns:\n      The current Cursor object with the recorded conditional changes specified by the\n      ``update_fn``. To generate a copy of the original object with the accumulated\n      changes, call the ``.build`` method after calling ``.apply_update``.\n    \"\"\"\n    for path, value in _traverse_tree((), self._obj, update_fn=update_fn):\n      child = self\n      for key, access_type in path[:-1]:\n        if access_type is AccessType.ITEM:\n          child = child[key]\n        else:  # access_type is AccessType.ATTR\n          child = getattr(child, key)\n      key, access_type = path[-1]\n      if access_type is AccessType.ITEM:\n        child[key] = value\n      else:  # access_type is AccessType.ATTR\n        setattr(child, key, value)\n\n    return self\n\n  def find(self, cond_fn: Callable[[str, Any], bool]) -> 'Cursor[A]':\n    \"\"\"Traverse the Cursor object and return a child Cursor object that fulfill the\n    conditions in the ``cond_fn``. The ``cond_fn`` has a function signature of ``(str, Any) -> bool``:\n\n    - The input arguments are the current key path (in the form of a string delimited\n      by ``'/'``) and value at that current key path\n    - The output is a boolean, denoting whether to return the child Cursor object at this path\n\n    Raises a :meth:`CursorFindError <flax.errors.CursorFindError>` if no object or more\n    than one object is found that fulfills the condition of the ``cond_fn``. We raise an\n    error because the user should always expect this method to return the only object whose\n    corresponding key path and value fulfill the condition of the ``cond_fn``.\n\n    .. note::\n      - If the ``cond_fn`` evaluates to True at a particular key path, this method will not recurse\n        any further down that branch; i.e. this method will find and return the \"earliest\" child node\n        that fulfills the condition in ``cond_fn`` in a particular key path\n      - ``.find`` WILL NOT search the the value at the top-most level of the pytree (i.e. the root\n        node). The ``cond_fn`` will be evaluated recursively, starting at the root node's children.\n\n    Example::\n\n      >>> import flax.linen as nn\n      >>> from flax.cursor import cursor\n      >>> import jax, jax.numpy as jnp\n\n      >>> class Model(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     x = nn.Dense(3)(x)\n      ...     x = nn.relu(x)\n      ...     x = nn.Dense(3)(x)\n      ...     x = nn.relu(x)\n      ...     x = nn.Dense(3)(x)\n      ...     x = nn.relu(x)\n      ...     return x\n\n      >>> params = Model().init(jax.random.PRNGKey(0), jnp.empty((1, 2)))['params']\n\n      >>> def cond_fn(path, value):\n      ...   '''Find the second dense layer params.'''\n      ...   return 'Dense_1' in path\n\n      >>> new_params = cursor(params).find(cond_fn)['bias'].set(params['Dense_1']['bias'] + 1)\n\n      >>> for layer in ('Dense_0', 'Dense_1', 'Dense_2'):\n      ...   if layer == 'Dense_1':\n      ...     assert (new_params[layer]['bias'] == params[layer]['bias'] + 1).all()\n      ...   else:\n      ...     assert (new_params[layer]['bias'] == params[layer]['bias']).all()\n\n      >>> c = cursor(params)\n      >>> c2 = c.find(cond_fn)\n      >>> c2['kernel'] += 2\n      >>> c2['bias'] += 2\n      >>> new_params = c.build()\n\n      >>> for layer in ('Dense_0', 'Dense_1', 'Dense_2'):\n      ...   if layer == 'Dense_1':\n      ...     assert (new_params[layer]['kernel'] == params[layer]['kernel'] + 2).all()\n      ...     assert (new_params[layer]['bias'] == params[layer]['bias'] + 2).all()\n      ...   else:\n      ...     assert (new_params[layer]['kernel'] == params[layer]['kernel']).all()\n      ...     assert (new_params[layer]['bias'] == params[layer]['bias']).all()\n\n      >>> assert jax.tree_util.tree_all(\n      ...       jax.tree_util.tree_map(\n      ...           lambda x, y: (x == y).all(),\n      ...           params,\n      ...           Model().init(jax.random.PRNGKey(0), jnp.empty((1, 2)))[\n      ...               'params'\n      ...           ],\n      ...       )\n      ...   ) # make sure original params are unchanged\n\n    Args:\n      cond_fn: the function that will conditionally find child Cursor objects\n    Returns:\n      A child Cursor object that fulfills the condition in the ``cond_fn``.\n    \"\"\"\n    generator = self.find_all(cond_fn)\n    try:\n      cursor = next(generator)\n    except StopIteration:\n      raise CursorFindError()\n    try:\n      cursor2 = next(generator)\n      raise CursorFindError(cursor, cursor2)\n    except StopIteration:\n      return cursor\n\n  def find_all(\n    self, cond_fn: Callable[[str, Any], bool]\n  ) -> Generator['Cursor[A]', None, None]:\n    \"\"\"Traverse the Cursor object and return a generator of child Cursor objects that fulfill the\n    conditions in the ``cond_fn``. The ``cond_fn`` has a function signature of ``(str, Any) -> bool``:\n\n    - The input arguments are the current key path (in the form of a string delimited\n      by ``'/'``) and value at that current key path\n    - The output is a boolean, denoting whether to return the child Cursor object at this path\n\n    .. note::\n      - If the ``cond_fn`` evaluates to True at a particular key path, this method will not recurse\n        any further down that branch; i.e. this method will find and return the \"earliest\" child nodes\n        that fulfill the condition in ``cond_fn`` in a particular key path\n      - ``.find_all`` WILL NOT search the the value at the top-most level of the pytree (i.e. the root\n        node). The ``cond_fn`` will be evaluated recursively, starting at the root node's children.\n\n    Example::\n\n      >>> import flax.linen as nn\n      >>> from flax.cursor import cursor\n      >>> import jax, jax.numpy as jnp\n\n      >>> class Model(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     x = nn.Dense(3)(x)\n      ...     x = nn.relu(x)\n      ...     x = nn.Dense(3)(x)\n      ...     x = nn.relu(x)\n      ...     x = nn.Dense(3)(x)\n      ...     x = nn.relu(x)\n      ...     return x\n\n      >>> params = Model().init(jax.random.PRNGKey(0), jnp.empty((1, 2)))['params']\n\n      >>> def cond_fn(path, value):\n      ...   '''Find all dense layer params.'''\n      ...   return 'Dense' in path\n\n      >>> c = cursor(params)\n      >>> for dense_params in c.find_all(cond_fn):\n      ...   dense_params['bias'] += 1\n      >>> new_params = c.build()\n\n      >>> for layer in ('Dense_0', 'Dense_1', 'Dense_2'):\n      ...   assert (new_params[layer]['bias'] == params[layer]['bias'] + 1).all()\n\n      >>> assert jax.tree_util.tree_all(\n      ...       jax.tree_util.tree_map(\n      ...           lambda x, y: (x == y).all(),\n      ...           params,\n      ...           Model().init(jax.random.PRNGKey(0), jnp.empty((1, 2)))[\n      ...               'params'\n      ...           ],\n      ...       )\n      ...   ) # make sure original params are unchanged\n\n    Args:\n      cond_fn: the function that will conditionally find child Cursor objects\n    Returns:\n      A generator of child Cursor objects that fulfill the condition in the ``cond_fn``.\n    \"\"\"\n    for path in _traverse_tree((), self._obj, cond_fn=cond_fn):\n      child = self\n      for key, access_type in path:\n        if access_type is AccessType.ITEM:\n          child = child[key]\n        else:  # access_type is AccessType.ATTR\n          child = getattr(child, key)\n      yield child\n\n  def __str__(self):\n    return str(self._obj)\n\n  def __repr__(self):\n    return self._pretty_repr()\n\n  def _pretty_repr(self, indent=2, _prefix_indent=0):\n    s = 'Cursor(\\n'\n    obj_str = repr(self._obj).replace(\n      '\\n', '\\n' + ' ' * (_prefix_indent + indent)\n    )\n    s += ' ' * (_prefix_indent + indent) + f'_obj={obj_str},\\n'\n    s += ' ' * (_prefix_indent + indent) + '_changes={'\n    if self._changes:\n      s += '\\n'\n      for key in self._changes:\n        str_key = repr(key)\n        prefix = ' ' * (_prefix_indent + 2 * indent) + str_key + ': '\n        s += (\n          prefix\n          + self._changes[key]._pretty_repr(\n            indent=indent, _prefix_indent=len(prefix)\n          )\n          + ',\\n'\n        )\n      s = s[\n        :-2\n      ]  # remove comma and newline character for last element in self._changes\n      s += '\\n' + ' ' * (_prefix_indent + indent) + '}\\n'\n    else:\n      s += '}\\n'\n    s += ' ' * _prefix_indent + ')'\n    return s\n\n  def __len__(self):\n    return len(self._obj)\n\n  def __iter__(self):\n    if isinstance(self._obj, (tuple, list)):\n      return (self[i] for i in range(len(self._obj)))\n    else:\n      raise NotImplementedError(\n        '__iter__ method only implemented for tuples and lists, not type'\n        f' {type(self._obj)}'\n      )\n\n  def __reversed__(self):\n    if isinstance(self._obj, (tuple, list)):\n      return (self[i] for i in range(len(self._obj) - 1, -1, -1))\n    else:\n      raise NotImplementedError(\n        '__reversed__ method only implemented for tuples and lists, not type'\n        f' {type(self._obj)}'\n      )\n\n  def __add__(self, other):\n    return self._obj + other\n\n  def __sub__(self, other):\n    return self._obj - other\n\n  def __mul__(self, other):\n    return self._obj * other\n\n  def __matmul__(self, other):\n    return self._obj @ other\n\n  def __truediv__(self, other):\n    return self._obj / other\n\n  def __floordiv__(self, other):\n    return self._obj // other\n\n  def __mod__(self, other):\n    return self._obj % other\n\n  def __divmod__(self, other):\n    return divmod(self._obj, other)\n\n  def __pow__(self, other):\n    return pow(self._obj, other)\n\n  def __lshift__(self, other):\n    return self._obj << other\n\n  def __rshift__(self, other):\n    return self._obj >> other\n\n  def __and__(self, other):\n    return self._obj & other\n\n  def __xor__(self, other):\n    return self._obj ^ other\n\n  def __or__(self, other):\n    return self._obj | other\n\n  def __radd__(self, other):\n    return other + self._obj\n\n  def __rsub__(self, other):\n    return other - self._obj\n\n  def __rmul__(self, other):\n    return other * self._obj\n\n  def __rmatmul__(self, other):\n    return other @ self._obj\n\n  def __rtruediv__(self, other):\n    return other / self._obj\n\n  def __rfloordiv__(self, other):\n    return other // self._obj\n\n  def __rmod__(self, other):\n    return other % self._obj\n\n  def __rdivmod__(self, other):\n    return divmod(other, self._obj)\n\n  def __rpow__(self, other):\n    return pow(other, self._obj)\n\n  def __rlshift__(self, other):\n    return other << self._obj\n\n  def __rrshift__(self, other):\n    return other >> self._obj\n\n  def __rand__(self, other):\n    return other & self._obj\n\n  def __rxor__(self, other):\n    return other ^ self._obj\n\n  def __ror__(self, other):\n    return other | self._obj\n\n  def __neg__(self):\n    return -self._obj\n\n  def __pos__(self):\n    return +self._obj\n\n  def __abs__(self):\n    return abs(self._obj)\n\n  def __invert__(self):\n    return ~self._obj\n\n  def __round__(self, ndigits=None):\n    return round(self._obj, ndigits)\n\n  def __lt__(self, other):\n    return self._obj < other\n\n  def __le__(self, other):\n    return self._obj <= other\n\n  def __eq__(self, other):\n    return self._obj == other\n\n  def __ne__(self, other):\n    return self._obj != other\n\n  def __gt__(self, other):\n    return self._obj > other\n\n  def __ge__(self, other):\n    return self._obj >= other\n\n\ndef cursor(obj: A) -> Cursor[A]:\n  \"\"\"Wrap :class:`Cursor <flax.cursor.Cursor>` over ``obj`` and return it.\n  Changes can then be applied to the Cursor object in the following ways:\n\n  - single-line change via the ``.set`` method\n  - multiple changes, and then calling the ``.build`` method\n  - multiple changes conditioned on the pytree path and node value via the\n    ``.apply_update`` method, and then calling the ``.build`` method\n\n  ``.set`` example::\n\n    >>> from flax.cursor import cursor\n\n    >>> dict_obj = {'a': 1, 'b': (2, 3), 'c': [4, 5]}\n    >>> modified_dict_obj = cursor(dict_obj)['b'][0].set(10)\n    >>> assert modified_dict_obj == {'a': 1, 'b': (10, 3), 'c': [4, 5]}\n\n  ``.build`` example::\n\n    >>> from flax.cursor import cursor\n\n    >>> dict_obj = {'a': 1, 'b': (2, 3), 'c': [4, 5]}\n    >>> c = cursor(dict_obj)\n    >>> c['b'][0] = 10\n    >>> c['a'] = (100, 200)\n    >>> modified_dict_obj = c.build()\n    >>> assert modified_dict_obj == {'a': (100, 200), 'b': (10, 3), 'c': [4, 5]}\n\n  ``.apply_update`` example::\n\n    >>> from flax.cursor import cursor\n    >>> from flax.training import train_state\n    >>> import optax\n\n    >>> def update_fn(path, value):\n    ...   '''Replace params with empty dictionary.'''\n    ...   if 'params' in path:\n    ...     return {}\n    ...   return value\n\n    >>> state = train_state.TrainState.create(\n    ...     apply_fn=lambda x: x,\n    ...     params={'a': 1, 'b': 2},\n    ...     tx=optax.adam(1e-3),\n    ... )\n    >>> c = cursor(state)\n    >>> state2 = c.apply_update(update_fn).build()\n    >>> assert state2.params == {}\n    >>> assert state.params == {'a': 1, 'b': 2} # make sure original params are unchanged\n\n  If the underlying ``obj`` is a ``list`` or ``tuple``, iterating over the Cursor object\n  to get the child Cursors is also possible::\n\n    >>> from flax.cursor import cursor\n\n    >>> c = cursor(((1, 2), (3, 4)))\n    >>> for child_c in c:\n    ...   child_c[1] *= -1\n    >>> assert c.build() == ((1, -2), (3, -4))\n\n  View the docstrings for each method to see more examples of their usage.\n\n  Args:\n    obj: the object you want to wrap the Cursor in\n  Returns:\n    A Cursor object wrapped around obj.\n  \"\"\"\n  return Cursor(obj, None)\n"
  },
  {
    "path": "flax/errors.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\"\"\"  # Use an empty top-level docstring so Sphinx won't output the one below.\n\"\"\"Flax error classes.\n\n=== When to create a Flax error class?\n\nIf an error message requires more explanation than a one-liner, it is useful to\nadd it as a separate error class. This may lead to some duplication with\nexisting documentation or docstrings, but it will provide users with more help\nwhen they are debugging a problem. We can also point to existing documentation\nfrom the error docstring directly.\n\n=== How to name the error class?\n\n* If the error occurs when doing something, name the error\n  <Verb><Object><TypeOfError>Error\n\n  For instance, if you want to raise an error when applying a module with an\n  invalid method, the error can be: ApplyModuleInvalidMethodError.\n\n <TypeOfError> is optional, for instance if there is only one error when\n  modifying a variable, the error can simply be: ModifyVariableError.\n\n* If there is no concrete action involved the only a description of the error is\n  sufficient. For instance: InvalidFilterError, NameInUseError, etc.\n\n\n=== Copy/pastable template for new error messages:\n\nclass Template(FlaxError):\n  \"\" \"\n\n  \"\" \"\n  def __init__(self):\n    super().__init__(f'')\n\"\"\"\n\n\nclass FlaxError(Exception):\n  def __init__(self, message):\n    error_page = (\n      'https://flax.readthedocs.io/en/latest/api_reference/flax.errors.html'\n    )\n    module_name = self.__class__.__module__\n    class_name = self.__class__.__name__\n    if error_page not in message: # do not add a FlaxError link on unpickling\n      message = f'{message} ({error_page}#{module_name}.{class_name})'\n    super().__init__(message)\n\n  def __reduce__(self):\n   return (FlaxError, (str(self),))\n\n\n#################################################\n# NNX errors                                    #\n#################################################\n\n\nclass TraceContextError(FlaxError):\n  pass\n\n\n#################################################\n# lazy_init.py errors                           #\n#################################################\n\n\nclass LazyInitError(FlaxError):\n  \"\"\"Lazy Init function has uncomputable return values.\n\n  This happens when passing an argument to lazy_init with ``jax.ShapeDtypeStruct``\n  that affects the initialized variables.\n  Make sure the init function only uses the shape and dtype or pass an\n  actual JAX array if this is impossible.\n\n  Example::\n\n    class Foo(nn.Module):\n      @compact\n      def __call__(self, x):\n        # This parameter depends on the input x\n        # this causes an error when using lazy_init.\n        k = self.param(\"kernel\", lambda _: x)\n        return x * k\n    Foo().lazy_init(random.key(0), jax.ShapeDtypeStruct((8, 4), jnp.float32))\n  \"\"\"\n\n  def __init__(self, partial_val):\n    super().__init__(\n      'Lazy init encountered a value that could with '\n      f'the given inputs (shape: {partial_val}).'\n    )\n\n\n#################################################\n# scope.py errors                               #\n#################################################\n\n\nclass InvalidRngError(FlaxError):\n  \"\"\"All rngs used in a Module should be passed to\n\n  :meth:`Module.init() <flax.linen.Module.init>` and\n  :meth:`Module.apply() <flax.linen.Module.apply>` appropriately. We explain\n  both separately using the following example::\n\n    class Bar(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        some_param = self.param('some_param', nn.initializers.zeros_init(), (1, ))\n        dropout_rng = self.make_rng('dropout')\n        x = nn.Dense(features=4)(x)\n        ...\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = Bar()(x)\n        ...\n\n  **PRNGs for Module.init()**\n\n  In this example, two rngs are used:\n\n  * ``params`` is used for initializing the parameters of the model. This rng\n    is used to initialize the ``some_params`` parameter, and for initializing\n    the weights of the ``Dense`` Module used in ``Bar``.\n\n  * ``dropout`` is used for the dropout rng that is used in ``Bar``.\n\n  So, ``Foo`` is initialized as follows::\n\n    init_rngs = {'params': random.key(0), 'dropout': random.key(1)}\n    variables = Foo().init(init_rngs, init_inputs)\n\n  If a Module only requires an rng for ``params``, you can use::\n\n       SomeModule().init(rng, ...)  # Shorthand for {'params': rng}\n\n\n  **PRNGs for Module.apply()**\n\n  When applying ``Foo``, only the rng for ``dropout`` is needed, because\n  ``params`` is only used for initializing the Module parameters::\n\n    Foo().apply(variables, inputs, rngs={'dropout': random.key(2)})\n\n  If a Module only requires an rng for ``params``, you don't have to provide\n  rngs for apply at all::\n\n       SomeModule().apply(variables, inputs)  # rngs=None\n  \"\"\"\n\n  def __init__(self, msg):\n    # For this error message we pass the entire message, since there are various\n    # different kinds of RNG errors and we want to be able to be more specific\n    # in the error message, while always linking to the same documentation.\n    super().__init__(msg)\n\n\nclass ApplyScopeInvalidVariablesTypeError(FlaxError):\n  \"\"\"When calling :meth:`Module.apply() <flax.linen.Module.apply>`, the first\n\n  argument should be a variable dict. For more explanation on variable dicts,\n  please see :mod:`flax.core.variables`.\n  \"\"\"\n\n  def __init__(self):\n    super().__init__(\n      'The first argument passed to an apply function should be '\n      'a dictionary of collections. Each collection should be a '\n      'dictionary with string keys.'\n    )\n\n\nclass ApplyScopeInvalidVariablesStructureError(FlaxError):\n  \"\"\"This error is thrown when the dict passed as ``variables`` to apply() has an\n\n  extra 'params' layer, i.e. {'params': {'params': ...}}.\n  For more explanation on variable dicts, please see :mod:`flax.core.variables`.\n  \"\"\"\n\n  def __init__(self, variables):\n    super().__init__(\n      f'Expect the `variables` (first argument) passed to apply() '\n      f'to be a dict with the structure {{\"params\": ...}}, but got a dict '\n      f'with an extra params layer, i.e.  {{\"params\": {{\"params\": ... }} }}. '\n      f'You should instead pass in your dict\\'s [\"params\"].'\n    )\n\n\nclass ScopeParamNotFoundError(FlaxError):\n  \"\"\"This error is thrown when trying to access a parameter that does not exist.\n\n  For instance, in the code below, the initialized embedding name 'embedding'\n  does not match the apply name 'embed'::\n\n    class Embed(nn.Module):\n      num_embeddings: int\n      features: int\n\n      @nn.compact\n      def __call__(self, inputs, embed_name='embedding'):\n        inputs = inputs.astype('int32')\n        embedding = self.param(embed_name,\n                               jax.nn.initializers.lecun_normal(),\n                               (self.num_embeddings, self.features))\n        return embedding[inputs]\n\n    model = Embed(4, 8)\n    variables = model.init(random.key(0), jnp.ones((5, 5, 1)))\n    _ = model.apply(variables, jnp.ones((5, 5, 1)), 'embed')\n  \"\"\"\n\n  def __init__(self, param_name, scope_path):\n    super().__init__(\n      f'Could not find parameter named \"{param_name}\" in scope '\n      f'\"{scope_path}\".'\n    )\n\n\nclass ScopeCollectionNotFound(FlaxError):\n  \"\"\"This error is thrown when trying to access a variable from an empty\n  collection.\n\n  There are two common causes:\n\n  1. | The collection was not passed to ``apply`` correctly.\n     | For example, you might have used ``module.apply(params, ...)`` instead\n     | of ``module.apply({'params': params}, ...)``.\n  2. | The collection is empty because the variables need to be initialized.\n     | In this case, you should have made the collection mutable during\n     | apply (e.g.: ``module.apply(variables, ..., mutable=['state'])``.\n  \"\"\"\n\n  def __init__(self, col_name, var_name, scope_path):\n    super().__init__(\n      f'Tried to access \"{var_name}\" from collection \"{col_name}\" in '\n      f'\"{scope_path}\" but the collection is empty.'\n    )\n\n\nclass ScopeParamShapeError(FlaxError):\n  \"\"\"This error is thrown when the shape of an existing parameter is different from\n\n  the shape of the return value of the ``init_fn``. This can happen when the\n  shape provided during :meth:`Module.apply() <flax.linen.Module.apply>` is\n  different from the one used when initializing the module.\n\n  For instance, the following code throws this error because the apply shape\n  (``(5, 5, 1)``) is different from the init shape (``(5, 5``). As a result, the\n  shape of the kernel during ``init`` is ``(1, 8)``, and the shape during\n  ``apply`` is ``(5, 8)``, which results in this error.::\n\n    class NoBiasDense(nn.Module):\n      features: int = 8\n\n      @nn.compact\n      def __call__(self, x):\n        kernel = self.param('kernel',\n                            lecun_normal(),\n                            (x.shape[-1], self.features))  # <--- ERROR\n        y = lax.dot_general(x, kernel,\n                            (((x.ndim - 1,), (0,)), ((), ())))\n        return y\n\n    variables = NoBiasDense().init(random.key(0), jnp.ones((5, 5, 1)))\n    _ = NoBiasDense().apply(variables, jnp.ones((5, 5)))\n  \"\"\"\n\n  def __init__(self, param_name, scope_path, value_shape, init_shape):\n    super().__init__(\n        f'For parameter \"{param_name}\" in \"{scope_path}\", the given '\n        f'initializer is expected to generate shape {init_shape}, but the '\n        f'existing parameter it received has shape {value_shape}.'\n    )\n\n\nclass ScopeVariableNotFoundError(FlaxError):\n  \"\"\"This error is thrown when trying to use a variable in a Scope in a collection\n\n  that is immutable. In order to create this variable, mark the collection as\n  mutable explicitly using the ``mutable`` keyword in\n  :meth:`Module.apply() <flax.linen.Module.apply>`.\n  \"\"\"\n\n  def __init__(self, name, col, scope_path):\n    super().__init__(\n      f'No Variable named \"{name}\" for collection \"{col}\" '\n      f'exists in \"{scope_path}\".'\n    )\n\n\nclass InvalidFilterError(FlaxError):\n  \"\"\"A filter should be either a boolean, a string or a container object.\"\"\"\n\n  def __init__(self, filter_like):\n    super().__init__(f'Invalid Filter: \"{filter_like}\"')\n\n\nclass InvalidScopeError(FlaxError):\n  \"\"\"A temporary Scope is only valid within the context in which it is created::\n\n  with Scope(variables, rngs=rngs).temporary() as root:\n    y = fn(root, *args, **kwargs)\n    # Here root is valid.\n  # Here root is invalid.\n  \"\"\"\n\n  def __init__(self, scope_name):\n    super().__init__(f'The scope \"{scope_name}\" is no longer valid.')\n\n\nclass ModifyScopeVariableError(FlaxError):\n  \"\"\"You cannot update a variable if the collection it belongs to is immutable.\n\n  When you are applying a Module, you should specify which variable collections\n  are mutable::\n\n    class MyModule(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        ...\n        var = self.variable('batch_stats', 'mean', ...)\n        var.value = ...\n        ...\n\n    v = MyModule.init(...)\n    ...\n    logits = MyModule.apply(v, batch)  # This throws an error.\n    logits = MyModule.apply(v, batch, mutable=['batch_stats'])  # This works.\n  \"\"\"\n\n  def __init__(self, col, variable_name, scope_path):\n    super().__init__(\n      f'Cannot update variable \"{variable_name}\" in '\n      f'\"{scope_path}\" because collection \"{col}\" is immutable.'\n    )\n\n\nclass ImmutableVariableError(FlaxError):\n  \"\"\"You cannot update a variable that is marked as immutable.\n\n  This error occurs when attempting to modify a Variable that has been set to\n  'immutable' mode. Variables in immutable mode are read-only and cannot be\n  changed after creation.\n\n  To fix this error, either:\n  1. Use a different variable mode (e.g., 'qdd' or 'pytree')\n  2. Or ensure you're not trying to modify the variable's value\n  \"\"\"\n\n  def __init__(self, message):\n    super().__init__(message)\n\n\nclass JaxTransformError(FlaxError):\n  \"\"\"JAX transforms and Flax modules cannot be mixed.\n\n  JAX's functional transformations expect pure function.\n  When you want to use JAX transformations **inside** Flax models,\n  you should make use of the Flax transformation wrappers\n  (e.g.: ``flax.linen.vmap``, ``flax.linen.scan``, etc.).\n  \"\"\"\n\n  def __init__(self):\n    super().__init__('Jax transforms and Flax models cannot be mixed.')\n\n\n#################################################\n# meta.py errors                               #\n#################################################\n\n\nclass PartitioningUnspecifiedError(FlaxError):\n  \"\"\"This error is raised when trying to add an axis to a Partitioned variable by\n\n  using a transformation (e.g.: ``scan``, ``vmap``) without specifying the\n  \"partition_name\" in the ``metadata_params`` dict.\n  \"\"\"\n\n  def __init__(self, target):\n    super().__init__(\n      'Trying to transform a Partitioned variable but \"partition_name\"'\n      f' is not specified in metadata_params: {target}'\n    )\n\n\n#################################################\n# module.py errors                              #\n#################################################\n\n\nclass NameInUseError(FlaxError):\n  \"\"\"This error is raised when trying to create a submodule, param, or variable\n\n  with an existing name. They are all considered to be in the same namespace.\n\n  **Sharing Submodules**\n\n  This is the wrong pattern for sharing submodules::\n\n    y = nn.Dense(feature=3, name='bar')(x)\n    z = nn.Dense(feature=3, name='bar')(x+epsilon)\n\n  Instead, modules should be shared by instance::\n\n    dense = nn.Dense(feature=3, name='bar')\n    y = dense(x)\n    z = dense(x+epsilon)\n\n  If submodules are not provided with a name, a unique name will be given to\n  them automatically::\n\n    class MyModule(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = MySubModule()(x)\n        x = MySubModule()(x)  # This is fine.\n        return x\n\n  **Parameters and Variables**\n\n  A parameter name can collide with a submodule or variable, since they are all\n  stored in the same variable dict::\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        bar = self.param('bar', nn.initializers.zeros_init(), (1, ))\n        embed = nn.Embed(num_embeddings=2, features=5, name='bar')  # <-- ERROR!\n\n  Variables should also have unique names, even if they have their own\n  collection::\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, inputs):\n        _ = self.param('mean', initializers.lecun_normal(), (2, 2))\n        _ = self.variable('stats', 'mean', initializers.zeros_init(), (2, 2))\n  \"\"\"\n\n  def __init__(self, key_type, value, module_name):\n    # key_type is in {param, variable, submodule}.\n    super().__init__(\n      f'Could not create {key_type} \"{value}\" in Module '\n      f'{module_name}: Name in use.'\n    )\n\n\nclass AssignSubModuleError(FlaxError):\n  \"\"\"You are only allowed to create submodules in two places:\n\n  1.  If your Module is noncompact: inside\n      :meth:`Module.setup() <flax.linen.Module.setup>`.\n  2.  If your Module is compact: inside the method wrapped in\n      :meth:`nn.compact() <flax.linen.compact>`.\n\n  For instance, the following code throws this error, because ``nn.Conv`` is\n  created in ``__call__``, which is not marked as compact::\n\n    class Foo(nn.Module):\n      def setup(self):\n        pass\n\n      def __call__(self, x):\n        conv = nn.Conv(features=3, kernel_size=3)\n\n    Foo().init(random.key(0), jnp.zeros((1,)))\n\n  Note that this error is also thrown if you partially defined a Module inside\n  setup::\n\n    class Foo(nn.Module):\n      def setup(self):\n        self.conv = functools.partial(nn.Conv, features=3)\n\n      def __call__(self, x):\n        x = self.conv(kernel_size=4)(x)\n        return x\n\n    Foo().init(random.key(0), jnp.zeros((1,)))\n\n  In this case, ``self.conv(kernel_size=4)`` is called from ``__call__``, which\n  is disallowed because it's neither within ``setup`` nor a method wrapped in\n  x``nn.compact``.\n  \"\"\"\n\n  def __init__(self, cls):\n    super().__init__(\n      f'Submodule {cls} must be defined in `setup()` or in a '\n      'method wrapped in `@compact`'\n    )\n\n\nclass SetAttributeInModuleSetupError(FlaxError):\n  \"\"\"You are not allowed to modify Module class attributes in\n\n  :meth:`Module.setup() <flax.linen.Module.setup>`::\n\n    class Foo(nn.Module):\n      features: int = 6\n\n      def setup(self):\n        self.features = 3  # <-- ERROR\n\n      def __call__(self, x):\n        return nn.Dense(self.features)(x)\n\n    variables = SomeModule().init(random.key(0), jnp.ones((1, )))\n\n  Instead, these attributes should be set when initializing the Module::\n\n    class Foo(nn.Module):\n      features: int = 6\n\n      @nn.compact\n      def __call__(self, x):\n        return nn.Dense(self.features)(x)\n\n    variables = SomeModule(features=3).init(random.key(0), jnp.ones((1, )))\n\n  TODO(marcvanzee): Link to a design note explaining why it's necessary for\n  modules to stay frozen (otherwise we can't safely clone them, which we use for\n  lifted transformations).\n  \"\"\"\n\n  def __init__(self):\n    super().__init__(f'Module construction attributes are frozen.')\n\n\nclass SetAttributeFrozenModuleError(FlaxError):\n  \"\"\"You can only assign Module attributes to ``self`` inside\n\n  :meth:`Module.setup() <flax.linen.Module.setup>`. Outside of that method, the\n  Module instance is frozen (i.e., immutable). This behavior is similar to\n  frozen Python dataclasses.\n\n  For instance, this error is raised in the following case::\n\n    class SomeModule(nn.Module):\n      @nn.compact\n      def __call__(self, x, num_features=10):\n        self.num_features = num_features  # <-- ERROR!\n        x = nn.Dense(self.num_features)(x)\n        return x\n\n    s = SomeModule().init(random.key(0), jnp.ones((5, 5)))\n\n  Similarly, the error is raised when trying to modify a submodule's attributes\n  after constructing it, even if this is done in the ``setup()`` method of the\n  parent module::\n\n    class Foo(nn.Module):\n        def setup(self):\n          self.dense = nn.Dense(features=10)\n          self.dense.features = 20  # <--- This is not allowed\n\n        def __call__(self, x):\n          return self.dense(x)\n  \"\"\"\n\n  def __init__(self, module_cls, attr_name, attr_val):\n    super().__init__(\n      f\"Can't set {attr_name}={attr_val} for Module of type \"\n      f'{module_cls}: Module instance is frozen outside of '\n      'setup method.'\n    )\n\n\nclass MultipleMethodsCompactError(FlaxError):\n  \"\"\"The ``@compact`` decorator may only be added to at most one method in a Flax\n\n  module. In order to resolve this, you can:\n\n  * remove ``@compact`` and define submodules and variables using\n    :meth:`Module.setup() <flax.linen.Module.setup>`.\n  * Use two separate modules that both have a unique ``@compact`` method.\n\n  TODO(marcvanzee): Link to a design note explaining the motivation behind this.\n  There is no need for an equivalent to ``hk.transparent`` and it makes submodules\n  much more sane because there is no need to prefix the method names.\n  \"\"\"\n\n  def __init__(self):\n    super().__init__(f'Only one method per class can be @compact')\n\n\nclass ReservedModuleAttributeError(FlaxError):\n  \"\"\"This error is thrown when creating a Module that is using reserved attributes.\n\n  The following attributes are reserved:\n\n  * ``parent``: The parent Module of this Module.\n  * ``name``: The name of this Module.\n  \"\"\"\n\n  def __init__(self, annotations):\n    super().__init__(\n      f'properties `parent` and `name` are reserved: {annotations}'\n    )\n\n\nclass ApplyModuleInvalidMethodError(FlaxError):\n  \"\"\"When calling :meth:`Module.apply() <flax.linen.Module.apply>`, you can specify\n\n  the method to apply using parameter ``method``. This error is thrown if the\n  provided parameter is not a method in the Module and not a function with at\n  least one argument.\n\n  Learn more on the reference docs for\n  :meth:`Module.apply() <flax.linen.Module.apply>`.\n  \"\"\"\n\n  def __init__(self, method):\n    super().__init__(\n      f'Cannot call apply(): {method} is not a valid function for apply().'\n    )\n\n\nclass CallCompactUnboundModuleError(FlaxError):\n  \"\"\"This error occurs when you are trying to call a Module directly, rather than\n\n  through :meth:`Module.apply() <flax.linen.Module.apply>`. For instance, the\n  error will be raised when trying to run this code::\n\n    from flax import linen as nn\n    import jax.numpy as jnp\n\n    test_dense = nn.Dense(10)\n    test_dense(jnp.ones((5,5)))\n\n  Instead, you should pass the variables (parameters and other state) via\n  :meth:`Module.apply() <flax.linen.Module.apply>` (or use\n  :meth:`Module.init() <flax.linen.Module.init>` to get initial variables)::\n\n    from jax import random\n    variables = test_dense.init(random.key(0), jnp.ones((5,5)))\n\n    y = test_dense.apply(variables, jnp.ones((5,5)))\n  \"\"\"\n\n  def __init__(self):\n    super().__init__(\"Can't call compact methods on unbound modules\")\n\n\nclass CallSetupUnboundModuleError(FlaxError):\n  \"\"\"This error occurs when you are trying to call ``.setup()`` directly.\n\n  For instance, the error will be raised when trying to run this code::\n\n    from flax import linen as nn\n    import jax.numpy as jnp\n\n    class MyModule(nn.Module):\n      def setup(self):\n        self.submodule = MySubModule()\n\n    module = MyModule()\n    module.setup() # <-- ERROR!\n    submodule = module.submodule\n\n  In general you shouldn't call ``.setup()`` yourself, if you need to get access\n  to a field or submodule defined inside ``setup`` you can instead create a\n  function\n  to extract it and pass it to ``nn.apply``::\n\n    # setup() will be called automatically by ``nn.apply``\n    def get_submodule(module):\n      return module.submodule.clone() # avoid leaking the Scope\n\n    empty_variables = {} # you can also use the real variables\n    submodule = nn.apply(get_submodule, module)(empty_variables)\n  \"\"\"\n\n  def __init__(self):\n    super().__init__(\"Can't call compact methods on unbound modules\")\n\n\nclass CallUnbindOnUnboundModuleError(FlaxError):\n  \"\"\"This error occurs when you are trying to call ``.unbind()`` on an unbound\n  Module. For instance, when you try running the following example,\n  an error will be raised::\n\n    from flax import linen as nn\n\n    class MyModule(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return nn.Dense(features=10)(x)\n\n    module = MyModule()\n    module.unbind() # <-- ERROR!\n\n  Instead, you should ``bind`` the Module to a variable collection before calling\n  ``.unbind()``::\n\n    bound_module = module.bind(variables)\n    ... # do something with bound_module\n    module = bound_module.unbind() # <-- OK!\n  \"\"\"\n\n  def __init__(self):\n    super().__init__(\"Can't call `unbind()` on unbound modules\")\n\nclass CallShareScopeOnUnboundModuleError(FlaxError):\n  \"\"\"This error occurs when you are trying to call ``nn.share_scope`` on an unbound\n  Module. For instance, when you try to use ``nn.share_scope`` at the top-level::\n\n    from flax import linen as nn\n\n    class CustomDense(nn.Dense):\n      def __call__(self, x):\n        return super().__call__(x) + 1\n\n    custom_dense = CustomDense(5)\n    dense = nn.Dense(5)  # has the parameters\n\n    nn.share_scope(custom_dense, dense) # <-- ERROR!\n  \"\"\"\n\n  def __init__(self):\n    super().__init__(\"Can't call `share_scope` on unbound modules\")\n\nclass InvalidInstanceModuleError(FlaxError):\n  \"\"\"This error occurs when you are trying to call ``.init()``, ``.init_with_output()``, ``.apply()`` or ``.bind()``\n\n  on the Module class itself, instead of an instance of the Module class.\n  For example, the error will be raised when trying to run this code::\n\n    class B(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return x\n\n    k = random.key(0)\n    x = random.uniform(random.key(1), (2,))\n    B.init(k, x)   # B is module class, not B() a module instance\n    B.apply(vs, x)   # similar issue with apply called on class instead of\n    instance.\n  \"\"\"\n\n  def __init__(self):\n    super().__init__(\n      'Can only call init, init_with_output or apply methods on an instance'\n      ' of the Module class, not the Module class itself'\n    )\n\n\nclass IncorrectPostInitOverrideError(FlaxError):\n  \"\"\"This error occurs when you overrode ``.__post_init__()`` without calling ``super().__post_init__()``.\n\n  For example, the error will be raised when trying to run this code::\n\n    from flax import linen as nn\n    import jax.numpy as jnp\n    import jax\n    class A(nn.Module):\n      x: float\n      def __post_init__(self):\n        self.x_square = self.x ** 2\n        # super().__post_init__() <-- forgot to add this line\n      @nn.compact\n      def __call__(self, input):\n        return input + 3\n\n    r = A(x=3)\n    r.init(jax.random.key(2), jnp.ones(3))\n  \"\"\"\n\n  def __init__(self):\n    super().__init__(\n      'Overrode `.__post_init__()` without calling `super().__post_init__()`'\n    )\n\n\nclass DescriptorAttributeError(FlaxError):\n  \"\"\"This error occurs when you are trying to access a property that is accessing a non-existent attribute.\n\n  For example, the error will be raised when trying to run this code::\n\n    class Foo(nn.Module):\n        @property\n        def prop(self):\n            return self.non_existent_field # ERROR!\n        def __call__(self, x):\n            return self.prop\n\n    foo = Foo()\n    variables = foo.init(jax.random.key(0), jnp.ones(shape=(1, 8)))\n  \"\"\"\n\n  def __init__(self):\n    super().__init__(\n      'Trying to access a property that is accessing a non-existent'\n      ' attribute.'\n    )\n\n\nclass InvalidCheckpointError(FlaxError):\n  \"\"\"A checkpoint cannot be stored in a directory that already has\n\n  a checkpoint at the current or a later step.\n\n  You can pass ``overwrite=True`` to disable this behavior and\n  overwrite existing checkpoints in the target directory.\n  \"\"\"\n\n  def __init__(self, path, step):\n    super().__init__(\n      f'Trying to save an outdated checkpoint at step: \"{step}\" and path:'\n      f' \"{path}\".'\n    )\n\n\nclass MPACheckpointingRequiredError(FlaxError):\n  \"\"\"To optimally save and restore a multiprocess array (GDA or jax Array outputted from pjit), use GlobalAsyncCheckpointManager.\n\n  You can create an GlobalAsyncCheckpointManager at top-level and pass it as\n  argument::\n\n    from jax.experimental.gda_serialization import serialization as gdas\n    gda_manager = gdas.GlobalAsyncCheckpointManager()\n    save_checkpoint(..., gda_manager=gda_manager)\n  \"\"\"\n\n  def __init__(self, path, step):\n    super().__init__(\n      f'Checkpoint failed at step: \"{step}\" and path: \"{path}\": Target '\n      'contains a multiprocess array should be saved/restored with a '\n      'GlobalAsyncCheckpointManager.'\n    )\n\n\nclass MPARestoreTargetRequiredError(FlaxError):\n  \"\"\"Provide a valid target when restoring a checkpoint with a multiprocess array.\n\n  Multiprocess arrays need a sharding (global meshes and partition specs) to be\n  initialized. Therefore, to restore a checkpoint that contains a multiprocess\n  array, make sure the ``target`` you passed contains valid multiprocess arrays\n  at the corresponding tree structure location. If you cannot provide a full\n  valid ``target``, consider ``allow_partial_mpa_restoration=True``.\n  \"\"\"\n\n  def __init__(self, path, step, key=None):\n    error_msg = (\n      f'Restore checkpoint failed at step: \"{step}\" and path: \"{path}\": '\n      'Checkpoints containing a multiprocess array need to be restored with '\n      'a target with pre-created arrays. If you cannot provide a full valid '\n      'target, consider ``allow_partial_mpa_restoration=True``. '\n    )\n    if key:\n      error_msg += f'This error fired when trying to restore array at {key}.'\n    super().__init__(error_msg)\n\n\nclass MPARestoreDataCorruptedError(FlaxError):\n  \"\"\"A multiprocess array stored in Google Cloud Storage doesn't contain a \"commit_success.txt\" file, which should be written at the end of the save.\n\n  Failure of finding it could indicate a corruption of your saved GDA data.\n  \"\"\"\n\n  def __init__(self, step, path):\n    super().__init__(\n      f'Restore checkpoint failed at step: \"{step}\" on multiprocess array at '\n      f' \"{path}\": No \"commit_success.txt\" found on this \"_gda\" directory. '\n      'Was its save halted before completion?'\n    )\n\n\n#################################################\n# transforms.py errors                          #\n#################################################\n\n\nclass TransformedMethodReturnValueError(FlaxError):\n  \"\"\"Transformed Module methods cannot return other Modules or Variables.\"\"\"\n\n  def __init__(self, name):\n    super().__init__(\n      f'Transformed module method {name} cannot return Modules or Variables.'\n    )\n\n\nclass TransformTargetError(FlaxError):\n  \"\"\"Linen transformations must be applied to Modules classes or functions taking a Module instance as the first argument.\n\n  This error occurs when passing an invalid target to a linen transform\n  (nn.vmap, nn.scan, etc.). This occurs for example when trying to\n  transform a Module instance::\n\n    nn.vmap(nn.Dense(features))(x)  # raises TransformTargetError\n\n  You can transform the ``nn.Dense`` class directly instead::\n\n    nn.vmap(nn.Dense)(features)(x)\n\n  Or you can create a function that takes the module instance as the first\n  argument::\n\n    class BatchDense(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return nn.vmap(\n            lambda mdl, x: mdl(x),\n            variable_axes={'params': 0}, split_rngs={'params':\n            True})(nn.Dense(3), x)\n  \"\"\"\n\n  def __init__(self, target):\n    super().__init__(\n      'Linen transformations must be applied to Modules classes or'\n      ' functions taking a Module instance as the first argument.'\n      f' The provided target is not a Module class or callable: {target}'\n    )\n\n\n#################################################\n# io.py errors                                  #\n#################################################\n\n\nclass AlreadyExistsError(FlaxError):\n  \"\"\"Attempting to overwrite a file via copy.\n\n  You can pass ``overwrite=True`` to disable this behavior and overwrite\n  existing files in.\n  \"\"\"\n\n  def __init__(self, path):\n    super().__init__(f'Trying overwrite an existing file: \"{path}\".')\n\n\n#################################################\n# cursor.py errors                                  #\n#################################################\n\n\nclass CursorFindError(FlaxError):\n  \"\"\"Error when calling :meth:`Cursor.find() <flax.cursor.Cursor.find>`.\n\n  This error occurs if no object or more than one object is found, given\n  the conditions of the ``cond_fn``.\n  \"\"\"\n\n  def __init__(self, cursor=None, cursor2=None):\n    if cursor and cursor2:\n      super().__init__(\n        'More than one object found given the conditions of the cond_fn. '\n        'The first two objects found have the following paths: '\n        f'{cursor._path} and {cursor2._path}'\n      )\n    else:\n      super().__init__('No object found given the conditions of the cond_fn.')\n\n\nclass TraverseTreeError(FlaxError):\n  \"\"\"Error when calling ``Cursor._traverse_tree()``. This function has two\n  modes:\n\n  - if ``update_fn`` is not None, it will traverse the tree and return a\n    generator of tuples containing the path where the ``update_fn`` was\n    applied and the newly modified value.\n  - if ``cond_fn`` is not None, it will traverse the tree and return a\n    generator of tuple paths that fulfilled the conditions of the ``cond_fn``.\n\n  This error occurs if either both ``update_fn`` and ``cond_fn`` are None,\n  or both are not None.\n  \"\"\"\n\n  def __init__(self, update_fn, cond_fn):\n    if update_fn is None and cond_fn is None:\n      super().__init__(\n        'Both update_fn and cond_fn are None. Exactly one of them must be'\n        ' None.'\n      )\n    else:\n      super().__init__(\n        'Both update_fn and cond_fn are not None. Exactly one of them must be'\n        ' not None.'\n      )\n"
  },
  {
    "path": "flax/experimental/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n"
  },
  {
    "path": "flax/experimental/nnx.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom absl import logging\n\nfrom flax.nnx import *\n\n\nlogging.warning(\n  \"Using 'flax.experimental.nnx' is deprecated. Please use 'flax.nnx' instead.\"\n)"
  },
  {
    "path": "flax/ids.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"UUIDs for Flax internals.\"\"\"\n\nimport threading\n\n\nclass UUIDManager:\n  \"\"\"Globally unique counter-based id manager.\n\n  We need globally unique key ids for Module and Variable object instances\n  to preserve and recreate sharing-by-reference relationship when lifting\n  transforms and adopting outside Modules.\n  - Use of id() is unacceptable because these identifiers are literally\n    pointers which can be recycled, so we rely on a globally unique counter id\n    instead.\n  - We need to handle copy/deepcopy uniqueness via a wrapped type.\n  \"\"\"\n\n  def __init__(self):\n    self._lock = threading.Lock()\n    self._id = 0\n\n  def __call__(self):\n    with self._lock:\n      self._id += 1\n      return FlaxId(self._id)\n\n\nuuid = UUIDManager()\n\n\nclass FlaxId:\n  \"\"\"Hashable wrapper for ids that handles uniqueness of copies.\"\"\"\n\n  def __init__(self, rawid):\n    self.id = rawid\n\n  def __eq__(self, other):\n    return isinstance(other, FlaxId) and other.id == self.id\n\n  def __hash__(self):\n    return hash(self.id)\n\n  def __repr__(self):\n    return f'FlaxId({self.id})'\n\n  def __deepcopy__(self, memo):\n    del memo\n    return uuid()\n\n  def __copy__(self):\n    return uuid()\n"
  },
  {
    "path": "flax/io.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"IO Abstraction Layer.\nThe sole purpose of this abstraction layer is to avoid requiring tensorflow\nas an open-source dependency solely for its tensorflow.io.gfile functions.\n\"\"\"\nimport contextlib\nimport glob as glob_module\nimport importlib\nimport os\nimport shutil\nfrom enum import Enum\n\nfrom absl import logging\n\nfrom . import errors\n\n# Global Modes and selective import of tensorflow.io gfile.\n\n\nclass BackendMode(Enum):\n  DEFAULT = 0\n  TF = 1\n\n\nio_mode = None\ngfile = None\n\nif importlib.util.find_spec('tensorflow'):\n  from tensorflow.io import gfile  # type: ignore\n\n  io_mode = BackendMode.TF\nelse:\n  logging.warning(\n    'Tensorflow library not found, tensorflow.io.gfile '\n    'operations will use native shim calls. '\n    \"GCS paths (i.e. 'gs://...') cannot be accessed.\"\n  )\n  io_mode = BackendMode.DEFAULT\n\n\n# Constants and Exceptions\n\n\nif io_mode == BackendMode.TF:\n  from tensorflow import errors as tf_errors  # type: ignore\n\n  NotFoundError = tf_errors.NotFoundError\nelse:\n  NotFoundError = FileNotFoundError\n\n\n# Overrides for testing.\n\n\n@contextlib.contextmanager\ndef override_mode(override: BackendMode):\n  # pylint: disable=g-doc-return-or-yield\n  \"\"\"Returns a context manager that changes backend IO mode.\n  Args:\n    override: BackendMode enum value to set IO mode inside context.\n  \"\"\"\n  # pylint: enable=g-doc-return-or-yield\n  global io_mode\n  io_mode_prev = io_mode\n  io_mode = override\n  try:\n    yield\n  finally:\n    io_mode = io_mode_prev\n\n\ndef set_mode(override: BackendMode):\n  \"\"\"Sets global io mode.\n  Args:\n    override: BackendMode enum value to set for IO mode.\n  \"\"\"\n  global io_mode\n  io_mode = override\n\n\n# tensorflow.io.gfile API shim functions.\n\n\ndef GFile(name, mode):  # pylint: disable=invalid-name\n  if io_mode == BackendMode.DEFAULT:\n    if 'b' in mode:\n      return open(name, mode)  # pylint: disable=unspecified-encoding\n    else:\n      return open(name, mode, encoding='utf-8')\n  elif io_mode == BackendMode.TF:\n    return gfile.GFile(name, mode)\n  else:\n    raise ValueError('Unknown IO Backend Mode.')\n\n\ndef listdir(path):\n  if io_mode == BackendMode.DEFAULT:\n    return os.listdir(path=path)\n  elif io_mode == BackendMode.TF:\n    return gfile.listdir(path=path)\n  else:\n    raise ValueError('Unknown IO Backend Mode.')\n\n\ndef isdir(path):\n  if io_mode == BackendMode.DEFAULT:\n    return os.path.isdir(path)\n  elif io_mode == BackendMode.TF:\n    return gfile.isdir(path)\n  else:\n    raise ValueError('Unknown IO Backend Mode.')\n\n\ndef copy(src, dst, overwrite=False):\n  if io_mode == BackendMode.DEFAULT:\n    if os.path.exists(dst) and not overwrite:\n      raise errors.AlreadyExistsError(dst)\n    shutil.copy(src, dst)\n    return\n  elif io_mode == BackendMode.TF:\n    return gfile.copy(src, dst, overwrite=overwrite)\n  else:\n    raise ValueError('Unknown IO Backend Mode.')\n\n\ndef rename(src, dst, overwrite=False):\n  if io_mode == BackendMode.DEFAULT:\n    if os.path.exists(dst) and not overwrite:\n      raise errors.AlreadyExistsError(dst)\n    return os.rename(src, dst)\n  elif io_mode == BackendMode.TF:\n    return gfile.rename(src, dst, overwrite=overwrite)\n  else:\n    raise ValueError('Unknown IO Backend Mode.')\n\n\ndef exists(path):\n  if io_mode == BackendMode.DEFAULT:\n    return os.path.exists(path)\n  elif io_mode == BackendMode.TF:\n    return gfile.exists(path)\n  else:\n    raise ValueError('Unknown IO Backend Mode.')\n\n\ndef makedirs(path):\n  if io_mode == BackendMode.DEFAULT:\n    return os.makedirs(path, exist_ok=True)\n  elif io_mode == BackendMode.TF:\n    return gfile.makedirs(path)\n  else:\n    raise ValueError('Unknown IO Backend Mode.')\n\n\ndef glob(pattern):\n  if io_mode == BackendMode.DEFAULT:\n    return [\n      path.rstrip('/') for path in glob_module.glob(pattern, recursive=False)\n    ]\n  elif io_mode == BackendMode.TF:\n    return gfile.glob(pattern)\n  else:\n    raise ValueError('Unknown IO Backend Mode.')\n\n\ndef remove(path):\n  \"\"\"Remove the file at path. Might fail if used on a directory path.\"\"\"\n  if io_mode == BackendMode.DEFAULT:\n    return os.remove(path)\n  elif io_mode == BackendMode.TF:\n    return gfile.remove(path)\n  else:\n    raise ValueError('Unknown IO Backend Mode.')\n\n\ndef rmtree(path):\n  \"\"\"Remove a directory and recursively all contents inside. Might fail if used on a file path.\"\"\"\n  if io_mode == BackendMode.DEFAULT:\n    return shutil.rmtree(path)\n  elif io_mode == BackendMode.TF:\n    return gfile.rmtree(path)\n  else:\n    raise ValueError('Unknown IO Backend Mode.')\n\n\ndef getsize(path):\n  \"\"\"Return the size, in bytes, of path.\"\"\"\n  if io_mode == BackendMode.DEFAULT:\n    return os.path.getsize(path)\n  elif io_mode == BackendMode.TF:\n    return gfile.stat(path).length\n  else:\n    raise ValueError('Unknown IO Backend Mode.')\n"
  },
  {
    "path": "flax/jax_utils.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Utilities we could consider upstreaming to Jax.\"\"\"\n\nimport collections\nimport itertools\nimport warnings\nfrom collections.abc import Iterable  # pylint: disable=g-importing-member\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom jax import core, lax\nfrom jax.extend import linear_util as lu\nfrom jax.interpreters import partial_eval as pe\n\n\ndef _pmap_device_order():\n  return jax.local_devices()\n\n\ndef replicate(tree, devices=None):\n  \"\"\"Replicates arrays to multiple devices.\n\n  Args:\n    tree: a pytree containing the arrays that should be replicated.\n    devices: the devices the data is replicated to\n      (default: same order as expected by ``jax.pmap()``).\n  Returns:\n    A new pytree containing the replicated arrays.\n  \"\"\"\n  devices = devices or _pmap_device_order()\n  return jax.device_put_replicated(tree, devices)\n\n\ndef unreplicate(tree):\n  \"\"\"Returns a single instance of a replicated array.\"\"\"\n  def _unreplicate_one(x):\n    # Avoid degraded performance under the new jax.pmap.\n    # Handle 0-dimensional (scalar) arrays - cannot index into them\n    if hasattr(x, 'ndim') and x.ndim == 0:\n      return x\n    if (\n        not hasattr(x, 'sharding')\n        or isinstance(x.sharding, jax.sharding.SingleDeviceSharding)\n        or len(jax.local_devices()) == 1\n    ):\n      return x[0]\n    if x.sharding.is_fully_replicated:\n      return x.addressable_shards[0].data\n    return x.addressable_shards[0].data.squeeze(0)\n\n  return jax.tree_util.tree_map(_unreplicate_one, tree)\n\n\ndef pmean(xs, axis_name):\n  warnings.warn('use jax.lax.pmean instead', DeprecationWarning)\n  return lax.pmean(xs, axis_name)\n\n\ndef partial_eval_by_shape(fn, input_spec, *args, **kwargs):\n  \"\"\"Lazily evaluate a function by using the shapes of the inputs.\n\n  This function is similar to ``jax.eval_shape`` with the key difference that\n  function outputs that can be computed without a concrete value of the\n  inputs are returned as is instead of only the shape. See for example\n  ``module.init_by_shape`` where this functionality is used to initialize a\n  model without using input data lr computation.\n\n  Args:\n    fn: the function to be lazily evaluated.\n    input_spec: an iterable of shapes or (shape, dtype) tuples specifying the\n      shape and type of the inputs. If unspecified the dtype is float32.\n    *args: other arguments passed to the module's apply function\n    **kwargs: keyword arguments passed to the module's apply function\n  Returns:\n    A pair consisting of the model output and an instance of Model\n  \"\"\"\n  # output cannot be returned in lazy_create because jax.eval_shape will only\n  # return the shape and dtype.\n  # TODO(mattjj,jheek): use a public JAX API\n  f = lambda *inputs: fn(*inputs, *args, **kwargs)\n  input_structs = [_parse_spec(spec) for spec in input_spec]\n  inputs_flat, in_tree = jax.tree_util.tree_flatten(input_structs)\n\n  debug_info = jax.api_util.debug_info(\"flax partial_eval_by_shape\", f,\n                                        (in_tree,), {})\n  f_flat, out_tree = jax.api_util.flatten_fun_nokwargs(\n    lu.wrap_init(f, debug_info=debug_info), in_tree)\n  in_pvals = [\n    pe.PartialVal.unknown(core.ShapedArray(x.shape, x.dtype))\n    for x in inputs_flat\n  ]\n  _, out_pvals, _ = pe.trace_to_jaxpr_nounits(f_flat, in_pvals)\n  out_flat = [\n    const if pv is None else jax.ShapeDtypeStruct(pv.shape, pv.dtype)\n    for pv, const in out_pvals\n  ]\n  return jax.tree_util.tree_unflatten(out_tree(), out_flat)\n\n\ndef _parse_spec(spec):\n  \"\"\"Parse an input spec of the form (shape, dtype) or shape into a jax.ShapeDtypeStruct.\"\"\"\n  spec = tuple(spec)\n  if len(spec) == 2 and isinstance(spec[0], Iterable):\n    return jax.ShapeDtypeStruct(tuple(spec[0]), spec[1])\n  else:\n    return jax.ShapeDtypeStruct(spec, jnp.float32)\n\n\ndef prefetch_to_device(iterator, size, devices=None):\n  \"\"\"Shard and prefetch batches on device.\n\n  This utility takes an iterator and returns a new iterator which fills an on\n  device prefetch buffer. Eager prefetching can improve the performance of\n  training loops significantly by overlapping compute and data transfer.\n\n  This utility is mostly useful for GPUs, for TPUs and CPUs it should not be\n  necessary -- the TPU & CPU memory allocators (normally) don't pick a memory\n  location that isn't free yet so they don't block. Instead those allocators OOM.\n\n  Args:\n    iterator: an iterator that yields a pytree of ndarrays where the first\n      dimension is sharded across devices.\n\n    size: the size of the prefetch buffer.\n\n      If you're training on GPUs, 2 is generally the best choice because this\n      guarantees that you can overlap a training step on GPU with a data\n      prefetch step on CPU.\n\n    devices: the list of devices to which the arrays should be prefetched.\n\n      Defaults to the order of devices expected by ``jax.pmap``.\n\n  Yields:\n    The original items from the iterator where each ndarray is now sharded to\n    the specified devices.\n  \"\"\"\n  queue = collections.deque()\n  devices = _pmap_device_order() if devices is None else devices\n\n  def _prefetch(xs):\n    return jax.device_put_sharded(list(xs), devices)\n\n  def enqueue(n):  # Enqueues *up to* `n` elements from the iterator.\n    for data in itertools.islice(iterator, n):\n      queue.append(jax.tree_util.tree_map(_prefetch, data))\n\n  enqueue(size)  # Fill up the buffer.\n  while queue:\n    yield queue.popleft()\n    enqueue(1)\n\n\ndef _scan_nd(body_fn, init, xs, n=1, unroll=(1,)):\n  \"\"\"Utility for performing an n-dimensional `lax.scan`.\n\n  The n-d scan is simply recursive call of 1-d scan.\n  Args:\n    body_fn: the body of the loop of type (c, x) -> (c, y).\n    init: initial value for the carry.\n    xs: a pytree of tensors to scan over.\n    n: number of dimensions to scan over (default: 1)\n  Returns:\n    A tuple of the final carry and the values returned by the body.\n  \"\"\"\n  if n == 1:\n    return lax.scan(body_fn, init, xs, unroll=unroll[0])\n  else:\n\n    def scan_body(c, x):\n      return _scan_nd(body_fn, c, x, n=n - 1, unroll=unroll[1:])\n\n    return lax.scan(scan_body, init, xs, unroll=unroll[0])\n\n\ndef _invert_perm(perm):\n  perm_inv = [0] * len(perm)\n  for i, j in enumerate(perm):\n    perm_inv[j] = i\n  return tuple(perm_inv)\n\n\ndef scan_in_dim(body_fn, init, xs, axis=(0,), unroll=(1,), keepdims=False):\n  \"\"\"utility for doing a scan along arbitrary dimensions.\n\n  See `lax.scan` for details on how the scan operation works.\n\n  Note on `unroll`: This argument gets left padded with ones to match the size\n  of `axis`. Doing so allows unrolls to performed from the innermost loop first.\n  For example, `scan_in_dim(..., axis=(1, 2, 3), unroll=5)` is equivalent to\n  `scan_in_dim(..., axis=(1, 2, 3), unroll=(1, 1, 5))`.\n\n  Args:\n    body_fn: the body of the loop of type (c, x) -> (c, y).\n    init: initial value for the carry.\n    xs: a pytree of tensors to scan over.\n    axis: the axis to scan over.\n    keepdims: keep the dimensions that are scanned over.\n    unroll: an optional positive integer, or tuple of positive integers\n      showing how many iterations of the loop to be unrolled into a single\n      iteration for each axis.\n  Returns:\n    A tuple of the final carry and the values returned by the body.\n  \"\"\"\n  if not isinstance(axis, Iterable):\n    axis = (axis,)\n\n  if not isinstance(unroll, Iterable):\n    unroll = (unroll,)\n\n  # Pad unroll with ones so we start unrolling from the innermost loop\n  len_diff = len(axis) - len(unroll)\n  unroll = (1,) * len_diff + unroll\n\n  def transpose_in(x):\n    perm = axis + tuple(np.delete(np.arange(x.ndim), axis))\n    return x.transpose(perm)\n\n  def transpose_out(x):\n    perm = axis + tuple(np.delete(np.arange(x.ndim), axis))\n    return x.transpose(_invert_perm(perm))\n\n  def body_wrapper(c, xs):\n    if keepdims:\n      xs = jax.tree_util.tree_map(\n        lambda x: x.reshape((1,) * len(axis) + x.shape), xs\n      )\n      xs = jax.tree_util.tree_map(transpose_out, xs)\n    c, ys = body_fn(c, xs)\n    if keepdims:\n      ys = jax.tree_util.tree_map(transpose_in, ys)\n      ys = jax.tree_util.tree_map(lambda x: x.reshape(x.shape[len(axis) :]), ys)\n    return c, ys\n\n  xs = jax.tree_util.tree_map(transpose_in, xs)\n  c, ys = _scan_nd(body_wrapper, init, xs, n=len(axis), unroll=unroll)\n  ys = jax.tree_util.tree_map(transpose_out, ys)\n  return c, ys\n\n\n# Copied from https://github.com/google-research/big_vision\ndef pad_shard_unpad(\n  wrapped, static_argnums=(0,), static_argnames=(), static_return=False\n):\n  \"\"\"Wraps a function with code that pads, shards, then un-shards, un-pads.\n\n  Args:\n    wrapped: the function to be wrapped. Signature is ``params, *args, *kwargs``.\n    static_argnums: indices of arguments to ``wrapped`` that should _not_ be\n      padded and sharded, but instead be forwarded as-is. The default is (0,)\n      because by far the most common use-case is to pass ``params`` first.\n    static_argnames: names of kwargs to ``wrapped`` that should _not_ be padded\n      and sharded, but instead be forwarded as-is.\n    static_return: whether not to un-shard, and un-pad the return value; static\n      return values are typically used with eval steps that compute metrics\n\n  Returns:\n    A new function that pads and shards its arguments before passing them to\n    the wrapped function, and un-shards and un-pads the returned pytree.\n\n    This is useful for calling a pmap'ed function with inputs that aren't\n    divisible by the number of devices. A typical use is:\n      @pad_shard_unpad\n      @jax.pmap\n      def forward(params, x): ...\n\n  Notes:\n    The padding is done in host-memory before being passed to the function, and\n    the values returned by the function are transferred back to host memory.\n\n    The returned function is augmented with a new keyword-only argument\n    ``min_device_batch`` that, if specified, forces padding inputs to at least\n    this size per device. This can be useful to avoid recompiles for the last\n    batch and reduce memory fragmentation.\n\n    For more information refer to https://flax.readthedocs.io/en/latest/guides/data_preprocessing/full_eval.html\n  \"\"\"\n\n  def pad_shard_unpad_wrapper(*args, min_device_batch=None, **kw):\n    d = jax.local_device_count()  # d = devices, b = batch\n    batch_sizes = set()\n    for i, a in enumerate(args):\n      if i not in static_argnums:\n        batch_sizes |= {t.shape[0] for t in jax.tree_util.tree_leaves(a)}\n    for k, v in kw.items():\n      if k not in static_argnames:\n        batch_sizes |= {t.shape[0] for t in jax.tree_util.tree_leaves(v)}\n    assert len(batch_sizes) == 1, f'Inconsistent batch-sizes: {batch_sizes}'\n    b = batch_sizes.pop()\n\n    def pad(x):\n      _, *shape = x.shape\n      db, rest = divmod(b, d)\n      if rest:\n        x = np.concatenate([x, np.zeros((d - rest, *shape), x.dtype)], axis=0)\n        db += 1\n      if min_device_batch and db < min_device_batch:\n        x = np.concatenate(\n          [x, np.zeros((d * (min_device_batch - db), *shape), x.dtype)]\n        )\n        db = min_device_batch\n      return x.reshape(d, db, *shape)\n\n    def maybe_pad(tree, actually_pad=True):\n      if not actually_pad:\n        return tree  # For call-site convenience below.\n      return jax.tree_util.tree_map(pad, tree)\n\n    args = [maybe_pad(a, i not in static_argnums) for i, a in enumerate(args)]\n    kw = {k: maybe_pad(v, k not in static_argnames) for k, v in kw.items()}\n    out = wrapped(*args, **kw)\n\n    def unpad(x):\n      # Transfer back before cutting, to reduce on-device shape diversity.\n      return jax.device_get(x).reshape([np.prod(x.shape[:2]), *x.shape[2:]])[:b]\n\n    return out if static_return else jax.tree_util.tree_map(unpad, out)\n\n  return pad_shard_unpad_wrapper\n"
  },
  {
    "path": "flax/linen/README.md",
    "content": "# Linen: A comfortable evolution of Flax\n\nLinen is a neural network API developed based on learning from our users and the broader JAX community. Linen improves on much of the former `flax.nn` API (removed since v0.4.0), such as submodule sharing and better support for non-trainable variables.\nMoreover, Linen builds on a \"functional core\", enabling direct usage of JAX transformations such as `vmap`, `remat` or `scan` inside your modules.\n\nIn Linen, Modules behave much closer to vanilla Python objects, while still letting you opt-in to the concise single-method pattern many of our users love.\n\nThe Linen Module API is stable and currently recommended for new projects. We are already supporting users in the OSS community and within Google. Minor changes may come to the top-level `apply` and `init` patterns, which we will communicate clearly. We plan a few improvements, including writing up short design notes, adding more design tests (see last link below), and an API for interactive module instances.\n\nPlease open a [discussion](https://github.com/google/flax/discussions) if you have any questions or thoughts.\n\n**See the [Linen API reference docs](https://flax.readthedocs.io/en/latest/api_reference/flax.linen/index.html)**, or take a look at our additional material:\n\n* 2-page intro to the [Linen Design Principles](https://docs.google.com/document/d/1ZlL_4bXCw5Xl0WstQw1GpnZqfb9JFOeUGAPcBVk-kn8)\n* [Slides from a talk to the JAX core team](https://docs.google.com/presentation/d/1ngKWUwsSqAwPRvATG8sAxMzu9ujv4N__cKsUofdNno0)\n* [Brief Intro to Linen](https://colab.research.google.com/github/google/flax/blob/main/docs/linen_intro.ipynb) in Colab\n* An [upgrade guide](https://docs.google.com/document/d/1hYavTVPaKVVe9Be8pCB7yW7r6dDv3RALVNit8NZca4c) + some additional questions we're considering\n* Ported [examples](https://github.com/google/flax/tree/main/examples)\n* \"Design tests\" used to ensure that our \"functional core\" supports [various advanced use-cases](https://github.com/google/flax/tree/main/tests/core/design), and that the mostly-syntactic-sugar Module abstraction\n  [doesn't get in the way](https://github.com/google/flax/tree/main/examples/linen_design_test)\n"
  },
  {
    "path": "flax/linen/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"The Flax Module system.\"\"\"\n\n\n# pylint: disable=g-multiple-import,useless-import-alias\n# re-export commonly used modules and functions\nfrom flax.core import (\n    DenyList as DenyList,\n    FrozenDict as FrozenDict,\n    broadcast as broadcast,\n    meta as meta,\n)\nfrom flax.core.meta import (\n    PARTITION_NAME as PARTITION_NAME,\n    Partitioned as Partitioned,\n    get_partition_spec as get_partition_spec,\n    get_sharding as get_sharding,\n    unbox as unbox,\n    with_partitioning as with_partitioning,\n)\nfrom flax.core.spmd import (\n    get_logical_axis_rules as get_logical_axis_rules,\n    logical_axis_rules as logical_axis_rules,\n    set_logical_axis_rules as set_logical_axis_rules,\n)\nfrom .activation import (\n    PReLU as PReLU,\n    celu as celu,\n    elu as elu,\n    gelu as gelu,\n    glu as glu,\n    hard_sigmoid as hard_sigmoid,\n    hard_silu as hard_silu,\n    hard_swish as hard_swish,\n    hard_tanh as hard_tanh,\n    leaky_relu as leaky_relu,\n    log_sigmoid as log_sigmoid,\n    log_softmax as log_softmax,\n    logsumexp as logsumexp,\n    normalize as normalize,\n    one_hot as one_hot,\n    relu6 as relu6,\n    relu as relu,\n    selu as selu,\n    sigmoid as sigmoid,\n    silu as silu,\n    soft_sign as soft_sign,\n    softmax as softmax,\n    softplus as softplus,\n    standardize as standardize,\n    swish as swish,\n    tanh as tanh,\n)\nfrom .attention import (\n    MultiHeadAttention as MultiHeadAttention,\n    MultiHeadDotProductAttention as MultiHeadDotProductAttention,\n    SelfAttention as SelfAttention,\n    combine_masks as combine_masks,\n    dot_product_attention_weights as dot_product_attention_weights,\n    dot_product_attention as dot_product_attention,\n    make_attention_mask as make_attention_mask,\n    make_causal_mask as make_causal_mask,\n)\nfrom .batch_apply import BatchApply as BatchApply\nfrom .combinators import Sequential as Sequential\nfrom .fp8_ops import (\n    Fp8DirectDotGeneralOp as Fp8DirectDotGeneralOp,\n    Fp8DotGeneral as Fp8DotGeneral,\n    Fp8DotGeneralOp as Fp8DotGeneralOp,\n    Fp8Einsum as Fp8Einsum,\n    NANOOFp8DotGeneralOp as NANOOFp8DotGeneralOp,\n)\nfrom .initializers import (\n    ones_init as ones_init,\n    ones as ones,\n    zeros_init as zeros_init,\n    zeros as zeros,\n)\nfrom .linear import (\n    ConvLocal as ConvLocal,\n    ConvTranspose as ConvTranspose,\n    Conv as Conv,\n    DenseGeneral as DenseGeneral,\n    Dense as Dense,\n    Einsum as Einsum,\n    Embed as Embed,\n)\nfrom .module import (\n    Module as Module,\n    Variable as Variable,\n    apply as apply,\n    compact_name_scope as compact_name_scope,\n    compact as compact,\n    disable_named_call as disable_named_call,\n    enable_named_call as enable_named_call,\n    init_with_output as init_with_output,\n    init as init,\n    intercept_methods as intercept_methods,\n    merge_param as merge_param,\n    nowrap as nowrap,\n    override_named_call as override_named_call,\n    share_scope as share_scope,\n)\nfrom .normalization import (\n    BatchNorm as BatchNorm,\n    GroupNorm as GroupNorm,\n    InstanceNorm as InstanceNorm,\n    LayerNorm as LayerNorm,\n    RMSNorm as RMSNorm,\n    SpectralNorm as SpectralNorm,\n    WeightNorm as WeightNorm,\n)\nfrom .pooling import (avg_pool as avg_pool, max_pool as max_pool, pool as pool)\nfrom .recurrent import (\n    Bidirectional as Bidirectional,\n    ConvLSTMCell as ConvLSTMCell,\n    GRUCell as GRUCell,\n    LSTMCell as LSTMCell,\n    MGUCell as MGUCell,\n    OptimizedLSTMCell as OptimizedLSTMCell,\n    RNNCellBase as RNNCellBase,\n    RNN as RNN,\n    SimpleCell as SimpleCell,\n)\nfrom .spmd import (\n    LogicallyPartitioned as LogicallyPartitioned,\n    logical_to_mesh,\n    logical_to_mesh_axes,\n    logical_to_mesh_sharding,\n    with_logical_constraint,\n    with_logical_partitioning as with_logical_partitioning,\n)\nfrom .stochastic import Dropout as Dropout\nfrom .summary import tabulate\nfrom .transforms import (\n    add_metadata_axis,\n    checkpoint as checkpoint,\n    cond as cond,\n    custom_vjp as custom_vjp,\n    fold_rngs as fold_rngs,\n    grad as grad,\n    jit as jit,\n    jvp as jvp,\n    map_variables as map_variables,\n    named_call as named_call,\n    remat_scan as remat_scan,\n    remat as remat,\n    scan as scan,\n    switch as switch,\n    value_and_grad as value_and_grad,\n    vjp as vjp,\n    vmap as vmap,\n    while_loop as while_loop,\n)\n# pylint: enable=g-multiple-import\n"
  },
  {
    "path": "flax/linen/activation.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Activation functions.\"\"\"\n\n# pylint: disable=unused-import\n# re-export activation functions from jax.nn\nfrom typing import Any, Optional\n\nfrom flax.linen.module import compact\nfrom flax.linen.module import Module\nfrom flax.linen.linear import Dense\nfrom flax.typing import Array, Dtype\n\nfrom jax.nn import celu\nfrom jax.nn import elu\nfrom jax.nn import gelu\nfrom jax.nn import glu\nfrom jax.nn import hard_sigmoid\nfrom jax.nn import hard_silu\nfrom jax.nn import hard_swish\nfrom jax.nn import hard_tanh\nfrom jax.nn import leaky_relu\nfrom jax.nn import log_sigmoid\nfrom jax.nn import log_softmax\nfrom jax.nn import logsumexp\nfrom jax.nn import one_hot\nfrom jax.nn import relu\nfrom jax.nn import relu6\nfrom jax.nn import selu\nfrom jax.nn import sigmoid\nfrom jax.nn import silu\nfrom jax.nn import soft_sign\nfrom jax.nn import softmax\nfrom jax.nn import softplus\nfrom jax.nn import standardize\nfrom jax.nn import swish\nimport jax.numpy as jnp\nfrom jax.numpy import tanh\n\n# Normalize is a deprecated alias of standardize\nnormalize = standardize\n\n# pylint: enable=unused-import\n\n\n\nclass PReLU(Module):\n  \"\"\"Parametric Rectified Linear Unit (PReLU) activation function.\n\n  Note that PReLU is a Flax layer and not a simple activation function, so\n  it needs to be initialized before being called.\n\n  Example usage::\n    >>> import flax.linen as nn\n\n    >>> class MLP(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     x = nn.Dense(2)(x)\n    ...     x = nn.PReLU()(x) # initialized\n    ...     return x\n\n  Attributes:\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    negative_slope_init: the value to initialize the negative slope\n      (default 0.01).\n  \"\"\"\n\n  param_dtype: Dtype = jnp.float32\n  negative_slope_init: float = 0.01\n\n  @compact\n  def __call__(self, inputs: Array) -> Array:\n    \"\"\"Applies an activation to the inputs.\n\n    Args:\n      inputs: the nd-array to apply the activation function to.\n\n    Returns:\n      The transformed input.\n    \"\"\"\n    negative_slope = self.param(\n        'negative_slope',\n        lambda k: jnp.asarray(self.negative_slope_init, self.param_dtype),\n    )\n    return jnp.where(\n        inputs >= 0, inputs, jnp.asarray(negative_slope, inputs.dtype) * inputs\n    )\n"
  },
  {
    "path": "flax/linen/attention.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Attention core modules for Flax.\"\"\"\nfrom __future__ import annotations\n\nimport functools\nimport inspect\nimport warnings\nfrom typing import Any, overload\nfrom collections.abc import Callable\n\nimport jax\nimport jax.numpy as jnp\nfrom jax import lax, random\n\nfrom flax.linen import initializers\nfrom flax.linen.dtypes import promote_dtype\nfrom flax.linen.linear import (\n  DenseGeneral,\n  default_kernel_init,\n)\nfrom flax.linen.module import Module, compact, merge_param\nfrom flax.linen.normalization import LayerNorm\nfrom flax.typing import (\n  Array,\n  PRNGKey,\n  Dtype,\n  Shape as Shape,\n  Initializer,\n  PrecisionLike,\n  DotGeneralT,\n)\n\n\ndef dot_product_attention_weights(\n    query: Array,\n    key: Array,\n    bias: Array | None = None,\n    mask: Array | None = None,\n    broadcast_dropout: bool = True,\n    dropout_rng: PRNGKey | None = None,\n    dropout_rate: float = 0.0,\n    deterministic: bool = False,\n    dtype: Dtype | None = None,\n    precision: PrecisionLike = None,\n    module: Module | None = None,\n    force_fp32_for_softmax: bool = False,\n    einsum_dot_general: Callable[..., Array] | None = None,\n    einsum: Callable[..., Array] | None = None,\n):\n  \"\"\"Computes dot-product attention weights given query and key.\n\n  Used by :func:`dot_product_attention`, which is what you'll most likely use.\n  But if you want access to the attention weights for introspection, then\n  you can directly call this function and call einsum yourself.\n\n  Args:\n    query: queries for calculating attention with shape of ``[batch...,\n      q_length, num_heads, qk_depth_per_head]``.\n    key: keys for calculating attention with shape of ``[batch..., kv_length,\n      num_heads, qk_depth_per_head]``.\n    bias: bias for the attention weights. This should be broadcastable to the\n      shape ``[batch..., num_heads, q_length, kv_length]``. This can be used for\n      incorporating causal masks, padding masks, proximity bias, etc.\n    mask: mask for the attention weights. This should be broadcastable to the\n      shape ``[batch..., num_heads, q_length, kv_length]``. This can be used for\n      incorporating causal masks. Attention weights are masked out if their\n      corresponding mask value is ``False``.\n    broadcast_dropout: bool: use a broadcasted dropout along batch dims.\n    dropout_rng: JAX PRNGKey: to be used for dropout\n    dropout_rate: dropout rate\n    deterministic: bool, deterministic or not (to apply dropout)\n    dtype: the dtype of the computation (default: infer from inputs and params)\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    module: the Module that will sow the attention weights into the\n      'intermediates' collection. Remember to mark 'intermediates' as mutable\n      via ``mutable=['intermediates']`` in order to have that collection\n      returned. If ``module`` is None, the attention weights will not be sowed.\n    force_fp32_for_softmax: bool, whether to force the softmax to be computed in\n      fp32. This is useful for mixed-precision training where higher precision\n      is desired for numerical stability.\n    einsum_dot_general: the dot_general to use in einsum.\n    einsum: If unspecified, default `jnp.einsum` will be used. This argument is\n      mutually exclusive with `precision` and `einsum_dot_general`.\n\n  Raises:\n    ValueError: if both `precision`/`einsum_dot_general` and `einsum` are\n      specified.\n\n  Returns:\n    Output of shape ``[batch..., num_heads, q_length, kv_length]``.\n  \"\"\"\n  if (precision or einsum_dot_general) and einsum:\n    raise ValueError(\n        'precision/einsum_dot_general and einsum are mutually exclusive. Please'\n        ' specify only one of them.'\n    )\n  if not einsum:\n    einsum = functools.partial(\n        jnp.einsum,\n        precision=precision,\n        _dot_general=einsum_dot_general\n        if einsum_dot_general\n        else jax.lax.dot_general,\n    )\n\n  query, key = promote_dtype(query, key, dtype=dtype)\n  dtype = query.dtype\n\n  assert query.ndim == key.ndim, 'q, k must have same rank.'\n  assert query.shape[:-3] == key.shape[:-3], 'q, k batch dims must match.'\n  assert query.shape[-2] == key.shape[-2], 'q, k num_heads must match.'\n  assert query.shape[-1] == key.shape[-1], 'q, k depths must match.'\n\n  # calculate attention matrix\n  depth = query.shape[-1]\n  query = query / jnp.sqrt(depth).astype(dtype)\n  # attn weight shape is (batch..., num_heads, q_length, kv_length)\n  attn_weights = einsum('...qhd,...khd->...hqk', query, key)\n\n  # apply attention bias: masking, dropout, proximity bias, etc.\n  if bias is not None:\n    attn_weights = attn_weights + bias\n  # apply attention mask\n  if mask is not None:\n    big_neg = jnp.finfo(dtype).min\n    attn_weights = jnp.where(mask, attn_weights, big_neg)\n\n  # normalize the attention weights\n  if force_fp32_for_softmax and dtype != jnp.float32:\n    attn_weights = jax.nn.softmax(attn_weights.astype(jnp.float32))\n  else:\n    attn_weights = jax.nn.softmax(attn_weights).astype(dtype)\n\n  if module:\n    module.sow('intermediates', 'attention_weights', attn_weights)\n\n  # apply attention dropout\n  if not deterministic and dropout_rate > 0.0:\n    keep_prob = 1.0 - dropout_rate\n    if broadcast_dropout:\n      # dropout is broadcast across the batch + head dimensions\n      dropout_shape = tuple([1] * (key.ndim - 2)) + attn_weights.shape[-2:]\n      keep = random.bernoulli(dropout_rng, keep_prob, dropout_shape)  # type: ignore\n    else:\n      keep = random.bernoulli(dropout_rng, keep_prob, attn_weights.shape)  # type: ignore\n    multiplier = keep.astype(dtype) / jnp.asarray(keep_prob, dtype=dtype)\n    attn_weights = attn_weights * multiplier\n\n  return attn_weights\n\n\ndef dot_product_attention(\n    query: Array,\n    key: Array,\n    value: Array,\n    bias: Array | None = None,\n    mask: Array | None = None,\n    broadcast_dropout: bool = True,\n    dropout_rng: PRNGKey | None = None,\n    dropout_rate: float = 0.0,\n    deterministic: bool = False,\n    dtype: Dtype | None = None,\n    precision: PrecisionLike = None,\n    module: Module | None = None,\n    force_fp32_for_softmax: bool = False,\n    einsum_dot_general: Callable[..., Array] | None = None,\n    qk_attn_weights_einsum: Callable[..., Array] | None = None,\n    attn_weights_value_einsum: Callable[..., Array] | None = None,\n):\n  \"\"\"Computes dot-product attention given query, key, and value.\n\n  This is the core function for applying attention based on\n  https://arxiv.org/abs/1706.03762. It calculates the attention weights given\n  query and key and combines the values using the attention weights.\n\n  .. note::\n    ``query``, ``key``, ``value`` needn't have any batch dimensions.\n\n  Args:\n    query: queries for calculating attention with shape of ``[batch...,\n      q_length, num_heads, qk_depth_per_head]``.\n    key: keys for calculating attention with shape of ``[batch..., kv_length,\n      num_heads, qk_depth_per_head]``.\n    value: values to be used in attention with shape of ``[batch..., kv_length,\n      num_heads, v_depth_per_head]``.\n    bias: bias for the attention weights. This should be broadcastable to the\n      shape ``[batch..., num_heads, q_length, kv_length]``. This can be used for\n      incorporating causal masks, padding masks, proximity bias, etc.\n    mask: mask for the attention weights. This should be broadcastable to the\n      shape ``[batch..., num_heads, q_length, kv_length]``. This can be used for\n      incorporating causal masks. Attention weights are masked out if their\n      corresponding mask value is ``False``.\n    broadcast_dropout: bool: use a broadcasted dropout along batch dims.\n    dropout_rng: JAX PRNGKey: to be used for dropout\n    dropout_rate: dropout rate\n    deterministic: bool, deterministic or not (to apply dropout)\n    dtype: the dtype of the computation (default: infer from inputs)\n    precision: numerical precision of the computation see ``jax.lax.Precision`\n      for details.\n    module: the Module that will sow the attention weights into the\n      'intermediates' collection. Remember to mark 'intermediates' as mutable\n      via ``mutable=['intermediates']`` in order to have that collection\n      returned. If ``module`` is None, the attention weights will not be sowed.\n    force_fp32_for_softmax: bool, whether to force the softmax to be computed in\n      fp32. This is useful for mixed-precision training where higher precision\n      is desired for numerical stability.\n    einsum_dot_general: the dot_general to use in `jnp.einsum`.\n    qk_attn_weights_einsum: the einsum for computing the attention weights. When\n      unspecified, the default `jnp.einsum` will be used. This argument is\n      mutually exclusive with `precision` and `einsum_dot_general`.\n    attn_weights_value_einsum: the einsum for computing the product of the\n      attention weights and the values. When unspecified, the default\n      `jnp.einsum` will be used. This argument is mutually exclusive with\n      `precision` and `einsum_dot_general`.\n\n  Returns:\n    Output of shape ``[batch..., q_length, num_heads, v_depth_per_head]``.\n\n  Raises:\n    ValueError: if both `precision`/`einsum_dot_general` and\n    `qk_attn_weights_einsum`/`attn_weights_value_einsum` are\n      specified.\n  \"\"\"\n  if (qk_attn_weights_einsum and not attn_weights_value_einsum) or (\n      not qk_attn_weights_einsum and attn_weights_value_einsum\n  ):\n    raise ValueError(\n        'qk_attn_weights_einsum and attn_weights_value_einsum must be specified'\n        ' together.'\n    )\n  if (precision or einsum_dot_general) and (\n      qk_attn_weights_einsum or attn_weights_value_einsum\n  ):\n    raise ValueError(\n        'precision/einsum_dot_general and'\n        ' qk_attn_weights_einsum/attn_weights_value_einsum are mutually'\n        ' exclusive. Please specify only one of them.'\n    )\n\n  query, key, value = promote_dtype(query, key, value, dtype=dtype)\n  dtype = query.dtype\n  assert key.ndim == query.ndim == value.ndim, 'q, k, v must have same rank.'\n  assert (\n    query.shape[:-3] == key.shape[:-3] == value.shape[:-3]\n  ), 'q, k, v batch dims must match.'\n  assert (\n    query.shape[-2] == key.shape[-2] == value.shape[-2]\n  ), 'q, k, v num_heads must match.'\n  assert key.shape[-3] == value.shape[-3], 'k, v lengths must match.'\n\n  # compute attention weights\n  attn_weights = dot_product_attention_weights(\n      query,\n      key,\n      bias,\n      mask,\n      broadcast_dropout,\n      dropout_rng,\n      dropout_rate,\n      deterministic,\n      dtype,\n      precision,\n      module,\n      force_fp32_for_softmax,\n      einsum_dot_general=einsum_dot_general,\n      einsum=qk_attn_weights_einsum,\n  )\n  if not attn_weights_value_einsum:\n    attn_weights_value_einsum = functools.partial(\n        jnp.einsum,\n        precision=precision,\n        _dot_general=einsum_dot_general\n        if einsum_dot_general\n        else jax.lax.dot_general,\n    )\n  # return weighted sum over values for each query position\n  return attn_weights_value_einsum(\n      '...hqk,...khd->...qhd',\n      attn_weights,\n      value,\n  )\n\n\nclass MultiHeadDotProductAttention(Module):\n  \"\"\"Multi-head dot-product attention.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax\n\n    >>> layer = nn.MultiHeadDotProductAttention(num_heads=8, qkv_features=16)\n    >>> key1, key2, key3, key4, key5, key6 = jax.random.split(jax.random.key(0), 6)\n    >>> shape = (4, 3, 2, 5)\n    >>> q, k, v = jax.random.uniform(key1, shape), jax.random.uniform(key2, shape), jax.random.uniform(key3, shape)\n    >>> variables = layer.init(jax.random.key(0), q)\n\n    >>> # different inputs for inputs_q, inputs_k and inputs_v\n    >>> out = layer.apply(variables, q, k, v)\n    >>> # equivalent to layer.apply(variables, inputs_q=q, inputs_k=k, inputs_v=k)\n    >>> out = layer.apply(variables, q, k)\n    >>> # equivalent to layer.apply(variables, inputs_q=q, inputs_k=q) and layer.apply(variables, inputs_q=q, inputs_k=q, inputs_v=q)\n    >>> out = layer.apply(variables, q)\n\n    >>> attention_kwargs = dict(\n    ...     num_heads=8,\n    ...     qkv_features=16,\n    ...     kernel_init=nn.initializers.ones,\n    ...     bias_init=nn.initializers.zeros,\n    ...     dropout_rate=0.5,\n    ...     deterministic=False,\n    ...     )\n    >>> class Module(nn.Module):\n    ...   attention_kwargs: dict\n    ...\n    ...   @nn.compact\n    ...   def __call__(self, x, dropout_rng=None):\n    ...     out1 = nn.MultiHeadDotProductAttention(**self.attention_kwargs)(x, dropout_rng=dropout_rng)\n    ...     out2 = nn.MultiHeadDotProductAttention(**self.attention_kwargs)(x, dropout_rng=dropout_rng)\n    ...     return out1, out2\n    >>> module = Module(attention_kwargs)\n    >>> variables = module.init({'params': key1, 'dropout': key2}, q)\n\n    >>> # out1 and out2 are different.\n    >>> out1, out2 = module.apply(variables, q, rngs={'dropout': key3})\n    >>> # out3 and out4 are different.\n    >>> # out1 and out3 are different. out2 and out4 are different.\n    >>> out3, out4 = module.apply(variables, q, rngs={'dropout': key4})\n    >>> # out1 and out2 are the same.\n    >>> out1, out2 = module.apply(variables, q, dropout_rng=key5)\n    >>> # out1 and out2 are the same as out3 and out4.\n    >>> # providing a `dropout_rng` arg will take precedence over the `rngs` arg in `.apply`\n    >>> out3, out4 = module.apply(variables, q, rngs={'dropout': key6}, dropout_rng=key5)\n\n  Attributes:\n    num_heads: Number of attention heads. Features (i.e. inputs_q.shape[-1])\n      should be divisible by the number of heads.\n    dtype: The dtype of the computation (default: infer from inputs and params)\n    param_dtype: The dtype passed to parameter initializers (default: float32)\n    qkv_features: Dimension of the key, query, and value.\n    out_features: Dimension of the last projection\n    broadcast_dropout: Use a broadcasted dropout along batch dims.\n    dropout_rate: Dropout rate.\n    deterministic: If False, the attention weight is masked randomly using\n      dropout, whereas if True, the attention weights are deterministic.\n    precision: Numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    kernel_init: Initializer for the kernel of the Dense layers.\n    out_kernel_init: Optional Initializer for the kernel of the output Dense layer,\n      if None, ``kernel_init`` will be used.\n    bias_init: Initializer for the bias of the Dense layers.\n    out_bias_init: Optional Initializer for the bias of the output Dense layer,\n      if None, ``bias_init`` will be used.\n    use_bias: Whether pointwise QKVO dense transforms use bias.\n    attention_fn: dot_product_attention or compatible function. Accepts query,\n      key, value, and returns output of shape ``[bs, dim1, dim2, ..., dimN,,\n      num_heads, value_channels]``\n    decode: Whether to prepare and use an autoregressive cache.\n    normalize_qk: Should QK normalization be applied (arxiv.org/abs/2302.05442).\n    qk_attn_weights_einsum_cls: factory function to create the einsum for\n      computing the attention weights.\n    attn_weights_value_einsum_cls: factory function to create the einsum for\n      computing the product of the attention weights and the values.\n  \"\"\"\n\n  num_heads: int\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  qkv_features: int | None = None\n  out_features: int | None = None\n  broadcast_dropout: bool = True\n  dropout_rate: float = 0.0\n  deterministic: bool | None = None\n  precision: PrecisionLike = None\n  kernel_init: Initializer = default_kernel_init\n  out_kernel_init: Initializer | None = None\n  bias_init: Initializer = initializers.zeros_init()\n  out_bias_init: Initializer | None = None\n  use_bias: bool = True\n  attention_fn: Callable[..., Array] = dot_product_attention\n  decode: bool = False\n  normalize_qk: bool = False\n  force_fp32_for_softmax: bool = False\n  # Deprecated, will be removed.\n  qkv_dot_general: DotGeneralT | None = None\n  out_dot_general: DotGeneralT | None = None\n  qkv_dot_general_cls: Any = None\n  out_dot_general_cls: Any = None\n  qk_attn_weights_einsum_cls: Callable[..., Callable[..., Array]] | None = None\n  attn_weights_value_einsum_cls: Callable[..., Callable[..., Array]] | None = (\n      None\n  )\n\n  @overload\n  def __call__(\n    self,\n    inputs_q: Array,\n    inputs_k: Array | None = None,\n    inputs_v: Array | None = None,\n    *,\n    mask: Array | None = None,\n    deterministic: bool | None = None,\n    dropout_rng: PRNGKey | None = None,\n    sow_weights: bool = False,\n  ):\n    ...\n\n  @overload\n  def __call__(\n    self,\n    inputs_q: Array,\n    *,\n    inputs_kv: Array | None = None,\n    mask: Array | None = None,\n    deterministic: bool | None = None,\n    dropout_rng: PRNGKey | None = None,\n    sow_weights: bool = False,\n  ):\n    ...\n\n  @compact\n  def __call__(\n    self,\n    inputs_q: Array,\n    inputs_k: Array | None = None,\n    inputs_v: Array | None = None,\n    *,\n    inputs_kv: Array | None = None,\n    mask: Array | None = None,\n    deterministic: bool | None = None,\n    dropout_rng: PRNGKey | None = None,\n    sow_weights: bool = False,\n  ):\n    \"\"\"Applies multi-head dot product attention on the input data.\n\n    Projects the inputs into multi-headed query, key, and value vectors,\n    applies dot-product attention and project the results to an output vector.\n\n    If both inputs_k and inputs_v are None, they will both copy the value of\n    inputs_q (self attention).\n    If only inputs_v is None, it will copy the value of inputs_k.\n\n    Args:\n      inputs_q: input queries of shape ``[batch_sizes..., length, features]``.\n      inputs_k: key of shape ``[batch_sizes..., length, features]``. If None,\n        inputs_k will copy the value of inputs_q.\n      inputs_v: values of shape ``[batch_sizes..., length, features]``. If None,\n        inputs_v will copy the value of inputs_k.\n      inputs_kv: key/values of shape ``[batch_sizes..., length, features]``. If\n        None, inputs_kv will copy the value of inputs_q. This arg will be\n        deprecated soon. Use inputs_k and inputs_v instead.\n      mask: attention mask of shape ``[batch_sizes..., num_heads, query_length,\n        key/value_length]``. Attention weights are masked out if their\n        corresponding mask value is ``False``.\n      deterministic: if false, the attention weight is masked randomly using\n        dropout, whereas if true, the attention weights are deterministic.\n      dropout_rng: optional rng key to pass to the attention layer's dropout\n        mask. Otherwise, self.make_rng('dropout') is used instead.\n      sow_weights: if ``True``, the attention weights are sowed into the\n        'intermediates' collection. Remember to mark 'intermediates' as\n        mutable via ``mutable=['intermediates']`` in order to have that\n        collection returned.\n\n    Returns:\n      output of shape ``[batch_sizes..., length, features]``.\n    \"\"\"\n    if inputs_kv is not None:\n      if inputs_k is not None or inputs_v is not None:\n        raise ValueError(\n          'If either `inputs_k` or `inputs_v` is not None, '\n          '`inputs_kv` must be None. If `inputs_kv` is not None, both `inputs_k` '\n          'and `inputs_v` must be None. We recommend using `inputs_k` and '\n          '`inputs_v` args, since `inputs_kv` will be deprecated soon. See '\n          'https://github.com/google/flax/discussions/3389 for more '\n          'information.'\n        )\n      inputs_k = inputs_v = inputs_kv\n      warnings.warn(\n        'The inputs_kv arg will be deprecated soon. '\n        'Use inputs_k and inputs_v instead. See '\n        'https://github.com/google/flax/discussions/3389 '\n        'for more information.',\n        DeprecationWarning,\n      )\n    else:\n      if inputs_k is None:\n        if inputs_v is not None:\n          raise ValueError(\n            '`inputs_k` cannot be None if `inputs_v` is not None. '\n            'To have both `inputs_k` and `inputs_v` be the same value, pass in the '\n            'value to `inputs_k` and leave `inputs_v` as None.'\n          )\n        inputs_k = inputs_q\n      if inputs_v is None:\n        inputs_v = inputs_k\n      elif inputs_v.shape[-1] == inputs_v.shape[-2]:\n        warnings.warn(\n          f'You are passing an array of shape {inputs_v.shape} '\n          'to the `inputs_v` arg, when you may have intended '\n          'to pass it to the `mask` arg. As of Flax version '\n          '0.7.4, the function signature of '\n          \"MultiHeadDotProductAttention's `__call__` method \"\n          'has changed to `__call__(inputs_q, inputs_k=None, '\n          'inputs_v=None, *, inputs_kv=None, mask=None, '\n          'deterministic=None)`. Use the kwarg `mask` instead. '\n          'See https://github.com/google/flax/discussions/3389 '\n          'and read the docstring for more information.',\n          DeprecationWarning,\n        )\n\n    features = self.out_features or inputs_q.shape[-1]\n    qkv_features = self.qkv_features or inputs_q.shape[-1]\n    assert qkv_features % self.num_heads == 0, (\n      f'Memory dimension ({qkv_features}) must be divisible by number of'\n      f' heads ({self.num_heads}).'\n    )\n    head_dim = qkv_features // self.num_heads\n\n    dense = functools.partial(\n      DenseGeneral,\n      axis=-1,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      features=(self.num_heads, head_dim),\n      kernel_init=self.kernel_init,\n      bias_init=self.bias_init,\n      use_bias=self.use_bias,\n      precision=self.precision,\n      dot_general=self.qkv_dot_general,\n      dot_general_cls=self.qkv_dot_general_cls,\n    )\n    # project inputs_q to multi-headed q/k/v\n    # dimensions are then [batch..., length, n_heads, n_features_per_head]\n    query, key, value = (\n      dense(name='query')(inputs_q),\n      dense(name='key')(inputs_k),\n      dense(name='value')(inputs_v),\n    )\n\n    if self.normalize_qk:\n      # Normalizing query and key projections stabilizes training with higher\n      # LR. See ViT-22B paper http://arxiv.org/abs/2302.05442 for analysis.\n      query = LayerNorm(\n        name='query_ln',\n        use_bias=False,\n        dtype=self.dtype,\n        param_dtype=self.param_dtype,\n      )(query)  # type: ignore[call-arg]\n      key = LayerNorm(\n        name='key_ln',\n        use_bias=False,\n        dtype=self.dtype,\n        param_dtype=self.param_dtype,\n      )(key)  # type: ignore[call-arg]\n\n    # During fast autoregressive decoding, we feed one position at a time,\n    # and cache the keys and values step by step.\n    if self.decode:\n      # detect if we're initializing by absence of existing cache data.\n      is_initialized = self.has_variable('cache', 'cached_key')\n      cached_key = self.variable(\n        'cache', 'cached_key', jnp.zeros, key.shape, key.dtype\n      )\n      cached_value = self.variable(\n        'cache', 'cached_value', jnp.zeros, value.shape, value.dtype\n      )\n      cache_index = self.variable(\n        'cache', 'cache_index', lambda: jnp.array(0, dtype=jnp.int32)\n      )\n      if is_initialized:\n        (\n          *batch_dims,\n          max_length,\n          num_heads,\n          depth_per_head,\n        ) = cached_key.value.shape\n        # shape check of cached keys against query input\n        expected_shape = tuple(batch_dims) + (1, num_heads, depth_per_head)\n        if expected_shape != query.shape:\n          raise ValueError(\n            'Autoregressive cache shape error, '\n            'expected query shape %s instead got %s.'\n            % (expected_shape, query.shape)\n          )\n        # update key, value caches with our new 1d spatial slices\n        cur_index = cache_index.value\n        zero = jnp.array(0, dtype=lax.dtype(cur_index.dtype))\n        indices: tuple[int | jax.Array, ...] = (zero,) * len(\n          batch_dims\n        ) + (\n          cur_index,\n          zero,\n          zero,\n        )\n        key = lax.dynamic_update_slice(cached_key.value, key, indices)\n        value = lax.dynamic_update_slice(cached_value.value, value, indices)\n        cached_key.value = key\n        cached_value.value = value\n        cache_index.value = cache_index.value + 1\n        # causal mask for cached decoder self-attention:\n        # our single query position should only attend to those key\n        # positions that have already been generated and cached,\n        # not the remaining zero elements.\n        mask = combine_masks(\n          mask,\n          jnp.broadcast_to(\n            jnp.arange(max_length) <= cur_index,\n            tuple(batch_dims) + (1, 1, max_length),\n          ),\n        )\n\n    if (\n      self.dropout_rate > 0.0\n    ):  # Require `deterministic` only if using dropout.\n      m_deterministic = merge_param(\n        'deterministic', self.deterministic, deterministic\n      )\n      if not m_deterministic and dropout_rng is None:\n        dropout_rng = self.make_rng('dropout')\n    else:\n      m_deterministic = True\n\n    # `qk_attn_weights_einsum` and `attn_weights_value_einsum` are optional\n    # arguments that can be used to override the default `jnp.einsum`. They\n    # exist for quantized einsum support in AQT.\n    qk_attn_weights_einsum = (\n        self.qk_attn_weights_einsum_cls()\n        if self.qk_attn_weights_einsum_cls\n        else None\n    )\n    attn_weights_value_einsum = (\n        self.attn_weights_value_einsum_cls()\n        if self.attn_weights_value_einsum_cls\n        else None\n    )\n    # apply attention\n    attn_args = (query, key, value)\n    # This kwargs list match the default nn.dot_product_attention.\n    # For custom `attention_fn`s, invalid kwargs will be filtered.\n    attn_kwargs = dict(\n      mask=mask,\n      dropout_rng=dropout_rng,\n      dropout_rate=self.dropout_rate,\n      broadcast_dropout=self.broadcast_dropout,\n      deterministic=m_deterministic,\n      dtype=self.dtype,\n      precision=self.precision,\n      force_fp32_for_softmax=self.force_fp32_for_softmax,\n      qk_attn_weights_einsum=qk_attn_weights_einsum,\n      attn_weights_value_einsum=attn_weights_value_einsum,\n    )\n    attn_kwargs = {\n        k: v\n        for k, v in attn_kwargs.items()\n        if k in inspect.signature(self.attention_fn).parameters\n    }\n    if sow_weights:\n      x = self.attention_fn(*attn_args, **attn_kwargs, module=self)\n    else:\n      x = self.attention_fn(*attn_args, **attn_kwargs)\n    # back to the original inputs dimensions\n    out = DenseGeneral(\n      features=features,\n      axis=(-2, -1),\n      kernel_init=self.out_kernel_init or self.kernel_init,\n      bias_init=self.out_bias_init or self.bias_init,\n      use_bias=self.use_bias,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      precision=self.precision,\n      dot_general=self.out_dot_general,\n      dot_general_cls=self.out_dot_general_cls,\n      name='out',  # type: ignore[call-arg]\n    )(x)\n    return out\n\n\nclass MultiHeadAttention(MultiHeadDotProductAttention):\n  \"\"\"Multi-head dot-product attention.\n  Alias for ``MultiHeadDotProductAttention``.\n\n  **NOTE**: ``MultiHeadAttention`` is a wrapper of ``MultiHeadDotProductAttention``,\n  and so their implementations are identical. However ``MultiHeadAttention`` layers\n  will, by default, be named ``MultiHeadAttention_{index}``, whereas ``MultiHeadDotProductAttention``\n  will be named ``MultiHeadDotProductAttention_{index}``. Therefore, this could affect\n  checkpointing, param collection names and RNG threading (since the layer name is\n  used when generating new RNG's) within the module.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax\n\n    >>> layer = nn.MultiHeadAttention(num_heads=8, qkv_features=16)\n    >>> key1, key2, key3, key4, key5, key6 = jax.random.split(jax.random.key(0), 6)\n    >>> shape = (4, 3, 2, 5)\n    >>> q, k, v = jax.random.uniform(key1, shape), jax.random.uniform(key2, shape), jax.random.uniform(key3, shape)\n    >>> variables = layer.init(jax.random.key(0), q)\n\n    >>> # different inputs for inputs_q, inputs_k and inputs_v\n    >>> out = layer.apply(variables, q, k, v)\n    >>> # equivalent to layer.apply(variables, inputs_q=q, inputs_k=k, inputs_v=k)\n    >>> out = layer.apply(variables, q, k)\n    >>> # equivalent to layer.apply(variables, inputs_q=q, inputs_k=q) and layer.apply(variables, inputs_q=q, inputs_k=q, inputs_v=q)\n    >>> out = layer.apply(variables, q)\n\n    >>> attention_kwargs = dict(\n    ...     num_heads=8,\n    ...     qkv_features=16,\n    ...     kernel_init=nn.initializers.ones,\n    ...     bias_init=nn.initializers.zeros,\n    ...     dropout_rate=0.5,\n    ...     deterministic=False,\n    ...     )\n    >>> class Module(nn.Module):\n    ...   attention_kwargs: dict\n    ...\n    ...   @nn.compact\n    ...   def __call__(self, x, dropout_rng=None):\n    ...     out1 = nn.MultiHeadAttention(**self.attention_kwargs)(x, dropout_rng=dropout_rng)\n    ...     out2 = nn.MultiHeadAttention(**self.attention_kwargs)(x, dropout_rng=dropout_rng)\n    ...     return out1, out2\n    >>> module = Module(attention_kwargs)\n    >>> variables = module.init({'params': key1, 'dropout': key2}, q)\n\n    >>> # out1 and out2 are different.\n    >>> out1, out2 = module.apply(variables, q, rngs={'dropout': key3})\n    >>> # out3 and out4 are different.\n    >>> # out1 and out3 are different. out2 and out4 are different.\n    >>> out3, out4 = module.apply(variables, q, rngs={'dropout': key4})\n    >>> # out1 and out2 are the same.\n    >>> out1, out2 = module.apply(variables, q, dropout_rng=key5)\n    >>> # out1 and out2 are the same as out3 and out4.\n    >>> # providing a `dropout_rng` arg will take precedence over the `rngs` arg in `.apply`\n    >>> out3, out4 = module.apply(variables, q, rngs={'dropout': key6}, dropout_rng=key5)\n\n  Attributes:\n    num_heads: number of attention heads. Features (i.e. inputs_q.shape[-1])\n      should be divisible by the number of heads.\n    dtype: the dtype of the computation (default: infer from inputs and params)\n    param_dtype: the dtype passed to parameter initializers (default: float32)\n    qkv_features: dimension of the key, query, and value.\n    out_features: dimension of the last projection\n    broadcast_dropout: bool: use a broadcasted dropout along batch dims.\n    dropout_rate: dropout rate\n    deterministic: if false, the attention weight is masked randomly using\n      dropout, whereas if true, the attention weights are deterministic.\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    kernel_init: initializer for the kernel of the Dense layers.\n    bias_init: initializer for the bias of the Dense layers.\n    use_bias: bool: whether pointwise QKVO dense transforms use bias.\n    attention_fn: dot_product_attention or compatible function. Accepts query,\n      key, value, and returns output of shape ``[bs, dim1, dim2, ..., dimN,,\n      num_heads, value_channels]``\n    decode: whether to prepare and use an autoregressive cache.\n    normalize_qk: should QK normalization be applied (arxiv.org/abs/2302.05442).\n  \"\"\"\n\n\nclass SelfAttention(MultiHeadDotProductAttention):\n  \"\"\"Self-attention special case of multi-head dot-product attention.\n  This layer is deprecated in favor of ``MultiHeadDotProductAttention``.\n\n  Example usage::\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n    >>> layer = nn.MultiHeadDotProductAttention(num_heads=8, qkv_features=16)\n    >>> variables = layer.init(jax.random.key(0), jnp.ones((4, 3, 2, 5)))\n  \"\"\"\n\n  @compact\n  def __call__(  # type: ignore\n    self,\n    inputs_q: Array,\n    mask: Array | None = None,\n    deterministic: bool | None = None,\n    dropout_rng: PRNGKey | None = None,\n    sow_weights: bool = False,\n  ):\n    \"\"\"Applies multi-head dot product self-attention on the input data.\n\n    Projects the inputs into multi-headed query, key, and value vectors,\n    applies dot-product attention and project the results to an output vector.\n\n    Args:\n      inputs_q: input queries of shape ``[batch_sizes..., length, features]``.\n      mask: attention mask of shape ``[batch_sizes..., num_heads, query_length,\n        key/value_length]``. Attention weights are masked out if their\n        corresponding mask value is ``False``.\n      deterministic: if false, the attention weight is masked randomly using\n        dropout, whereas if true, the attention weights are deterministic.\n\n    Returns:\n      output of shape ``[batch_sizes..., length, features]``.\n    \"\"\"\n    warnings.warn(\n      'SelfAttention will be deprecated soon. Use '\n      '`MultiHeadDotProductAttention.__call__(inputs_q)` instead. '\n      'See https://github.com/google/flax/discussions/3389 '\n      'for more information.',\n      DeprecationWarning,\n    )\n    return super().__call__(\n      inputs_q,\n      mask=mask,\n      deterministic=deterministic,\n      dropout_rng=dropout_rng,\n      sow_weights=sow_weights,\n    )\n\n\n# mask-making utility functions\n\n\ndef make_attention_mask(\n  query_input: Array,\n  key_input: Array,\n  pairwise_fn: Callable[..., Any] = jnp.multiply,\n  extra_batch_dims: int = 0,\n  dtype: Dtype = jnp.float32,\n):\n  \"\"\"Mask-making helper for attention weights.\n\n  In case of 1d inputs (i.e., ``[batch..., len_q]``, ``[batch..., len_kv]``, the\n  attention weights will be ``[batch..., heads, len_q, len_kv]`` and this\n  function will produce ``[batch..., 1, len_q, len_kv]``.\n\n  Args:\n    query_input: a batched, flat input of query_length size\n    key_input: a batched, flat input of key_length size\n    pairwise_fn: broadcasting elementwise comparison function\n    extra_batch_dims: number of extra batch dims to add singleton axes for, none\n      by default\n    dtype: mask return dtype\n\n  Returns:\n    A ``[batch..., 1, len_q, len_kv]`` shaped mask for 1d attention.\n  \"\"\"\n  mask = pairwise_fn(\n    jnp.expand_dims(query_input, axis=-1), jnp.expand_dims(key_input, axis=-2)\n  )\n  mask = jnp.expand_dims(mask, axis=-3)\n  mask = jnp.expand_dims(mask, axis=tuple(range(extra_batch_dims)))\n  return mask.astype(dtype)\n\n\ndef make_causal_mask(\n  x: Array, extra_batch_dims: int = 0, dtype: Dtype = jnp.float32\n) -> Array:\n  \"\"\"Make a causal mask for self-attention.\n\n  In case of 1d inputs (i.e., ``[batch..., len]``, the self-attention weights\n  will be ``[batch..., heads, len, len]`` and this function will produce a\n  causal mask of shape ``[batch..., 1, len, len]``.\n\n  Args:\n    x: input array of shape ``[batch..., len]``\n    extra_batch_dims: number of batch dims to add singleton axes for, none by\n      default\n    dtype: mask return dtype\n\n  Returns:\n    A ``[batch..., 1, len, len]`` shaped causal mask for 1d attention.\n  \"\"\"\n  idxs = jnp.broadcast_to(jnp.arange(x.shape[-1], dtype=jnp.int32), x.shape)\n  return make_attention_mask(\n    idxs,\n    idxs,\n    jnp.greater_equal,\n    extra_batch_dims=extra_batch_dims,\n    dtype=dtype,\n  )\n\n\ndef combine_masks(\n  *masks: Array | None, dtype: Dtype = jnp.float32\n) -> Array | None:\n  \"\"\"Combine attention masks.\n\n  Args:\n    *masks: set of attention mask arguments to combine, some can be None.\n    dtype: dtype for the returned mask.\n\n  Returns:\n    Combined mask, reduced by logical and, returns None if no masks given.\n  \"\"\"\n  masks_list = [m for m in masks if m is not None]\n  if not masks_list:\n    return None\n  assert all(\n    map(lambda x: x.ndim == masks_list[0].ndim, masks_list)\n  ), f'masks must have same rank: {tuple(map(lambda x: x.ndim, masks_list))}'\n  mask, *other_masks = masks_list\n  for other_mask in other_masks:\n    mask = jnp.logical_and(mask, other_mask)\n  return mask.astype(dtype)\n"
  },
  {
    "path": "flax/linen/batch_apply.py",
    "content": "# Copyright 2023 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Batch apply.\"\"\"\n\nimport jax, jax.numpy as jnp\nimport numpy as np\n\n\ndef ndim_at_least(x, num_dims):\n  if not (isinstance(x, jax.Array) or isinstance(x, np.ndarray)):\n    x = jnp.asarray(x)\n  return x.ndim >= num_dims\n\ndef arbitrary_mergeable_leaf(min_num_dims, args, kwargs):\n  for a in jax.tree_util.tree_leaves(args):\n    if ndim_at_least(a, min_num_dims):\n      return a\n  for k in jax.tree_util.tree_leaves(kwargs):\n    if ndim_at_least(k, min_num_dims):\n      return k\n  # Couldn't find a satisfactory leaf.\n  return None\n\ndef merge_leading_dims(x, num_dims):\n  \"\"\"Merge leading dimensions.\"\"\"\n  # Don't merge if there aren't dimensions to merge.\n  if not ndim_at_least(x, num_dims):\n    return x\n\n  new_shape = (np.prod(x.shape[:num_dims]),) + x.shape[num_dims:]\n  return x.reshape(new_shape)\n\ndef split_leading_dim(x, to_dim):\n  new_shape = to_dim + x.shape[1:]\n  return x.reshape(new_shape)\n\nclass BatchApply:\n  r\"\"\"Temporarily merges leading dimensions of input tensors.\n\n  Merges the leading dimensions of a tensor into a single dimension, runs the\n  given callable, then splits the leading dimension of the result to match the\n  input.\n\n  Input arrays whose rank is smaller than the number of dimensions to collapse\n  are passed unmodified.\n\n  This may be useful for applying a module to each timestep of e.g. a\n  ``[Time, Batch, ...]`` array.\n\n  For some ``f``\\ s and platforms, this may be more efficient than\n  :func:`jax.vmap`, especially when combined with other transformations like\n  :func:`jax.grad`.\n\n  Example usage::\n\n    >>> import jax, jax.numpy as jnp\n\n    >>> a = jax.random.normal(jax.random.key(0), [2, 3, 4])\n    >>> b = jax.random.normal(jax.random.key(1), [4])\n\n    >>> def raises(a, b):\n    ...   if len(a.shape) != 2:\n    ...     raise ValueError(\"a must be shape 2\")\n    ...   if len(b.shape) != 1:\n    ...     raise ValueError(\"b must be shape 1\")\n    ...   return jnp.dot(a, b)\n\n    >>> out = BatchApply(raises)(a, b)\n    >>> expected_merged_leading = raises(a.reshape(2*3, 4), b)\n    >>> expected = expected_merged_leading.reshape((2, 3) + expected_merged_leading.shape[1:])\n    >>> np.testing.assert_array_equal(out, expected)\n  \"\"\"\n\n  def __init__(self, f, num_dims=2):\n    \"\"\"Constructs a :class:`BatchApply` module.\n\n    Args:\n      f: The callable to be applied to the reshaped array.\n      num_dims: The number of dimensions to merge.\n    \"\"\"\n    self._f = f\n    self.num_dims = num_dims\n\n  def __call__(self, *args, **kwargs):\n    example = arbitrary_mergeable_leaf(self.num_dims, args, kwargs)\n    if example is None:\n      raise ValueError(\n        'BatchApply requires at least one input with ndim >= '\n        f'{self.num_dims}.'\n      )\n\n    merge = lambda x: merge_leading_dims(x, self.num_dims)\n    split = lambda x: split_leading_dim(x, example.shape[:self.num_dims])\n    args = jax.tree_util.tree_map(merge, args)\n    kwargs = jax.tree_util.tree_map(merge, kwargs)\n    outputs = self._f(*args, **kwargs)\n    return jax.tree_util.tree_map(split, outputs)"
  },
  {
    "path": "flax/linen/combinators.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Combinators of modules, such as a Sequential.\"\"\"\n\nfrom typing import Any\nfrom collections.abc import Callable, Sequence\n\nfrom flax.linen.module import Module, compact\n\n\nclass Sequential(Module):\n  \"\"\"Applies a linear chain of Modules.\n\n  Meant to be used only for the simple case of fusing together callables where\n  the input of a particular module/op is the output of the previous one.\n\n  Modules will be applied in the order that they are passed in the constructor.\n\n  The ``__call__`` method of Sequential accepts any input and forwards it to the\n  first module it contains. It chains the output sequentially to the input of\n  the next module and returns the output of the final module.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n\n    >>> class Foo(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     return nn.Sequential([nn.Dense(4),\n    ...                           nn.relu,\n    ...                           nn.Dense(2),\n    ...                           nn.log_softmax])(x)\n\n  Since `Sequential.__call__` is a `compact` method, you can also pass functions\n  that construct Modules inline if you need shape inference::\n\n    module = nn.Sequential([\n        # << more layers\n        lambda x: SomeModule(x.shape[-1])(x), # shape inference\n        # << more layers\n    ])\n\n  This combinator supports also layers that return multiple outputs if returned\n  as a tuple or a dictionary. If the output of a layer is a ``tuple`` it will be\n  expanded as ``*args`` in the next layer, if its a ``dict`` it\n  will be expanded as ``**kwargs``.\n\n  Example usage::\n\n    >>> class CrossAttentionBlock(nn.Module):\n    ...   num_heads: int = 2\n    ...   qkv_features: int = 16\n    ...\n    ...   @nn.compact\n    ...   def __call__(self, query, key_value):\n    ...     output = nn.MultiHeadDotProductAttention(\n    ...       num_heads=self.num_heads, qkv_features=self.qkv_features)(query,\n    ...                                                                 key_value)\n    ...     output = nn.Dense(self.qkv_features)(output)\n    ...     return dict(query=output, key_value=key_value)  # also works for tuples\n\n    >>> from typing import Sequence\n    >>> class CrossAttentionNetwork(nn.Module):\n    ...   num_layers: Sequence[int]\n    ...\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     return nn.Sequential([CrossAttentionBlock() for _ in\n    ...                           range(self.num_layers)])(query, key_value)\n\n\n  Attributes:\n    layers: A sequence of callables to be applied in order.\n\n  Raises:\n    ValueError: If layers is not a sequence.\n  \"\"\"\n\n  layers: Sequence[Callable[..., Any]]\n\n  def __post_init__(self):\n    if not isinstance(self.layers, Sequence):\n      raise ValueError(\n        f\"'layers' must be a sequence, got '{type(self.layers).__name__}'.\"\n      )\n    super().__post_init__()\n\n  @compact\n  def __call__(self, *args, **kwargs):\n    if not self.layers:\n      raise ValueError(f'Empty Sequential module {self.name}.')\n\n    outputs = self.layers[0](*args, **kwargs)\n    for layer in self.layers[1:]:\n      if isinstance(outputs, tuple):\n        outputs = layer(*outputs)\n      elif isinstance(outputs, dict):\n        outputs = layer(**outputs)\n      else:\n        outputs = layer(outputs)\n    return outputs\n"
  },
  {
    "path": "flax/linen/dtypes.py",
    "content": "# Copyright 2022 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"APIs for handling dtypes in Linen Modules.\"\"\"\n\nfrom typing import Any, TypeVar\nfrom flax.typing import Dtype\nfrom jax import numpy as jnp\n\nT = TypeVar('T', bound=tuple)\n\ndef canonicalize_dtype(\n  *args, dtype: Dtype | None = None, inexact: bool = True\n) -> Dtype:\n  \"\"\"Canonicalize an optional dtype to the definitive dtype.\n\n  If the ``dtype`` is None this function will infer the dtype. If it is not\n  None it will be returned unmodified or an exceptions is raised if the dtype\n  is invalid.\n  from the input arguments using ``jnp.result_type``.\n\n  Args:\n    *args: JAX array compatible values. None values\n      are ignored.\n    dtype: Optional dtype override. If specified the arguments are cast to\n      the specified dtype instead and dtype inference is disabled.\n    inexact: When True, the output dtype must be a subdtype\n    of `jnp.inexact`. Inexact dtypes are real or complex floating points. This\n    is useful when you want to apply operations that don't work directly on\n    integers like taking a mean for example.\n  Returns:\n    The dtype that *args should be cast to.\n  \"\"\"\n  if dtype is None:\n    args_filtered = [jnp.asarray(x) for x in args if x is not None]\n    dtype = jnp.result_type(*args_filtered)\n    if inexact and not jnp.issubdtype(dtype, jnp.inexact):\n      dtype = jnp.promote_types(jnp.float32, dtype)\n  if inexact and not jnp.issubdtype(dtype, jnp.inexact):\n    raise ValueError(f'Dtype must be inexact: {dtype}')\n  return dtype\n\n\ndef promote_dtype(*args, dtype=None, inexact=True) -> list[Any]:\n  \"\"\" \"Promotes input arguments to a specified or inferred dtype.\n\n  All args are cast to the same dtype. See ``canonicalize_dtype`` for how\n  this dtype is determined.\n\n  The behavior of promote_dtype is mostly a convinience wrapper around\n  ``jax.numpy.promote_types``. The differences being that it automatically casts\n  all input to the inferred dtypes, allows inference to be overridden by a\n  forced dtype, and has an optional check to garantuee the resulting dtype is\n  inexact.\n\n  Args:\n    *args: JAX array compatible values. None values are returned as is.\n    dtype: Optional dtype override. If specified the arguments are cast to the\n      specified dtype instead and dtype inference is disabled.\n    inexact: When True, the output dtype must be a subdtype of `jnp.inexact`.\n      Inexact dtypes are real or complex floating points. This is useful when\n      you want to apply operations that don't work directly on integers like\n      taking a mean for example.\n\n  Returns:\n    The arguments cast to arrays of the same dtype.\n  \"\"\"\n  dtype = canonicalize_dtype(*args, dtype=dtype, inexact=inexact)\n  return [jnp.asarray(x, dtype) if x is not None else None for x in args]\n"
  },
  {
    "path": "flax/linen/experimental/layers_with_named_axes.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Experimental layers with named axes for the partitioning API.\"\"\"\nimport dataclasses\nfrom typing import Any\nfrom collections.abc import Callable, Iterable, Sequence\n\nimport jax.numpy as jnp\nfrom jax import lax\n\nfrom flax import linen as nn\nfrom flax.linen import initializers\nfrom flax.linen.partitioning import param_with_axes, with_sharding_constraint\nfrom flax.typing import (\n  Array,\n  Dtype,\n  Axes,\n  Initializer,\n  PrecisionLike,\n  DotGeneralT,\n)\n\n# Type annotations\nActivation = Callable[..., Array]\n\n\ndefault_kernel_init = initializers.lecun_normal()\ndefault_embed_init = initializers.variance_scaling(\n  1.0, 'fan_in', 'normal', out_axis=0\n)\n\n\nclass Dense(nn.Module):\n  \"\"\"A Dense layer with named axes for :meth:`jax.experimental.pjit.pjit`.\n\n  .. warning:: This class is hightly EXPERIMENTAL and the API is likely to\n      change. For regular (non-pjit) use, please use\n      :class:`flax.linen.linear.Dense`.\n\n  Attributes:\n    features: the number of output features.\n    use_bias: whether to add a bias to the output (default: True).\n    dtype: the dtype of the computation (default: float32).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    precision: numerical precision of the computation see `jax.lax.Precision`\n      for details.\n    kernel_init: initializer function for the weight matrix.\n    bias_init: initializer function for the bias.\n  \"\"\"\n\n  features: int\n  use_bias: bool = True\n  dtype: Dtype = jnp.float32\n  param_dtype: Dtype = jnp.float32\n  precision: PrecisionLike = None\n  kernel_init: Initializer = default_kernel_init\n  bias_init: Initializer = initializers.zeros_init()\n  kernel_axes: tuple[str, ...] = ()\n  # Deprecated. Will be removed.\n  dot_general: DotGeneralT | None = None\n  dot_general_cls: Any = None\n\n  @nn.compact\n  def __call__(self, inputs: Array) -> Array:\n    \"\"\"Applies a linear transformation to the inputs along the last dimension.\n\n    Args:\n      inputs: The nd-array to be transformed.\n\n    Returns:\n      The transformed input.\n    \"\"\"\n    inputs = jnp.asarray(inputs, self.dtype)\n    kernel = param_with_axes(\n      'kernel',\n      self.kernel_init,\n      (inputs.shape[-1], self.features),\n      self.param_dtype,\n      axes=self.kernel_axes,\n    )\n    kernel = jnp.asarray(kernel, self.dtype)\n\n    if self.dot_general_cls is not None:\n      dot_general = self.dot_general_cls()\n    elif self.dot_general is not None:\n      dot_general = self.dot_general\n    else:\n      dot_general = lax.dot_general\n    y = dot_general(\n      inputs,\n      kernel,\n      (((inputs.ndim - 1,), (0,)), ((), ())),\n      precision=self.precision,\n    )\n    if self.use_bias:\n      bias = param_with_axes(\n        'bias',\n        self.bias_init,\n        (self.features,),\n        self.param_dtype,\n        axes=(self.kernel_axes[-1],),\n      )\n      bias = jnp.asarray(bias, self.dtype)\n      y += jnp.reshape(bias, (1,) * (y.ndim - 1) + (-1,))\n    return y\n\n\nclass Embed(nn.Module):\n  \"\"\"An embedding layer with named axes for :meth:`jax.experimental.pjit.pjit`.\n\n  .. warning:: This class is hightly EXPERIMENTAL and the API is likely to\n      change. For regular (non-pjit) use, please use\n      :class:`flax.linen.linear.Embed`.\n\n  Attributes:\n    num_embeddings: number of embeddings.\n    features: number of feature dimensions for each embedding.\n    dtype: the dtype of the embedding vectors (default: float32).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    embedding_init: embedding initializer.\n    one_hot: performs the gather with a one-hot contraction rather than a true\n      gather. This is currently needed for SPMD partitioning.\n  \"\"\"\n\n  num_embeddings: int\n  features: int\n  cast_input_dtype: Dtype | None = None\n  dtype: Dtype = jnp.float32\n  param_dtype: Dtype = jnp.float32\n  attend_dtype: Dtype | None = None\n  embedding_init: Initializer = default_embed_init\n  one_hot: bool = False\n  embedding: Array = dataclasses.field(init=False)\n\n  def setup(self):\n    self.embedding = param_with_axes(\n      'embedding',\n      self.embedding_init,\n      (self.num_embeddings, self.features),\n      self.param_dtype,\n      axes=('vocab', 'embed'),\n    )\n\n  def __call__(self, inputs: Array) -> Array:\n    \"\"\"Embeds the inputs along the last dimension.\n\n    Args:\n      inputs: input data, all dimensions are considered batch dimensions.\n\n    Returns:\n      Output which is embedded input data.  The output shape follows the input,\n      with an additional `features` dimension appended.\n    \"\"\"\n    if self.cast_input_dtype:\n      inputs = inputs.astype(self.cast_input_dtype)\n    if not jnp.issubdtype(inputs.dtype, jnp.integer):\n      raise ValueError('Input type must be an integer or unsigned integer.')\n    if self.one_hot:\n      iota = lax.iota(jnp.int32, self.num_embeddings)\n      one_hot = jnp.array(inputs[..., jnp.newaxis] == iota, dtype=self.dtype)\n      output = jnp.dot(one_hot, jnp.asarray(self.embedding, self.dtype))\n    else:\n      output = jnp.asarray(self.embedding, self.dtype)[inputs]\n      output = with_sharding_constraint(output, ('batch', 'length', 'embed'))\n    return output\n\n  def attend(self, query: Array) -> Array:\n    \"\"\"Attend over the embedding using a query array.\n\n    Args:\n      query: array with last dimension equal the feature depth `features` of the\n        embedding.\n\n    Returns:\n      An array with final dim `num_embeddings` corresponding to the batched\n      inner-product of the array of query vectors against each embedding.\n      Commonly used for weight-sharing between embeddings and logit transform\n      in NLP models.\n    \"\"\"\n    dtype = self.attend_dtype if self.attend_dtype is not None else self.dtype\n    return jnp.dot(query, jnp.asarray(self.embedding, dtype).T)\n\n\ndef _canonicalize_axes(rank: int, axes: Axes) -> Sequence[int]:\n  \"\"\"Returns a tuple of deduplicated, sorted, and positive axes.\"\"\"\n  if not isinstance(axes, Iterable):\n    axes = (axes,)\n  return tuple({rank + axis if axis < 0 else axis for axis in axes})\n\n\ndef _abs_sq(x):\n  \"\"\"Computes the elementwise square of the absolute value |x|^2.\"\"\"\n  if jnp.iscomplexobj(x):\n    return lax.square(lax.real(x)) + lax.square(lax.imag(x))\n  else:\n    return lax.square(x)\n\n\ndef _compute_stats(x: Array, axes: Axes):\n  \"\"\"Computes mean and variance statistics.\n\n  This implementation takes care of a few important details:\n  - Computes in float32 precision for half precision inputs\n  -  mean and variance is computable in a single XLA fusion,\n    by using Var = E[|x|^2] - |E[x]|^2 instead of Var = E[|x - E[x]|^2]).\n  - Clips negative variances to zero which can happen due to\n    roundoff errors. This avoids downstream NaNs.\n  - Supports averaging across a parallel axis and subgroups of a parallel axis\n    with a single `lax.pmean` call to avoid latency.\n  \"\"\"\n  # promote x to at least float32, this avoids half precision computation\n  # but preserves double or complex floating points\n  x = jnp.asarray(x, jnp.promote_types(jnp.float32, jnp.result_type(x)))\n  mean = jnp.mean(x, axes)\n  mean2 = jnp.mean(_abs_sq(x), axes)\n  # mean2 - _abs_sq(mean) is not guaranteed to be non-negative due\n  # to floating point round-off errors.\n  var = jnp.maximum(0.0, mean2 - _abs_sq(mean))\n  return mean, var\n\n\ndef _normalize(\n  mdl: nn.Module,\n  x: Array,\n  mean: Array,\n  var: Array,\n  reduction_axes: Axes,\n  feature_axes: Axes,\n  dtype: Dtype,\n  param_dtype: Dtype,\n  epsilon: float,\n  use_bias: bool,\n  use_scale: bool,\n  bias_init: Initializer,\n  scale_init: Initializer,\n):\n  \"\"\" \"Normalizes the input of a normalization layer and optionally applies a learned scale and bias.\n\n  A separate bias and scale is learned for each feature as specified by\n  feature_axes.\n  \"\"\"\n  reduction_axes = _canonicalize_axes(x.ndim, reduction_axes)\n  feature_axes = _canonicalize_axes(x.ndim, feature_axes)\n  stats_shape = list(x.shape)\n  for axis in reduction_axes:\n    stats_shape[axis] = 1\n  mean = mean.reshape(stats_shape)\n  var = var.reshape(stats_shape)\n  feature_shape = [1] * x.ndim\n  reduced_feature_shape = []\n  for ax in feature_axes:\n    feature_shape[ax] = x.shape[ax]\n    reduced_feature_shape.append(x.shape[ax])\n  y = x - mean\n  mul = lax.rsqrt(var + epsilon)\n  if use_scale:\n    scale = mdl.param_with_axes(\n      'scale', scale_init, reduced_feature_shape, param_dtype, axes=('embed',)\n    ).reshape(feature_shape)\n    mul *= scale\n  y *= mul\n  if use_bias:\n    bias = mdl.param_with_axes(\n      'bias', bias_init, reduced_feature_shape, param_dtype, axes=('embed',)\n    ).reshape(feature_shape)\n    y += bias\n  return jnp.asarray(y, dtype)\n\n\nclass LayerNorm(nn.Module):\n  \"\"\"Layer normalization (https://arxiv.org/abs/1607.06450) with named axes for :meth:`jax.experimental.pjit.pjit`.\n\n  .. warning:: This class is hightly EXPERIMENTAL and the API is likely to\n      change. For regular (non-pjit) use, please use\n      :class:`flax.linen.normalization.LayerNorm`.\n\n  Operates on the last axis of the input data.\n\n  It normalizes the activations of the layer for each given example in a\n  batch independently, rather than across a batch like Batch Normalization.\n  i.e. applies a transformation that maintains the mean activation within\n  each example close to 0 and the activation standard deviation close to 1.\n\n  Attributes:\n    epsilon: A small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the computation (default: float32).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    use_bias:  If True, bias (beta) is added.\n    use_scale: If True, multiply by scale (gamma). When the next layer is linear\n      (also e.g. nn.relu), this can be disabled since the scaling will be done\n      by the next layer.\n    bias_init: Initializer for bias, by default, zero.\n    scale_init: Initializer for scale, by default, one.\n  \"\"\"\n\n  epsilon: float = 1e-6\n  dtype: Any = jnp.float32\n  param_dtype: Dtype = jnp.float32\n  use_bias: bool = True\n  use_scale: bool = True\n  bias_init: Initializer = initializers.zeros_init()\n  scale_init: Initializer = initializers.ones_init()\n\n  @nn.compact\n  def __call__(self, x):\n    \"\"\"Applies layer normalization on the input.\n\n    Args:\n      x: the inputs\n\n    Returns:\n      Normalized inputs (the same shape as inputs).\n    \"\"\"\n    reduction_axes = (-1,)\n    feature_axes = (-1,)\n\n    mean, var = _compute_stats(x, reduction_axes)\n\n    return _normalize(\n      self,\n      x,\n      mean,\n      var,\n      reduction_axes,\n      feature_axes,\n      self.dtype,\n      self.param_dtype,\n      self.epsilon,\n      self.use_bias,\n      self.use_scale,\n      self.bias_init,\n      self.scale_init,\n    )\n"
  },
  {
    "path": "flax/linen/fp8_ops.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport dataclasses\nimport itertools\nimport numpy as np\nimport warnings\nfrom functools import partial\n\nfrom typing import Any\nDType = Any\n\nimport jax\nfrom jax import custom_jvp, custom_vjp, lax, random\nfrom jax import numpy as jnp\nfrom jax._src import core\nfrom jax._src import dtypes\nfrom jax.typing import DTypeLike\n\ntry:\n  from jax._src import earray\n  from jax._src.interpreters import pxla\n  CAN_USE_EARRAY = True\nexcept (ModuleNotFoundError, ImportError):\n  CAN_USE_EARRAY = False\n\nfrom flax.linen import initializers, module\n\nOVERWRITE_WITH_GRADIENT = '_overwrite_with_gradient'\n\n# Define a custom dtype for FP8 meta params.\nclass Fp8MetaTyRules:\n  # tell JAX how to lower this dtype to an HLO dtype\n  @staticmethod\n  def physical_element_aval(dtype) -> core.ShapedArray:\n    return core.ShapedArray((), dtype.float_dtype)\n\n  if jax.__version_info__ < (0, 4, 29):\n    @staticmethod\n    def replicate_trailing_dims(ctx, val, aval):\n      del ctx, aval\n      return val\n\n    @staticmethod\n    def logical_sharding(aval, phys_sharding):\n      return phys_sharding\n\n    @staticmethod\n    def physical_sharding(aval, sharding):\n      return sharding  # unlike KeyTyRules, assume same shape\n\n  # allow conversions to and from the corresponding float type\n  @staticmethod\n  def convert_from(fp8_meta_dtype, other_dtype) -> bool:\n    return fp8_meta_dtype.float_dtype == other_dtype\n\n  @staticmethod\n  def convert_to(other_dtype, fp8_meta_dtype) -> bool:\n    return fp8_meta_dtype.float_dtype == other_dtype\n\n  # define how autodiff should accumulate these values\n  @staticmethod\n  def add(dt, x, y):\n    from_fp8_meta = partial(lax.convert_element_type, new_dtype=dt.float_dtype)\n    to_fp8_meta = partial(lax.convert_element_type, new_dtype=dt)\n    return to_fp8_meta(lax.max(from_fp8_meta(x), from_fp8_meta(y)))\n\n  @staticmethod\n  def zero(dt):\n    neginf = np.array(-np.inf if dtypes.supports_inf(dt.float_dtype)\n                      else dtypes.finfo(dt.float_dtype).min, dt.float_dtype)\n    return lax.convert_element_type(neginf, dt)\n\n  @staticmethod\n  def tangent_dtype(dtype):\n    return dtype\n\n  @staticmethod\n  def full(shape, fill_value, dtype):\n    fill_value = lax.convert_element_type(fill_value, dtype.float_dtype)\n    out_raw = lax.full(shape, fill_value, dtype.float_dtype)\n    return lax.convert_element_type(out_raw, dtype)\n\n  @staticmethod\n  def global_sharded_result_handler(aval, out_sharding, committed):\n    if not CAN_USE_EARRAY:\n      raise NotImplementedError(\"convert back under the jit\")\n\n    phys_sharding = out_sharding  # unlike KeyTyRules, assume same shape\n    phys_aval = core.physical_aval(aval)\n    phys_handler_maker = pxla.global_result_handlers[core.ShapedArray]\n    phys_handler = phys_handler_maker(phys_aval, phys_sharding, committed)\n    return lambda bufs: earray.EArray(aval, phys_handler(bufs))\n\n\n# class to use as second argument to jax.dtypes.issubdtype\nclass fp8_meta_dtype(dtypes.extended): pass\n\n# parameterized datatype for use in e.g. lax.convert_element_type\n@dataclasses.dataclass(frozen=True)\nclass fp8_meta_dtype_wrapper(dtypes.ExtendedDType):\n  float_dtype: dtypes.DType\n  _rules: type = Fp8MetaTyRules\n  type: type = fp8_meta_dtype\n\n  def __repr__(self) -> str:\n    nbits = dtypes.finfo(self.float_dtype).bits\n    return f'fp8_meta{nbits}'\n  name = property(__repr__)\n\nfm32 = fp8_meta_dtype_wrapper(jnp.float32)\nfp32_max_grad = fp8_meta_dtype_wrapper(jnp.float32)\n\ndef get_fp8_max(fp8_dtype, out_dtype):\n  assert fp8_dtype in (jnp.float8_e4m3fn, jnp.float8_e5m2,\n                       jnp.float8_e4m3fnuz, jnp.float8_e5m2fnuz)\n  return jnp.finfo(fp8_dtype).max.astype(out_dtype)\n\ndef quantize(x, q_dtype, scale, compute_dtype):\n  # Explicitly cast the max values to the compute dtype to avoid unnecessary\n  # casting to FP32 during the subsequent math operations.\"\n  dtype_max = get_fp8_max(q_dtype, compute_dtype)\n  scaled_x = x / jnp.broadcast_to(scale.astype(compute_dtype), x.shape)\n  clipped_x = jnp.clip(scaled_x, -dtype_max, dtype_max)\n  return clipped_x.astype(q_dtype)\n\n\ndef dequantize(x, dq_dtype, scale):\n  return x.astype(dq_dtype) * jnp.broadcast_to(scale.astype(dq_dtype), x.shape)\n\ndef qdq(x, q_dtype, scale, compute_dtype):\n  qx = quantize(x, q_dtype, scale, compute_dtype)\n  return dequantize(qx, x.dtype, scale)\n\n\ndef compute_scale(amax, scale, fp8_max, margin=0):\n  # The algorithm for computing the new scale is sourced from\n  #   https://docs.nvidia.com/deeplearning/transformer-engine/user-guide/api/jax.html#transformer_engine.jax.update_fp8_metas\n  # wherein the `original_scale` corresponds to the reciprocal of the `scale`\n  # passed in this function.\n  scale = 1.0 / scale\n\n  sf = (fp8_max / amax) / (2**margin)\n  sf = jnp.where(amax > 0.0, sf, scale)\n  sf = jnp.where(jnp.isfinite(amax), sf, scale)\n\n  return 1.0 / sf\n\n\ndef compute_amax_history(x, amax_history):\n  amax_update = jnp.max(jnp.abs(x)).astype(amax_history.dtype)\n  new_history = jnp.roll(amax_history, shift=-1, axis=0).at[0].set(amax_update)\n  return new_history\n\n\ndef update_fp8_meta(\n  x, q_dtype, scale, amax_history\n):\n  is_fmax32 = (scale.dtype == fm32 and amax_history.dtype == fm32)\n  # convert fm32->f32 so we can do math\n  if is_fmax32:\n    amax_history = lax.convert_element_type(amax_history, jnp.float32)\n    scale = lax.convert_element_type(scale, jnp.float32)\n\n  # Update the fp8 meta\n  dtype_max = get_fp8_max(q_dtype, jnp.float32)\n  amax_from_history = jnp.max(amax_history, axis=0)\n\n  new_scale = compute_scale(amax_from_history, scale, dtype_max)\n  new_history = compute_amax_history(x, amax_history)\n\n  if is_fmax32:\n    new_history = lax.convert_element_type(new_history, fp32_max_grad)\n    new_scale = lax.convert_element_type(new_scale, fp32_max_grad)\n  return new_scale, new_history\n\ndef quantize_dequantize_update(x, q_dtype, scale, amax_history, compute_dtype):\n  updated_scale, updated_history = update_fp8_meta(x, q_dtype, scale, amax_history)\n  qdq_x = qdq(x, q_dtype, _fm32_to_float32(updated_scale), compute_dtype)\n  return qdq_x, updated_scale, updated_history\n\ndef _fm32_to_float32(value):\n  if value.dtype == fm32:\n    return lax.convert_element_type(value, jnp.float32)\n  return value\n\ndef dot_general_transpose_lhs(g, x, y, *, dimension_numbers, precision,\n                              preferred_element_type: DTypeLike | None,\n                              swap_ans=False):\n  def _remaining(original, *removed_lists):\n    removed = set(itertools.chain(*removed_lists))\n    return [i for i in original if i not in removed]\n\n  def _ranges_like(*xs):\n    start = 0\n    for x in xs:\n      x_len = len(x)\n      yield range(start, start + x_len)\n      start += x_len\n\n  (x_contract, y_contract), (x_batch, y_batch) = dimension_numbers\n  x_ndim = x.aval.ndim\n  x_kept = _remaining(range(x_ndim), x_contract, x_batch)\n  y_kept = _remaining(range(np.ndim(y)), y_contract, y_batch)\n  if swap_ans:\n    ans_batch, ans_y, _ = _ranges_like(x_batch, y_kept, x_kept)\n  else:\n    ans_batch, _, ans_y = _ranges_like(x_batch, x_kept, y_kept)\n  dims = ((ans_y, y_kept), (ans_batch, y_batch))\n  x_contract_sorted_by_y = list(np.take(x_contract, np.argsort(y_contract)))\n  out_axes = np.argsort(list(x_batch) + x_kept + x_contract_sorted_by_y)\n  x_bar = lax.transpose(\n    lax.dot_general(\n      g, y, dims, precision=precision,\n      preferred_element_type=preferred_element_type\n    ),\n    tuple(out_axes)\n  )\n  return x_bar\n\ndef dot_general_transpose_rhs(g, x, y, *, dimension_numbers, precision,\n                              preferred_element_type: DTypeLike | None):\n  (x_contract, y_contract), (x_batch, y_batch) = dimension_numbers\n  swapped_dimension_numbers = ((y_contract, x_contract), (y_batch, x_batch))\n  y_bar = dot_general_transpose_lhs(\n    g, y, x, dimension_numbers=swapped_dimension_numbers, precision=precision,\n    preferred_element_type=preferred_element_type,\n    swap_ans=True)\n  return y_bar\n\n@partial(custom_vjp, nondiff_argnums=(0, 1))\ndef in_qdq(compute_dtype, q_dtype, inp, scale, amax_history):\n  qin, _, _ = quantize_dequantize_update(\n    inp, q_dtype, scale, amax_history, compute_dtype\n  )\n  return qin\n\n\ndef in_qdq_fwd(compute_dtype, q_dtype, inp, scale, amax_history):\n  qin, new_scale, new_history = quantize_dequantize_update(\n    inp, q_dtype, scale, amax_history, compute_dtype\n  )\n  return qin, (new_scale, new_history)\n\n\ndef in_qdq_bwd(compute_dtype, q_dtype, res, g):\n  new_scale, new_history = res\n  q_g = g\n  return q_g, new_scale, new_history\n\n\nin_qdq.defvjp(in_qdq_fwd, in_qdq_bwd)\n\n\n@partial(custom_vjp, nondiff_argnums=(0, 1))\ndef out_qdq(compute_dtype, q_dtype, out, scale, amax_history):\n  return out\n\n\ndef out_qdq_fwd(compute_dtype, q_dtype, out, scale, amax_history):\n  return out, (scale, amax_history)\n\n\ndef out_qdq_bwd(compute_dtype, q_dtype, res, g):\n  scale, amax_history = res\n  q_g, new_scale, new_history = quantize_dequantize_update(\n    g, q_dtype, scale, amax_history, compute_dtype\n  )\n  return q_g, new_scale, new_history\n\n\nout_qdq.defvjp(out_qdq_fwd, out_qdq_bwd)\n\n\n@partial(custom_vjp, nondiff_argnums=(0, 1))\ndef in_q(compute_dtype, q_dtype, inp, scale, amax_history):\n  new_scale, _ = update_fp8_meta(inp, q_dtype, scale, amax_history)\n  qin = quantize(inp, q_dtype, _fm32_to_float32(new_scale), compute_dtype)\n  return qin, new_scale\n\ndef in_q_fwd(compute_dtype, q_dtype, inp, scale, amax_history):\n  new_scale, new_history = update_fp8_meta(inp, q_dtype, scale, amax_history)\n  qin = quantize(inp, q_dtype, _fm32_to_float32(new_scale), compute_dtype)\n  return (qin, new_scale), (new_scale, new_history)\n\ndef in_q_bwd(compute_dtype, q_dtype, res, _):\n  new_scale, new_history = res\n  # We don't compute gradients for inp, scale and amax_history, but we pass through scale and history\n  return None, new_scale, new_history\n\nin_q.defvjp(in_q_fwd, in_q_bwd)\n\n\n@partial(custom_vjp, nondiff_argnums=(0, ))\ndef out_dq(dq_type, lhs_scale, rhs_scale, out):\n  q_out = dequantize(\n    out,\n    dq_type,\n    _fm32_to_float32(lhs_scale) * _fm32_to_float32(rhs_scale)\n  )\n  return q_out\n\ndef out_dq_fwd(dq_type, lhs_scale, rhs_scale, out):\n  return out_dq(dq_type, lhs_scale, rhs_scale, out), None\n\ndef out_dq_bwd(dq_type, _, g):\n  return None, None, g\n\nout_dq.defvjp(out_dq_fwd, out_dq_bwd)\n\n@partial(custom_vjp, nondiff_argnums=(8, 9))\ndef quantized_dot(\n    lhs,\n    q_lhs,\n    lhs_scale,  # scale for this step\n    rhs,\n    q_rhs,\n    rhs_scale,  # scale for this step\n    out_grad_scale, # scale from previous step\n    out_grad_amax_history, # amax history from previous step\n    dimension_numbers,\n    preferred_element_type=None\n):\n  return lax.dot_general(\n      q_lhs,\n      q_rhs,\n      dimension_numbers,\n      preferred_element_type=preferred_element_type,\n      precision=lax.Precision.DEFAULT,\n  )\n\ndef quantized_dot_fwd(\n    lhs,\n    q_lhs,\n    lhs_scale,\n    rhs,\n    q_rhs,\n    rhs_scale,\n    out_grad_scale,\n    out_grad_amax_history,\n    dimension_numbers,\n    preferred_element_type,\n):\n  out = lax.dot_general(\n      q_lhs,\n      q_rhs,\n      dimension_numbers,\n      preferred_element_type=preferred_element_type,\n      precision=lax.Precision.DEFAULT,\n  )\n  res = (\n      lhs,\n      q_lhs,\n      lhs_scale,\n      rhs,\n      q_rhs,\n      rhs_scale,\n      out_grad_scale,\n      out_grad_amax_history,\n  )\n  return out, res\n\ndef quantized_dot_bwd(\n    dimension_numbers,\n    preferred_element_type,\n    res,\n    g\n):\n  (\n      lhs,\n      q_lhs,\n      lhs_scale,\n      rhs,\n      q_rhs,\n      rhs_scale,\n      out_grad_scale,\n      out_grad_amax_history,\n  ) = res\n\n  new_out_grad_scale, new_out_grad_amax_history = update_fp8_meta(\n      g,\n      jnp.float8_e5m2,\n      out_grad_scale,\n      out_grad_amax_history,\n  )\n\n  q_g = quantize(\n      g,\n      jnp.float8_e5m2,\n      _fm32_to_float32(new_out_grad_scale),\n      preferred_element_type\n  )\n\n  grad_lhs = dot_general_transpose_lhs(\n      q_g,\n      lhs,\n      q_rhs,\n      dimension_numbers=dimension_numbers,\n      precision=lax.Precision.HIGHEST,\n      preferred_element_type=preferred_element_type,\n  )\n\n  grad_lhs = dequantize(\n      grad_lhs,\n      preferred_element_type,\n      _fm32_to_float32(rhs_scale) * _fm32_to_float32(new_out_grad_scale)\n  )\n\n  grad_rhs = dot_general_transpose_rhs(\n      q_g,\n      q_lhs,\n      rhs,\n      dimension_numbers=dimension_numbers,\n      precision=lax.Precision.HIGHEST,\n      preferred_element_type=preferred_element_type,\n  )\n\n  grad_rhs = dequantize(\n      grad_rhs,\n      preferred_element_type,\n      _fm32_to_float32(lhs_scale) * _fm32_to_float32(new_out_grad_scale)\n  )\n\n  return (\n      grad_lhs,\n      None,\n      None,\n      grad_rhs,\n      None,\n      None,\n      new_out_grad_scale,\n      new_out_grad_amax_history,\n  )\n\nquantized_dot.defvjp(quantized_dot_fwd, quantized_dot_bwd)\n\n# Wrapper function to achieve the same effect as the dot_general function\n# but with fp8 quantization and dequantization.\ndef fp8_scaled_dot_general(\n    lhs,\n    rhs,\n    dimension_numbers,\n    precision=None,\n    preferred_element_type=None,\n    *,\n    lhs_scale=None,\n    rhs_scale=None,\n    grad_scale=None,\n    lhs_amax_history=None,\n    rhs_amax_history=None,\n    grad_amax_history=None,\n    quantize_compute_type=jnp.float32,\n):\n  if precision != None:\n    warnings.warn(\n      'The function fp8_scaled_dot_general will set the \"precision\" and '\n      'disregard any provided \"precision\" argument.'\n    )\n  q_lhs, new_lhs_scale = in_q(\n      quantize_compute_type, jnp.float8_e4m3fn, lhs, lhs_scale, lhs_amax_history\n  )\n  q_rhs, new_rhs_scale = in_q(\n      quantize_compute_type, jnp.float8_e4m3fn, rhs, rhs_scale, rhs_amax_history\n  )\n  y = quantized_dot(\n      lhs,\n      q_lhs,\n      new_lhs_scale,\n      rhs,\n      q_rhs,\n      new_rhs_scale,\n      grad_scale,\n      grad_amax_history,\n      dimension_numbers,\n      preferred_element_type\n  )\n  y = out_dq(\n      dq_type=preferred_element_type,\n      lhs_scale=new_lhs_scale,\n      rhs_scale=new_rhs_scale,\n      out=y\n  )\n  return y  # type: ignore\n\n@partial(custom_jvp, nondiff_argnums=(2, 3, 4))\ndef dot_general_with_precision(\n  lhs, rhs, dimension_numbers, precision=None, preferred_element_type=None\n):\n  if precision != None or preferred_element_type != None:\n    warnings.warn(\n      'The function dot_general_with_precision will set the '\n      'precision/preferred_element_type and disregard any provided '\n      'values.'\n    )\n  return lax.dot_general(\n    lhs, rhs, dimension_numbers, precision=lax.Precision.DEFAULT\n  )\n\n\n@dot_general_with_precision.defjvp\ndef dot_general_with_precision_jvp(\n  dimension_numbers, precision, preferred_element_type, primals, tangents\n):\n  lhs, rhs = primals\n  lhs_dot, rhs_dot = tangents\n\n  out = lax.dot_general(\n    lhs, rhs, dimension_numbers, precision=lax.Precision.DEFAULT\n  )\n  grad_out = lax.dot_general(\n    lhs_dot, rhs, dimension_numbers, precision=lax.Precision.HIGHEST\n  ) + lax.dot_general(\n    lhs, rhs_dot, dimension_numbers, precision=lax.Precision.HIGHEST\n  )\n  return out, grad_out\n\n\ndef _parse_dot_inputs(*args, **kwargs):\n  assert len(args) == 3\n  x = args[0]\n  k = args[1]\n  dimension_numbers = args[2]\n\n  # Use the `k.dtype` since it aligns with the `dtype` of its layers,\n  # namely, the computation data type.\n  comp_dtype = k.dtype\n  x = jnp.asarray(x, comp_dtype)\n  return x, k, dimension_numbers, comp_dtype\n\n\nclass Fp8DotGeneralBase(module.Module):\n  amax_history_length: int = 1024\n  e4m3_dtype: DType = jnp.float8_e4m3fn\n  e5m2_dtype: DType = jnp.float8_e5m2\n\n  def setup(self) -> None:\n    scale_args = (\n      initializers.ones_init(),\n      random.PRNGKey(0),\n      (1,),\n      jnp.float32,\n    )\n    amax_history_args = (\n      initializers.zeros_init(),\n      random.PRNGKey(0),\n      (self.amax_history_length,),\n      jnp.float32,\n    )\n\n    self.input_amax_history = self.variable(\n      OVERWRITE_WITH_GRADIENT, 'input_amax_history', *amax_history_args\n    )\n    self.kernel_amax_history = self.variable(\n      OVERWRITE_WITH_GRADIENT, 'kernel_amax_history', *amax_history_args\n    )\n    self.output_grad_amax_history = self.variable(\n      OVERWRITE_WITH_GRADIENT, 'output_grad_amax_history', *amax_history_args\n    )\n\n    self.input_scale = self.variable(\n      OVERWRITE_WITH_GRADIENT, 'input_scale', *scale_args\n    )\n    self.kernel_scale = self.variable(\n      OVERWRITE_WITH_GRADIENT, 'kernel_scale', *scale_args\n    )\n    self.output_grad_scale = self.variable(\n      OVERWRITE_WITH_GRADIENT, 'output_grad_scale', *scale_args\n    )\n\n\nclass Fp8DotGeneralOp(Fp8DotGeneralBase):\n  def __post_init__(self):\n    super().__post_init__()\n    if type(self) is Fp8DotGeneralOp:\n      warnings.warn(\n        'The Fp8DotGeneralOp is deprecated. Use Fp8DirectDotGeneralOp or '\n        'Fp8Einsum instead.',\n        DeprecationWarning,\n      )\n\n  def __call__(self, *args, **kwargs):\n    x, k, dimension_numbers, comp_dtype = _parse_dot_inputs(\n      *args, **kwargs\n    )\n    x_qdq = in_qdq(\n      comp_dtype, self.e4m3_dtype, x, self.input_scale.value, self.input_amax_history.value\n    )\n    k_qdq = in_qdq(\n      comp_dtype, self.e4m3_dtype, k, self.kernel_scale.value, self.kernel_amax_history.value\n    )\n\n    y_qdq = dot_general_with_precision(x_qdq, k_qdq, dimension_numbers)  # type: ignore\n    y = out_qdq(\n      comp_dtype,\n      self.e5m2_dtype,\n      y_qdq,\n      self.output_grad_scale.value,\n      self.output_grad_amax_history.value,\n    )\n\n    return y  # type: ignore\n\nclass Fp8DirectDotGeneralOp(Fp8DotGeneralBase):\n  def __call__(self, *args, **kwargs):\n    x, k, dimension_numbers, comp_dtype = _parse_dot_inputs(\n      *args, **kwargs\n    )\n\n    y = fp8_scaled_dot_general(\n      x,\n      k,\n      dimension_numbers,\n      precision=None,\n      preferred_element_type=x.dtype,\n      lhs_scale=self.input_scale.value,\n      rhs_scale=self.kernel_scale.value,\n      grad_scale=self.output_grad_scale.value,\n      lhs_amax_history=self.input_amax_history.value,\n      rhs_amax_history=self.kernel_amax_history.value,\n      grad_amax_history=self.output_grad_amax_history.value,\n      quantize_compute_type=comp_dtype,\n    )\n\n    return y  # type: ignore\n\nclass NANOOFp8DotGeneralOp(Fp8DotGeneralOp):\n  e4m3_dtype: DType = jnp.float8_e4m3fnuz\n  e5m2_dtype: DType = jnp.float8_e5m2fnuz\n\nclass Fp8Einsum(Fp8DotGeneralBase):\n\n  def __call__(self, eqn, lhs: jnp.ndarray, rhs: jnp.ndarray,\n               precision: lax.Precision | None = None,\n               preferred_element_type: DTypeLike | None = None) -> jnp.ndarray:\n    # Here we assume that the rhs is the weight and its dtype is the actual compute dtype (not storage dtype).\n    # TODO(kaixih@nvidia): Better way to handle this?\n    actual_compute_dtype = rhs.dtype\n    lhs = lhs.astype(actual_compute_dtype)\n\n    dot_general_fn = partial(\n        fp8_scaled_dot_general,\n        lhs_scale=self.input_scale.value,\n        rhs_scale=self.kernel_scale.value,\n        grad_scale=self.output_grad_scale.value,\n        lhs_amax_history=self.input_amax_history.value,\n        rhs_amax_history=self.kernel_amax_history.value,\n        grad_amax_history=self.output_grad_amax_history.value,\n        quantize_compute_type=actual_compute_dtype\n    )\n    out = jnp.einsum(eqn, lhs, rhs, precision=precision,\n                     preferred_element_type=preferred_element_type,\n                     _dot_general=dot_general_fn)\n    return out\n\n# Alias for backward compatibility\nFp8DotGeneral = Fp8DirectDotGeneralOp\n"
  },
  {
    "path": "flax/linen/initializers.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Initializers for Flax.\"\"\"\n\n# pylint: disable=unused-import\n# re-export initializer functions from jax.nn\nfrom jax.nn.initializers import constant as constant\nfrom jax.nn.initializers import delta_orthogonal as delta_orthogonal\nfrom jax.nn.initializers import glorot_normal as glorot_normal\nfrom jax.nn.initializers import glorot_uniform as glorot_uniform\nfrom jax.nn.initializers import he_normal as he_normal\nfrom jax.nn.initializers import he_uniform as he_uniform\nfrom jax.nn.initializers import kaiming_normal as kaiming_normal\nfrom jax.nn.initializers import kaiming_uniform as kaiming_uniform\nfrom jax.nn.initializers import lecun_normal as lecun_normal\nfrom jax.nn.initializers import lecun_uniform as lecun_uniform\nfrom jax.nn.initializers import normal as normal\nfrom jax.nn.initializers import ones as ones\nfrom jax.nn.initializers import orthogonal as orthogonal\nfrom jax.nn.initializers import truncated_normal as truncated_normal\nfrom jax.nn.initializers import uniform as uniform\nfrom jax.nn.initializers import variance_scaling as variance_scaling\nfrom jax.nn.initializers import xavier_normal as xavier_normal\nfrom jax.nn.initializers import xavier_uniform as xavier_uniform\nfrom jax.nn.initializers import zeros as zeros\nfrom flax.typing import Initializer as Initializer\n\n# pylint: enable=unused-import\n\n\ndef zeros_init() -> Initializer:\n  \"\"\"Builds an initializer that returns a constant array full of zeros.\n\n  >>> import jax, jax.numpy as jnp\n  >>> from flax.linen.initializers import zeros_init\n  >>> zeros_initializer = zeros_init()\n  >>> zeros_initializer(jax.random.key(42), (2, 3), jnp.float32)\n  Array([[0., 0., 0.],\n         [0., 0., 0.]], dtype=float32)\n  \"\"\"\n  return zeros\n\n\ndef ones_init() -> Initializer:\n  \"\"\"Builds an initializer that returns a constant array full of ones.\n\n  >>> import jax, jax.numpy as jnp\n  >>> from flax.linen.initializers import ones_init\n  >>> ones_initializer = ones_init()\n  >>> ones_initializer(jax.random.key(42), (3, 2), jnp.float32)\n  Array([[1., 1.],\n         [1., 1.],\n         [1., 1.]], dtype=float32)\n  \"\"\"\n  return ones\n"
  },
  {
    "path": "flax/linen/kw_only_dataclasses.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Support for keyword-only fields in dataclasses for Python versions <3.10.\n\nThis module provides wrappers for `dataclasses.dataclass` and\n`dataclasses.field` that simulate support for keyword-only fields for Python\nversions before 3.10 (which is the version where dataclasses added keyword-only\nfield support).  If this module is imported in Python 3.10+, then\n`kw_only_dataclasses.dataclass` and `kw_only_dataclasses.field` will simply be\naliases for `dataclasses.dataclass` and `dataclasses.field`.\n\nFor earlier Python versions, when constructing a dataclass, any fields that have\nbeen marked as keyword-only (including inherited fields) will be moved to the\nend of the constructor's argument list. This makes it possible to have a base\nclass that defines a field with a default, and a subclass that defines a field\nwithout a default. E.g.:\n\n>>> from flax.linen import kw_only_dataclasses\n>>> @kw_only_dataclasses.dataclass\n... class Parent:\n...   name: str = kw_only_dataclasses.field(default='', kw_only=True)\n\n>>> @kw_only_dataclasses.dataclass\n... class Child(Parent):\n...   size: float  # required.\n\n>>> import inspect\n>>> print(inspect.signature(Child.__init__))\n(self, size: float, name: str = '') -> None\n\n\n(If we used `dataclasses` rather than `kw_only_dataclasses` for the above\nexample, then it would have failed with TypeError \"non-default argument\n'size' follows default argument.\")\n\nWARNING: fields marked as keyword-only will not *actually* be turned into\nkeyword-only parameters in the constructor; they will only be moved to the\nend of the parameter list (after all non-keyword-only parameters).\n\"\"\"\n\nimport dataclasses\nimport functools\nimport inspect\nimport sys\nfrom types import MappingProxyType\nfrom typing import Any, TypeVar\n\nimport typing_extensions as tpe\n\nimport flax\n\nM = TypeVar('M', bound='flax.linen.Module')\nFieldName = str\nAnnotation = Any\nDefault = Any\n\n\nclass _KwOnlyType:\n  \"\"\"Metadata tag used to tag keyword-only fields.\"\"\"\n\n  def __repr__(self):\n    return 'KW_ONLY'\n\n\nKW_ONLY = _KwOnlyType()\n\n\ndef field(*, metadata=None, kw_only=dataclasses.MISSING, **kwargs):\n  \"\"\"Wrapper for dataclasses.field that adds support for kw_only fields.\n\n  Args:\n    metadata: A mapping or None, containing metadata for the field.\n    kw_only: If true, the field will be moved to the end of `__init__`'s\n      parameter list.\n    **kwargs: Keyword arguments forwarded to `dataclasses.field`\n\n  Returns:\n    A `dataclasses.Field` object.\n  \"\"\"\n  if kw_only is not dataclasses.MISSING and kw_only:\n    if (\n      kwargs.get('default', dataclasses.MISSING) is dataclasses.MISSING\n      and kwargs.get('default_factory', dataclasses.MISSING)\n      is dataclasses.MISSING\n    ):\n      raise ValueError('Keyword-only fields with no default are not supported.')\n    if metadata is None:\n      metadata = {}\n    metadata[KW_ONLY] = True\n  return dataclasses.field(metadata=metadata, **kwargs)\n\n\n@tpe.dataclass_transform(field_specifiers=(field,))  # type: ignore[literal-required]\ndef dataclass(cls=None, extra_fields=None, **kwargs):\n  \"\"\"Wrapper for dataclasses.dataclass that adds support for kw_only fields.\n\n  Args:\n    cls: The class to transform (or none to return a decorator).\n    extra_fields: A list of `(name, type, Field)` tuples describing extra fields\n      that should be added to the dataclass.  This is necessary for linen's\n      use-case of this module, since the base class (linen.Module) is *not* a\n      dataclass.  In particular, linen.Module class is used as the base for both\n      frozen and non-frozen dataclass subclasses; but the frozen status of a\n      dataclass must match the frozen status of any base dataclasses.\n    **kwargs: Additional arguments for `dataclasses.dataclass`.\n\n  Returns:\n    `cls`.\n  \"\"\"\n\n  def wrap(cls):\n    return _process_class(cls, extra_fields=extra_fields, **kwargs)\n\n  return wrap if cls is None else wrap(cls)\n\n\ndef _process_class(cls: type[M], extra_fields=None, **kwargs):\n  \"\"\"Transforms `cls` into a dataclass that supports kw_only fields.\"\"\"\n  if sys.version_info < (3, 14) and '__annotations__' not in cls.__dict__:\n    cls.__annotations__ = {}\n\n  # The original __dataclass_fields__ dicts for all base classes.  We will\n  # modify these in-place before turning `cls` into a dataclass, and then\n  # restore them to their original values.\n  base_dataclass_fields = {}  # dict[cls, cls.__dataclass_fields__.copy()]\n\n  # The keyword only fields from `cls` or any of its base classes.\n  kw_only_fields: dict[FieldName, tuple[Annotation, Default]] = {}\n\n  # Scan for KW_ONLY marker.\n  kw_only_name = None\n  for name, annotation in cls.__annotations__.items():\n    if annotation is KW_ONLY:\n      if kw_only_name is not None:\n        raise TypeError('Multiple KW_ONLY markers')\n      kw_only_name = name\n    elif kw_only_name is not None:\n      if not hasattr(cls, name):\n        raise ValueError(\n          'Keyword-only fields with no default are not supported.'\n        )\n      default = getattr(cls, name)\n      if isinstance(default, dataclasses.Field):\n        default.metadata = MappingProxyType({**default.metadata, KW_ONLY: True})\n      else:\n        default = field(default=default, kw_only=True)\n      setattr(cls, name, default)\n  if kw_only_name:\n    del cls.__annotations__[kw_only_name]\n\n  # Inject extra fields.\n  if extra_fields:\n    for name, annotation, default in extra_fields:\n      if not (isinstance(name, str) and isinstance(default, dataclasses.Field)):\n        raise ValueError(\n          'Expected extra_fields to a be a list of '\n          '(name, type, Field) tuples.'\n        )\n      setattr(cls, name, default)\n      cls.__annotations__[name] = annotation\n\n  # Extract kw_only fields from base classes' __dataclass_fields__.\n  for base in reversed(cls.__mro__[1:]):\n    if not dataclasses.is_dataclass(base):\n      continue\n    if sys.version_info < (3, 14):\n      base_annotations = base.__dict__.get('__annotations__', {})\n    else:\n      base_annotations = inspect.get_annotations(base)\n\n    base_dataclass_fields[base] = dict(\n      getattr(base, '__dataclass_fields__', {})\n    )\n    for base_field in list(dataclasses.fields(base)):\n      field_name = base_field.name\n      if base_field.metadata.get(KW_ONLY) or field_name in kw_only_fields:\n        kw_only_fields[field_name] = (\n          base_annotations.get(field_name),\n          base_field,\n        )\n        del base.__dataclass_fields__[field_name]\n\n  # Remove any keyword-only fields from this class.\n  if sys.version_info < (3, 14):\n    cls_annotations = cls.__dict__['__annotations__']\n  else:\n    cls_annotations = cls.__annotations__\n  for name, annotation in list(cls_annotations.items()):\n    value = getattr(cls, name, None)\n    if (\n      isinstance(value, dataclasses.Field) and value.metadata.get(KW_ONLY)\n    ) or name in kw_only_fields:\n      del cls_annotations[name]\n      kw_only_fields[name] = (annotation, value)\n\n  # Add keyword-only fields at the end of __annotations__, in the order they\n  # were found in the base classes and in this class.\n  for name, (annotation, default) in kw_only_fields.items():\n    setattr(cls, name, default)\n    cls_annotations.pop(name, None)\n    cls_annotations[name] = annotation\n\n  create_init = '__init__' not in vars(cls) and kwargs.get('init', True)\n\n  # Apply the dataclass transform.\n  transformed_cls: type[M] = dataclasses.dataclass(cls, **kwargs)\n\n  # Restore the base classes' __dataclass_fields__.\n  for _cls, fields in base_dataclass_fields.items():\n    _cls.__dataclass_fields__ = fields\n\n  if create_init:\n    dataclass_init = transformed_cls.__init__\n    # use sum to count the number of init fields that are not keyword-only\n    expected_num_args = sum(\n      f.init and not f.metadata.get(KW_ONLY, False)\n      for f in dataclasses.fields(transformed_cls)\n    )\n\n    @functools.wraps(dataclass_init)\n    def init_wrapper(self, *args, **kwargs):\n      num_args = len(args)\n      if num_args > expected_num_args:\n        # we add + 1 to each to account for `self`, matching python's\n        # default error message\n        raise TypeError(\n          f'__init__() takes {expected_num_args + 1} positional '\n          f'arguments but {num_args + 1} were given'\n        )\n\n      dataclass_init(self, *args, **kwargs)\n\n    init_wrapper.__signature__ = inspect.signature(dataclass_init)  # type: ignore\n    transformed_cls.__init__ = init_wrapper  # type: ignore[method-assign]\n\n  # Return the transformed dataclass\n  return transformed_cls\n"
  },
  {
    "path": "flax/linen/linear.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Linear modules.\"\"\"\n\nfrom collections.abc import Iterable, Sequence\nfrom typing import Any, Protocol\n\nfrom flax.core import meta\nfrom flax.linen import initializers\nfrom flax.linen import module\nfrom flax.linen.dtypes import promote_dtype\nfrom flax.linen.module import Module, compact\nfrom flax.typing import (\n    Array,\n    ConvGeneralDilatedT,\n    DotGeneralT,\n    Dtype,\n    Initializer,\n    LaxPadding,\n    PRNGKey as PRNGKey,\n    PaddingLike,\n    PrecisionLike,\n    Shape as Shape,\n)\nimport jax\nfrom jax import eval_shape, lax\nfrom jax.core import ShapedArray\nimport jax.numpy as jnp\nimport numpy as np\nimport opt_einsum\n\nclass PromoteDtypeFn(Protocol):\n  def __call__(\n    self, *args: jax.Array | None, dtype: Any = None, inexact: bool = True\n  ) -> list[jax.Array | None]: ...\n\ndefault_kernel_init = initializers.lecun_normal()\n\n\ndef _normalize_axes(axes: tuple[int, ...], ndim: int) -> tuple[int, ...]:\n  # A tuple by convention. len(axes_tuple) then also gives the rank efficiently.\n  return tuple(sorted(ax if ax >= 0 else ndim + ax for ax in axes))\n\n\ndef _canonicalize_tuple(x: Sequence[int] | int) -> tuple[int, ...]:\n  if isinstance(x, Iterable):\n    return tuple(x)\n  else:\n    return (x,)\n\n\nclass DenseGeneral(Module):\n  \"\"\"A linear transformation with flexible axes.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> # equivalent to `nn.Dense(features=4)`\n    >>> layer = nn.DenseGeneral(features=4)\n    >>> # output features (4, 5)\n    >>> layer = nn.DenseGeneral(features=(4, 5))\n    >>> params = layer.init(jax.random.key(0), jnp.ones((1, 3)))\n    >>> jax.tree_util.tree_map(jnp.shape, params)\n    {'params': {'bias': (4, 5), 'kernel': (3, 4, 5)}}\n    >>> # apply transformation on the the second and last axes\n    >>> layer = nn.DenseGeneral(features=(4, 5), axis=(1, -1))\n    >>> params = layer.init(jax.random.key(0), jnp.ones((1, 3, 6, 7)))\n    >>> jax.tree_util.tree_map(jnp.shape, params)\n    {'params': {'bias': (4, 5), 'kernel': (3, 7, 4, 5)}}\n\n  Attributes:\n    features: int or tuple with number of output features.\n    axis: int or tuple with axes to apply the transformation on. For instance,\n      (-2, -1) will apply the transformation to the last two axes.\n    batch_dims: tuple with batch axes.\n    use_bias: whether to add a bias to the output (default: True).\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    kernel_init: initializer function for the weight matrix.\n    bias_init: initializer function for the bias.\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(inputs, kernel, bias)``\n      and a ``dtype`` keyword argument, and return a tuple of arrays with the\n      promoted dtype.\n  \"\"\"\n\n  features: int | Sequence[int]\n  axis: int | Sequence[int] = -1\n  batch_dims: Sequence[int] = ()\n  use_bias: bool = True\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  kernel_init: Initializer = default_kernel_init\n  bias_init: Initializer = initializers.zeros_init()\n  precision: PrecisionLike = None\n  promote_dtype: PromoteDtypeFn = promote_dtype\n  # Deprecated. Will be removed.\n  dot_general: DotGeneralT | None = None\n  dot_general_cls: Any = None\n\n  @compact\n  def __call__(self, inputs: Array) -> Array:\n    \"\"\"Applies a linear transformation to the inputs along multiple dimensions.\n\n    Args:\n      inputs: The nd-array to be transformed.\n\n    Returns:\n      The transformed input.\n    \"\"\"\n    features = _canonicalize_tuple(self.features)\n    axis = _canonicalize_tuple(self.axis)\n    batch_dims = _canonicalize_tuple(self.batch_dims)\n    if batch_dims:\n      max_dim = np.max(batch_dims)\n      if set(batch_dims) != set(range(max_dim + 1)):\n        raise ValueError(\n          'batch_dims %s must be consecutive leading '\n          'dimensions starting from 0.' % str(batch_dims)\n        )\n\n    ndim = inputs.ndim\n    n_batch_dims = len(batch_dims)\n    axis = _normalize_axes(axis, ndim)\n    batch_dims = _normalize_axes(batch_dims, ndim)\n    n_axis, n_features = len(axis), len(features)\n\n    def kernel_init_wrap(rng, shape, dtype=jnp.float32):\n      flat_shape = (\n        np.prod(shape[:n_batch_dims])\n        * np.prod(shape[n_batch_dims : n_axis + n_batch_dims]),\n        np.prod(shape[-n_features:]),\n      )\n      flat_shape = jax.tree_util.tree_map(int, flat_shape)\n      kernel = self.kernel_init(rng, flat_shape, dtype)\n      if isinstance(kernel, meta.AxisMetadata):\n        return meta.replace_boxed(kernel, jnp.reshape(kernel.unbox(), shape))\n      return jnp.reshape(kernel, shape)\n\n    batch_shape = tuple(inputs.shape[ax] for ax in batch_dims)\n    # batch and non-contracting dims of input with 1s for batch dims.\n    expanded_batch_shape = tuple(\n      inputs.shape[ax] if ax in batch_dims else 1\n      for ax in range(inputs.ndim)\n      if ax not in axis\n    )\n    kernel_shape = tuple(inputs.shape[ax] for ax in axis) + features\n    kernel = self.param(\n      'kernel', kernel_init_wrap, batch_shape + kernel_shape, self.param_dtype\n    )\n\n    batch_ind = tuple(range(n_batch_dims))\n    contract_ind = tuple(range(n_batch_dims, n_axis + n_batch_dims))\n\n    if self.use_bias:\n\n      def bias_init_wrap(rng, shape, dtype=jnp.float32):\n        flat_shape = (\n          np.prod(shape[:n_batch_dims]) * np.prod(shape[-n_features:]),\n        )\n        flat_shape = jax.tree_util.tree_map(int, flat_shape)\n        bias = self.bias_init(rng, flat_shape, dtype)\n        if isinstance(bias, meta.AxisMetadata):\n          return meta.replace_boxed(bias, jnp.reshape(bias.unbox(), shape))\n        return jnp.reshape(bias, shape)\n\n      bias = self.param(\n        'bias', bias_init_wrap, batch_shape + features, self.param_dtype\n      )\n    else:\n      bias = None\n\n    inputs, kernel, bias = self.promote_dtype(\n      inputs, kernel, bias, dtype=self.dtype\n    )\n\n    if self.dot_general_cls is not None:\n      dot_general = self.dot_general_cls()\n    elif self.dot_general is not None:\n      dot_general = self.dot_general\n    else:\n      dot_general = lax.dot_general\n    out = dot_general(\n      inputs,\n      kernel,\n      ((axis, contract_ind), (batch_dims, batch_ind)),\n      precision=self.precision,\n    )\n    # dot_general output has shape [batch_dims/group_dims] + [feature_dims]\n    if self.use_bias:\n      # expand bias shape to broadcast bias over batch dims.\n      assert bias is not None\n      bias = jnp.reshape(bias, expanded_batch_shape + features)\n      out += bias\n    return out\n\n\nclass Dense(Module):\n  \"\"\"A linear transformation applied over the last dimension of the input.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> layer = nn.Dense(features=4)\n    >>> params = layer.init(jax.random.key(0), jnp.ones((1, 3)))\n    >>> jax.tree_util.tree_map(jnp.shape, params)\n    {'params': {'bias': (4,), 'kernel': (3, 4)}}\n\n  Attributes:\n    features: the number of output features.\n    use_bias: whether to add a bias to the output (default: True).\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    kernel_init: initializer function for the weight matrix.\n    bias_init: initializer function for the bias.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(inputs, kernel, bias)``\n      and a ``dtype`` keyword argument, and return a tuple of arrays with the\n      promoted dtype.\n  \"\"\"\n\n  features: int\n  use_bias: bool = True\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  precision: PrecisionLike = None\n  kernel_init: Initializer = default_kernel_init\n  bias_init: Initializer = initializers.zeros_init()\n  promote_dtype: PromoteDtypeFn = promote_dtype\n  dot_general: DotGeneralT | None = None\n  dot_general_cls: Any = None\n\n  @compact\n  def __call__(self, inputs: Array) -> Array:\n    \"\"\"Applies a linear transformation to the inputs along the last dimension.\n\n    Args:\n      inputs: The nd-array to be transformed.\n\n    Returns:\n      The transformed input.\n    \"\"\"\n    kernel = self.param(\n      'kernel',\n      self.kernel_init,\n      (jnp.shape(inputs)[-1], self.features),\n      self.param_dtype,\n    )\n    if self.use_bias:\n      bias = self.param(\n        'bias', self.bias_init, (self.features,), self.param_dtype\n      )\n    else:\n      bias = None\n    inputs, kernel, bias = self.promote_dtype(\n      inputs, kernel, bias, dtype=self.dtype\n    )\n    assert inputs is not None\n    assert kernel is not None\n\n    if self.dot_general_cls is not None:\n      dot_general = self.dot_general_cls()\n    elif self.dot_general is not None:\n      dot_general = self.dot_general\n    else:\n      dot_general = lax.dot_general\n    y = dot_general(\n      inputs,\n      kernel,\n      (((inputs.ndim - 1,), (0,)), ((), ())),\n      precision=self.precision,\n    )\n    if bias is not None:\n      y += jnp.reshape(bias, (1,) * (y.ndim - 1) + (-1,))\n    return y\n\n\nclass Einsum(Module):\n  \"\"\"An einsum transformation with learnable kernel and bias.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> layer = nn.Einsum((5, 6, 7), 'abc,cde->abde')\n    >>> variables = layer.init(jax.random.key(0), jnp.ones((3, 4, 5)))\n    >>> jax.tree_util.tree_map(jnp.shape, variables)\n    {'params': {'bias': (6, 7), 'kernel': (5, 6, 7)}}\n\n  Attributes:\n    shape: the shape of the kernel.\n    einsum_str: a string to denote the einsum equation. The equation must\n      have exactly two operands, the lhs being the input passed in, and\n      the rhs being the learnable kernel. Exactly one of ``einsum_str``\n      in the constructor argument and call argument must be not None,\n      while the other must be None.\n    use_bias: whether to add a bias to the output (default: True).\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    kernel_init: initializer function for the weight matrix.\n    bias_init: initializer function for the bias.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(inputs, kernel, bias)``\n      and a ``dtype`` keyword argument, and return a tuple of arrays with the\n      promoted dtype.\n  \"\"\"\n\n  shape: Shape\n  einsum_str: str | None = None\n  use_bias: bool = True\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  precision: PrecisionLike = None\n  kernel_init: Initializer = default_kernel_init\n  bias_init: Initializer = initializers.zeros_init()\n  promote_dtype: PromoteDtypeFn = promote_dtype\n  preferred_element_type: Dtype | None = None\n\n  @compact\n  def __call__(self, inputs: Array, einsum_str: str | None = None) -> Array:\n    \"\"\"Applies a linear transformation to the inputs along the last dimension.\n\n    Args:\n      inputs: The nd-array to be transformed.\n      einsum_str: a string to denote the einsum equation. The equation must\n        have exactly two operands, the lhs being the input passed in, and\n        the rhs being the learnable kernel. The ``einsum_str`` passed into\n        the call method will take precedence over the ``einsum_str`` passed\n        into the constructor.\n\n    Returns:\n      The transformed input.\n    \"\"\"\n    einsum_str = module.merge_param('einsum_str', self.einsum_str, einsum_str)\n\n    einsum_str = einsum_str.replace(' ', '')\n    if '->' not in einsum_str:\n      raise ValueError(\n        '`einsum_str` equation must be explicit and include \"->\".'\n      )\n    if einsum_str.count(',') != 1:\n      raise ValueError(\n        '`einsum_str` equation must have exactly two operands and '\n        'therefore, exactly one comma character, instead of '\n        f'{einsum_str.count(\",\")}'\n      )\n\n    kernel = self.param(\n      'kernel',\n      self.kernel_init,\n      self.shape,\n      self.param_dtype,\n    )\n\n    if self.use_bias:\n      bias_shape, broadcasted_bias_shape = self._get_bias_shape(\n        einsum_str, inputs, kernel\n      )\n      bias = self.param('bias', self.bias_init, bias_shape, self.param_dtype)\n    else:\n      bias = None\n\n    inputs, kernel, bias = self.promote_dtype(\n      inputs, kernel, bias, dtype=self.dtype\n    )\n\n    y = jnp.einsum(\n      einsum_str,\n      inputs,\n      kernel,\n      precision=self.precision,\n      preferred_element_type=self.preferred_element_type,\n    )\n\n    if bias is not None:\n      y += jnp.reshape(bias, broadcasted_bias_shape)\n    return y\n\n  def _get_bias_shape(self, einsum_str: str, lhs: Array, rhs: Array):\n    \"\"\"Infer the bias shape and broadcasted bias shape given the ``einsum_str``,\n    ``lhs`` and ``rhs`` arrays. This is needed for instantiating the bias\n    parameter and adding the bias to the output during forward inference.\n\n    This function first replaces all ellipses with actual letter characters,\n    then computes the bias shape by checking to see which axes in the rhs\n    array remain in the resulting array after einsumming. These axes are the\n    embedding/feature dimensions, and all other axes in rhs are reduction axes.\n    \"\"\"\n    # More details on the parsing function: https://github.com/dgasmith/opt_einsum/blob/c826bb7df16f470a69f7bf90598fc27586209d11/opt_einsum/parser.py#L246\n    # returns the einsum string representation of the operands and result, with\n    # ellipsis replaced by actual letter characters\n    operands_str, result_str, _ = opt_einsum.parser.parse_einsum_input(\n      (einsum_str, lhs, rhs)\n    )\n\n    # rhs_dict is a dict{character:index} mapping that maps every character in\n    # the rhs einsum string representation to its corresponding index position in the string\n    rhs_dict = {c: i for i, c in enumerate(operands_str.split(',')[1])}\n    assert len(rhs_dict) == len(self.shape)\n\n    broadcasted_bias_shape = [1] * len(result_str)\n    bias_shape = []\n    for i, c in enumerate(result_str):\n      if c in rhs_dict:\n        broadcasted_bias_shape[i] = self.shape[rhs_dict[c]]\n        bias_shape.append(self.shape[rhs_dict[c]])\n\n    return bias_shape, broadcasted_bias_shape\n\n\ndef _conv_dimension_numbers(input_shape):\n  \"\"\"Computes the dimension numbers based on the input shape.\"\"\"\n  ndim = len(input_shape)\n  lhs_spec = (0, ndim - 1) + tuple(range(1, ndim - 1))\n  rhs_spec = (ndim - 1, ndim - 2) + tuple(range(0, ndim - 2))\n  out_spec = lhs_spec\n  return lax.ConvDimensionNumbers(lhs_spec, rhs_spec, out_spec)\n\n\ndef canonicalize_padding(padding: PaddingLike, rank: int) -> LaxPadding:\n  \"\"\" \"Canonicalizes conv padding to a jax.lax supported format.\"\"\"\n  if isinstance(padding, str):\n    return padding\n  if isinstance(padding, int):\n    return [(padding, padding)] * rank\n  if isinstance(padding, Sequence) and len(padding) == rank:\n    new_pad = []\n    for p in padding:\n      if isinstance(p, int):\n        new_pad.append((p, p))\n      elif isinstance(p, tuple) and len(p) == 2:\n        new_pad.append(p)\n      else:\n        break\n    if len(new_pad) == rank:\n      return new_pad\n  raise ValueError(\n    f'Invalid padding format: {padding}, should be str, int,'\n    f' or a sequence of len {rank} where each element is an'\n    ' int or pair of ints.'\n  )\n\n\nclass _Conv(Module):\n  \"\"\"Convolution Module wrapping ``lax.conv_general_dilated``.\n\n  Attributes:\n    features: number of convolution filters.\n    kernel_size: shape of the convolutional kernel. An integer will be\n      interpreted as a tuple of the single integer.\n    strides: an integer or a sequence of `n` integers, representing the\n      inter-window strides (default: 1).\n    padding: either the string ``'SAME'``, the string ``'VALID'``, the string\n      ``'CIRCULAR'`` (periodic boundary conditions), the string `'REFLECT'`\n      (reflection across the padding boundary), or a sequence of ``n`` ``(low,\n      high)`` integer pairs that give the padding to apply before and after each\n      spatial dimension. A single int is interpreted as applying the same padding\n      in all dims and assign a single int in a sequence causes the same padding\n      to be used on both sides. ``'CAUSAL'`` padding for a 1D convolution will\n      left-pad the convolution axis, resulting in same-sized output.\n    input_dilation: an integer or a sequence of ``n`` integers, giving the\n      dilation factor to apply in each spatial dimension of ``inputs``\n      (default: 1). Convolution with input dilation ``d`` is equivalent to\n      transposed convolution with stride ``d``.\n    kernel_dilation: an integer or a sequence of ``n`` integers, giving the\n      dilation factor to apply in each spatial dimension of the convolution\n      kernel (default: 1). Convolution with kernel dilation\n      is also known as 'atrous convolution'.\n    feature_group_count: integer, default 1. If specified divides the input\n      features into groups.\n    use_bias: whether to add a bias to the output (default: True).\n    mask: Optional mask for the weights during masked convolution. The mask must\n          be the same shape as the convolution weight matrix.\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    kernel_init: initializer for the convolutional kernel.\n    bias_init: initializer for the bias.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(inputs, kernel, bias)``\n      and a ``dtype`` keyword argument, and return a tuple of arrays with the\n      promoted dtype.\n  \"\"\"\n\n  features: int\n  kernel_size: int | Sequence[int]\n  strides: None | int | Sequence[int] = 1\n  padding: PaddingLike = 'SAME'\n  input_dilation: None | int | Sequence[int] = 1\n  kernel_dilation: None | int | Sequence[int] = 1\n  feature_group_count: int = 1\n  use_bias: bool = True\n  mask: Array | None = None\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  precision: PrecisionLike = None\n  kernel_init: Initializer = default_kernel_init\n  bias_init: Initializer = initializers.zeros_init()\n  promote_dtype: PromoteDtypeFn = promote_dtype\n  # Deprecated. Will be removed.\n  conv_general_dilated: ConvGeneralDilatedT | None = None\n  conv_general_dilated_cls: Any = None\n\n  @property\n  def shared_weights(self) -> bool:  # type: ignore\n    \"\"\"Defines whether weights are shared or not between different pixels.\n\n    Returns:\n      ``True`` to use shared weights in convolution (regular convolution).\n      ``False`` to use different weights at different pixels, a.k.a.\n      \"locally connected layer\", \"unshared convolution\", or \"local convolution\".\n\n    \"\"\"\n    ...\n\n  @compact\n  def __call__(self, inputs: Array) -> Array:\n    \"\"\"Applies a (potentially unshared) convolution to the inputs.\n\n    Args:\n      inputs: input data with dimensions ``(*batch_dims, spatial_dims..., features)``.\n        This is the channels-last convention, i.e. NHWC for a 2d convolution and\n        NDHWC for a 3D convolution. Note: this is different from the input convention\n        used by ``lax.conv_general_dilated``, which puts the spatial dimensions last.\n        Note: If the input has more than 1 batch dimension, all batch dimensions\n        are flattened into a single dimension for the convolution and restored\n        before returning.  In some cases directly vmap'ing the layer may yield\n        better performance than this default flattening approach.  If the input\n        lacks a batch dimension it will be added for the convolution and removed\n        n return, an allowance made to enable writing single-example code.\n\n    Returns:\n      The convolved data.\n    \"\"\"\n\n    kernel_size: Sequence[int]\n    if isinstance(self.kernel_size, int):\n      kernel_size = (self.kernel_size,)\n    else:\n      kernel_size = tuple(self.kernel_size)\n\n    def maybe_broadcast(\n      x: int | Sequence[int] | None,\n    ) -> tuple[int, ...]:\n      if x is None:\n        # backward compatibility with using None as sentinel for\n        # broadcast 1\n        x = 1\n      if isinstance(x, int):\n        return (x,) * len(kernel_size)\n      return tuple(x)\n\n    # Combine all input batch dimensions into a single leading batch axis.\n    num_batch_dimensions = inputs.ndim - (len(kernel_size) + 1)\n    if num_batch_dimensions != 1:\n      input_batch_shape = inputs.shape[:num_batch_dimensions]\n      flat_input_shape = (-1,) + inputs.shape[\n        num_batch_dimensions:\n      ]\n      inputs = jnp.reshape(inputs, flat_input_shape)\n\n    # self.strides or (1,) * (inputs.ndim - 2)\n    strides = maybe_broadcast(self.strides)\n    input_dilation = maybe_broadcast(self.input_dilation)\n    kernel_dilation = maybe_broadcast(self.kernel_dilation)\n\n    padding_lax = canonicalize_padding(self.padding, len(kernel_size))\n    if padding_lax in ('CIRCULAR', 'REFLECT'):\n      assert isinstance(padding_lax, str)\n      kernel_size_dilated = [\n        (k - 1) * d + 1 for k, d in zip(kernel_size, kernel_dilation)\n      ]\n      zero_pad: list[tuple[int, int]] = [(0, 0)]\n      pads = (\n        zero_pad\n        + [((k - 1) // 2, k // 2) for k in kernel_size_dilated]\n        + [(0, 0)]\n      )\n      padding_mode = {'CIRCULAR': 'wrap', 'REFLECT': 'reflect'}[padding_lax]\n      inputs = jnp.pad(inputs, pads, mode=padding_mode)\n      padding_lax = 'VALID'\n    elif padding_lax == 'CAUSAL':\n      if len(kernel_size) != 1:\n        raise ValueError(\n          'Causal padding is only implemented for 1D convolutions.'\n        )\n      left_pad = kernel_dilation[0] * (kernel_size[0] - 1)\n      pads = [(0, 0), (left_pad, 0), (0, 0)]\n      inputs = jnp.pad(inputs, pads)\n      padding_lax = 'VALID'\n\n    dimension_numbers = _conv_dimension_numbers(inputs.shape)\n    in_features = jnp.shape(inputs)[-1]\n\n    if self.shared_weights:\n      # One shared convolutional kernel for all pixels in the output.\n      assert in_features % self.feature_group_count == 0\n      kernel_shape = kernel_size + (\n        in_features // self.feature_group_count,\n        self.features,\n      )\n\n    else:\n      if self.feature_group_count != 1:\n        raise NotImplementedError(\n          '`lax.conv_general_dilated_local` does not support '\n          f'`feature_group_count != 1`, got `{self.feature_group_count}`.'\n        )\n\n      # Need to know the spatial output shape of a standard convolution to\n      # create the unshared convolution kernel.\n      if self.conv_general_dilated_cls is not None:\n        conv_general_dilated = self.conv_general_dilated_cls()\n      elif self.conv_general_dilated is not None:\n        conv_general_dilated = self.conv_general_dilated\n      else:\n        conv_general_dilated = lax.conv_general_dilated\n      conv_output_shape = eval_shape(\n        lambda lhs, rhs: conv_general_dilated(  # pylint: disable=g-long-lambda\n          lhs=lhs,\n          rhs=rhs,\n          window_strides=strides,\n          padding=padding_lax,\n          dimension_numbers=dimension_numbers,\n          lhs_dilation=input_dilation,\n          rhs_dilation=kernel_dilation,\n        ),\n        inputs,\n        ShapedArray(kernel_size + (in_features, self.features), inputs.dtype),\n      ).shape\n\n      # One (unshared) convolutional kernel per each pixel in the output.\n      kernel_shape = conv_output_shape[1:-1] + (\n        np.prod(kernel_size) * in_features,\n        self.features,\n      )\n\n    if self.mask is not None and self.mask.shape != kernel_shape:\n      raise ValueError(\n        'Mask needs to have the same shape as weights. '\n        f'Shapes are: {self.mask.shape}, {kernel_shape}'\n      )\n\n    kernel = self.param(\n      'kernel', self.kernel_init, kernel_shape, self.param_dtype\n    )\n\n    if self.mask is not None:\n      kernel *= self.mask\n\n    if self.use_bias:\n      if self.shared_weights:\n        # One bias weight per output channel, shared between pixels.\n        bias_shape = (self.features,)\n      else:\n        # One bias weight per output entry, unshared betwen pixels.\n        bias_shape = conv_output_shape[1:]\n\n      bias = self.param('bias', self.bias_init, bias_shape, self.param_dtype)\n    else:\n      bias = None\n\n    inputs, kernel, bias = self.promote_dtype(\n      inputs, kernel, bias, dtype=self.dtype\n    )\n    assert inputs is not None\n    assert kernel is not None\n\n    if self.shared_weights:\n      if self.conv_general_dilated_cls is not None:\n        conv_general_dilated = self.conv_general_dilated_cls()\n      elif self.conv_general_dilated is not None:\n        conv_general_dilated = self.conv_general_dilated\n      else:\n        conv_general_dilated = lax.conv_general_dilated\n      y = conv_general_dilated(\n        inputs,\n        kernel,\n        strides,\n        padding_lax,\n        lhs_dilation=input_dilation,\n        rhs_dilation=kernel_dilation,\n        dimension_numbers=dimension_numbers,\n        feature_group_count=self.feature_group_count,\n        precision=self.precision,\n      )\n    else:\n      y = lax.conv_general_dilated_local(\n        lhs=inputs,\n        rhs=kernel,\n        window_strides=strides,\n        padding=padding_lax,\n        filter_shape=kernel_size,\n        lhs_dilation=input_dilation,\n        rhs_dilation=kernel_dilation,\n        dimension_numbers=dimension_numbers,\n        precision=self.precision,\n      )\n\n    if self.use_bias:\n      bias = bias.reshape((1,) * (y.ndim - bias.ndim) + bias.shape)  # type: ignore\n      y += bias\n\n    if num_batch_dimensions != 1:\n      output_shape = input_batch_shape + y.shape[1:]\n      y = jnp.reshape(y, output_shape)\n    return y\n\n\nclass Conv(_Conv):\n  \"\"\"Convolution Module wrapping ``lax.conv_general_dilated``.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> # valid padding\n    >>> layer = nn.Conv(features=4, kernel_size=(3,), padding='VALID')\n    >>> out, variables = layer.init_with_output(jax.random.key(0), jnp.ones((1, 8, 3)))\n    >>> jax.tree_util.tree_map(jnp.shape, variables)\n    {'params': {'bias': (4,), 'kernel': (3, 3, 4)}}\n    >>> out.shape\n    (1, 6, 4)\n    >>> # circular padding with stride 2\n    >>> layer = nn.Conv(features=4, kernel_size=(3, 3), strides=2, padding='CIRCULAR')\n    >>> out, variables = layer.init_with_output(jax.random.key(0), jnp.ones((1, 8, 3)))\n    >>> jax.tree_util.tree_map(jnp.shape, variables)\n    {'params': {'bias': (4,), 'kernel': (3, 3, 3, 4)}}\n    >>> out.shape\n    (1, 4, 4)\n    >>> # apply lower triangle mask\n    >>> mask = jnp.tril(jnp.ones((3, 3, 4)))\n    >>> layer = nn.Conv(features=4, kernel_size=(3,), mask=mask, padding='VALID')\n    >>> variables = layer.init(jax.random.key(0), jnp.ones((1, 8, 3)))\n\n  Attributes:\n    features: number of convolution filters.\n    kernel_size: shape of the convolutional kernel. An integer will be\n      interpreted as a tuple of the single integer.\n    strides: an integer or a sequence of `n` integers, representing the\n      inter-window strides (default: 1).\n    padding: either the string ``'SAME'``, the string ``'VALID'``, the string\n      ``'CIRCULAR'`` (periodic boundary conditions), or a sequence of ``n``\n      ``(low, high)`` integer pairs that give the padding to apply before and\n      after each spatial dimension. A single int is interpreted as applying the\n      same padding in all dims and assign a single int in a sequence causes the\n      same padding to be used on both sides. ``'CAUSAL'`` padding for a 1D\n      convolution will left-pad the convolution axis, resulting in same-sized\n      output.\n    input_dilation: an integer or a sequence of ``n`` integers, giving the\n      dilation factor to apply in each spatial dimension of ``inputs`` (default:\n      1). Convolution with input dilation ``d`` is equivalent to transposed\n      convolution with stride ``d``.\n    kernel_dilation: an integer or a sequence of ``n`` integers, giving the\n      dilation factor to apply in each spatial dimension of the convolution\n      kernel (default: 1). Convolution with kernel dilation is also known as\n      'atrous convolution'.\n    feature_group_count: integer, default 1. If specified divides the input\n      features into groups.\n    use_bias: whether to add a bias to the output (default: True).\n    mask: Optional mask for the weights during masked convolution. The mask must\n      be the same shape as the convolution weight matrix.\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    precision: numerical precision of the computation see ``jax.lax.Precision`\n      for details.\n    kernel_init: initializer for the convolutional kernel.\n    bias_init: initializer for the bias.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(inputs, kernel, bias)``\n      and a ``dtype`` keyword argument, and return a tuple of arrays with the\n      promoted dtype.\n  \"\"\"\n\n  @property\n  def shared_weights(self) -> bool:\n    return True\n\n\nclass ConvLocal(_Conv):\n  \"\"\"Local convolution Module wrapping ``lax.conv_general_dilated_local``.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> # valid padding\n    >>> layer = nn.ConvLocal(features=4, kernel_size=(3,), padding='VALID')\n    >>> out, variables = layer.init_with_output(jax.random.key(0), jnp.ones((1, 8, 3)))\n    >>> jax.tree_util.tree_map(jnp.shape, variables)\n    {'params': {'bias': (6, 4), 'kernel': (6, 9, 4)}}\n    >>> out.shape\n    (1, 6, 4)\n    >>> # circular padding with stride 2\n    >>> layer = nn.ConvLocal(features=4, kernel_size=(3, 3), strides=2, padding='CIRCULAR')\n    >>> out, variables = layer.init_with_output(jax.random.key(0), jnp.ones((1, 8, 3)))\n    >>> jax.tree_util.tree_map(jnp.shape, variables)\n    {'params': {'bias': (1, 4, 4), 'kernel': (1, 4, 27, 4)}}\n    >>> out.shape\n    (1, 4, 4)\n    >>> # apply lower triangle mask\n    >>> mask = jnp.tril(jnp.ones((6, 9, 4)))\n    >>> layer = nn.ConvLocal(features=4, kernel_size=(3,), mask=mask, padding='VALID')\n    >>> variables = layer.init(jax.random.key(0), jnp.ones((1, 8, 3)))\n\n  Attributes:\n    features: number of convolution filters.\n    kernel_size: shape of the convolutional kernel. An integer will be\n      interpreted as a tuple of the single integer.\n    strides: an integer or a sequence of `n` integers, representing the\n      inter-window strides (default: 1).\n    padding: either the string ``'SAME'``, the string ``'VALID'``, the string\n      ``'CIRCULAR'`` (periodic boundary conditions), or a sequence of ``n``\n      ``(low, high)`` integer pairs that give the padding to apply before and\n      after each spatial dimension. A single int is interpreted as applying the\n      same padding in all dims and assign a single int in a sequence causes the\n      same padding to be used on both sides. ``'CAUSAL'`` padding for a 1D\n      convolution will left-pad the convolution axis, resulting in same-sized\n      output.\n    input_dilation: an integer or a sequence of ``n`` integers, giving the\n      dilation factor to apply in each spatial dimension of ``inputs`` (default:\n      1). Convolution with input dilation ``d`` is equivalent to transposed\n      convolution with stride ``d``.\n    kernel_dilation: an integer or a sequence of ``n`` integers, giving the\n      dilation factor to apply in each spatial dimension of the convolution\n      kernel (default: 1). Convolution with kernel dilation is also known as\n      'atrous convolution'.\n    feature_group_count: integer, default 1. If specified divides the input\n      features into groups.\n    use_bias: whether to add a bias to the output (default: True).\n    mask: Optional mask for the weights during masked convolution. The mask must\n      be the same shape as the convolution weight matrix.\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    kernel_init: initializer for the convolutional kernel.\n    bias_init: initializer for the bias.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(inputs, kernel, bias)``\n      and a ``dtype`` keyword argument, and return a tuple of arrays with the\n      promoted dtype.\n  \"\"\"\n\n  @property\n  def shared_weights(self) -> bool:\n    return False\n\n\nclass ConvTranspose(Module):\n  \"\"\"Convolution Module wrapping ``lax.conv_transpose``.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> # valid padding\n    >>> layer = nn.ConvTranspose(features=4, kernel_size=(3,), padding='VALID')\n    >>> out, variables = layer.init_with_output(jax.random.key(0), jnp.ones((1, 8, 3)))\n    >>> jax.tree_util.tree_map(jnp.shape, variables)\n    {'params': {'bias': (4,), 'kernel': (3, 3, 4)}}\n    >>> out.shape\n    (1, 10, 4)\n    >>> # circular padding with stride 2\n    >>> layer = nn.ConvTranspose(features=4, kernel_size=(6, 6), strides=(2, 2), padding='CIRCULAR', transpose_kernel=True)\n    >>> out, variables = layer.init_with_output(jax.random.key(0), jnp.ones((1, 15, 15, 3)))\n    >>> jax.tree_util.tree_map(jnp.shape, variables)\n    {'params': {'bias': (4,), 'kernel': (6, 6, 4, 3)}}\n    >>> out.shape\n    (1, 30, 30, 4)\n    >>> # apply lower triangle mask\n    >>> mask = jnp.tril(jnp.ones((3, 3, 4)))\n    >>> layer = nn.ConvTranspose(features=4, kernel_size=(3,), mask=mask, padding='VALID')\n    >>> variables = layer.init(jax.random.key(0), jnp.ones((1, 8, 3)))\n\n  Attributes:\n    features: number of convolution filters.\n    kernel_size: shape of the convolutional kernel. For 1D convolution, the\n      kernel size can be passed as an integer, which will be interpreted as a\n      tuple of the single integer. For all other cases, it must be a sequence of\n      integers.\n    strides: an integer or a sequence of `n` integers, representing the\n      inter-window strides.\n    padding: either the string `'SAME'`, the string `'VALID'`, the string\n      `'CIRCULAR'` (periodic boundary conditions), or a sequence of `n` `(low,\n      high)` integer pairs that give the padding to apply before and after each\n      spatial dimension. A single int is interpreted as applying the same\n      padding in all dims and assign a single int in a sequence causes the same\n      padding to be used on both sides.\n    kernel_dilation: ``None``, or an integer or a sequence of ``n`` integers,\n      giving the dilation factor to apply in each spatial dimension of the convolution\n      kernel. Convolution with kernel dilation is also known as 'atrous\n      convolution'.\n    use_bias: whether to add a bias to the output (default: True).\n    mask: Optional mask for the weights during masked convolution. The mask must\n      be the same shape as the convolution weight matrix.\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    kernel_init: initializer for the convolutional kernel.\n    bias_init: initializer for the bias.\n    transpose_kernel: if ``True`` flips spatial axes and swaps the input/output\n      channel axes of the kernel.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(inputs, kernel, bias)``\n      and a ``dtype`` keyword argument, and return a tuple of arrays with the\n      promoted dtype.\n  \"\"\"\n\n  features: int\n  kernel_size: int | Sequence[int]\n  strides: Sequence[int] | None = None\n  padding: PaddingLike = 'SAME'\n  kernel_dilation: Sequence[int] | None = None\n  use_bias: bool = True\n  mask: Array | None = None\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  precision: PrecisionLike = None\n  kernel_init: Initializer = default_kernel_init\n  bias_init: Initializer = initializers.zeros_init()\n  transpose_kernel: bool = False\n  promote_dtype: PromoteDtypeFn = promote_dtype\n  preferred_element_type: Dtype | None = None\n\n  @compact\n  def __call__(self, inputs: Array) -> Array:\n    \"\"\"Applies a transposed convolution to the inputs.\n\n    Behaviour mirrors of ``jax.lax.conv_transpose``.\n\n    Args:\n      inputs: input data with dimensions ``(*batch_dims, spatial_dims..., features).``\n        This is the channels-last convention, i.e. NHWC for a 2d convolution and NDHWC\n        for a 3D convolution. Note: this is different from the input convention used by\n        ``lax.conv_general_dilated``, which puts the spatial dimensions last.\n        Note: If the input has more than 1 batch dimension, all batch dimensions\n        are flattened into a single dimension for the convolution and restored\n        before returning.  In some cases directly vmap'ing the layer may yield\n        better performance than this default flattening approach.  If the input\n        lacks a batch dimension it will be added for the convolution and removed\n        n return, an allowance made to enable writing single-example code.\n\n    Returns:\n      The convolved data.\n    \"\"\"\n    kernel_size: tuple[int, ...]\n    if isinstance(self.kernel_size, int):\n      kernel_size = (self.kernel_size,)\n    else:\n      kernel_size = tuple(self.kernel_size)\n\n    def maybe_broadcast(\n      x: int | Sequence[int] | None,\n    ) -> tuple[int, ...]:\n      if x is None:\n        # backward compatibility with using None as sentinel for\n        # broadcast 1\n        x = 1\n      if isinstance(x, int):\n        return (x,) * len(kernel_size)\n      return tuple(x)\n\n    # Combine all input batch dimensions into a single leading batch axis.\n    num_batch_dimensions = inputs.ndim - (len(kernel_size) + 1)\n    if num_batch_dimensions != 1:\n      input_batch_shape = inputs.shape[:num_batch_dimensions]\n      flat_input_shape = (-1,) + inputs.shape[\n        num_batch_dimensions:\n      ]\n      inputs = jnp.reshape(inputs, flat_input_shape)\n\n    strides = maybe_broadcast(self.strides)\n    kernel_dilation = maybe_broadcast(self.kernel_dilation)\n\n    in_features = jnp.shape(inputs)[-1]\n    if self.transpose_kernel:\n      kernel_shape = kernel_size + (self.features, in_features)\n    else:\n      kernel_shape = kernel_size + (in_features, self.features)\n\n    if self.mask is not None and self.mask.shape != kernel_shape:\n      raise ValueError(\n        'Mask needs to have the same shape as weights. '\n        f'Shapes are: {self.mask.shape}, {kernel_shape}'\n      )\n\n    kernel = self.param(\n      'kernel', self.kernel_init, kernel_shape, self.param_dtype\n    )\n\n    if self.mask is not None:\n      kernel *= self.mask\n\n    padding_lax = canonicalize_padding(self.padding, len(kernel_size))\n    if padding_lax == 'CIRCULAR':\n      padding_lax = 'VALID'\n\n    if self.use_bias:\n      bias = self.param(\n        'bias', self.bias_init, (self.features,), self.param_dtype\n      )\n    else:\n      bias = None\n\n    inputs, kernel, bias = self.promote_dtype(\n      inputs, kernel, bias, dtype=self.dtype\n    )\n    assert inputs is not None\n    assert kernel is not None\n\n    y = lax.conv_transpose(\n      inputs,\n      kernel,\n      strides,\n      padding_lax,\n      rhs_dilation=kernel_dilation,\n      transpose_kernel=self.transpose_kernel,\n      precision=self.precision,\n      preferred_element_type=self.preferred_element_type,\n    )\n\n    if self.padding == 'CIRCULAR':\n      # For circular padding, we need to identify the size of the final output\n      # (\"period\") along each spatial dimension, pad each dimension to an\n      # integer number of periods, and wrap the array periodically around each\n      # dimension. Padding should be done in such a way that the start of the\n      # original input data inside the padded array is located at integer\n      # number of periods - otherwise the result would be circularly shifted.\n\n      # Compute period along each spatial dimension - it's input size scaled\n      # by the stride.\n      scaled_x_dims = [\n        x_dim * stride\n        for x_dim, stride in zip(jnp.shape(inputs)[1:-1], strides)\n      ]\n      # Compute difference between the current size of y and the final output\n      # size, and complement this difference to 2 * period - that gives how\n      # much we need to pad.\n      size_diffs = [\n        -(y_dim - x_dim) % (2 * x_dim)\n        for y_dim, x_dim in zip(y.shape[1:-1], scaled_x_dims)\n      ]\n      if self.transpose_kernel:\n        # If the kernel is transposed, the \"+1\" is put on the right to\n        # mirror the regular convolution. If the same kernel parameters are used\n        # as for Conv, this layer then computes the proper transpose convolution.\n        total_pad = [\n          (size_diff // 2, (size_diff + 1) // 2) for size_diff in size_diffs\n        ]\n      else:\n        # Divide the padding equally between left and right. The choice to put\n        # \"+1\" on the left (and not on the right) represents a convention for\n        # aligning even-sized kernels.\n        total_pad = [\n          ((size_diff + 1) // 2, size_diff // 2) for size_diff in size_diffs\n        ]\n      y = jnp.pad(y, [(0, 0)] + total_pad + [(0, 0)])\n      # Wrap the result periodically around each spatial dimension,\n      # one by one.\n      for i in range(1, y.ndim - 1):\n        y = y.reshape(\n          y.shape[:i] + (-1, scaled_x_dims[i - 1]) + y.shape[i + 1 :]\n        )\n        y = y.sum(axis=i)\n\n    if self.use_bias:\n      y += jnp.reshape(bias, (1,) * (y.ndim - 1) + (-1,))  # type: ignore\n\n    if num_batch_dimensions != 1:\n      output_shape = input_batch_shape + y.shape[1:]\n      y = jnp.reshape(y, output_shape)\n\n    return y\n\n\ndefault_embed_init = initializers.variance_scaling(\n  1.0, 'fan_in', 'normal', out_axis=0\n)\n\n\nclass Embed(Module):\n  \"\"\"Embedding Module.\n\n  A parameterized function from integers [0, ``num_embeddings``) to\n  ``features``-dimensional vectors. This ``Module`` will create an ``embedding``\n  matrix with shape ``(num_embeddings, features)``. When calling this layer,\n  the input values will be used to 0-index into the ``embedding`` matrix.\n  Indexing on a value greater than or equal to ``num_embeddings`` will result\n  in ``nan`` values. When ``num_embeddings`` equals to 1, it will\n  broadcast the ``embedding`` matrix to input shape with ``features``\n  dimension appended.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> layer = nn.Embed(num_embeddings=5, features=3)\n    >>> indices_input = jnp.array([[0, 1, 2], [-1, -2, -3]])\n    >>> variables = layer.init(jax.random.key(0), indices_input)\n    >>> variables\n    {'params': {'embedding': Array([[ 0.04396089, -0.9328513 , -0.97328115],\n           [ 0.41147125,  0.66334754,  0.49469155],\n           [ 0.09719624,  0.49861377,  0.49519277],\n           [-0.13316602,  0.6697022 ,  0.3710195 ],\n           [-0.5039532 ,  0.287319  ,  1.4369922 ]], dtype=float32)}}\n    >>> # get the first three and last three embeddings\n    >>> layer.apply(variables, indices_input)\n    Array([[[ 0.04396089, -0.9328513 , -0.97328115],\n            [ 0.41147125,  0.66334754,  0.49469155],\n            [ 0.09719624,  0.49861377,  0.49519277]],\n    <BLANKLINE>\n           [[-0.5039532 ,  0.287319  ,  1.4369922 ],\n            [-0.13316602,  0.6697022 ,  0.3710195 ],\n            [ 0.09719624,  0.49861377,  0.49519277]]], dtype=float32)\n\n  Attributes:\n    num_embeddings: number of embeddings / vocab size.\n    features: number of feature dimensions for each embedding.\n    dtype: the dtype of the embedding vectors (default: same as embedding).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    embedding_init: embedding initializer.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(embedding,)`` during ``__call__``\n      or ``(query, embedding)`` during ``attend``, and a ``dtype`` keyword argument,\n      and return a tuple of arrays with the promoted dtype.\n  \"\"\"\n\n  num_embeddings: int\n  features: int\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  embedding_init: Initializer = default_embed_init\n  promote_dtype: PromoteDtypeFn = promote_dtype\n\n  def setup(self):\n    self.embedding = self.param(\n      'embedding',\n      self.embedding_init,\n      (self.num_embeddings, self.features),\n      self.param_dtype,\n    )\n\n  def __call__(self, inputs: Array) -> Array:\n    \"\"\"Embeds the inputs along the last dimension.\n\n    Args:\n      inputs: input data, all dimensions are considered batch dimensions.\n        Values in the input array must be integers.\n\n    Returns:\n      Output which is embedded input data.  The output shape follows the input,\n      with an additional ``features`` dimension appended.\n    \"\"\"\n    if not jnp.issubdtype(inputs.dtype, jnp.integer):\n      raise ValueError('Input type must be an integer or unsigned integer.')\n    # Use take because fancy indexing numpy arrays with JAX indices does not\n    # work correctly.\n    (embedding,) = self.promote_dtype(\n      self.embedding, dtype=self.dtype, inexact=False\n    )\n    assert embedding is not None\n    if self.num_embeddings == 1:\n      return jnp.broadcast_to(embedding, inputs.shape + (self.features,))\n    return jnp.take(embedding, inputs, axis=0)\n\n  def attend(self, query: Array) -> Array:\n    \"\"\"Attend over the embedding using a query array.\n\n    Args:\n      query: array with last dimension equal the feature depth ``features`` of the\n        embedding.\n\n    Returns:\n      An array with final dim ``num_embeddings`` corresponding to the batched\n      inner-product of the array of query vectors against each embedding.\n      Commonly used for weight-sharing between embeddings and logit transform\n      in NLP models.\n    \"\"\"\n    embedding: Array\n    query, embedding = self.promote_dtype(\n      query, self.embedding, dtype=self.dtype\n    )\n    assert query is not None\n    assert embedding is not None\n    return jnp.dot(query, embedding.T)\n"
  },
  {
    "path": "flax/linen/module.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Flax Module.\"\"\"\n\nimport contextlib\nimport dataclasses\nimport enum\nimport functools\nimport inspect\nimport sys\nimport threading\nimport typing\nimport weakref\nfrom types import MappingProxyType\nfrom typing import (\n  Any,\n  Literal,\n  Optional,\n  TypeVar,\n  Union,\n  overload,\n)\nfrom collections.abc import Callable, Iterable, Iterator, Mapping\n\nimport jax\nimport jax.numpy as jnp\nimport typing_extensions as tpe\n\nimport flax\nimport flax.linen as nn\nfrom flax import (\n  config,\n  core,\n  errors,\n  serialization,\n  traceback_util,\n  traverse_util,\n)\nfrom flax.core import Scope, meta, partial_eval\nfrom flax.core.frozen_dict import FrozenDict\nfrom flax.core.scope import (\n  CollectionFilter,\n  DenyList,\n  Variable,\n  union_filters,\n)\nfrom flax.ids import FlaxId, uuid\nfrom flax.linen import kw_only_dataclasses\nfrom flax.typing import (\n  RNGSequences,\n  PRNGKey,\n  FrozenVariableDict,\n  VariableDict,\n)\n\ntraceback_util.register_exclusion(__file__)\n\n\nT = TypeVar('T')\nK = TypeVar('K')\nM = TypeVar('M', bound='Module')\n_CallableT = TypeVar('_CallableT', bound=Callable)\n\n\n# Used for abstractly testing module behavior.\nTestScope = type(\n  'TestScope',\n  (Scope,),\n  {'make_rng': lambda self, name: jax.random.key(0)},\n)\n\n\n# pylint: disable=protected-access,attribute-defined-outside-init\ndef _get_fn_name(fn):\n  if isinstance(fn, functools.partial):\n    return _get_fn_name(fn.func)\n  return getattr(fn, '__name__', 'unnamed_function')\n\n\ndef _indent(x: str, num_spaces: int):\n  indent_str = ' ' * num_spaces\n  lines = x.split('\\n')\n  # skip last line because it is always empty and should not be indented.\n  assert not lines[-1]\n  return '\\n'.join(indent_str + line for line in lines[:-1]) + '\\n'\n\n\ndef _attr_repr(value: Any):\n  if callable(value) and (\n    (isinstance(value, nn.Module) and value.__dict__.get('__name__', None))\n    or (not isinstance(value, nn.Module) and getattr(value, '__name__', None))\n  ):\n    value_rep = value.__name__\n  else:\n    value_rep = repr(value)\n  return value_rep\n\n\ndef _module_repr(module: 'Module', num_spaces: int = 4):\n  \"\"\"Returns a pretty printed representation of the module.\"\"\"\n  cls = type(module)\n  try:\n    fields = dataclasses.fields(cls)\n  except TypeError:\n    # Edge case with no fields e.g. module = nn.Module() causes error later.\n    return object.__repr__(module)\n  cls_name = cls.__name__\n  rep = ''\n\n  attributes = {\n    f.name: f.type\n    for f in fields\n    if f.name not in ('parent', 'name') and f.repr\n  }\n  child_modules = {\n    k: v\n    for k, v in module._state.children.items()  # pytype: disable=attribute-error\n    if isinstance(v, Module)\n  }\n  if attributes:\n    rep += '# attributes\\n'\n    for attr in attributes.keys():\n      # TODO(jheek): can we get a nice string representation of attribute types?\n      value = module.__dict__.get(attr, None)\n      value_rep = _attr_repr(value)\n      rep += f'{attr} = {value_rep}\\n'\n  if child_modules:\n    rep += '# children\\n'\n    for name, child in child_modules.items():\n      child_rep = _module_repr(child, num_spaces)\n      rep += f'{name} = {child_rep}\\n'\n  if rep:\n    return f'{cls_name}(\\n{_indent(rep, num_spaces)})'\n  else:\n    return f'{cls_name}()'\n\n\n# Tabulation utilities.\n# -----------------------------------------------------------------------------\n@dataclasses.dataclass\nclass _CallInfo:\n  index: int\n  path: tuple[str, ...]\n  module: 'Module'\n  rngs: dict[str, core.scope.PRNGKey | core.scope.LazyRng] | None\n  mutable: bool\n  method: str\n  args: tuple[Any, ...]\n  kwargs: dict[str, Any]\n  outputs: Any\n\n\n@dataclasses.dataclass\nclass _CallInfoContext(threading.local):\n  index: int\n  calls: list[_CallInfo]\n\n  def get_call_index(self) -> int:\n    index = self.index\n    self.index += 1\n    return index\n\n\n@contextlib.contextmanager\ndef _tabulate_context():\n  _context.call_info_stack.append(_CallInfoContext(0, []))\n  try:\n    yield\n  finally:\n    _context.call_info_stack.pop()\n\n\n# Track parent relationship across Modules.\n# -----------------------------------------------------------------------------\nclass _DynamicContext(threading.local):\n  \"\"\"Dynamic context.\"\"\"\n\n  # TODO(marcvanzee): switch to using contextvars once minimum python version is\n  # 3.7\n\n  def __init__(self):\n    self.module_stack: list['Module' | None] = [\n      None,\n    ]\n    self.capture_stack = []\n    self.call_info_stack: list[_CallInfoContext] = []\n\n\n# The global context\n_context = _DynamicContext()\n\n\nclass _Sentinel:\n  def __copy__(self):\n    return self  # Do not copy singleton sentinel.\n\n  def __deepcopy__(self, memo):\n    del memo\n    return self  # Do not copy singleton sentinel.\n\n  def __reduce__(self):\n    return _get_unspecified_parent, ()\n\n\ndef _get_unspecified_parent():\n  return _unspecified_parent\n\n\n_unspecified_parent = _Sentinel()\n\n\n# Enable automatic named_call wrapping for labelling profile traces.\n# -----------------------------------------------------------------------------\n_use_named_call = config.flax_profile\n\n\ndef _derive_profiling_name(module, fn):\n  fn_name = _get_fn_name(fn)\n  method_suffix = f'.{fn_name}' if fn_name != '__call__' else ''\n  module_name = module.name or module.__class__.__name__\n  return f'{module_name}{method_suffix}'\n\n\ndef enable_named_call():\n  \"\"\"Enables named call wrapping for labelling profile traces.\n\n  When named call wrapping is enabled all JAX ops executed in a Module\n  will be run under ``jax.named_scope``. The ``Module`` class name will\n  show up around the operations belonging to that Module in the\n  Tensorboard profiling UI, simplifying the profiling process.\n\n  Note that ``jax.named_scope`` only works for\n  compiled functions (e.g.: using jax.jit or jax.pmap).\n  \"\"\"\n  global _use_named_call\n  _use_named_call = True\n\n\ndef disable_named_call():\n  \"\"\"Disables named call wrapping.\n\n  See ``enable_named_call``\n  \"\"\"\n  global _use_named_call\n  _use_named_call = False\n\n\n@contextlib.contextmanager\ndef override_named_call(enable: bool = True):\n  # pylint: disable=g-doc-return-or-yield\n  \"\"\"Returns a context manager that enables/disables named call wrapping.\n\n  Args:\n    enable: If true, enables named call wrapping for labelling profile traces.\n      (see ``enabled_named_call``).\n  \"\"\"\n  # pylint: enable=g-doc-return-or-yield\n  global _use_named_call\n  use_named_call_prev = _use_named_call\n  _use_named_call = enable\n  try:\n    yield\n  finally:\n    _use_named_call = use_named_call_prev\n\n\n# Intercept module methods.\n# -----------------------------------------------------------------------------\n@dataclasses.dataclass(frozen=True)\nclass InterceptorContext:\n  \"\"\"Read only state showing the calling context for method interceptors.\n\n  Attributes:\n    module: The Module instance whose method is being called.\n    method_name: The name of the method being called on the module.\n    orig_method: The original method defined on the module. Calling it will\n      short circuit all other interceptors.\n  \"\"\"\n\n  module: 'Module'\n  method_name: str\n  orig_method: Callable[..., Any]\n\n\nclass ThreadLocalStack(threading.local):\n  \"\"\"Thread-local stack.\"\"\"\n\n  def __init__(self):\n    self._storage = []\n\n  def push(self, elem: Any) -> None:\n    self._storage.append(elem)\n\n  def pop(self) -> Any:\n    return self._storage.pop()\n\n  def __iter__(self) -> Iterator[Any]:\n    return iter(reversed(self._storage))\n\n  def __len__(self) -> int:\n    return len(self._storage)\n\n  def __repr__(self) -> str:\n    return f'{self.__class__.__name__}({self._storage})'\n\n\nArgs = tuple[Any]\nKwargs = dict[str, Any]\nNextGetter = Callable[..., Any]\nInterceptor = Callable[[NextGetter, Args, Kwargs, InterceptorContext], Any]\n_global_interceptor_stack = ThreadLocalStack()\n\n\n@contextlib.contextmanager\ndef intercept_methods(interceptor: Interceptor):\n  # pylint: disable=g-doc-return-or-yield\n  r\"\"\"Registers a new method interceptor.\n\n  Method interceptors allow you to (at a distance) intercept method calls to\n  modules. It works similarly to decorators. You could modify args/kwargs before\n  calling the underlying method and/or modify the result returning from calling\n  the underlying method. Or you could completely skip calling the underlying\n  method and decide to do something differently.  For example::\n\n    >>> import flax.linen as nn\n    >>> import jax.numpy as jnp\n    ...\n    >>> class Foo(nn.Module):\n    ...   def __call__(self, x):\n    ...     return x\n    ...\n    >>> def my_interceptor1(next_fun, args, kwargs, context):\n    ...   print('calling my_interceptor1')\n    ...   return next_fun(*args, **kwargs)\n    ...\n    >>> foo = Foo()\n    >>> with nn.intercept_methods(my_interceptor1):\n    ...   _ = foo(jnp.ones([1]))\n    calling my_interceptor1\n\n  You could also register multiple interceptors on the same method. Interceptors\n  will run in order. For example::\n\n    >>> def my_interceptor2(next_fun, args, kwargs, context):\n    ...   print('calling my_interceptor2')\n    ...   return next_fun(*args, **kwargs)\n    ...\n    >>> with nn.intercept_methods(my_interceptor1), \\\n    ...      nn.intercept_methods(my_interceptor2):\n    ...   _ = foo(jnp.ones([1]))\n    calling my_interceptor1\n    calling my_interceptor2\n\n  You could skip other interceptors by directly calling the\n  ``context.orig_method``. For example::\n\n    >>> def my_interceptor3(next_fun, args, kwargs, context):\n    ...   print('calling my_interceptor3')\n    ...   return context.orig_method(*args, **kwargs)\n    >>> with nn.intercept_methods(my_interceptor3), \\\n    ...      nn.intercept_methods(my_interceptor1), \\\n    ...      nn.intercept_methods(my_interceptor2):\n    ...   _ = foo(jnp.ones([1]))\n    calling my_interceptor3\n\n  The following methods couldn't be intercepted:\n\n  1. Methods decoratored with ``nn.nowrap``.\n  2. Dunder methods including ``__eq__``, ``__repr__``, ``__init__``, ``__hash__``, and ``__post_init__``.\n  3. Module dataclass fields.\n  4. Module descriptors.\n\n  Args:\n    interceptor: A method interceptor.\n  \"\"\"\n  _global_interceptor_stack.push(interceptor)\n  try:\n    yield\n  finally:\n    assert _global_interceptor_stack.pop() is interceptor\n\n\ndef run_interceptors(\n  orig_method: Callable[..., Any],\n  module: 'Module',\n  *args,\n  **kwargs,\n) -> Any:\n  \"\"\"Runs method interceptors.\"\"\"\n  method_name = _get_fn_name(orig_method)\n  fun = functools.partial(orig_method, module)\n  context = InterceptorContext(module, method_name, fun)\n\n  def wrap_interceptor(interceptor, fun):\n    \"\"\"Wraps `fun` with `interceptor`.\"\"\"\n\n    @functools.wraps(fun)\n    def wrapped(*args, **kwargs):\n      return interceptor(fun, args, kwargs, context)\n\n    return wrapped\n\n  # Wraps interceptors around the original method. The innermost interceptor is\n  # the last one added and directly wrapped around the original bound method.\n  for interceptor in _global_interceptor_stack:\n    fun = wrap_interceptor(interceptor, fun)\n  return fun(*args, **kwargs)\n\n\n# Utilities for pytrees of Modules defined inside setup()\n# -----------------------------------------------------------------------------\n\n\ndef _sorted_items(x):\n  \"\"\"Returns items of a dict ordered by keys.\"\"\"\n  return sorted(x.items(), key=lambda x: x[0])\n\n\ndef _get_suffix_value_pairs(\n  tree_or_leaf: Any,\n) -> list[tuple[str, type['Module']]]:\n  \"\"\"Helper for naming pytrees of submodules.\"\"\"\n  dict_or_leaf = serialization.to_state_dict(tree_or_leaf)\n  if not isinstance(dict_or_leaf, dict) or not dict_or_leaf:\n    return [('', tree_or_leaf)]\n  else:\n    flat_dict = traverse_util.flatten_dict(dict_or_leaf)\n    return [('_' + '_'.join(k), v) for k, v in _sorted_items(flat_dict)]\n\n\ndef _map_over_modules_in_tree(fn, tree_or_leaf):\n  \"\"\"Helper for mapping function over submodules.\"\"\"\n  dict_or_leaf = serialization.to_state_dict(tree_or_leaf)\n  if not isinstance(dict_or_leaf, dict) or not dict_or_leaf:\n    return fn('', tree_or_leaf)\n  else:\n    flat_dict = traverse_util.flatten_dict(dict_or_leaf, keep_empty_nodes=True)\n    mapped_flat_dict = {\n      k: fn('_' + '_'.join(k), v) for k, v in _sorted_items(flat_dict)\n    }\n    return serialization.from_state_dict(\n      tree_or_leaf, traverse_util.unflatten_dict(mapped_flat_dict)\n    )\n\n\ndef _freeze_attr(val: Any) -> Any:\n  \"\"\"Recursively wrap the given attribute `var` in ``FrozenDict``.\"\"\"\n  if isinstance(val, (dict, FrozenDict)):\n    return FrozenDict({k: _freeze_attr(v) for k, v in val.items()})\n  elif isinstance(val, tuple):\n    # Special case namedtuples and special JAX tuple structures otherwise they\n    # would be downgraded to normal tuples.\n    if hasattr(val, '_fields') or type(val).__name__ == 'PartitionSpec':\n      return type(val)(*[_freeze_attr(v) for v in val])\n    else:\n      return tuple(_freeze_attr(v) for v in val)\n  elif isinstance(val, list):\n    return tuple(_freeze_attr(v) for v in val)\n  else:\n    return val\n\n\n# Method wrapping of \"compact methods\" and setup()\n# -----------------------------------------------------------------------------\ndef compact(fun: _CallableT) -> _CallableT:\n  \"\"\"Marks the given module method allowing inlined submodules.\n\n  Methods wrapped in @compact can define submodules directly within the method.\n\n  For instance::\n\n    >>> import flax.linen as nn\n\n    >>> class Foo(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x, features):\n    ...     x = nn.Dense(features)(x)\n    ...     ...\n    ...     return x\n\n  At most one method in each Module may be wrapped with @compact.\n\n  Args:\n    fun: The Module method to mark as compact.\n\n  Returns:\n    The given function ``fun`` marked as compact.\n  \"\"\"\n  fun.compact = True  # type: ignore[attr-defined]\n  return fun\n\n\ndef nowrap(fun: _CallableT) -> _CallableT:\n  \"\"\"Marks the given module method as a helper method that needn't be wrapped.\n\n  Methods wrapped in ``@nowrap`` are private helper methods that needn't be wrapped\n  with the state handler or a separate named_call transform.\n\n  This is needed in several concrete instances:\n   - if you're subclassing a method like Module.param and don't want this\n     overridden core function decorated with the state management wrapper.\n   - If you want a method to be callable from an unbound Module (e.g.: a\n     function of construction of arguments that doesn't depend on params/RNGs).\n     If you want to learn more about how Flax Modules manage their state read the\n     [The Flax Module lifecycle](https://flax.readthedocs.io/en/latest/developer_notes/module_lifecycle.html)\n     guide.\n\n  For instance::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> class Foo(nn.Module):\n    ...   num_features: int\n\n    ...   @nn.nowrap\n    ...   def _make_dense(self, num_features):\n    ...     return nn.Dense(num_features)\n\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     # now safe to use constructor helper even if using named_call\n    ...     dense = self._make_dense(self.num_features)\n    ...     return dense(x)\n\n  Args:\n    fun: The Module method to mark as nowrap.\n\n  Returns:\n    The given function ``fun`` marked as nowrap.\n  \"\"\"\n  fun.nowrap = True  # type: ignore[attr-defined]\n  return fun\n\n\ndef compact_name_scope(fun: _CallableT) -> _CallableT:\n  \"\"\"Creates compact submodules from a method.\n\n  This is a decorator that allows you to define compact submodules from a\n  method. It's intention is to make it easier to port code Haiku code to Flax\n  by providing the same functionality.\n\n  Example::\n\n    >>> import flax.linen as nn\n    >>> import jax\n    >>> import jax.numpy as jnp\n    >>> from flax.core import pretty_repr\n    ...\n    >>> class Foo(nn.Module):\n    ...   @nn.compact_name_scope\n    ...   def up(self, x):\n    ...     return nn.Dense(3)(x)\n    ...\n    ...   @nn.compact_name_scope\n    ...   def down(self, x):\n    ...     return nn.Dense(3)(x)\n    ...\n    ...   def __call__(self, x):\n    ...     return self.up(x) + self.down(x)\n    ...\n    >>> module = Foo()\n    >>> variables = module.init(jax.random.PRNGKey(0), jnp.ones((1, 2)))\n    >>> params = variables['params']\n    >>> print(pretty_repr(jax.tree_util.tree_map(jnp.shape, params)))\n    {\n        down: {\n            Dense_0: {\n                bias: (3,),\n                kernel: (2, 3),\n            },\n        },\n        up: {\n            Dense_0: {\n                bias: (3,),\n                kernel: (2, 3),\n            },\n        },\n    }\n\n  You can also use ``compact_name_scope`` inside ``@compact`` methods or even\n  other\n  ``compact_name_scope`` methods. Methods that are decorated with\n  ``compact_name_scope``\n  can also be called directly from ``init`` or ``apply`` via the ``method``\n  argument::\n\n    >>> y_down = module.apply({'params': params}, jnp.ones((1, 2)), method='down')\n    >>> y_down.shape\n    (1, 3)\n\n  Args:\n    fun: The Module method to mark as compact_name_scope.\n\n  Returns:\n    The given function ``fun`` marked as compact_name_scope.\n  \"\"\"\n\n  @functools.wraps(fun)\n  def compact_name_scope_wrapper(self: nn.Module, *args, **kwargs):\n    name = fun.__name__\n    if not hasattr(self, '_compact_name_scope_modules'):\n      raise ValueError(\n        f'Cannot call compact_name_scope method {name!r} on a Module that has not been '\n        f'setup. This is likely because you are calling {name!r} '\n        'from outside of init or apply.'\n      )\n    module = self._compact_name_scope_modules[name]\n    return module(*args, **kwargs)\n\n  compact_name_scope_wrapper.compact_name_scope = True  # type: ignore[attr-defined]\n  compact_name_scope_wrapper.inner_fun = fun  # type: ignore[attr-defined]\n  compact_name_scope_wrapper.nowrap = True  # type: ignore[attr-defined]\n  return compact_name_scope_wrapper  # type: ignore[return-value]\n\n\ndef _get_local_method_names(\n  cls: Any, exclude: Iterable[str] = ()\n) -> tuple[str, ...]:\n  \"\"\"Gets method names of a class, excluding class and static methods.\n\n  Args:\n    cls: The class to get method names for.\n    exclude: Names to exclude from output.\n\n  Returns:\n    A list of method names.\n  \"\"\"\n  true_methods = set()\n  for m in cls.__dict__:\n    if callable(cls.__dict__[m]) and not inspect.isclass(\n      cls.__dict__[m]\n    ):  # pytype: disable=not-supported-yet\n      mtype = type(cls.__dict__[m])\n      if mtype != staticmethod and mtype != classmethod:\n        true_methods.add(m)\n  return tuple(true_methods.difference(set(exclude)))\n\n\ndef _get_local_descriptor_names(\n  cls: Any, exclude: Iterable[str] = ()\n) -> tuple[str, ...]:\n  \"\"\"Gets descriptor names of a class.\n\n  Args:\n    cls: The class to get property names for.\n    exclude: Names to exclude from output.\n\n  Returns:\n    A list of property names.\n  \"\"\"\n  true_properties = set()\n  for m, attr in cls.__dict__.items():\n    if not callable(attr) and (\n      hasattr(attr, '__get__')\n      or hasattr(attr, '__set__')\n      or hasattr(attr, '__delete__')\n    ):\n      mtype = type(attr)\n      if mtype != staticmethod and mtype != classmethod:\n        true_properties.add(m)\n  return tuple(true_properties.difference(set(exclude)))\n\n\ndef wrap_method_once(fun: Callable[..., Any]) -> Callable[..., Any]:\n  \"\"\"Manages Module state for a given user-defined method.\n\n  Args:\n    fun: User-defined Module method to manage state for.\n\n  Returns:\n    Wrapped method.\n  \"\"\"\n  # Don't rewrap methods that have already had the state management wrapper\n  # applied in the decorator stack.  This wrapper should always be applied\n  # before transformation wrappers.\n  if hasattr(fun, 'method_handler_wrapped'):\n    return fun\n\n  @functools.wraps(fun)\n  def wrapped_module_method(*args, **kwargs):\n    # We might have incorrectly wrappped a callable\n    # that is not a method. Check whether the first arg is self,\n    # otherwise call the wrapped function as is.\n    if args and isinstance(args[0], Module):\n      self, args = args[0], args[1:]\n      return self._call_wrapped_method(fun, args, kwargs)\n    else:\n      return fun(*args, **kwargs)\n\n  wrapped_module_method.method_handler_wrapped = True  # type: ignore[attr-defined]\n  return wrapped_module_method\n\n\ndef wrap_descriptor_once(descriptor) -> 'DescriptorWrapper':\n  \"\"\"Wraps a descriptor to give better error messages.\n\n  Args:\n    descriptor: User-defined Module attribute descriptor.\n\n  Returns:\n    Wrapped descriptor.\n  \"\"\"\n  # Don't rewrap descriptors.\n  if isinstance(descriptor, DescriptorWrapper):\n    return descriptor\n\n  return create_descriptor_wrapper(descriptor)\n\n\ndef _wrap_hash(hash_fn: Callable[..., Any]) -> Callable[..., Any]:\n  \"\"\"Wraps a hash function with some check for Flax Modules.\"\"\"\n\n  @functools.wraps(hash_fn)\n  def wrapped(self):\n    if self.scope is not None:\n      raise TypeError(\"Can't call __hash__ on modules that hold variables.\")\n    try:\n      hash_value = hash_fn(self)\n    except TypeError as exc:\n      raise TypeError(\n        'Failed to hash Flax Module.  '\n        'The module probably contains unhashable attributes.  '\n        f'Module={self}'\n      ) from exc\n    return hash_value\n\n  return wrapped\n\n\ndef _get_unbound_fn(method_or_fn: Callable[..., Any]) -> Callable[..., Any]:\n  \"\"\"Returns an unbound function from a method that is possibly bound.\n\n  This means that if the passed function belongs of an instance of a class, then\n  the returned function does no longer depend on the instance, which is passed\n  as the first argument to the function.\n\n  Args:\n    method_or_fn: A class method or function.\n\n  Returns:\n    An unbound version of input function.\n  \"\"\"\n  if inspect.ismethod(method_or_fn) and isinstance(\n    method_or_fn.__self__, Module\n  ):  # pytype: disable=attribute-error\n    method_or_fn = method_or_fn.__func__  # pytype: disable=attribute-error\n\n  # The method should be callable, and it should have at least one argument\n  # representing the class that is passed in.\n  if (\n    not callable(method_or_fn)\n    or len(inspect.signature(method_or_fn).parameters) < 1\n  ):\n    raise errors.ApplyModuleInvalidMethodError(method_or_fn)\n\n  return method_or_fn\n\n\ndef _map_submodules(fn: Callable[['Module'], Any], tree):\n  \"\"\"Map a function over all submodules in a tree.\"\"\"\n  g = lambda _, x: fn(x) if isinstance(x, Module) else x\n  return _freeze_attr(_map_over_modules_in_tree(g, tree))\n\n\nclass SetupState(enum.IntEnum):\n  # setup() has not been called.\n  NEW = 0\n  # setup() has been called outside a transform boundary.\n  TRANSFORMED = 1\n  # setup() has been called.\n  DONE = 2\n\n\n@dataclasses.dataclass\nclass _ModuleInternalState:\n  \"\"\"Ephemeral Module Evaluation State.\n\n  For clarity, we collect all of the temporary flags and ephemeral state used by\n  Modules for autonaming and error messages here, alongside the rules used\n  to pass this ephemeral state across transform boundaries.\n  \"\"\"\n\n  in_compact_method: bool = False\n  in_setup: bool = False\n  setup_called: SetupState = SetupState.NEW\n  is_initialized: bool = False\n  autoname_cursor: dict[str, int] = dataclasses.field(default_factory=dict)\n  children: dict[str, Union[str, 'Module']] = dataclasses.field(\n    default_factory=dict\n  )\n\n  def reset(self) -> None:\n    \"\"\"Resets transient state.\n\n    This function is called after each module method, so only attributes that\n    are method-dependent are reset.\n    \"\"\"\n    self.in_compact_method = False\n    self.in_setup = False\n    self.autoname_cursor = dict()\n\n  def export(self) -> '_ModuleInternalState':\n    \"\"\"Exports transform-preserved state across transform boundary.\"\"\"\n    setup_state = (\n      SetupState.TRANSFORMED if self.setup_called else SetupState.NEW\n    )\n    cloned = _ModuleInternalState(\n      in_compact_method=self.in_compact_method,\n      in_setup=self.in_setup,\n      setup_called=setup_state,\n      is_initialized=self.is_initialized,\n      autoname_cursor=dict(self.autoname_cursor),\n    )\n    return cloned\n\n  def reimport(self, other: '_ModuleInternalState') -> None:\n    \"\"\"Re-imports transform-preserved state from across transform boundary.\"\"\"\n    self.in_compact_method = other.in_compact_method\n    self.in_setup = other.in_setup\n    self.is_initialized = other.is_initialized\n    self.autoname_cursor = dict(other.autoname_cursor)\n\n\n_uninitialized_module_internal_state = _ModuleInternalState()\n\n\n_UNDEFINED_COPY_PICKLE_METHODS = (\n  '__getstate__',\n  '__setstate__',\n  '__getnewargs_ex__',\n  '__reduce__',\n  '__reduce_ex__',\n  '__copy__',\n  '__deepcopy__',\n)\n\n\n_caches: 'weakref.WeakKeyDictionary[Scope, weakref.WeakValueDictionary[FlaxId, Module]]' = weakref.WeakKeyDictionary()\n\n\ntuple_reduce = lambda xs, x: xs + (x,)\ntuple_init = lambda: ()\n\n\ncapture_call_intermediates = lambda _, method_name: method_name == '__call__'\n\n\nclass ParentDescriptor:\n  \"\"\"Wraps parent module references in weak refs.\n\n  This prevents reference cycles from forming via parent links which can lead\n  to accidental OOMs in eager mode due to slow garbage collection as well as\n  spurious tracer leaks during jit compilation.\n\n  Note: \"descriptors\" are the underlying python mechanism for implementing\n  dynamic @property decorators.  We need to use a raw descriptor instead of the\n  more common decorator in order to force that the appropriate getter/setter\n  logic applies in subclasses even after various dataclass transforms.\n  \"\"\"\n\n  def __get__(self, obj, objtype=None):\n    # check if obj is None, happens during %autoreload\n    if obj is None:\n      return None\n    parent = object.__getattribute__(obj, '_parent_ref')\n    return parent() if isinstance(parent, weakref.ReferenceType) else parent\n\n  def __set__(self, obj, value):\n    maybe_weak = weakref.ref(value) if isinstance(value, Module) else value\n    object.__setattr__(obj, '_parent_ref', maybe_weak)\n\n\nclass Descriptor(tpe.Protocol):\n  __isabstractmethod__: bool\n\n  def __get__(self, obj, objtype=None) -> Any:\n    ...\n\n  def __set__(self, obj, value) -> None:\n    ...\n\n  def __delete__(self, obj) -> None:\n    ...\n\n  def __set_name__(self, owner, name) -> None:\n    ...\n\n\nclass DescriptorWrapper:\n  pass\n\n\ndef create_descriptor_wrapper(descriptor: Descriptor):\n  \"\"\"Creates a descriptor wrapper that calls a get_fn on the descriptor.\"\"\"\n\n  class _DescriptorWrapper(DescriptorWrapper):\n    \"\"\"A descriptor that can wrap any descriptor.\"\"\"\n\n    if hasattr(descriptor, '__isabstractmethod__'):\n      __isabstractmethod__ = descriptor.__isabstractmethod__\n\n    def __init__(self, wrapped: Descriptor):\n      self.wrapped = wrapped\n\n    # conditionally define descriptor methods\n    if hasattr(descriptor, '__get__'):\n\n      def __get__(self, *args, **kwargs):\n        # here we will catch internal AttributeError and re-raise it as a\n        # more informative and correct error message.\n        try:\n          return self.wrapped.__get__(*args, **kwargs)\n        except AttributeError as e:\n          raise errors.DescriptorAttributeError() from e\n\n    if hasattr(descriptor, '__set__'):\n\n      def __set__(self, *args, **kwargs):\n        return self.wrapped.__set__(*args, **kwargs)\n\n    if hasattr(descriptor, '__delete__'):\n\n      def __delete__(self, *args, **kwargs):\n        return self.wrapped.__delete__(*args, **kwargs)\n\n    if hasattr(descriptor, '__set_name__'):\n\n      def __set_name__(self, *args, **kwargs):\n        self.wrapped.__set_name__(*args, **kwargs)\n\n    def __getattr__(self, name):\n      if 'wrapped' not in vars(self):\n        raise AttributeError()\n      return getattr(self.wrapped, name)\n\n  return _DescriptorWrapper(descriptor)\n\n\n# Base Module definition.\n# -----------------------------------------------------------------------------\n\n\ndef module_field(*, kw_only: bool = False, default: Any | None = ...) -> Any:\n  ...\n\n\n# The ModuleBase class is created only to make static analyzers happy\n# mainly pytype and pyright. Some notes:\n# * pyright (correctly) complains that Module itself is not a dataclass, even\n#   though all its subclasses and intances ARE dataclasses. Because there is no\n#   way to annotate this in a way that pyright understands, we create a\n#   ModuleBase class decorated with `dataclass_transform` such that pyright\n#   thinks Module is a dataclass (in reality only subclasses are instantiated\n#   so this is fine).\n# * The `__dataclass_fields__` attribute is needed because pytype seems to\n#   not understand the `dataclass_transform` decorator, therefore we need\n#   to add the attribute manually.\n# * Other attributes are annotated for completeness. Because we are using\n#   the `if typing.TYPE_CHECKING` pattern, these annotations are not present\n#   at runtime so they don't affect the dataclass behavior.\n@tpe.dataclass_transform(field_specifiers=(module_field,))  # type: ignore[literal-required]\nclass ModuleBase:\n  if typing.TYPE_CHECKING:\n    scope: Scope | None\n    _state: _ModuleInternalState\n    _parent_ref: Union['Module', weakref.ReferenceType['Module'], None]\n    __dataclass_fields__: dict[str, dataclasses.Field]\n\n\nclass Module(ModuleBase):\n  \"\"\"Base class for all neural network modules.\n\n  Layers and models should subclass this class.\n\n  All Flax Modules are Python 3.7\n  `dataclasses <https://docs.python.org/3/library/dataclasses.html>`_. Since\n  dataclasses take over ``__init__``, you should instead override :meth:`setup`,\n  which is automatically called to initialize the module.\n\n  Modules can contain submodules, and in this way can be nested in a tree\n  structure. Submodels can be assigned as regular attributes inside the\n  :meth:`setup` method.\n\n  You can define arbitrary \"forward pass\" methods on your Module subclass.\n  While no methods are special-cased, ``__call__`` is a popular choice because\n  it allows you to use module instances as if they are functions::\n\n    >>> from flax import linen as nn\n    >>> from typing import Tuple\n\n    >>> class Module(nn.Module):\n    ...   features: Tuple[int, ...] = (16, 4)\n\n    ...   def setup(self):\n    ...     self.dense1 = nn.Dense(self.features[0])\n    ...     self.dense2 = nn.Dense(self.features[1])\n\n    ...   def __call__(self, x):\n    ...     return self.dense2(nn.relu(self.dense1(x)))\n\n  Optionally, for more concise module implementations where submodules\n  definitions are co-located with their usage, you can use the\n  :meth:`compact` wrapper.\n  \"\"\"\n\n  if typing.TYPE_CHECKING:\n    name: str | None = module_field(kw_only=True, default=None)\n    parent: Union['Module', _Sentinel, None] = module_field(\n      kw_only=True, default=None\n    )\n\n    def __init__(self, *args, **kwargs):\n      # this stub makes sure pytype accepts constructor arguments.\n      pass\n\n    def __call__(self, *args, **kwargs) -> Any:\n      # this stub allows pytype to accept Modules as Callables.\n      pass\n\n  @classmethod\n  def __init_subclass__(cls, kw_only: bool = False, **kwargs: Any) -> None:\n    \"\"\"Automatically initializes all subclasses as custom dataclasses.\"\"\"\n    super().__init_subclass__(**kwargs)\n    # All Flax Modules are dataclasses.  We force this convention since\n    # it encourages the stateless behavior needed to clone module instances for\n    # functional transformation.  Instead of using a python metaclass, we\n    # automatically transform Modules into dataclasses at subclass creation\n    # time, and we set the last dataclass arguments to `parent` and `name`.\n    cls._customized_dataclass_transform(kw_only)\n    # We wrap user-defined methods including setup and __call__ to enforce\n    # a number of different checks and to provide clear error messages.\n    cls._find_compact_name_scope_methods()\n    cls._wrap_module_attributes()\n    # Set empty class defaults.\n    cls._state = _uninitialized_module_internal_state  # type: ignore[attr-defined]\n    cls.scope: Scope | None = None  # type: ignore\n    # Handles weak referencing of parent Modules to prevent reference cycles.\n    cls._parent_ref = None  # type: ignore[attr-defined]\n    cls.parent = ParentDescriptor()  # type: ignore[assignment]\n\n  @classmethod\n  def _customized_dataclass_transform(cls, kw_only: bool):\n    \"\"\"Transforms `cls` into a dataclass, with custom additional behavior.\n\n    1. Inject `parent` and `name` fields.  (If they are already present,\n       then check that they have the expected types.)\n    2. Set compare, hash, and repr to False for non-init fields.\n    3. Generate a hash function (if not provided by cls).\n    \"\"\"\n    # Check reserved attributes have expected type annotations.\n    if sys.version_info < (3, 14):\n      annotations = dict(cls.__dict__.get('__annotations__', {}))\n    else:\n      annotations = inspect.get_annotations(cls)\n    if annotations.get('parent', _ParentType) != _ParentType:\n      raise errors.ReservedModuleAttributeError(annotations)\n    if annotations.get('name', str) not in ('str', str, Optional[str]):\n      raise errors.ReservedModuleAttributeError(annotations)\n\n    # any non-init field will only be set in setup\n    # During __hash__ and __eq__ the field is not set yet\n    # so it should not be used in compare, hash or repr.\n    for field in annotations:\n      field_meta = getattr(cls, field, None)\n      if isinstance(field_meta, dataclasses.Field) and not field_meta.init:\n        field_meta.compare = False\n        field_meta.hash = False\n        field_meta.repr = False\n\n    extra_fields = [\n      (\n        'parent',\n        _ParentType,\n        kw_only_dataclasses.field(\n          repr=False, default=_unspecified_parent, kw_only=True\n        ),\n      ),\n      (\n        'name',\n        Optional[str],\n        kw_only_dataclasses.field(default=None, kw_only=True),\n      ),\n    ]\n\n    if kw_only:\n      if tuple(sys.version_info)[:3] >= (3, 10, 0):\n        for (\n          name,\n          annotation,  # pytype: disable=invalid-annotation\n          default,\n        ) in extra_fields:\n          setattr(cls, name, default)\n          cls.__annotations__[name] = annotation\n        dataclasses.dataclass(  # type: ignore[call-overload]\n          unsafe_hash='__hash__' not in cls.__dict__,\n          repr=False,\n          kw_only=True,\n        )(cls)\n      else:\n        raise TypeError('`kw_only` is not available before Py 3.10.')\n    else:\n      # Now apply dataclass transform (which operates in-place).\n      # Do generate a hash function only if not provided by the class.\n      kw_only_dataclasses.dataclass(\n        cls,\n        unsafe_hash='__hash__' not in cls.__dict__,\n        repr=False,\n        extra_fields=extra_fields,\n      )  # pytype: disable=wrong-keyword-args\n\n    cls.__hash__ = _wrap_hash(cls.__hash__)  # type: ignore[method-assign]\n\n  @classmethod\n  def _find_compact_name_scope_methods(cls):\n    \"\"\"Finds all compact_name_scope methods in the class.\"\"\"\n    methods = [m[0] for m in inspect.getmembers(cls, predicate=callable)]\n    compact_name_scope_fns = tuple(\n      method_name\n      for method_name in methods\n      if hasattr(getattr(cls, method_name), 'compact_name_scope')\n    )\n    cls._compact_name_scope_methods = compact_name_scope_fns\n\n  @classmethod\n  def _wrap_module_attributes(cls):\n    \"\"\"Wraps user-defined non-inherited methods and descriptors with state\n\n    management functions.\n    \"\"\"\n    # wrap methods\n    method_exclusions = [f.name for f in dataclasses.fields(cls)] + [\n      '__eq__',\n      '__repr__',\n      '__init__',\n      '__hash__',\n      '__post_init__',\n    ]\n    for key in _get_local_method_names(cls, exclude=method_exclusions):\n      method = getattr(cls, key)\n      if hasattr(method, 'nowrap'):\n        continue\n      setattr(cls, key, wrap_method_once(method))\n\n    # wrap descriptors\n    descriptor_exclusions = [f.name for f in dataclasses.fields(cls)] + [\n      'parent',\n      '__dict__',\n    ]\n    for key in _get_local_descriptor_names(cls, descriptor_exclusions):\n      # don't use getattr here, since it will call the descriptor\n      descriptor = cls.__dict__[key]\n      if hasattr(descriptor, 'nowrap'):\n        continue\n      setattr(cls, key, wrap_descriptor_once(descriptor))\n    return cls\n\n  def _call_wrapped_method(self, fun, args, kwargs):\n    \"\"\"Calls a wrapped method.\n\n    This function is responsible for setting up the thread local state\n    correctly before calling the method and cleaning up afterwards.\n    This includes storing intermediates, setup of the compact scope,\n    and making sure setup is called before any other method.\n\n    Args:\n      fun: The wrapped method.\n      args: Named arguments passed to ``fun``.\n      kwargs: Keyword arguments passed to ``fun``.\n\n    Returns:\n      The results of calling ``fun``.\n    \"\"\"\n    is_compact_method = hasattr(fun, 'compact')\n    fun_name = _get_fn_name(fun)\n    is_setup_method = fun_name == 'setup'\n    add_call_info = not is_setup_method and len(_context.call_info_stack) > 0\n    # We lazily call setup() only when needed.\n    if is_setup_method:\n      if self.scope is None:\n        raise errors.CallSetupUnboundModuleError()\n      is_recurrent = self._state.in_setup\n      self._state.in_setup = True\n    else:\n      self._try_setup()\n\n    if is_compact_method:\n      if self.scope is None:\n        raise errors.CallCompactUnboundModuleError()\n      is_recurrent = self._state.in_compact_method\n      self._state.in_compact_method = True\n    _context.module_stack.append(self)\n    try:\n      # get call info\n      if add_call_info:\n        assert self.scope is not None\n        call_index = _context.call_info_stack[-1].get_call_index()\n\n      if _global_interceptor_stack:\n        run_fun = functools.partial(run_interceptors, fun)\n      else:\n        run_fun = fun\n\n      # call method\n      if _use_named_call:\n        with jax.named_scope(_derive_profiling_name(self, fun)):\n          y = run_fun(self, *args, **kwargs)\n      else:\n        y = run_fun(self, *args, **kwargs)\n\n      if _context.capture_stack:\n        filter_fn = _context.capture_stack[-1]\n        if filter_fn and filter_fn(self, fun_name):\n          self.sow('intermediates', fun_name, y)\n      if add_call_info:\n        _args, _kwargs, _y = flax.linen.summary._represent_tree(\n          (args, kwargs, y)\n        )\n        _context.call_info_stack[-1].calls.append(\n          _CallInfo(\n            call_index,\n            self.path,\n            self.clone(),\n            self.scope.rngs,\n            self.scope.mutable,\n            fun.__name__,\n            _args,\n            _kwargs,\n            _y,\n          )\n        )\n      return y\n    finally:\n      _context.module_stack.pop()\n      if is_compact_method:\n        object.__setattr__(self, 'scope', self.scope.rewound())\n      # setup or compact calls can be recurrent for example due to super calls\n      # resetting the state would cause is compact/setup method\n      # to be set to False prematurely.\n      if (is_compact_method or is_setup_method) and not is_recurrent:\n        self._state.reset()\n\n  def __setattr__(self, name: str, val: Any):\n    \"\"\"Sets an attribute on this Module.\n\n    We overload setattr solely to support pythonic naming via assignment of\n    submodules in the special :meth:`setup` function::\n\n      self.submodule_name = MyModule(...)\n\n    We also support lists and other general pytrees, e.g.::\n\n      self.submodules = [MyModule0(..), MyModule1(..), ...]\n\n    Args:\n      name: Attribute to set.\n      val: Value of the attribute.\n    \"\"\"\n    fields = self.__dataclass_fields__  # pytype: disable=attribute-error\n    is_dataclass_attr = name in fields and fields[name].init\n\n    if not self._state.in_setup:\n      if not self._state.is_initialized:\n        # Setting attributes before end of Module.__post_init__()\n        object.__setattr__(self, name, val)\n        return\n      else:\n        # If the attribute is a python special method, we allow setting it (this\n        # is useful e.g. for IPython auto-reload).\n        if name.startswith('__'):\n          object.__setattr__(self, name, val)\n          return\n        # We're past all initialization and setup logic:\n        # Raises a TypeError just like frozen python dataclasses.\n        raise errors.SetAttributeFrozenModuleError(\n          self.__class__.__name__, name, val\n        )\n\n    # We're inside the setup() method:\n    if is_dataclass_attr:\n      # These names are specified as dataclass fields. They should not be\n      # initialized within the setup() method, but can be modified freely\n      # before it.\n      raise errors.SetAttributeInModuleSetupError()\n\n    # Values (that may be variables or submodules) are being defined and\n    # attached in setup(), we run some extra logic in that case.\n    self._register_submodules(name, val)\n\n  def __getattr__(self, name: str) -> Any:\n    \"\"\"Call setup() before getting any setup-defined attributes.\"\"\"\n    # We don't want to return anything for python copy / pickle methods.\n    if name in _UNDEFINED_COPY_PICKLE_METHODS:\n      raise AttributeError()\n    self._try_setup()\n    if name in self.__dict__:\n      return self.__dict__[name]\n    else:\n      msg = f'\"{self.__class__.__name__}\" object has no attribute \"{name}\".'\n      if self.scope is None:\n        msg += (\n          f' If \"{name}\" is defined in \\'.setup()\\', remember these fields '\n          \"are only accessible from inside 'init' or 'apply'.\"\n        )\n      raise AttributeError(msg)\n\n  def __dir__(self) -> list[str]:\n    \"\"\"Call setup() before listing attributes.\"\"\"\n    self._try_setup()\n    return object.__dir__(self)  # type: ignore\n\n  def __post_init__(self) -> None:\n    # DO NOT REMOVE - Marker for internal logging.\n    # In dataclasses, __init__ is overridden to process dataclass arguments,\n    # and __post_init__ is called immediately afterwards. Here, depending on the\n    # type of `parent` passed to initialize the Module, we either defer\n    # initialization, attach this Module as a submodule of a parent, or bind\n    # this Module at the top-level to variables and rngs.\n\n    object.__setattr__(self, '_id', uuid())\n    object.__setattr__(self, '_state', _ModuleInternalState())\n\n    # Typically we set the parent based on the dynamic module context.\n    if self.parent is _unspecified_parent:  # pytype: disable=attribute-error\n      object.__setattr__(self, 'parent', _context.module_stack[-1])\n\n    # Initialization is deferred for top level Modules or any other \"orphan\"\n    # Modules until attachment by __setattr__ i.e. MyModule(..., parent=None)\n    if self.parent is None:\n      return\n\n    # Register submodule on parent Module.\n    if isinstance(self.parent, Module):\n      # When initializing an unnamed Module inside setup()\n      # initialization is deferred until attachment by __setattr__\n      # i.e. self.mymodule = MyModule(...)\n      self.name: str | None\n      if (\n        self.parent._state.in_setup and self.name is None\n      ):  # pytype: disable=attribute-error\n        return\n      if not self.parent._initialization_allowed:\n        raise errors.AssignSubModuleError(self.__class__.__name__)\n      # Autonaming of submodules.\n      if self.name is None:  # pytype: disable=attribute-error\n        prefix = f'{self.__class__.__name__}'\n        cursor = self.parent._state.autoname_cursor.get(prefix, 0)\n        self.name = f'{prefix}_{cursor}'\n        self.parent._state.autoname_cursor[prefix] = cursor + 1\n      # Allow scope aliasing under transforms for submodules defined in setup.\n      reuse_scopes = (\n        self.parent._state.in_setup\n        and self.parent._state.setup_called == SetupState.TRANSFORMED\n      )\n      # Perform name-collision check.\n      if self.parent._name_taken(self.name, reuse_scopes=reuse_scopes):\n        parent_class = self.parent.__class__.__name__\n        raise errors.NameInUseError('submodule', self.name, parent_class)\n      # Finalize attachment to parent and scope initialization.\n      self.parent._state.children[self.name] = self\n      assert self.parent.scope is not None\n      object.__setattr__(\n        self, 'scope', self.parent.scope.push(self.name, reuse=reuse_scopes)\n      )\n\n    # Top-level invocation with a functional Scope.\n    elif isinstance(self.parent, Scope):\n      object.__setattr__(self, 'scope', self.parent)\n    else:\n      raise ValueError('parent must be None, Module or Scope')\n\n    # eagerly bind submodules if scope is available\n    if self.scope is not None:\n      for field in dataclasses.fields(self):\n        if field.name not in ('parent', 'name') and field.init:\n          self._register_submodules(field.name, getattr(self, field.name))\n\n    self._state.is_initialized = True\n\n  def __repr__(self) -> str:\n    return _module_repr(self)\n\n  def setup(self) -> None:\n    \"\"\"Initializes a Module lazily (similar to a lazy ``__init__``).\n\n    ``setup`` is called once lazily on a module instance when a module\n    is bound, immediately before any other methods like ``__call__`` are\n    invoked, or before a ``setup``-defined attribute on ``self`` is accessed.\n\n    This can happen in three cases:\n\n      1. Immediately when invoking :meth:`apply`, :meth:`init` or\n         :meth:`init_and_output`.\n\n      2. Once the module is given a name by being assigned to an attribute of\n         another module inside the other module's ``setup`` method\n         (see :meth:`__setattr__`)::\n\n            >>> class MyModule(nn.Module):\n            ...   def setup(self):\n            ...     submodule = nn.Conv(...)\n\n            ...     # Accessing `submodule` attributes does not yet work here.\n\n            ...     # The following line invokes `self.__setattr__`, which gives\n            ...     # `submodule` the name \"conv1\".\n            ...     self.conv1 = submodule\n\n            ...     # Accessing `submodule` attributes or methods is now safe and\n            ...     # either causes setup() to be called once.\n\n      3. Once a module is constructed inside a method wrapped with\n         :meth:`compact`, immediately before another method is called or\n         ``setup`` defined attribute is accessed.\n    \"\"\"\n    pass\n\n  def _register_submodules(self, name, val):\n    \"\"\"Registers a submodule.\"\"\"\n    assert self.scope, 'Trying to register submodules on unbound scope.'\n    root = self.scope.root\n    cache = _caches.get(root, weakref.WeakValueDictionary())\n    _caches[root] = cache\n    queue = []\n    preserve_adopted_names = config.flax_preserve_adopted_names\n    if hasattr(type(self), 'preserve_adopted_names'):\n      preserve_adopted_names = type(self).preserve_adopted_names\n\n    def adopt_attr_modules(cache, queue, suffix, subvalue):\n      if isinstance(subvalue, Module):\n        current_name = subvalue.name\n        adopted_name = None\n        if subvalue.parent is None:\n          # Preserve sharing-by-reference relationships during adoption\n          # via cache keyed on unique instance ids.\n          key = subvalue._id\n          # Module was passed from outside. It needs to be cloned.\n          # Outside modules are named by attachment, not an outer name,\n          # UNLESS we're using new adopted name policy, in which case an existing\n          # name will be used, as is often supplied by config systems.\n          if preserve_adopted_names:\n            adopted_name = object.__getattribute__(subvalue, 'name')\n          if key in cache:\n            subvalue = cache[key]\n          else:\n            subvalue = subvalue.clone(name=None)\n            cache[key] = subvalue\n        if subvalue.name is None:\n          object.__setattr__(subvalue, 'parent', self)\n          if adopted_name is None:\n            adopted_name = (\n              f'{name}{suffix}'\n              if not isinstance(subvalue, CompactNameScope)\n              else current_name\n            )\n          object.__setattr__(subvalue, 'name', adopted_name)\n          queue.append(subvalue)\n      return subvalue\n\n    val = _freeze_attr(\n      _map_over_modules_in_tree(\n        functools.partial(adopt_attr_modules, cache, queue), val\n      )\n    )\n    object.__setattr__(self, name, val)\n    for x in queue:\n      x.__post_init__()\n\n  def _try_setup(self, shallow: bool = False) -> None:\n    \"\"\"Tries to setup module if scope is available and setup has not been called yet.\"\"\"\n    if (\n      self.scope\n      and not self._state.in_setup\n      and self._state.setup_called != SetupState.DONE\n    ):\n      try:\n        self._state.in_setup = True\n        # A shallow setup will only register attribute submodules but it does\n        # not call the user's setup. This avoids running before a\n        # transformation.\n        for field in dataclasses.fields(self):\n          if field.name not in ('parent', 'name') and field.init:\n            self._register_submodules(field.name, getattr(self, field.name))\n        if not shallow:\n          self.setup()\n          # create NonTransparent Modules\n          self._compact_name_scope_modules = {\n            name: CompactNameScope(\n              getattr(type(self), name).inner_fun, lambda: self, name=name\n            )\n            for name in self._compact_name_scope_methods\n          }\n\n        # We run static checks abstractly once for setup before any transforms\n        # to detect name collisions and other python errors.\n        elif self._state.setup_called == SetupState.NEW:\n          self._validate_setup()\n      finally:\n        self._state.in_setup = False\n        if not shallow:\n          self._state.setup_called = SetupState.DONE\n\n  def _validate_setup(self) -> None:\n    \"\"\"Abstractly evaluates setup only to run static checks.\"\"\"\n\n    def run_setup_only(x):\n      wrapped_id = wrap_method_once(lambda m, x: x)\n      with TestScope({}, rngs={}, mutable=True).temporary() as root:\n        return wrapped_id(self.clone(parent=root), x)\n\n    _ = jax.eval_shape(run_setup_only, 0)\n\n  def _name_taken(\n    self,\n    name: str,\n    reuse_scopes: bool = False,\n    collection: str | None = None,\n  ) -> bool:\n    assert self.scope is not None\n    if reuse_scopes:\n      return False\n    return self.scope.name_reserved(name, collection)\n\n  @property\n  def _initialization_allowed(self):\n    return (\n      not self._state.is_initialized  # allow eager attachment in post-init\n      or self._state.in_setup\n      or self._state.in_compact_method\n    )\n\n  @property\n  def path(self):\n    \"\"\"Get the path of this Module. Top-level root modules have an empty path ``()``.\n    Note that this method can only be used on bound modules that have a valid scope.\n\n    Example usage::\n\n      >>> import flax.linen as nn\n      >>> import jax, jax.numpy as jnp\n\n      >>> class SubModel(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     print(f'SubModel path: {self.path}')\n      ...     return x\n\n      >>> class Model(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     print(f'Model path: {self.path}')\n      ...     return SubModel()(x)\n\n      >>> model = Model()\n      >>> variables = model.init(jax.random.key(0), jnp.ones((1, 2)))\n      Model path: ()\n      SubModel path: ('SubModel_0',)\n    \"\"\"\n\n    if self.scope is None:\n      raise ValueError(\"Can't access module paths on unbound modules.\")\n\n    return self.scope.path\n\n  def clone(\n    self: M,\n    *,\n    parent: Union[Scope, 'Module', _Sentinel] | None = None,\n    _deep_clone: bool | weakref.WeakValueDictionary = False,\n    _reset_names: bool = False,\n    **updates,\n  ) -> M:\n    \"\"\"Creates a clone of this Module, with optionally updated arguments.\n\n    NOTE: end users are encouraged to use the ``copy`` method.  ``clone`` is used\n      primarily for internal routines, and ``copy`` offers simpler arguments and\n      better defaults.\n\n    Args:\n      parent: The parent of the clone. The clone will have no parent if no\n        explicit parent is specified.\n      _deep_clone: A boolean or a weak value dictionary to control deep cloning\n        of submodules. If True, submodules will be cloned recursively. If a weak\n        value dictionary is passed, it will be used to cache cloned submodules.\n        This flag is used by init/apply/bind to avoid scope leakage.\n      _reset_names: If True, ``name=None`` is also passed to submodules when\n        cloning. Resetting names in submodules is necessary when calling ``.unbind``.\n      **updates: Attribute updates.\n\n    Returns:\n      A clone of the this Module with the updated attributes and parent.\n    \"\"\"\n    attrs = {\n      f.name: getattr(self, f.name) for f in dataclasses.fields(self) if f.init\n    }\n\n    attrs.update(parent=parent, **updates)\n\n    # Here we implement deep cloning of submodules, this is necessary to avoid scope leakage\n    # from external submodules into init/apply/bind while preserving sharing-by-reference\n    # relationships between submodules.\n    if _deep_clone != False:\n      # We use a weak value dictionary to cache cloned submodules. When a shared\n      # submodule is cloned, its only cloned once else its fetched from the cache.\n      cache = (\n        weakref.WeakValueDictionary()\n        if isinstance(_deep_clone, bool)\n        else _deep_clone\n      )\n\n      def clone_fn(m: Module) -> Module:\n        if hasattr(m, '_id'):\n          key = m._id\n          if key in cache:\n            return cache[key]\n          else:\n            if _reset_names:\n              clone = m.clone(\n                _deep_clone=cache, _reset_names=_reset_names, name=None\n              )\n            else:\n              clone = m.clone(_deep_clone=cache)\n            cache[key] = clone\n            return clone\n        else:\n          # If the module doesn't have an _id attribute it could be a mock object\n          # so we return it as is.\n          return m\n\n      # _map_submodules will map over all submodules inside attrs\n      # value here can be any pytree, non-module values are ignored\n      for field_name, value in attrs.items():\n        if field_name == 'parent':\n          continue\n        attrs[field_name] = _map_submodules(clone_fn, value)\n\n    module = self.__class__(**attrs)\n\n    return module\n\n  def copy(\n    self: M,\n    *,\n    parent: Union[Scope, 'Module', _Sentinel] | None = _unspecified_parent,\n    name: str | None = None,\n    **updates,\n  ) -> M:\n    \"\"\"Creates a copy of this Module, with optionally updated arguments.\n\n    Args:\n      parent: The parent of the copy.  By default the current module is taken\n        as parent if not explicitly specified.\n      name: A new name for the copied Module, by default a new automatic name\n        will be given.\n      **updates: Attribute updates.\n\n    Returns:\n      A copy of the this Module with the updated name, parent, and attributes.\n    \"\"\"\n    return self.clone(\n      parent=parent, name=name, _deep_clone=True, _reset_names=False, **updates\n    )\n\n  @overload\n  def variable(\n    self,\n    col: str,\n    name: str,\n    init_fn: Callable[..., T] | None = None,\n    *init_args,\n  ) -> Variable[T]:\n    ...\n\n  @overload\n  def variable(\n    self,\n    col: str,\n    name: str,\n    init_fn: Callable[..., T] | None = None,\n    *init_args,\n    unbox: Literal[True],\n    **init_kwargs,\n  ) -> Variable[T]:\n    ...\n\n  @overload\n  def variable(\n    self,\n    col: str,\n    name: str,\n    init_fn: Callable[..., T] | None = None,\n    *init_args,\n    unbox: Literal[False],\n    **init_kwargs,\n  ) -> Variable[meta.AxisMetadata[T]]:\n    ...\n\n  @overload\n  def variable(\n    self,\n    col: str,\n    name: str,\n    init_fn: Callable[..., T] | None = None,\n    *init_args,\n    unbox: bool = True,\n    **init_kwargs,\n  ) -> Variable[T] | Variable[meta.AxisMetadata[T]]:\n    ...\n\n  def variable(\n    self,\n    col: str,\n    name: str,\n    init_fn: Callable[..., T] | None = None,\n    *init_args,\n    unbox: bool = True,\n    **init_kwargs,\n  ) -> Variable[T] | Variable[meta.AxisMetadata[T]]:\n    \"\"\"Declares and returns a variable in this Module.\n\n    See :mod:`flax.core.variables` for more information. See also :meth:`param`\n    for a shorthand way to define read-only variables in the \"params\"\n    collection.\n\n    Contrary to :meth:`param`, all arguments passing using ``init_fn`` should be\n    passed on explicitly::\n\n      >>> class Foo(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     x = nn.Dense(4)(x)\n      ...     key = self.make_rng('stats')\n      ...     mean = self.variable('stats', 'mean', nn.initializers.lecun_normal(), key, x.shape)\n      ...     ...\n      ...     return x * mean.value\n      >>> variables = Foo().init({'params': jax.random.key(0), 'stats': jax.random.key(1)}, jnp.ones((2, 3)))\n      >>> jax.tree_util.tree_map(jnp.shape, variables)\n      {'params': {'Dense_0': {'bias': (4,), 'kernel': (3, 4)}}, 'stats': {'mean': (2, 4)}}\n\n    In the example above, the function ``lecun_normal`` expects two arguments:\n    ``key`` and ``shape``, and both have to be passed on. The PRNG for ``stats``\n    has to be provided explicitly when calling :meth:`init` and :meth:`apply`.\n\n    Args:\n      col: The variable collection name.\n      name: The variable name.\n      init_fn: The function that will be called to compute the initial value of\n        this variable. This function will only be called the first time this\n        variable is used in this module. If None, the variable must already be\n        initialized otherwise an error is raised.\n      *init_args: The positional arguments to pass to init_fn.\n      unbox: If True, ``AxisMetadata`` instances are replaced by their unboxed\n        value, see ``flax.nn.meta.unbox`` (default: True).\n      **init_kwargs: The key-word arguments to pass to init_fn\n\n    Returns:\n      A :class:`flax.core.variables.Variable` that can be read or set via\n      \".value\" attribute. Throws an error if the variable exists already.\n    \"\"\"\n    if not self._initialization_allowed:\n      raise ValueError(\n        'Variables must be initialized in `setup()` or in a method '\n        'wrapped in `@compact`'\n      )\n    if self._name_taken(name, collection=col):\n      raise errors.NameInUseError('variable', name, self.__class__.__name__)\n    assert self.scope is not None\n    v = self.scope.variable(\n      col, name, init_fn, *init_args, unbox=unbox, **init_kwargs\n    )\n    self._state.children[name] = col\n    return v\n\n  @overload\n  def param(\n    self, name: str, init_fn: Callable[..., T], *init_args,\n  ) -> T:\n    ...\n\n  @overload\n  def param(\n      self,\n      name: str,\n      init_fn: Callable[..., meta.AxisMetadata[T]] | Callable[..., T],\n      *init_args,\n      unbox: Literal[True],\n      **init_kwargs,\n  ) -> T:\n    ...\n\n  @overload\n  def param(\n      self,\n      name: str,\n      init_fn: Callable[..., T],\n      *init_args,\n      unbox: Literal[False],\n      **init_kwargs,\n  ) -> T:\n    ...\n\n  @overload\n  def param(\n    self,\n    name: str,\n    init_fn: Callable[..., T | meta.AxisMetadata[T]],\n    *init_args,\n    unbox: bool,\n    **init_kwargs,\n  ) -> T | meta.AxisMetadata[T]:\n    ...\n  def param(\n    self,\n    name: str,\n    init_fn: Callable[..., T | meta.AxisMetadata[T]],\n    *init_args,\n    unbox: bool = True,\n    **init_kwargs,\n  ) -> T | meta.AxisMetadata[T]:\n    \"\"\"Declares and returns a parameter in this Module.\n\n    Parameters are read-only variables in the collection named \"params\". See\n    :mod:`flax.core.variables` for more details on variables.\n\n    The first argument of ``init_fn`` is assumed to be a PRNG key, which is\n    provided automatically and does not have to be passed using ``init_args``\n    or ``init_kwargs``::\n\n      >>> class Foo(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     x = nn.Dense(4)(x)\n      ...     mean = self.param('mean', nn.initializers.lecun_normal(), x.shape)\n      ...     ...\n      ...     return x * mean\n      >>> variables = Foo().init({'params': jax.random.key(0), 'stats': jax.random.key(1)}, jnp.ones((2, 3)))\n      >>> jax.tree_util.tree_map(jnp.shape, variables)\n      {'params': {'Dense_0': {'bias': (4,), 'kernel': (3, 4)}, 'mean': (2, 4)}}\n\n    In the example above, the function ``lecun_normal`` expects two arguments:\n    ``key`` and ``shape``, but only ``shape`` has to be provided explicitly;\n    ``key`` is set automatically using the PRNG for ``params`` that is passed\n    when initializing the module using :meth:`init`.\n\n    Args:\n      name: The parameter name.\n      init_fn: The function that will be called to compute the initial value of\n        this variable. This function will only be called the first time this\n        parameter is used in this module.\n      *init_args: The positional arguments to pass to init_fn.\n      unbox: If True, ``AxisMetadata`` instances are replaced by their unboxed\n        value, see ``flax.nn.meta.unbox`` (default: True).\n      **init_kwargs: The key-word arguments to pass to init_fn.\n\n    Returns:\n      The value of the initialized parameter. Throws an error if the parameter\n      exists already.\n    \"\"\"\n    if not self._initialization_allowed:\n      raise ValueError(\n        'Parameters must be initialized in `setup()` or in a method '\n        'wrapped in `@compact`'\n      )\n    if self._name_taken(name, collection='params'):\n      raise errors.NameInUseError('param', name, self.__class__.__name__)\n    assert self.scope is not None\n    v: T | meta.AxisMetadata[T] = self.scope.param(\n        name, init_fn, *init_args, unbox=unbox, **init_kwargs\n    )\n    self._state.children[name] = 'params'\n    return v\n\n  def has_variable(self, col: str, name: str) -> bool:\n    \"\"\"Checks if a variable of given collection and name exists in this Module.\n\n    See :mod:`flax.core.variables` for more explanation on variables and\n    collections.\n\n    Args:\n      col: The variable collection name.\n      name: The name of the variable.\n\n    Returns:\n      True if the variable exists.\n    \"\"\"\n    if self.scope is None:\n      raise ValueError(\"Can't access variables on unbound modules\")\n    return self.scope.has_variable(col, name)\n\n  def is_mutable_collection(self, col: str) -> bool:\n    \"\"\"Returns true if the collection ``col`` is mutable.\"\"\"\n    if self.scope is None:\n      raise ValueError(\"Can't check mutability on unbound modules\")\n    return self.scope.is_mutable_collection(col)\n\n  def has_rng(self, name: str) -> bool:\n    \"\"\"Returns true if a PRNGSequence with name ``name`` exists.\"\"\"\n    if self.scope is None:\n      raise ValueError(\"Can't query for RNGs on unbound modules\")\n    return self.scope.has_rng(name)\n\n  def make_rng(self, name: str = 'params') -> PRNGKey:\n    \"\"\"Returns a new RNG key from a given RNG sequence for this Module.\n\n    The new RNG key is split from the previous one. Thus, every call to\n    ``make_rng`` returns a new RNG key, while still guaranteeing full\n    reproducibility.\n\n    .. note::\n      If an invalid name is passed (i.e. no RNG key was passed by\n      the user in ``.init`` or ``.apply`` for this name), then ``name``\n      will default to ``'params'``.\n\n    Example::\n\n      >>> import jax\n      >>> import flax.linen as nn\n\n      >>> class ParamsModule(nn.Module):\n      ...   def __call__(self):\n      ...     return self.make_rng('params')\n      >>> class OtherModule(nn.Module):\n      ...   def __call__(self):\n      ...     return self.make_rng('other')\n\n      >>> key = jax.random.key(0)\n      >>> params_out, _ = ParamsModule().init_with_output({'params': key})\n      >>> # self.make_rng('other') will default to using the 'params' RNG stream\n      >>> other_out, _ = OtherModule().init_with_output({'params': key})\n      >>> assert params_out == other_out\n\n    Learn more about RNG's by reading the Flax RNG guide:\n    https://flax.readthedocs.io/en/latest/guides/flax_fundamentals/rng_guide.html\n\n    Args:\n      name: The RNG sequence name.\n\n    Returns:\n      The newly generated RNG key.\n    \"\"\"\n    if self.scope is None:\n      raise ValueError(\"Can't use RNGs on unbound modules\")\n    return self.scope.make_rng(name)\n\n  def is_initializing(self) -> bool:\n    \"\"\"Returns True if running under self.init(...) or nn.init(...)().\n\n    This is a helper method to handle the common case of simple initialization\n    where we wish to have setup logic occur when only called under\n    ``module.init`` or ``nn.init``.  For more complicated multi-phase\n    initialization scenarios it is better to test for the mutability of\n    particular variable collections or for the presence of particular\n    variables that potentially need to be initialized.\n    \"\"\"\n    if self.scope is None:\n      raise ValueError(\"Can't check if running under init() on unbound modules\")\n    return self.scope.get_flag('initializing', False)\n\n  def _module_checks(self):\n    \"\"\"Run standard runtime checks.\"\"\"\n\n    if not isinstance(self, Module):\n      raise errors.InvalidInstanceModuleError()\n\n    overridden_post_init = self.__post_init__ != Module.__post_init__\n    if overridden_post_init and not hasattr(self, '_id'):\n      raise errors.IncorrectPostInitOverrideError()\n\n  @traceback_util.api_boundary\n  def bind(\n    self: M,\n    variables: VariableDict,\n    *args,\n    rngs: RNGSequences | None = None,\n    mutable: CollectionFilter = False,\n  ) -> M:\n    \"\"\"Creates an interactive Module instance by binding variables and RNGs.\n\n    ``bind`` provides an \"interactive\" instance of a Module directly without\n    transforming a function with ``apply``. This is particularly useful for\n    debugging and interactive use cases like notebooks where a function would\n    limit the ability to split up code into different cells.\n\n    Once the variables (and optionally RNGs) are bound to a ``Module`` it\n    becomes a stateful object. Note that idiomatic JAX is functional and\n    therefore an interactive instance does not mix well with vanilla JAX APIs.\n    ``bind()`` should only be used for interactive experimentation, and in all\n    other cases we strongly encourage users to use ``apply()`` instead.\n\n    Example::\n\n      >>> import jax\n      >>> import jax.numpy as jnp\n      >>> import flax.linen as nn\n\n      >>> class AutoEncoder(nn.Module):\n      ...   def setup(self):\n      ...     self.encoder = nn.Dense(3)\n      ...     self.decoder = nn.Dense(5)\n      ...\n      ...   def __call__(self, x):\n      ...     return self.decoder(self.encoder(x))\n\n      >>> x = jnp.ones((16, 9))\n      >>> ae = AutoEncoder()\n      >>> variables = ae.init(jax.random.key(0), x)\n      >>> model = ae.bind(variables)\n      >>> z = model.encoder(x)\n      >>> x_reconstructed = model.decoder(z)\n\n    Args:\n      variables: A dictionary containing variables keyed by variable\n        collections. See :mod:`flax.core.variables` for more details about\n        variables.\n      *args: Named arguments (not used).\n      rngs: a dict of PRNGKeys to initialize the PRNG sequences.\n      mutable: Can be bool, str, or list. Specifies which collections should be\n        treated as mutable: ``bool``: all/no collections are mutable. ``str``:\n        The name of a single mutable collection. ``list``: A list of names of\n        mutable collections.\n\n    Returns:\n      A copy of this instance with bound variables and RNGs.\n    \"\"\"\n    Module._module_checks(self)\n\n    del args\n    scope = core.bind(variables, rngs=rngs, mutable=mutable)\n    return self.clone(parent=scope, _deep_clone=True)\n\n  def unbind(self: M) -> tuple[M, VariableDict]:\n    \"\"\"Returns an unbound copy of a Module and its variables.\n\n    ``unbind`` helps create a stateless version of a bound Module.\n\n    An example of a common use case: to extract a sub-Module defined inside\n    ``setup()`` and its corresponding variables: 1) temporarily ``bind`` the\n    parent Module; and then 2) ``unbind`` the desired sub-Module. (Recall that\n    ``setup()`` is only called when the Module is bound.)::\n\n      >>> class Encoder(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     ...\n      ...     return nn.Dense(256)(x)\n\n      >>> class Decoder(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     ...\n      ...     return nn.Dense(784)(x)\n\n      >>> class AutoEncoder(nn.Module):\n      ...   def setup(self):\n      ...     self.encoder = Encoder()\n      ...     self.decoder = Decoder()\n      ...\n      ...   def __call__(self, x):\n      ...     return self.decoder(self.encoder(x))\n\n      >>> module = AutoEncoder()\n      >>> variables = module.init(jax.random.key(0), jnp.ones((1, 784)))\n\n      >>> # Extract the Encoder sub-Module and its variables\n      >>> encoder, encoder_vars = module.bind(variables).encoder.unbind()\n\n    Returns:\n      A tuple with an unbound copy of this Module and its variables.\n    \"\"\"\n    Module._module_checks(self)\n\n    if self.scope is None:\n      raise errors.CallUnbindOnUnboundModuleError()\n\n    variables = self.variables\n    module = self.clone(_deep_clone=True, _reset_names=True, name=None)\n    return module, variables\n\n  @traceback_util.api_boundary\n  def apply(\n    self,\n    variables: VariableDict,\n    *args,\n    rngs: PRNGKey | RNGSequences | None = None,\n    method: Callable[..., Any] | str | None = None,\n    mutable: CollectionFilter = False,\n    capture_intermediates: bool | Callable[['Module', str], bool] = False,\n    **kwargs,\n  ) -> Any | tuple[Any, FrozenVariableDict | dict[str, Any]]:\n    \"\"\"Applies a module method to variables and returns output and modified variables.\n\n    Note that ``method`` should be set if one would like to call ``apply`` on a\n    different class method than ``__call__``. For instance, suppose a\n    Transformer modules has a method called ``encode``, then the following calls\n    ``apply`` on that method::\n\n      >>> import flax.linen as nn\n      >>> import jax, jax.numpy as jnp\n      >>> import numpy as np\n\n      >>> class Transformer(nn.Module):\n      ...   def encode(self, x):\n      ...     ...\n\n      >>> x = jnp.ones((16, 9))\n      >>> model = Transformer()\n      >>> variables = model.init(jax.random.key(0), x, method=Transformer.encode)\n\n      >>> encoded = model.apply(variables, x, method=Transformer.encode)\n\n    If a function instance is provided, the unbound function is used. For\n    instance, the example below is equivalent to the one above::\n\n      >>> encoded = model.apply(variables, x, method=model.encode)\n\n    You can also pass a string to a callable attribute of the module. For\n    example, the previous can be written as::\n\n      >>> encoded = model.apply(variables, x, method='encode')\n\n    Note ``method`` can also be a function that is not defined in\n    ``Transformer``. In that case, the function should have at least one\n    argument representing an instance of the Module class::\n\n      >>> def other_fn(instance, x):\n      ...   # instance.some_module_attr(...)\n      ...   instance.encode\n      ...   ...\n\n      >>> model.apply(variables, x, method=other_fn)\n\n    If you pass a single ``PRNGKey``, Flax will use it to feed the ``'params'``\n    RNG stream.  If you want to use a different RNG stream or need to use\n    multiple streams, you can pass a dictionary mapping each RNG stream name\n    to its corresponding ``PRNGKey`` to ``apply``. If ``self.make_rng(name)``\n    is called on an RNG stream name that isn't passed by the user, it will\n    default to using the ``'params'`` RNG stream.\n\n    Example::\n\n      >>> class Foo(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x, add_noise=False):\n      ...     x = nn.Dense(16)(x)\n      ...     x = nn.relu(x)\n      ...\n      ...     if add_noise:\n      ...       # Add gaussian noise\n      ...       noise_key = self.make_rng('noise')\n      ...       x = x + jax.random.normal(noise_key, x.shape)\n      ...\n      ...     return nn.Dense(1)(x)\n\n      >>> x = jnp.empty((1, 7))\n      >>> module = Foo()\n      >>> rngs = {'params': jax.random.key(0), 'noise': jax.random.key(1)}\n      >>> variables = module.init(rngs, x)\n      >>> out0 = module.apply(variables, x, add_noise=True, rngs=rngs)\n\n      >>> rngs['noise'] = jax.random.key(0)\n      >>> out1 = module.apply(variables, x, add_noise=True, rngs=rngs)\n      >>> # different output (key(1) vs key(0))\n      >>> np.testing.assert_raises(AssertionError, np.testing.assert_allclose, out0, out1)\n\n      >>> del rngs['noise']\n      >>> # self.make_rng('noise') will default to using the 'params' RNG stream\n      >>> out2 = module.apply(variables, x, add_noise=True, rngs=rngs)\n      >>> # same output (key(0))\n      >>> np.testing.assert_allclose(out1, out2)\n\n      >>> # passing in a single key is equivalent to passing in {'params': key}\n      >>> out3 = module.apply(variables, x, add_noise=True, rngs=jax.random.key(0))\n      >>> # same output (key(0))\n      >>> np.testing.assert_allclose(out2, out3)\n\n    Args:\n      variables: A dictionary containing variables keyed by variable\n        collections. See :mod:`flax.core.variables` for more details about\n        variables.\n      *args: Named arguments passed to the specified apply method.\n      rngs: a dict of PRNGKeys to initialize the PRNG sequences. The \"params\"\n        PRNG sequence is used to initialize parameters.\n      method: A function to call apply on. This is generally a function in the\n        module. If provided, applies this method. If not provided, applies the\n        ``__call__`` method of the module. A string can also be provided to\n        specify a method by name.\n      mutable: Can be bool, str, or list. Specifies which collections should be\n        treated as mutable: ``bool``: all/no collections are mutable. ``str``:\n        The name of a single mutable collection. ``list``: A list of names of\n        mutable collections.\n      capture_intermediates: If ``True``, captures intermediate return values of\n        all Modules inside the \"intermediates\" collection. By default, only the\n        return values of all ``__call__`` methods are stored. A function can be\n        passed to change the filter behavior. The filter function takes the\n        Module instance and method name and returns a bool indicating whether\n        the output of that method invocation should be stored.\n      **kwargs: Keyword arguments passed to the specified apply method.\n\n    Returns:\n      If ``mutable`` is False, returns output. If any collections are\n      mutable, returns ``(output, vars)``, where ``vars`` are is a dict\n      of the modified collections.\n    \"\"\"\n    Module._module_checks(self)\n\n    if rngs is not None and not isinstance(rngs, dict):\n      if not core.scope._is_valid_rng(rngs):\n        raise errors.InvalidRngError(\n          'RNGs should be of shape (2,) or PRNGKey in Module '\n          f'{self.__class__.__name__}, but rngs are: {rngs}'\n        )\n      rngs = {'params': rngs}\n\n    if isinstance(method, str):\n      attribute_name = method\n      method = getattr(self, attribute_name)\n      if not callable(method):\n        class_name = type(self).__name__\n        raise TypeError(\n          f\"'{class_name}.{attribute_name}' must be a callable, got\"\n          f' {type(method)}.'\n        )\n      # if the `method` string is a submodule, we create a lambda function\n      # that calls the submodule, forwarding all arguments.\n      if isinstance(method, Module):\n        method = lambda self, *args, **kwargs: getattr(self, attribute_name)(\n          *args, **kwargs\n        )\n    elif method is None:\n      method = self.__call__\n    method = _get_unbound_fn(method)\n    return apply(\n      method,\n      self,\n      mutable=mutable,\n      capture_intermediates=capture_intermediates,\n    )(variables, *args, **kwargs, rngs=rngs)\n\n  @traceback_util.api_boundary\n  def init_with_output(\n    self,\n    rngs: PRNGKey | RNGSequences,\n    *args,\n    method: Callable[..., Any] | str | None = None,\n    mutable: CollectionFilter = DenyList('intermediates'),\n    capture_intermediates: bool | Callable[['Module', str], bool] = False,\n    **kwargs,\n  ) -> tuple[Any, FrozenVariableDict | dict[str, Any]]:\n    \"\"\"Initializes a module method with variables and returns output and modified variables.\n\n    Args:\n      rngs: The rngs for the variable collections.\n      *args: Named arguments passed to the init function.\n      method: An optional method. If provided, applies this method. If not\n        provided, applies the ``__call__`` method. A string can also be\n        provided to specify a method by name.\n      mutable: Can be bool, str, or list. Specifies which collections should be\n        treated as mutable: ``bool``: all/no collections are mutable. ``str``:\n        The name of a single mutable collection. ``list``: A list of names of\n        mutable collections. By default, all collections except \"intermediates\"\n        are mutable.\n      capture_intermediates: If ``True``, captures intermediate return values of\n        all Modules inside the \"intermediates\" collection. By default only the\n        return values of all ``__call__`` methods are stored. A function can be\n        passed to change the filter behavior. The filter function takes the\n        Module instance and method name and returns a bool indicating whether\n        the output of that method invocation should be stored.\n      **kwargs: Keyword arguments passed to the init function.\n\n    Returns:\n      ``(output, vars)``, where ``vars`` are is a dict of the modified\n      collections.\n    \"\"\"\n    Module._module_checks(self)\n\n    if not isinstance(rngs, dict):\n      if not core.scope._is_valid_rng(rngs):\n        raise errors.InvalidRngError(\n          'RNGs should be of shape (2,) or PRNGKey in Module '\n          f'{self.__class__.__name__}, but rngs are: {rngs}'\n        )\n      rngs = {'params': rngs}\n\n    if isinstance(method, str):\n      attribute_name = method\n      method = getattr(self, attribute_name)\n      if not callable(method):\n        class_name = type(self).__name__\n        raise TypeError(\n          f\"'{class_name}.{attribute_name}' must be a callable, got\"\n          f' {type(method)}.'\n        )\n    elif method is None:\n      method = self.__call__\n    method = _get_unbound_fn(method)\n    return init_with_output(\n      method,\n      self,\n      mutable=mutable,\n      capture_intermediates=capture_intermediates,\n    )(rngs, *args, **kwargs)\n\n  @traceback_util.api_boundary\n  def init(\n    self,\n    rngs: PRNGKey | RNGSequences,\n    *args,\n    method: Callable[..., Any] | str | None = None,\n    mutable: CollectionFilter = DenyList('intermediates'),\n    capture_intermediates: bool | Callable[['Module', str], bool] = False,\n    **kwargs,\n  ) -> FrozenVariableDict | dict[str, Any]:\n    \"\"\"Initializes a module method with variables and returns modified variables.\n\n    ``init`` takes as first argument either a single ``PRNGKey``, or a\n    dictionary mapping variable collections names to their ``PRNGKeys``, and\n    will call ``method`` (which is the module's ``__call__`` function by\n    default) passing ``*args`` and ``**kwargs``, and returns\n    a dictionary of initialized variables.\n\n    Example::\n\n      >>> import flax.linen as nn\n      >>> import jax, jax.numpy as jnp\n      >>> import numpy as np\n\n      >>> class Foo(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x, train):\n      ...     x = nn.Dense(16)(x)\n      ...     x = nn.BatchNorm(use_running_average=not train)(x)\n      ...     x = nn.relu(x)\n      ...     return nn.Dense(1)(x)\n\n      >>> x = jnp.empty((1, 7))\n      >>> module = Foo()\n      >>> key = jax.random.key(0)\n      >>> variables = module.init(key, x, train=False)\n\n    If you pass a single ``PRNGKey``, Flax will use it to feed the ``'params'``\n    RNG stream.  If you want to use a different RNG stream or need to use\n    multiple streams, you can pass a dictionary mapping each RNG stream name\n    to its corresponding ``PRNGKey`` to ``init``. If ``self.make_rng(name)``\n    is called on an RNG stream name that isn't passed by the user, it will\n    default to using the ``'params'`` RNG stream.\n\n    Example::\n\n      >>> class Foo(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     x = nn.Dense(16)(x)\n      ...     x = nn.relu(x)\n      ...\n      ...     other_variable = self.variable(\n      ...       'other_collection',\n      ...       'other_variable',\n      ...       lambda x: jax.random.normal(self.make_rng('other_rng'), x.shape),\n      ...       x,\n      ...     )\n      ...     x = x + other_variable.value\n      ...\n      ...     return nn.Dense(1)(x)\n\n      >>> module = Foo()\n      >>> rngs = {'params': jax.random.key(0), 'other_rng': jax.random.key(1)}\n      >>> variables0 = module.init(rngs, x)\n\n      >>> rngs['other_rng'] = jax.random.key(0)\n      >>> variables1 = module.init(rngs, x)\n      >>> # equivalent params (key(0))\n      >>> _ = jax.tree_util.tree_map(\n      ...   np.testing.assert_allclose, variables0['params'], variables1['params']\n      ... )\n      >>> # different other_variable (key(1) vs key(0))\n      >>> np.testing.assert_raises(\n      ...   AssertionError,\n      ...   np.testing.assert_allclose,\n      ...   variables0['other_collection']['other_variable'],\n      ...   variables1['other_collection']['other_variable'],\n      ... )\n\n      >>> del rngs['other_rng']\n      >>> # self.make_rng('other_rng') will default to using the 'params' RNG stream\n      >>> variables2 = module.init(rngs, x)\n      >>> # equivalent params (key(0))\n      >>> _ = jax.tree_util.tree_map(\n      ...   np.testing.assert_allclose, variables1['params'], variables2['params']\n      ... )\n      >>> # equivalent other_variable (key(0))\n      >>> np.testing.assert_allclose(\n      ...   variables1['other_collection']['other_variable'],\n      ...   variables2['other_collection']['other_variable'],\n      ... )\n\n      >>> # passing in a single key is equivalent to passing in {'params': key}\n      >>> variables3 = module.init(jax.random.key(0), x)\n      >>> # equivalent params (key(0))\n      >>> _ = jax.tree_util.tree_map(\n      ...   np.testing.assert_allclose, variables2['params'], variables3['params']\n      ... )\n      >>> # equivalent other_variable (key(0))\n      >>> np.testing.assert_allclose(\n      ...   variables2['other_collection']['other_variable'],\n      ...   variables3['other_collection']['other_variable'],\n      ... )\n\n    Jitting ``init`` initializes a model lazily using only the shapes of the\n    provided arguments, and avoids computing the forward pass with actual\n    values. Example::\n\n      >>> module = nn.Dense(1)\n      >>> init_jit = jax.jit(module.init)\n      >>> variables = init_jit(jax.random.key(0), x)\n\n    ``init`` is a light wrapper over ``apply``, so other ``apply`` arguments\n    like ``method``, ``mutable``, and ``capture_intermediates`` are also\n    available.\n\n    Args:\n      rngs: The rngs for the variable collections.\n      *args: Named arguments passed to the init function.\n      method: An optional method. If provided, applies this method. If not\n        provided, applies the ``__call__`` method. A string can also be provided\n        to specify a method by name.\n      mutable: Can be bool, str, or list. Specifies which collections should be\n        treated as mutable: ``bool``: all/no collections are mutable. ``str``:\n        The name of a single mutable collection. ``list``: A list of names of\n        mutable collections. By default all collections except \"intermediates\"\n        are mutable.\n      capture_intermediates: If ``True``, captures intermediate return values of\n        all Modules inside the \"intermediates\" collection. By default only the\n        return values of all ``__call__`` methods are stored. A function can be\n        passed to change the filter behavior. The filter function takes the\n        Module instance and method name and returns a bool indicating whether\n        the output of that method invocation should be stored.\n      **kwargs: Keyword arguments passed to the init function.\n\n    Returns:\n      The initialized variable dict.\n    \"\"\"\n    Module._module_checks(self)\n\n    _, v_out = self.init_with_output(\n      rngs,\n      *args,\n      method=method,\n      mutable=mutable,\n      capture_intermediates=capture_intermediates,\n      **kwargs,\n    )\n    return v_out\n\n  @traceback_util.api_boundary\n  def lazy_init(\n    self,\n    rngs: PRNGKey | RNGSequences,\n    *args,\n    method: Callable[..., Any] | None = None,\n    mutable: CollectionFilter = DenyList('intermediates'),\n    **kwargs,\n  ) -> FrozenVariableDict:\n    \"\"\"Initializes a module without computing on an actual input.\n\n    lazy_init will initialize the variables without doing unnecessary compute.\n    The input data should be passed as a ``jax.ShapeDtypeStruct`` which\n    specifies the shape and dtype of the input but no concrete data.\n\n    Example::\n\n      >>> model = nn.Dense(features=256)\n      >>> variables = model.lazy_init(\n      ...     jax.random.key(0), jax.ShapeDtypeStruct((1, 128), jnp.float32))\n\n    The args and kwargs args passed to ``lazy_init`` can be a mix of\n    concrete (jax arrays, scalars, bools) and abstract (ShapeDtypeStruct)\n    values. Concrete values are only necessary for arguments that affect\n    the initialization of variables. For example, the model might expect\n    a keyword arg that enables/disables a subpart of the model.\n    In this case, an explicit value (True/Flase) should be passed otherwise\n    ``lazy_init`` cannot infer which variables should be initialized.\n\n    Args:\n      rngs: The rngs for the variable collections.\n      *args: arguments passed to the init function.\n      method: An optional method. If provided, applies this method. If not\n        provided, applies the ``__call__`` method.\n      mutable: Can be bool, str, or list. Specifies which collections should be\n        treated as mutable: ``bool``: all/no collections are mutable. ``str``:\n        The name of a single mutable collection. ``list``: A list of names of\n        mutable collections. By default all collections except \"intermediates\"\n        are mutable.\n      **kwargs: Keyword arguments passed to the init function.\n\n    Returns:\n      The initialized variable dict.\n    \"\"\"\n    Module._module_checks(self)\n\n    def lazy_wrapper(rngs, *args, **kwargs):\n      return self.init(rngs, *args, method=method, mutable=mutable, **kwargs)\n\n    return partial_eval.lazy_init(lazy_wrapper)(rngs, *args, **kwargs)\n\n  @property\n  def variables(self) -> VariableDict:\n    \"\"\"Returns the variables in this module.\"\"\"\n    if self.scope is None:\n      raise ValueError(\"Can't access variables on unbound modules\")\n    return self.scope.variables()\n\n  def get_variable(self, col: str, name: str, default: T | None = None) -> T:\n    \"\"\"Retrieves the value of a Variable.\n\n    Args:\n      col: the variable collection.\n      name: the name of the variable.\n      default: the default value to return if the variable does not exist in\n        this scope.\n\n    Returns:\n      The value of the input variable, of the default value if the variable\n      doesn't exist in this scope.\n    \"\"\"\n    if self.scope is None:\n      raise ValueError(\"Can't access variables on unbound modules\")\n    return self.scope.get_variable(col, name, default)\n\n  def put_variable(self, col: str, name: str, value: Any):\n    \"\"\"Updates the value of the given variable if it is mutable, or an error otherwise.\n\n    Args:\n      col: the variable collection.\n      name: the name of the variable.\n      value: the new value of the variable.\n    \"\"\"\n    if self.scope is None:\n      raise ValueError(\"Can't access variables on unbound modules\")\n    self.scope.put_variable(col, name, value)\n\n  @overload\n  def sow(self, col: str, name: str, value: Any) -> bool:\n    ...\n\n  @overload\n  def sow(\n    self,\n    col: str,\n    name: str,\n    value: T,\n    reduce_fn: Callable[[K, T], K] = tuple_reduce,\n    init_fn: Callable[[], K] = tuple_init,  # type: ignore\n  ) -> bool:\n    ...\n\n  def sow(\n    self,\n    col: str,\n    name: str,\n    value: T,\n    reduce_fn: Callable[[K, T], K] = tuple_reduce,\n    init_fn: Callable[[], K] = tuple_init,  # type: ignore\n  ) -> bool:\n    \"\"\"Stores a value in a collection.\n\n    Collections can be used to collect intermediate values without\n    the overhead of explicitly passing a container through each Module call.\n\n    If the target collection is not mutable ``sow`` behaves like a no-op\n    and returns ``False``.\n\n    Example::\n\n      >>> import jax\n      >>> import jax.numpy as jnp\n      >>> import flax.linen as nn\n\n      >>> class Foo(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     h = nn.Dense(4)(x)\n      ...     self.sow('intermediates', 'h', h)\n      ...     return nn.Dense(2)(h)\n\n      >>> x = jnp.ones((16, 9))\n      >>> model = Foo()\n      >>> variables = model.init(jax.random.key(0), x)\n      >>> y, state = model.apply(variables, x, mutable=['intermediates'])\n      >>> jax.tree.map(jnp.shape, state['intermediates'])\n      {'h': ((16, 4),)}\n\n    By default the values are stored in a tuple and each stored value\n    is appended at the end. This way all intermediates can be tracked when\n    the same module is called multiple times. Alternatively, a custom\n    init/reduce function can be passed::\n\n      >>> class Foo2(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     init_fn = lambda: 0\n      ...     reduce_fn = lambda a, b: a + b\n      ...     self.sow('intermediates', 'h', x,\n      ...               init_fn=init_fn, reduce_fn=reduce_fn)\n      ...     self.sow('intermediates', 'h', x * 2,\n      ...               init_fn=init_fn, reduce_fn=reduce_fn)\n      ...     return x\n\n      >>> x = jnp.ones((1, 1))\n      >>> model = Foo2()\n      >>> variables = model.init(jax.random.key(0), x)\n      >>> y, state = model.apply(\n      ...     variables, x, mutable=['intermediates'])\n      >>> print(state['intermediates'])\n      {'h': Array([[3.]], dtype=float32)}\n\n    Args:\n      col: The name of the variable collection.\n      name: The name of the variable.\n      value: The value of the variable.\n      reduce_fn: The function used to combine the existing value with the new\n        value. The default is to append the value to a tuple.\n      init_fn: For the first value stored, ``reduce_fn`` will be passed the result\n        of ``init_fn`` together with the value to be stored. The default is an\n        empty tuple.\n\n    Returns:\n      ``True`` if the value has been stored successfully, ``False`` otherwise.\n    \"\"\"\n    if self.scope is None:\n      raise ValueError(\"Can't store variables on unbound modules\")\n    if not self.scope.is_mutable_collection(col):\n      return False\n    if self.scope.has_variable(col, name):\n      xs = self.scope.get_variable(col, name)\n    else:\n      self.scope.reserve(name, col)\n      self._state.children[name] = col\n      xs = init_fn()\n    xs = reduce_fn(xs, value)\n    self.scope.put_variable(col, name, xs)\n    return True\n\n  def perturb(\n    self, name: str, value: T, collection: str = 'perturbations'\n  ) -> T:\n    \"\"\"Add an zero-value variable ('perturbation') to the intermediate value.\n\n    The gradient of ``value`` would be the same as the gradient of this\n    perturbation variable. Therefore, if you define your loss function with\n    both params and perturbations as standalone arguments, you can get the\n    intermediate gradients of ``value`` by running ``jax.grad`` on the perturbation\n    argument.\n\n    .. note::\n      This is an experimental API and may be tweaked later for better\n      performance and usability.\n      At its current stage, it creates extra dummy variables that occupies extra\n      memory space. Use it only to debug gradients in training.\n\n    Example::\n\n      >>> class Foo(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     x = nn.Dense(3)(x)\n      ...     x = self.perturb('dense3', x)\n      ...     return nn.Dense(2)(x)\n\n      >>> def loss(variables, inputs, targets):\n      ...   preds = model.apply(variables, inputs)\n      ...   return jnp.square(preds - targets).mean()\n\n      >>> x = jnp.ones((2, 9))\n      >>> y = jnp.ones((2, 2))\n      >>> model = Foo()\n      >>> variables = model.init(jax.random.key(0), x)\n      >>> intm_grads = jax.grad(loss, argnums=0)(variables, x, y)\n      >>> print(intm_grads['perturbations']['dense3'])\n      [[-0.04684732  0.06573904 -0.3194327 ]\n       [-0.04684732  0.06573904 -0.3194327 ]]\n\n    If perturbations are not passed to ``apply``, ``perturb`` behaves like a no-op\n    so you can easily disable the behavior when not needed::\n\n      >>> model.apply(variables, x) # works as expected\n      Array([[-0.04579116,  0.50412744],\n             [-0.04579116,  0.50412744]], dtype=float32)\n      >>> model.apply({'params': variables['params']}, x) # behaves like a no-op\n      Array([[-0.04579116,  0.50412744],\n             [-0.04579116,  0.50412744]], dtype=float32)\n      >>> intm_grads = jax.grad(loss, argnums=0)({'params': variables['params']}, x, y)\n      >>> 'perturbations' not in intm_grads\n      True\n    \"\"\"\n    if self.scope is None:\n      raise ValueError(\"Can't store variables on unbound modules\")\n\n    if self.is_mutable_collection(collection):\n      if not self.scope.has_variable(collection, name):\n        self.scope.reserve(name, collection)\n        self._state.children[name] = collection\n        zeros = jax.tree.map(jnp.zeros_like, value)\n        self.scope.put_variable(collection, name, zeros)  # type: ignore\n\n    if collection in self.scope.root._variables:\n      if self.scope.has_variable(collection, name):\n        old_value = self.scope.get_variable(collection, name)\n        value = jax.tree.map(jnp.add, value, old_value)  # type: ignore\n      else:\n        raise ValueError(f\"Perturbation collection {collection} present, but \"\n                         f\"missing perturbation variable {name}\")\n\n    return value\n\n  def tabulate(\n    self,\n    rngs: PRNGKey | RNGSequences,\n    *args,\n    depth: int | None = None,\n    show_repeated: bool = False,\n    mutable: CollectionFilter = DenyList('intermediates'),\n    console_kwargs: Mapping[str, Any] | None = None,\n    table_kwargs: Mapping[str, Any] = MappingProxyType({}),\n    column_kwargs: Mapping[str, Any] = MappingProxyType({}),\n    compute_flops: bool = False,\n    compute_vjp_flops: bool = False,\n    **kwargs,\n  ) -> str:\n    \"\"\"Creates a summary of the Module represented as a table.\n\n    This method has the same signature and internally calls ``Module.init``,\n    but instead of returning the variables, it returns the string summarizing\n    the Module in a table. ``tabulate`` uses ``jax.eval_shape`` to run the forward\n    computation without consuming any FLOPs or allocating memory.\n\n    Additional arguments can be passed into the ``console_kwargs`` argument, for\n    example, ``{'width': 120}``. For a full list of ``console_kwargs`` arguments,\n    see:\n    https://rich.readthedocs.io/en/stable/reference/console.html#rich.console.Console\n\n    Example::\n\n      >>> import flax.linen as nn\n      >>> import jax, jax.numpy as jnp\n\n      >>> class Foo(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     h = nn.Dense(4)(x)\n      ...     return nn.Dense(2)(h)\n\n      >>> x = jnp.ones((16, 9))\n\n      >>> # print(Foo().tabulate(\n      >>> #     jax.random.key(0), x, compute_flops=True, compute_vjp_flops=True))\n\n    This gives the following output::\n\n                                            Foo Summary\n      ┏━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓\n      ┃ path    ┃ module ┃ inputs        ┃ outputs       ┃ flops ┃ vjp_flops ┃ params          ┃\n      ┡━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩\n      │         │ Foo    │ float32[16,9] │ float32[16,2] │ 1504  │ 4460      │                 │\n      ├─────────┼────────┼───────────────┼───────────────┼───────┼───────────┼─────────────────┤\n      │ Dense_0 │ Dense  │ float32[16,9] │ float32[16,4] │ 1216  │ 3620      │ bias:           │\n      │         │        │               │               │       │           │ float32[4]      │\n      │         │        │               │               │       │           │ kernel:         │\n      │         │        │               │               │       │           │ float32[9,4]    │\n      │         │        │               │               │       │           │                 │\n      │         │        │               │               │       │           │ 40 (160 B)      │\n      ├─────────┼────────┼───────────────┼───────────────┼───────┼───────────┼─────────────────┤\n      │ Dense_1 │ Dense  │ float32[16,4] │ float32[16,2] │ 288   │ 840       │ bias:           │\n      │         │        │               │               │       │           │ float32[2]      │\n      │         │        │               │               │       │           │ kernel:         │\n      │         │        │               │               │       │           │ float32[4,2]    │\n      │         │        │               │               │       │           │                 │\n      │         │        │               │               │       │           │ 10 (40 B)       │\n      ├─────────┼────────┼───────────────┼───────────────┼───────┼───────────┼─────────────────┤\n      │         │        │               │               │       │     Total │ 50 (200 B)      │\n      └─────────┴────────┴───────────────┴───────────────┴───────┴───────────┴─────────────────┘\n\n                                    Total Parameters: 50 (200 B)\n\n    **Note**: rows order in the table does not represent execution order,\n    instead it aligns with the order of keys in ``variables`` which are sorted\n    alphabetically.\n\n    **Note**: ``vjp_flops`` returns ``0`` if the module is not differentiable.\n\n    Args:\n      rngs: The rngs for the variable collections as passed to ``Module.init``.\n      *args: The arguments to the forward computation.\n      depth: controls how many submodule deep the summary can go. By default,\n        its ``None`` which means no limit. If a submodule is not shown because of\n        the depth limit, its parameter count and bytes will be added to the row\n        of its first shown ancestor such that the sum of all rows always adds\n        up to the total number of parameters of the Module.\n      show_repeated: If ``True``, repeated calls to the same module will be shown\n        in the table, otherwise only the first call will be shown. Default is\n        ``False``.\n      mutable: Can be bool, str, or list. Specifies which collections should be\n        treated as mutable: ``bool``: all/no collections are mutable. ``str``:\n        The name of a single mutable collection. ``list``: A list of names of\n        mutable collections. By default, all collections except 'intermediates'\n        are mutable.\n      console_kwargs: An optional dictionary with additional keyword arguments\n        that are passed to ``rich.console.Console`` when rendering the table.\n        Default arguments are ``{'force_terminal': True, 'force_jupyter':\n        False}``.\n      table_kwargs: An optional dictionary with additional keyword arguments\n        that are passed to ``rich.table.Table`` constructor.\n      column_kwargs: An optional dictionary with additional keyword arguments\n        that are passed to ``rich.table.Table.add_column`` when adding columns to\n        the table.\n      compute_flops: whether to include a ``flops`` column in the table listing\n        the estimated FLOPs cost of each module forward pass. Does incur actual\n        on-device computation / compilation / memory allocation, but still\n        introduces overhead for large modules (e.g. extra 20 seconds for a\n        Stable Diffusion's UNet, whereas otherwise tabulation would finish in 5\n        seconds).\n      compute_vjp_flops: whether to include a ``vjp_flops`` column in the table\n        listing the estimated FLOPs cost of each module backward pass.\n        Introduces a compute overhead of about 2-3X of ``compute_flops``.\n      **kwargs: keyword arguments to pass to the forward computation.\n\n    Returns:\n      A string summarizing the Module.\n    \"\"\"\n    from flax.linen import summary\n\n    tabulate_fn = summary.tabulate(\n      self,\n      rngs,\n      depth=depth,\n      show_repeated=show_repeated,\n      mutable=mutable,\n      console_kwargs=console_kwargs,\n      table_kwargs=table_kwargs,\n      column_kwargs=column_kwargs,\n      compute_flops=compute_flops,\n      compute_vjp_flops=compute_vjp_flops,\n    )\n    return tabulate_fn(*args, **kwargs)\n\n  def module_paths(\n    self,\n    rngs: PRNGKey | RNGSequences,\n    *args,\n    show_repeated: bool = False,\n    mutable: CollectionFilter = DenyList('intermediates'),\n    **kwargs,\n  ) -> dict[str, 'Module']:\n    \"\"\"Returns a dictionary mapping module paths to module instances.\n\n    This method has the same signature and internally calls ``Module.init``,\n    but instead of returning the variables, it returns a dictionary mapping\n    module paths to unbounded copies of module instances that were used\n    at runtime. ``module_paths`` uses ``jax.eval_shape`` to run the forward\n    computation without consuming any FLOPs or allocating memory.\n\n    Example::\n\n      >>> import flax.linen as nn\n      >>> import jax, jax.numpy as jnp\n\n      >>> class Foo(nn.Module):\n      ...   @nn.compact\n      ...   def __call__(self, x):\n      ...     h = nn.Dense(4)(x)\n      ...     return nn.Dense(2)(h)\n\n      >>> x = jnp.ones((16, 9))\n      >>> modules = Foo().module_paths(jax.random.key(0), x)\n      >>> print({\n      ...     p: type(m).__name__ for p, m in modules.items()\n      ... })\n      {'': 'Foo', 'Dense_0': 'Dense', 'Dense_1': 'Dense'}\n\n    Args:\n      rngs: The rngs for the variable collections as passed to ``Module.init``.\n      *args: The arguments to the forward computation.\n      show_repeated: If ``True``, repeated calls to the same module will be\n        shown in the table, otherwise only the first call will be shown.\n        Default is ``False``.\n      mutable: Can be bool, str, or list. Specifies which collections should\n        be treated as mutable: ``bool``: all/no collections are mutable.\n        ``str``: The name of a single mutable collection. ``list``: A list of\n        names of mutable collections. By default, all collections except\n        'intermediates' are mutable.\n      **kwargs: keyword arguments to pass to the forward computation.\n\n    Returns:\n      A dict`ionary mapping module paths to module instances.\n    \"\"\"\n    from flax.linen import summary\n\n    table = summary._get_module_table(\n      module=self,\n      depth=None,\n      show_repeated=show_repeated,\n      compute_flops=False,\n      compute_vjp_flops=False,\n    )(rngs, *args, **kwargs, mutable=mutable)\n\n    return {'/'.join(row.path): row.module_copy for row in table}\n\n\n_ParentType = Union[Module, Scope, _Sentinel, None]\n\n\ndef merge_param(name: str, a: T | None, b: T | None) -> T:\n  \"\"\"Merges construction- and call-time argument.\n\n  This is a utility for supporting a pattern where a Module hyperparameter\n  can be passed either to ``__init__`` or ``__call__``, and the value that is\n  not ``None`` will be used.\n\n  Example::\n\n    >>> import flax.linen as nn\n    >>> from typing import Optional\n\n    >>> class Foo(nn.Module):\n    ...   train: Optional[bool] = None\n\n    ...   def __call__(self, train: Optional[bool] = None):\n    ...     train = nn.merge_param('train', self.train, train)\n\n  An error is thrown when both arguments are ``None`` or both values are not\n  ``None``.\n\n  Args:\n    name: the name of the parameter. Used for error messages.\n    a: option a\n    b: option b\n\n  Returns:\n    a or b whichever is not ``None``.\n  \"\"\"\n  if a is None and b is None:\n    raise ValueError(\n      f'Parameter \"{name}\" must be passed to the constructor or at call time.'\n    )\n  if a is not None and b is not None:\n    raise ValueError(\n      f'Parameter \"{name}\" was passed to the constructor and at call time.'\n      ' Should be passed just once.'\n    )\n  if a is None:\n    assert b is not None\n    return b\n  return a\n\n\n@traceback_util.api_boundary\ndef apply(\n  fn: Callable[..., Any],\n  module: Module,\n  mutable: CollectionFilter = False,\n  capture_intermediates: bool | Callable[[Module, str], bool] = False,\n) -> Callable[..., Any]:\n  \"\"\"Creates an apply function to call ``fn`` with a bound module.\n\n  Unlike ``Module.apply`` this function returns a new function with the\n  signature ``(variables, *args, rngs=None, **kwargs) -> T`` where ``T`` is the\n  return type of ``fn``. If ``mutable`` is not ``False`` the return type is a\n  tuple where the second item is a ``FrozenDict`` with the mutated variables.\n\n  The apply function that is returned can be directly composed with\n  JAX transformations like ``jax.jit``::\n\n    >>> class Foo(nn.Module):\n    ...   def encode(self, x):\n    ...     ...\n    ...   def decode(self, x):\n    ...     ...\n\n    >>> def f(foo, x):\n    ...   z = foo.encode(x)\n    ...   y = foo.decode(z)\n    ...   # ...\n    ...   return y\n\n    >>> variables = {}\n    >>> foo = Foo()\n    >>> f_jitted = jax.jit(nn.apply(f, foo))\n    >>> f_jitted(variables, jnp.ones((1, 3)))\n\n  Args:\n    fn: The function that should be applied. The first argument passed will be\n      a module instance of the ``module`` with variables and RNGs bound to it.\n    module: The ``Module`` that will be used to bind variables and RNGs to. The\n      ``Module`` passed as the first argument to ``fn`` will be a clone of\n      module.\n    mutable: Can be bool, str, or list. Specifies which collections should be\n      treated as mutable: ``bool``: all/no collections are mutable. ``str``: The\n      name of a single mutable collection. ``list``: A list of names of mutable\n      collections.\n    capture_intermediates: If ``True``, captures intermediate return values of all\n      Modules inside the \"intermediates\" collection. By default, only the return\n      values of all `__call__` methods are stored. A function can be passed to\n      change the filter behavior. The filter function takes the Module instance\n      and method name and returns a bool indicating whether the output of that\n      method invocation should be stored.\n\n  Returns:\n    The apply function wrapping ``fn``.\n  \"\"\"\n\n  @functools.wraps(fn)\n  def scope_fn(scope, *args, **kwargs):\n    _context.capture_stack.append(capture_intermediates)\n    try:\n      return fn(module.clone(parent=scope, _deep_clone=True), *args, **kwargs)\n    finally:\n      _context.capture_stack.pop()\n\n  if capture_intermediates is True:  # pylint: disable=g-bool-id-comparison\n    capture_intermediates = capture_call_intermediates\n  if capture_intermediates:\n    mutable = union_filters(mutable, 'intermediates')\n  return core.apply(scope_fn, mutable=mutable)\n\n\n@traceback_util.api_boundary\ndef init_with_output(\n  fn: Callable[..., Any],\n  module: Module,\n  mutable: CollectionFilter = DenyList('intermediates'),\n  capture_intermediates: bool | Callable[[Module, str], bool] = False,\n) -> Callable[..., tuple[Any, FrozenVariableDict | dict[str, Any]]]:\n  \"\"\"Creates an init function to call ``fn`` with a bound module that also returns the function outputs.\n\n  Unlike ``Module.init_with_output`` this function returns a new function with\n  the signature ``(rngs, *args, **kwargs) -> (T, variables)`` where ``T`` is the\n  return type of ``fn``. The rngs can be a dict of PRNGKeys or a single\n  ```PRNGKey`` which is equivalent to passing a dict with one PRNGKey with the\n  name \"params\".\n\n  The init function that is returned can be directly composed with\n  JAX transformations like ``jax.jit``::\n\n    >>> class Foo(nn.Module):\n    ...   def encode(self, x):\n    ...     ...\n    ...   def decode(self, x):\n    ...     ...\n\n    >>> def f(foo, x):\n    ...   z = foo.encode(x)\n    ...   y = foo.decode(z)\n    ...   # ...\n    ...   return y\n\n    >>> foo = Foo()\n    >>> f_jitted = jax.jit(nn.init_with_output(f, foo))\n    >>> y, variables = f_jitted(jax.random.key(0), jnp.ones((1, 3)))\n\n  Args:\n    fn: The function that should be applied. The first argument passed will be\n      a module instance of the ``module`` with variables and RNGs bound to it.\n    module: The ``Module`` that will be used to bind variables and RNGs to. The\n      ``Module`` passed as the first argument to ``fn`` will be a clone of\n      module.\n    mutable: Can be bool, str, or list. Specifies which collections should be\n      treated as mutable: ``bool``: all/no collections are mutable. ``str``: The\n      name of a single mutable collection. ``list``: A list of names of mutable\n      collections. By default, all collections except \"intermediates\" are\n      mutable.\n    capture_intermediates: If ``True``, captures intermediate return values of all\n      Modules inside the \"intermediates\" collection. By default, only the return\n      values of all `__call__` methods are stored. A function can be passed to\n      change the filter behavior. The filter function takes the Module instance\n      and method name and returns a bool indicating whether the output of that\n      method invocation should be stored.\n\n  Returns:\n    The init function wrapping ``fn``.\n  \"\"\"\n\n  @functools.wraps(fn)\n  def scope_fn(scope, *args, **kwargs):\n    _context.capture_stack.append(capture_intermediates)\n    try:\n      return fn(module.clone(parent=scope, _deep_clone=True), *args, **kwargs)\n    finally:\n      _context.capture_stack.pop()\n\n  if capture_intermediates is True:  # pylint: disable=g-bool-id-comparison\n    capture_intermediates = capture_call_intermediates\n  if capture_intermediates:\n    mutable = union_filters(mutable, 'intermediates')\n  return core.init(scope_fn, mutable=mutable)\n\n\n@traceback_util.api_boundary\ndef init(\n  fn: Callable[..., Any],\n  module: Module,\n  mutable: CollectionFilter = DenyList('intermediates'),\n  capture_intermediates: bool | Callable[[Module, str], bool] = False,\n) -> Callable[..., FrozenVariableDict | dict[str, Any]]:\n  \"\"\"Creates an init function to call ``fn`` with a bound module.\n\n  Unlike ``Module.init`` this function returns a new function with the signature\n  ``(rngs, *args, **kwargs) -> variables``.\n  The rngs can be a dict of PRNGKeys or a single ```PRNGKey`` which is\n  equivalent to passing a dict with one PRNGKey with the name \"params\".\n\n  The init function that is returned can be directly composed with\n  JAX transformations like ``jax.jit``::\n\n    >>> class Foo(nn.Module):\n    ...   def encode(self, x):\n    ...     ...\n    ...   def decode(self, x):\n    ...     ...\n\n    >>> def f(foo, x):\n    ...   z = foo.encode(x)\n    ...   y = foo.decode(z)\n    ...   # ...\n    ...   return y\n\n    >>> foo = Foo()\n    >>> f_jitted = jax.jit(nn.init(f, foo))\n    >>> variables = f_jitted(jax.random.key(0), jnp.ones((1, 3)))\n\n  Args:\n    fn: The function that should be applied. The first argument passed will be\n      a module instance of the ``module`` with variables and RNGs bound to it.\n    module: The ``Module`` that will be used to bind variables and RNGs to. The\n      ``Module`` passed as the first argument to ``fn`` will be a clone of\n      module.\n    mutable: Can be bool, str, or list. Specifies which collections should be\n      treated as mutable: ``bool``: all/no collections are mutable. ``str``: The\n      name of a single mutable collection. ``list``: A list of names of mutable\n      collections. By default, all collections except \"intermediates\" are\n      mutable.\n    capture_intermediates: If `True`, captures intermediate return values of all\n      Modules inside the \"intermediates\" collection. By default, only the return\n      values of all `__call__` methods are stored. A function can be passed to\n      change the filter behavior. The filter function takes the Module instance\n      and method name and returns a bool indicating whether the output of that\n      method invocation should be stored.\n\n  Returns:\n    The init function wrapping ``fn``.\n  \"\"\"\n  init_fn = init_with_output(fn, module, mutable, capture_intermediates)\n\n  @functools.wraps(init_fn)\n  def init_wrapper(*args, **kwargs):\n    return init_fn(*args, **kwargs)[1]\n\n  return init_wrapper\n\n\n# TODO(cgarciae): we are defining CompactNameScope just to\n# avoid a pytype bug with the Flax overlay. We should aim to\n# remove in the at some point as its not ergonomic.\nif not typing.TYPE_CHECKING:\n\n  class CompactNameScope(Module):\n    fn: Callable\n    module_fn: Callable[[], Module]\n\n    @compact\n    def __call__(self, *args, **kwargs) -> Any:\n      return self.fn(self.module_fn(), *args, **kwargs)\nelse:\n\n  @dataclasses.dataclass\n  class CompactNameScope:\n    fn: Callable\n    module_fn: Callable\n    name: str\n\n    def __call__(self, *args, **kwargs) -> Any:\n      ...\n\n\ndef share_scope(module: Module, other: Module, /):\n  \"\"\"Modifies one of the Modules such that they share the same scope. This is useful\n  when you want to wrap a Module and extend its functionality without changing the\n  parameter structure.\n\n  ``share_scope`` takes two Modules, ``module`` and ``other``. ``module`` will use\n  ``other``'s scope if ``other`` has a scope and its not a descendant of``module``'s\n  scope::\n\n    >>> import flax.linen as nn\n    >>> import jax\n    >>> from jax import numpy as jnp, random\n    ...\n    >>> class DenseLoRA(nn.Module):\n    ...   base: nn.Dense\n    ...   rank: int\n    ...\n    ...   def setup(self):\n    ...     nn.share_scope(self, self.base)\n    ...\n    ...   @nn.compact\n    ...   def __call__(self, x: jax.Array):\n    ...     din, dout = x.shape[-1], self.base.features\n    ...     A = self.param('A', nn.zeros_init(), (din, self.rank))\n    ...     B = self.param('B', nn.zeros_init(), (self.rank, dout))\n    ...     return self.base(x) + x @ A @ B\n    ...\n    >>> class Model(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x: jax.Array):\n    ...     dense = nn.Dense(10) # base scope\n    ...     return DenseLoRA(dense, rank=2)(x) # reuse the base scope\n    ...\n    >>> model = Model()\n    ...\n    >>> params = model.init(random.key(0), jnp.ones((1, 5)))['params']\n    >>> list(params['Dense_0'].keys())\n    ['A', 'B', 'kernel', 'bias']\n\n  When ``other``'s scope is a descendant of ``module``'s scope then ``other``\n  will use ``module``'s scope instead::\n\n    >>> class DenseLoRA(nn.Module):\n    ...   features: int\n    ...   rank: int\n    ...\n    ...   def setup(self):\n    ...     self.child = nn.Dense(self.features)\n    ...     nn.share_scope(self, self.child)\n    ...\n    ...   @nn.compact\n    ...   def __call__(self, x: jax.Array):\n    ...     din, dout = x.shape[-1], self.features\n    ...     A = self.param('A', nn.zeros_init(), (din, self.rank))\n    ...     B = self.param('B', nn.zeros_init(), (self.rank, dout))\n    ...     return self.child(x) + x @ A @ B\n    ...\n    >>> class Model(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x: jax.Array):\n    ...     return DenseLoRA(10, rank=2)(x)\n    ...\n    >>> model = Model()\n    ...\n    >>> params = model.init(random.key(0), jnp.ones((1, 5)))['params']\n    >>> list(params['DenseLoRA_0'].keys())\n    ['A', 'B', 'kernel', 'bias']\n  \"\"\"\n  if module.scope is None or other.scope is None:\n    raise errors.CallShareScopeOnUnboundModuleError()\n\n  def _is_child_scope(scope: Scope, other: Scope) -> bool:\n    target: Scope | None = other\n\n    while target is not None:\n      if target is scope:\n        return True\n      target = target.parent\n    return False\n\n  if _is_child_scope(module.scope, other.scope):\n    # Child is a true child, overwrite its scope\n    module_to_update = other\n    new_scope = module.scope\n  else:\n    # Child has its own independent scope, overwrite\n    # parent scope, so that we preserve the sharing\n    module_to_update = module\n    new_scope = other.scope\n\n  old_scope = module_to_update.scope\n  object.__setattr__(module_to_update, 'scope', new_scope)\n\n  # Reattach all the children to the new scope as well.\n  for m in module_to_update._state.children.values():\n    if not isinstance(m, Module):\n      continue\n    # Should we go recursively to check if any of the ancestors point to the old\n    # scope?\n    if m.scope and m.scope.parent == old_scope:\n      # Reserve the scope, so that if there is a conflict we can raise an error.\n      if isinstance(m.scope.name, str):\n        new_scope.reserve(m.scope.name)\n      m.scope.parent = new_scope\n"
  },
  {
    "path": "flax/linen/normalization.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Normalization modules for Flax.\"\"\"\n\nimport dataclasses\nimport functools\nfrom typing import Any\nfrom collections.abc import Iterable\n\nimport jax\nimport jax.numpy as jnp\nfrom jax import lax\nfrom jax.nn import initializers\n\nfrom flax.linen import dtypes, module, transforms\nfrom flax.typing import (\n  Array,\n  PRNGKey as PRNGKey,\n  Dtype,\n  Shape as Shape,\n  Initializer,\n  Axes,\n)\n\nfield = dataclasses.field\ncanonicalize_dtype = dtypes.canonicalize_dtype\ncompact = module.compact\nModule = module.Module\nmerge_param = module.merge_param\nmap_variables = transforms.map_variables\n\n\ndef _canonicalize_axes(rank: int, axes: Axes) -> tuple[int, ...]:\n  \"\"\"Returns a tuple of deduplicated, sorted, and positive axes.\"\"\"\n  if not isinstance(axes, Iterable):\n    axes = (axes,)\n  return tuple({rank + axis if axis < 0 else axis for axis in axes})\n\n\ndef _abs_sq(x):\n  \"\"\"Computes the elementwise square of the absolute value |x|^2.\"\"\"\n  if jnp.iscomplexobj(x):\n    return lax.square(lax.real(x)) + lax.square(lax.imag(x))\n  else:\n    return lax.square(x)\n\n\ndef _compute_stats(\n    x: Array,\n    axes: Axes,\n    dtype: Dtype | None,\n    axis_name: str | None = None,\n    axis_index_groups: Any = None,\n    use_mean: bool = True,\n    use_fast_variance: bool = True,\n    mask: Array | None = None,\n    force_float32_reductions=True,\n):\n  \"\"\"Computes mean and variance statistics.\n\n  This implementation takes care of a few important details:\n  - By default, computes in float32 precision for stability\n    in half precision training.\n  - If `use_fast_variance` is `True`, mean and variance are computed using\n    Var = E[|x|^2] - |E[x]|^2, instead of Var = E[|x - E[x]|^2]), in a single\n    XLA fusion.\n  - Clips negative variances to zero which can happen due to\n    roundoff errors. This avoids downstream NaNs.\n  - Supports averaging across a parallel axis and subgroups of a parallel axis\n    with a single `lax.pmean` call to avoid latency.\n\n  Arguments:\n    x: Input array.\n    axes: The axes in ``x`` to compute mean and variance statistics for.\n    dtype: Optional dtype specifying the minimal precision. Statistics are\n      always at least float32 for stability (default: dtype of x).\n    axis_name: Optional name for the pmapped axis to compute mean over. Note,\n      this is only used for pmap and shard map. For SPMD jit, you do not need to\n      manually synchronize. Just make sure that the axes are correctly annotated\n      and XLA:SPMD will insert the necessary collectives.\n    axis_index_groups: Optional groups of indices within that named axis.\n    use_mean: If true, calculate the mean from the input and use it when\n      computing the variance. If false, set the mean to zero and compute the\n      variance without subtracting the mean.\n    use_fast_variance: If true, use a faster, but less numerically stable,\n      calculation for the variance.\n    mask: Binary array of shape broadcastable to `inputs` tensor, indicating the\n      positions for which the mean and variance should be computed.\n    force_float32_reductions: If false, this will skip float32 promotion and use\n      the input dtype or inherited dtype from ``x``.\n\n  Returns:\n    A pair ``(mean, var)``.\n  \"\"\"\n  if dtype is None:\n    dtype = jnp.result_type(x)\n  # promote x to at least float32, this avoids half precision computation\n  # but preserves double or complex floating points\n  if force_float32_reductions:\n    dtype = jnp.promote_types(dtype, jnp.float32)\n  if isinstance(x, jax.Array):\n    x = x.astype(dtype)\n  else:\n    x = jnp.asarray(x, dtype)\n  axes = _canonicalize_axes(x.ndim, axes)\n\n  def maybe_distributed_mean(*xs, mask=None):\n    if mask is not None:\n      mask = jnp.asarray(mask, dtype=bool)\n    mus = tuple(x.mean(axes, where=mask) for x in xs)\n    if axis_name is None:\n      return mus if len(xs) > 1 else mus[0]\n    else:\n      # In the distributed case we stack multiple arrays to speed comms.\n      if len(xs) > 1:\n        reduced_mus = lax.pmean(\n          jnp.stack(mus, axis=0),\n          axis_name,\n          axis_index_groups=axis_index_groups,\n        )\n        return tuple(reduced_mus[i] for i in range(len(xs)))\n      else:\n        return lax.pmean(mus[0], axis_name, axis_index_groups=axis_index_groups)\n\n  if use_mean:\n    if use_fast_variance:\n      mu, mu2 = maybe_distributed_mean(x, _abs_sq(x), mask=mask)\n      # mean2 - _abs_sq(mean) is not guaranteed to be non-negative due\n      # to floating point round-off errors.\n      var = jnp.maximum(0.0, mu2 - _abs_sq(mu))\n    else:\n      mu = maybe_distributed_mean(x, mask=mask)\n      var = maybe_distributed_mean(\n        _abs_sq(x - jnp.expand_dims(mu, axes)), mask=mask\n      )\n  else:\n    var = maybe_distributed_mean(_abs_sq(x), mask=mask)\n    mu = jnp.zeros_like(var)\n  return mu, var\n\n\ndef _normalize(\n  mdl: Module,\n  x: Array,\n  mean: Array,\n  var: Array,\n  reduction_axes: Axes,\n  feature_axes: Axes,\n  dtype: Dtype | None,\n  param_dtype: Dtype,\n  epsilon: float,\n  use_bias: bool,\n  use_scale: bool,\n  bias_init: Initializer,\n  scale_init: Initializer,\n  force_float32_reductions: bool = True\n):\n  \"\"\"Normalizes the input of a normalization layer and optionally applies a learned scale and bias.\n\n  Arguments:\n    mdl: Module to apply the normalization in (normalization params will reside\n      in this module).\n    x: The input.\n    mean: Mean to use for normalization.\n    var: Variance to use for normalization.\n    reduction_axes: The axes in ``x`` to reduce.\n    feature_axes: Axes containing features. A separate bias and scale is learned\n      for each specified feature.\n    dtype: The dtype of the result (default: infer from input and params).\n    param_dtype: The dtype of the parameters.\n    epsilon: Normalization epsilon.\n    use_bias: If true, add a bias term to the output.\n    use_scale: If true, scale the output.\n    bias_init: Initialization function for the bias term.\n    scale_init: Initialization function for the scaling function.\n    force_float32_reductions: If false, the scale and bias parameters use the\n      param_dtype. Otherwise, they will have at least float32 precision due to\n      the mean and var being promoted to float32.\n\n  Returns:\n    The normalized input.\n  \"\"\"\n  reduction_axes = _canonicalize_axes(x.ndim, reduction_axes)\n  feature_axes = _canonicalize_axes(x.ndim, feature_axes)\n  feature_shape = [1] * x.ndim\n  reduced_feature_shape = []\n  for ax in feature_axes:\n    feature_shape[ax] = x.shape[ax]\n    reduced_feature_shape.append(x.shape[ax])\n\n  mean = jnp.expand_dims(mean, reduction_axes)\n  var = jnp.expand_dims(var, reduction_axes)\n  y = x - mean\n  mul = lax.rsqrt(var + epsilon)\n  args = [x]\n  if use_scale:\n    scale = mdl.param(\n      'scale', scale_init, reduced_feature_shape, param_dtype\n    ).reshape(feature_shape)\n    if not force_float32_reductions:\n      scale = jnp.asarray(scale, param_dtype)\n    mul *= scale\n    args.append(scale)\n  y *= mul\n  if use_bias:\n    bias = mdl.param(\n      'bias', bias_init, reduced_feature_shape, param_dtype\n    ).reshape(feature_shape)\n    if not force_float32_reductions:\n      bias = jnp.asarray(bias, param_dtype)\n    y += bias\n    args.append(bias)\n  dtype = dtypes.canonicalize_dtype(*args, dtype=dtype)\n  return jnp.asarray(y, dtype)\n\n\ndef _l2_normalize(x, axis=None, eps=1e-12):\n  \"\"\"Normalizes along dimension `axis` using an L2 norm.\n\n  This specialized function exists for numerical stability reasons.\n\n  Args:\n    x: An input ndarray.\n    axis: Dimension along which to normalize, e.g. `1` to separately normalize\n      vectors in a batch. Passing `None` views `t` as a flattened vector when\n      calculating the norm (equivalent to Frobenius norm).\n    eps: Epsilon to avoid dividing by zero.\n\n  Returns:\n    An array of the same shape as 'x' L2-normalized along 'axis'.\n  \"\"\"\n  return x * jax.lax.rsqrt((x * x).sum(axis=axis, keepdims=True) + eps)\n\n\nclass BatchNorm(Module):\n  \"\"\"BatchNorm Module.\n\n  Usage Note:\n  If we define a model with BatchNorm, for example::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n    >>> BN = nn.BatchNorm(momentum=0.9, epsilon=1e-5, dtype=jnp.float32)\n\n  The initialized variables dict will contain, in addition to a 'params'\n  collection, a separate 'batch_stats' collection that will contain all the\n  running statistics for all the BatchNorm layers in a model::\n\n    >>> x = jax.random.normal(jax.random.key(0), (5, 6))\n    >>> variables = BN.init(jax.random.key(1), x, use_running_average=False)\n    >>> jax.tree_util.tree_map(jnp.shape, variables)\n    {'batch_stats': {'mean': (6,), 'var': (6,)}, 'params': {'bias': (6,), 'scale': (6,)}}\n\n  We then update the batch_stats during training by specifying that the\n  ``batch_stats`` collection is mutable in the ``apply`` method for our\n  module.::\n\n    >>> y, new_batch_stats = BN.apply(variables, x, mutable=['batch_stats'], use_running_average=False)\n\n  During eval we would define BN with ``use_running_average=True`` and use the\n  batch_stats collection from training to set the statistics.  In this case\n  we are not mutating the batch statistics collection, and needn't mark it\n  mutable::\n\n    >>> y = BN.apply(variables, x, mutable=['batch_stats'], use_running_average=True)\n\n  Attributes:\n    use_running_average: if True, the statistics stored in batch_stats will be\n      used instead of computing the batch statistics on the input.\n    axis: the feature or non-batch axis of the input.\n    momentum: decay rate for the exponential moving average of the batch\n      statistics.\n    epsilon: a small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    use_bias:  if True, bias (beta) is added.\n    use_scale: if True, multiply by scale (gamma). When the next layer is linear\n      (also e.g. nn.relu), this can be disabled since the scaling will be done\n      by the next layer.\n    bias_init: initializer for bias, by default, zero.\n    scale_init: initializer for scale, by default, one.\n    axis_name: the axis name used to combine batch statistics from multiple\n      devices. See ``jax.pmap`` for a description of axis names (default: None).\n      Note, this is only used for pmap and shard map. For SPMD jit, you do not\n      need to manually synchronize. Just make sure that the axes are correctly\n      annotated and XLA:SPMD will insert the necessary collectives.\n    axis_index_groups: groups of axis indices within that named axis\n      representing subsets of devices to reduce over (default: None). For\n      example, ``[[0, 1], [2, 3]]`` would independently batch-normalize over the\n      examples on the first two and last two devices. See ``jax.lax.psum`` for\n      more details. This argument is currently not supported for SPMD jit.\n    use_fast_variance: If true, use a faster, but less numerically stable,\n      calculation for the variance.\n  \"\"\"\n\n  use_running_average: bool | None = None\n  axis: int = -1\n  momentum: float = 0.99\n  epsilon: float = 1e-5\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  use_bias: bool = True\n  use_scale: bool = True\n  bias_init: Initializer = initializers.zeros\n  scale_init: Initializer = initializers.ones\n  axis_name: str | None = None\n  axis_index_groups: Any = None\n  use_fast_variance: bool = True\n  force_float32_reductions: bool = True\n\n  @compact\n  def __call__(\n      self,\n      x,\n      use_running_average: bool | None = None,\n      *,\n      mask: jax.Array | None = None,\n  ):\n    \"\"\"Normalizes the input using batch statistics.\n\n    .. note::\n      During initialization (when ``self.is_initializing()`` is ``True``) the running\n      average of the batch statistics will not be updated. Therefore, the inputs\n      fed during initialization don't need to match that of the actual input\n      distribution and the reduction axis (set with ``axis_name``) does not have\n      to exist.\n\n    Args:\n      x: the input to be normalized.\n      use_running_average: if true, the statistics stored in batch_stats will be\n        used instead of computing the batch statistics on the input.\n      mask: Binary array of shape broadcastable to ``inputs`` tensor, indicating\n        the positions for which the mean and variance should be computed.\n\n    Returns:\n      Normalized inputs (the same shape as inputs).\n    \"\"\"\n\n    use_running_average = module.merge_param(\n      'use_running_average', self.use_running_average, use_running_average\n    )\n    feature_axes = _canonicalize_axes(x.ndim, self.axis)\n    reduction_axes = tuple(i for i in range(x.ndim) if i not in feature_axes)\n    feature_shape = [x.shape[ax] for ax in feature_axes]\n\n    ra_mean = self.variable(\n        'batch_stats',\n        'mean',\n        lambda s: jnp.zeros(\n            s,\n            jnp.float32 if self.force_float32_reductions else self.param_dtype,\n        ),\n        feature_shape,\n    )\n    ra_var = self.variable(\n        'batch_stats',\n        'var',\n        lambda s: jnp.ones(\n            s,\n            jnp.float32 if self.force_float32_reductions else self.param_dtype,\n        ),\n        feature_shape,\n    )\n\n    if use_running_average:\n      mean = (\n          ra_mean.value\n          if self.force_float32_reductions\n          else jnp.asarray(ra_mean.value, self.param_dtype)\n      )\n      var = (\n          ra_var.value\n          if self.force_float32_reductions\n          else jnp.asarray(ra_var.value, self.param_dtype)\n      )\n    else:\n      mean, var = _compute_stats(\n          x,\n          reduction_axes,\n          dtype=self.dtype,\n          axis_name=self.axis_name if not self.is_initializing() else None,\n          axis_index_groups=self.axis_index_groups,\n          use_fast_variance=self.use_fast_variance,\n          mask=mask,\n          force_float32_reductions=self.force_float32_reductions,\n      )\n\n      if not self.is_initializing():\n        ra_mean.value = (\n          self.momentum * ra_mean.value + (1 - self.momentum) * mean\n        )\n        ra_var.value = self.momentum * ra_var.value + (1 - self.momentum) * var\n\n    return _normalize(\n      self,\n      x,\n      mean,\n      var,\n      reduction_axes,\n      feature_axes,\n      self.dtype,\n      self.param_dtype,\n      self.epsilon,\n      self.use_bias,\n      self.use_scale,\n      self.bias_init,\n      self.scale_init,\n      self.force_float32_reductions,\n    )\n\n\nclass LayerNorm(Module):\n  \"\"\"Layer normalization (https://arxiv.org/abs/1607.06450).\n\n  LayerNorm normalizes the activations of the layer for each given example in a\n  batch independently, rather than across a batch like Batch Normalization.\n  i.e. applies a transformation that maintains the mean activation within\n  each example close to 0 and the activation standard deviation close to 1.\n\n  .. note::\n    This normalization operation is identical to InstanceNorm and GroupNorm;\n    the difference is simply which axes are reduced and the shape of the feature\n    axes (i.e. the shape of the learnable scale and bias parameters).\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax\n    >>> import numpy as np\n\n    >>> x = jax.random.normal(jax.random.key(0), (3, 4, 5, 6))\n    >>> layer = nn.LayerNorm()\n    >>> variables = layer.init(jax.random.key(1), x)\n    >>> variables\n    {'params': {'scale': Array([1., 1., 1., 1., 1., 1.], dtype=float32), 'bias': Array([0., 0., 0., 0., 0., 0.], dtype=float32)}}\n    >>> y = layer.apply(variables, x)\n\n    >>> y = nn.LayerNorm(reduction_axes=(1, 2, 3)).apply(variables, x)\n    >>> y2 = nn.GroupNorm(num_groups=1).apply(variables, x)\n    >>> np.testing.assert_allclose(y, y2)\n\n    >>> y = nn.LayerNorm(reduction_axes=(1, 2), feature_axes=-1).apply(variables, x)\n    >>> y2 = nn.InstanceNorm(feature_axes=-1).apply(variables, x)\n    >>> np.testing.assert_allclose(y, y2)\n\n  Attributes:\n    epsilon: A small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    use_bias:  If True, bias (beta) is added.\n    use_scale: If True, multiply by scale (gamma). When the next layer is linear\n      (also e.g. nn.relu), this can be disabled since the scaling will be done\n      by the next layer.\n    bias_init: Initializer for bias, by default, zero.\n    scale_init: Initializer for scale, by default, one.\n    reduction_axes: Axes for computing normalization statistics.\n    feature_axes: Feature axes for learned bias and scaling.\n    axis_name: the axis name used to combine batch statistics from multiple\n      devices. See ``jax.pmap`` for a description of axis names (default: None).\n      This is only needed if the model is subdivided across devices, i.e. the\n      array being normalized is sharded across devices within a pmap or shard\n      map. For SPMD jit, you do not need to manually synchronize. Just make sure\n      that the axes are correctly annotated and XLA:SPMD will insert the\n      necessary collectives.\n    axis_index_groups: groups of axis indices within that named axis\n      representing subsets of devices to reduce over (default: None). For\n      example, ``[[0, 1], [2, 3]]`` would independently batch-normalize over the\n      examples on the first two and last two devices. See ``jax.lax.psum`` for\n      more details. This argument is currently not supported for SPMD jit.\n    use_fast_variance: If true, use a faster, but less numerically stable,\n      calculation for the variance.\n  \"\"\"\n\n  epsilon: float = 1e-6\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  use_bias: bool = True\n  use_scale: bool = True\n  bias_init: Initializer = initializers.zeros\n  scale_init: Initializer = initializers.ones\n  reduction_axes: Axes = -1\n  feature_axes: Axes = -1\n  axis_name: str | None = None\n  axis_index_groups: Any = None\n  use_fast_variance: bool = True\n  force_float32_reductions: bool = True\n\n  @compact\n  def __call__(self, x, *, mask: jax.Array | None = None):\n    \"\"\"Applies layer normalization on the input.\n\n    Args:\n      x: the inputs\n      mask: Binary array of shape broadcastable to ``inputs`` tensor, indicating\n        the positions for which the mean and variance should be computed.\n\n    Returns:\n      Normalized inputs (the same shape as inputs).\n    \"\"\"\n    mean, var = _compute_stats(\n        x,\n        self.reduction_axes,\n        self.dtype,\n        self.axis_name,\n        self.axis_index_groups,\n        use_fast_variance=self.use_fast_variance,\n        mask=mask,\n        force_float32_reductions=self.force_float32_reductions,\n    )\n\n    return _normalize(\n      self,\n      x,\n      mean,\n      var,\n      self.reduction_axes,\n      self.feature_axes,\n      self.dtype,\n      self.param_dtype,\n      self.epsilon,\n      self.use_bias,\n      self.use_scale,\n      self.bias_init,\n      self.scale_init,\n      self.force_float32_reductions,\n    )\n\n\nclass RMSNorm(Module):\n  \"\"\"RMS Layer normalization (https://arxiv.org/abs/1910.07467).\n\n  RMSNorm normalizes the activations of the layer for each given example in a\n  batch independently, rather than across a batch like Batch Normalization.\n  Unlike LayerNorm which re-centers the mean to be 0 and normalizes by the\n  standard deviation of the activations, RMSNorm does not re-center at all\n  and instead normalizes by the root mean square of the activations.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax\n\n    >>> x = jax.random.normal(jax.random.key(0), (5, 6))\n    >>> layer = nn.RMSNorm()\n    >>> variables = layer.init(jax.random.key(1), x)\n    >>> variables\n    {'params': {'scale': Array([1., 1., 1., 1., 1., 1.], dtype=float32)}}\n    >>> y = layer.apply(variables, x)\n\n  Attributes:\n    epsilon: A small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    use_scale: If True, multiply by scale (gamma). When the next layer is linear\n      (also e.g. nn.relu), this can be disabled since the scaling will be done\n      by the next layer.\n    scale_init: Initializer for scale, by default, one.\n    reduction_axes: Axes for computing normalization statistics.\n    feature_axes: Feature axes for learned bias and scaling.\n    axis_name: the axis name used to combine batch statistics from multiple\n      devices. See ``jax.pmap`` for a description of axis names (default: None).\n      This is only needed if the model is subdivided across devices, i.e. the\n      array being normalized is sharded across devices within a pmap or shard\n      map. For SPMD jit, you do not need to manually synchronize. Just make sure\n      that the axes are correctly annotated and XLA:SPMD will insert the\n      necessary collectives.\n    axis_index_groups: groups of axis indices within that named axis\n      representing subsets of devices to reduce over (default: None). For\n      example, ``[[0, 1], [2, 3]]`` would independently batch-normalize over the\n      examples on the first two and last two devices. See ``jax.lax.psum`` for\n      more details. This argument is currently not supported for SPMD jit.\n    use_fast_variance: If true, use a faster, but less numerically stable,\n      calculation for the variance.\n  \"\"\"\n\n  epsilon: float = 1e-6\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  use_scale: bool = True\n  scale_init: Initializer = initializers.ones\n  reduction_axes: Axes = -1\n  feature_axes: Axes = -1\n  axis_name: str | None = None\n  axis_index_groups: Any = None\n  use_fast_variance: bool = True\n  force_float32_reductions: bool = True\n\n  @compact\n  def __call__(self, x, *, mask: jax.Array | None = None):\n    \"\"\"Applies RMS layer normalization on the input.\n\n    Args:\n      x: the inputs\n      mask: Binary array of shape broadcastable to ``inputs`` tensor, indicating\n        the positions for which the mean and variance should be computed.\n\n    Returns:\n      Normalized inputs (the same shape as inputs).\n    \"\"\"\n    mean, var = _compute_stats(\n        x,\n        self.reduction_axes,\n        self.dtype,\n        self.axis_name,\n        self.axis_index_groups,\n        use_mean=False,\n        use_fast_variance=self.use_fast_variance,\n        mask=mask,\n        force_float32_reductions=self.force_float32_reductions,\n    )\n\n    return _normalize(\n      self,\n      x,\n      mean,\n      var,\n      self.reduction_axes,\n      self.feature_axes,\n      self.dtype,\n      self.param_dtype,\n      self.epsilon,\n      False,\n      self.use_scale,\n      initializers.zeros,\n      self.scale_init,\n      self.force_float32_reductions,\n    )\n\n\nclass GroupNorm(Module):\n  \"\"\"Group normalization (arxiv.org/abs/1803.08494).\n\n  This op is similar to batch normalization, but statistics are shared across\n  equally-sized groups of channels and not shared across batch dimension.\n  Thus, group normalization does not depend on the batch composition and does\n  not require maintaining internal state for storing statistics.\n  The user should either specify the total number of channel groups or the\n  number of channels per group.\n\n  .. note::\n    LayerNorm is a special case of GroupNorm where ``num_groups=1``, and\n    InstanceNorm is a special case of GroupNorm where ``group_size=1``.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax\n    >>> import numpy as np\n\n    >>> x = jax.random.normal(jax.random.key(0), (3, 4, 5, 6))\n    >>> layer = nn.GroupNorm(num_groups=3)\n    >>> variables = layer.init(jax.random.key(1), x)\n    >>> variables\n    {'params': {'scale': Array([1., 1., 1., 1., 1., 1.], dtype=float32), 'bias': Array([0., 0., 0., 0., 0., 0.], dtype=float32)}}\n    >>> y = layer.apply(variables, x)\n\n    >>> y = nn.GroupNorm(num_groups=1).apply(variables, x)\n    >>> y2 = nn.LayerNorm(reduction_axes=(1, 2, 3)).apply(variables, x)\n    >>> np.testing.assert_allclose(y, y2)\n\n    >>> y = nn.GroupNorm(num_groups=None, group_size=1).apply(variables, x)\n    >>> y2 = nn.InstanceNorm(feature_axes=-1).apply(variables, x)\n    >>> np.testing.assert_allclose(y, y2)\n\n  Attributes:\n    num_groups: the total number of channel groups. The default value of 32 is\n      proposed by the original group normalization paper.\n    group_size: the number of channels in a group.\n    epsilon: A small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    use_bias:  If True, bias (beta) is added.\n    use_scale: If True, multiply by scale (gamma). When the next layer is linear\n      (also e.g. nn.relu), this can be disabled since the scaling will be done\n      by the next layer.\n    bias_init: Initializer for bias, by default, zero.\n    scale_init: Initializer for scale, by default, one.\n    reduction_axes: List of axes used for computing normalization statistics.\n      This list must include the final dimension, which is assumed to be the\n      feature axis. Furthermore, if the input used at call time has additional\n      leading axes compared to the data used for initialisation, for example due\n      to batching, then the reduction axes need to be defined explicitly.\n    axis_name: the axis name used to combine batch statistics from multiple\n      devices. See ``jax.pmap`` for a description of axis names (default: None).\n      This is only needed if the model is subdivided across devices, i.e. the\n      array being normalized is sharded across devices within a pmap or shard\n      map. For SPMD jit, you do not need to manually synchronize. Just make sure\n      that the axes are correctly annotated and XLA:SPMD will insert the\n      necessary collectives.\n    axis_index_groups: groups of axis indices within that named axis\n      representing subsets of devices to reduce over (default: None). For\n      example, ``[[0, 1], [2, 3]]`` would independently batch-normalize over the\n      examples on the first two and last two devices. See ``jax.lax.psum`` for\n      more details. This argument is currently not supported for SPMD jit.\n    use_fast_variance: If true, use a faster, but less numerically stable,\n      calculation for the variance.\n  \"\"\"\n\n  num_groups: int | None = 32\n  group_size: int | None = None\n  epsilon: float = 1e-6\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  use_bias: bool = True\n  use_scale: bool = True\n  bias_init: Initializer = initializers.zeros\n  scale_init: Initializer = initializers.ones\n  reduction_axes: Axes | None = None\n  axis_name: str | None = None\n  axis_index_groups: Any = None\n  use_fast_variance: bool = True\n  force_float32_reductions: bool = True\n\n  @compact\n  def __call__(self, x, *, mask: jax.Array | None = None):\n    \"\"\"Applies group normalization to the input (arxiv.org/abs/1803.08494).\n\n    Args:\n      x: the input of shape ``...C`` where ``C`` is a channels dimension and ``...``\n        represents an arbitrary number of extra dimensions that can be used to\n        accumulate statistics over. If no reduction axes have been specified\n        then all additional dimensions ``...`` will be used to accumulate\n        statistics apart from the leading dimension which is assumed to\n        represent the batch.\n      mask: Binary array of shape broadcastable to ``inputs`` tensor, indicating\n        the positions for which the mean and variance should be computed.\n\n    Returns:\n      Normalized inputs (the same shape as inputs).\n    \"\"\"\n    if self.reduction_axes is not None:\n      reduction_axes = self.reduction_axes\n    else:\n      reduction_axes = list(range(1, x.ndim - 1)) + [-1]\n    feature_axis = -1\n\n    reduction_axes = _canonicalize_axes(x.ndim, reduction_axes)\n\n    if reduction_axes[-1] != (feature_axis % x.ndim):\n      raise ValueError(\n          'The reduction axes must include the final dimension '\n          'as this is assumed to be the feature axis.'\n      )\n\n    if (self.num_groups is None and self.group_size is None) or (\n      self.num_groups is not None and self.group_size is not None\n    ):\n      raise ValueError(\n        'Either `num_groups` or `group_size` should be '\n        'specified. If `group_size` is to be specified, '\n        'pass `num_groups=None` as argument to override '\n        'the default `num_groups` value of 32.'\n      )\n\n    channels = x.shape[-1]\n    if self.group_size is not None:\n      if channels % self.group_size != 0:\n        raise ValueError(\n          'Number of channels ({}) is not multiple of the '\n          'group size ({}).'.format(channels, self.group_size)\n        )\n      num_groups = channels // self.group_size\n    else:\n      num_groups = self.num_groups\n      assert isinstance(num_groups, int)\n\n    if num_groups <= 0 or channels % num_groups != 0:\n      raise ValueError(\n        'Number of groups ({}) does not divide the number'\n        ' of channels ({}).'.format(num_groups, channels)\n      )\n\n    group_size = x.shape[-1] // num_groups\n    group_shape = x.shape[:-1] + (num_groups, group_size)\n\n    if mask is not None:\n      mask = mask.reshape(mask.shape[:-1] + (num_groups, group_size))\n\n    mean, var = _compute_stats(\n        x.reshape(group_shape),\n        list(reduction_axes[:-1]) + [-1],\n        self.dtype,\n        self.axis_name,\n        self.axis_index_groups,\n        use_fast_variance=self.use_fast_variance,\n        mask=mask,\n        force_float32_reductions=self.force_float32_reductions,\n    )\n    mean = jnp.repeat(mean, group_size, axis=-1)\n    var = jnp.repeat(var, group_size, axis=-1)\n\n    return _normalize(\n      self,\n      x,\n      mean,\n      var,\n      reduction_axes[:-1],\n      (feature_axis,),\n      self.dtype,\n      self.param_dtype,\n      self.epsilon,\n      self.use_bias,\n      self.use_scale,\n      self.bias_init,\n      self.scale_init,\n      self.force_float32_reductions,\n    )\n\n\nclass InstanceNorm(Module):\n  \"\"\"Instance normalization (https://arxiv.org/abs/1607.08022v3).\n\n  InstanceNorm normalizes the activations of the layer for each channel (rather\n  than across all channels like Layer Normalization), and for each given example\n  in a batch independently (rather than across an entire batch like Batch\n  Normalization). i.e. applies a transformation that maintains the mean activation\n  within each channel within each example close to 0 and the activation standard\n  deviation close to 1.\n\n  .. note::\n    This normalization operation is identical to LayerNorm and GroupNorm; the\n    difference is simply which axes are reduced and the shape of the feature axes\n    (i.e. the shape of the learnable scale and bias parameters).\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax\n    >>> import numpy as np\n\n    >>> # dimensions: (batch, height, width, channel)\n    >>> x = jax.random.normal(jax.random.key(0), (2, 3, 4, 5))\n    >>> layer = nn.InstanceNorm()\n    >>> variables = layer.init(jax.random.key(1), x)\n    >>> variables\n    {'params': {'scale': Array([1., 1., 1., 1., 1.], dtype=float32), 'bias': Array([0., 0., 0., 0., 0.], dtype=float32)}}\n    >>> y = layer.apply(variables, x)\n\n    >>> # having a channel_axis of -1 in InstanceNorm is identical to reducing all non-batch,\n    >>> # non-channel axes and using the feature_axes as the feature_axes in LayerNorm\n    >>> y2 = nn.LayerNorm(reduction_axes=[1, 2], feature_axes=-1).apply(variables, x)\n    >>> np.testing.assert_allclose(y, y2, atol=1e-7)\n    >>> y3 = nn.GroupNorm(num_groups=x.shape[-1]).apply(variables, x)\n    >>> np.testing.assert_allclose(y, y3, atol=1e-7)\n\n  Attributes:\n    epsilon: A small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    use_bias:  If True, bias (beta) is added.\n    use_scale: If True, multiply by scale (gamma). When the next layer is linear\n      (also e.g. nn.relu), this can be disabled since the scaling will be done\n      by the next layer.\n    bias_init: Initializer for bias, by default, zero.\n    scale_init: Initializer for scale, by default, one.\n    feature_axes: Axes for features. The learned bias and scaling parameters will\n      be in the shape defined by the feature axes. All other axes except the batch\n      axes (which is assumed to be the leading axis) will be reduced.\n    axis_name: the axis name used to combine batch statistics from multiple\n      devices. See ``jax.pmap`` for a description of axis names (default: None).\n      This is only needed if the model is subdivided across devices, i.e. the\n      array being normalized is sharded across devices within a pmap or shard\n      map. For SPMD jit, you do not need to manually synchronize. Just make sure\n      that the axes are correctly annotated and XLA:SPMD will insert the\n      necessary collectives.\n    axis_index_groups: groups of axis indices within that named axis\n      representing subsets of devices to reduce over (default: None). For\n      example, ``[[0, 1], [2, 3]]`` would independently batch-normalize over the\n      examples on the first two and last two devices. See ``jax.lax.psum`` for\n      more details. This argument is currently not supported for SPMD jit.\n    use_fast_variance: If true, use a faster, but less numerically stable,\n      calculation for the variance.\n  \"\"\"\n\n  epsilon: float = 1e-6\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  use_bias: bool = True\n  use_scale: bool = True\n  bias_init: Initializer = initializers.zeros\n  scale_init: Initializer = initializers.ones\n  feature_axes: Axes = -1\n  axis_name: str | None = None\n  axis_index_groups: Any = None\n  use_fast_variance: bool = True\n  force_float32_reductions: bool = True\n\n  @compact\n  def __call__(self, x, *, mask: jax.Array | None = None):\n    \"\"\"Applies instance normalization on the input.\n\n    Args:\n      x: the inputs\n      mask: Binary array of shape broadcastable to ``inputs`` tensor, indicating\n        the positions for which the mean and variance should be computed.\n\n    Returns:\n      Normalized inputs (the same shape as inputs).\n    \"\"\"\n    feature_axes = _canonicalize_axes(x.ndim, self.feature_axes)\n    if 0 in feature_axes:\n      raise ValueError('The channel axes cannot include the leading dimension '\n                       'as this is assumed to be the batch axis.')\n    reduction_axes = [i for i in range(1, x.ndim) if i not in feature_axes]\n\n    mean, var = _compute_stats(\n        x,\n        reduction_axes,\n        self.dtype,\n        self.axis_name,\n        self.axis_index_groups,\n        use_fast_variance=self.use_fast_variance,\n        mask=mask,\n        force_float32_reductions=self.force_float32_reductions,\n    )\n\n    return _normalize(\n      self,\n      x,\n      mean,\n      var,\n      reduction_axes,\n      feature_axes,\n      self.dtype,\n      self.param_dtype,\n      self.epsilon,\n      self.use_bias,\n      self.use_scale,\n      self.bias_init,\n      self.scale_init,\n      self.force_float32_reductions,\n    )\n\n\nclass SpectralNorm(Module):\n  \"\"\"Spectral normalization.\n\n  See:\n\n  - https://arxiv.org/abs/1802.05957\n  - https://arxiv.org/abs/1805.08318\n  - https://arxiv.org/abs/1809.11096\n\n  Spectral normalization normalizes the weight params so that the spectral\n  norm of the matrix is equal to 1. This is implemented as a layer wrapper\n  where each wrapped layer will have its params spectral normalized before\n  computing its ``__call__`` output.\n\n  .. note::\n    The initialized variables dict will contain, in addition to a 'params'\n    collection, a separate 'batch_stats' collection that will contain a\n    ``u`` vector and ``sigma`` value, which are intermediate values used\n    when performing spectral normalization. During training, we pass in\n    ``update_stats=True`` and ``mutable=['batch_stats']`` so that ``u``\n    and ``sigma`` are updated with the most recently computed values using\n    power iteration. This will help the power iteration method approximate\n    the true singular value more accurately over time. During eval, we pass\n    in ``update_stats=False`` to ensure we get deterministic behavior from\n    the model.\n\n  Example usage::\n\n    >>> import flax, flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n    >>> import optax\n\n    >>> class Foo(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x, train):\n    ...     x = nn.Dense(3)(x)\n    ...     # only spectral normalize the params of the second Dense layer\n    ...     x = nn.SpectralNorm(nn.Dense(4))(x, update_stats=train)\n    ...     x = nn.Dense(5)(x)\n    ...     return x\n\n    >>> # init\n    >>> x = jnp.ones((1, 2))\n    >>> y = jnp.ones((1, 5))\n    >>> model = Foo()\n    >>> variables = model.init(jax.random.PRNGKey(0), x, train=False)\n    >>> flax.core.freeze(jax.tree_util.tree_map(jnp.shape, variables))\n    FrozenDict({\n        batch_stats: {\n            SpectralNorm_0: {\n                Dense_1/kernel/sigma: (),\n                Dense_1/kernel/u: (1, 4),\n            },\n        },\n        params: {\n            Dense_0: {\n                bias: (3,),\n                kernel: (2, 3),\n            },\n            Dense_1: {\n                bias: (4,),\n                kernel: (3, 4),\n            },\n            Dense_2: {\n                bias: (5,),\n                kernel: (4, 5),\n            },\n        },\n    })\n\n    >>> # train\n    >>> def train_step(variables, x, y):\n    ...   def loss_fn(params):\n    ...     logits, updates = model.apply(\n    ...         {'params': params, 'batch_stats': variables['batch_stats']},\n    ...         x,\n    ...         train=True,\n    ...         mutable=['batch_stats'],\n    ...     )\n    ...     loss = jnp.mean(optax.l2_loss(predictions=logits, targets=y))\n    ...     return loss, updates\n    ...\n    ...   (loss, updates), grads = jax.value_and_grad(loss_fn, has_aux=True)(\n    ...       variables['params']\n    ...   )\n    ...   return {\n    ...       'params': jax.tree_util.tree_map(\n    ...           lambda p, g: p - 0.1 * g, variables['params'], grads\n    ...       ),\n    ...       'batch_stats': updates['batch_stats'],\n    ...   }, loss\n    >>> for _ in range(10):\n    ...   variables, loss = train_step(variables, x, y)\n\n    >>> # inference / eval\n    >>> out = model.apply(variables, x, train=False)\n\n  Attributes:\n    layer_instance: Module instance that is wrapped with SpectralNorm\n    n_steps: How many steps of power iteration to perform to approximate the\n      singular value of the weight params.\n    epsilon: A small float added to l2-normalization to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    error_on_non_matrix: Spectral normalization is only defined on matrices. By\n      default, this module will return scalars unchanged and flatten\n      higher-order tensors in their leading dimensions. Setting this flag to\n      True will instead throw an error if a weight tensor with dimension greater\n      than 2 is used by the layer.\n    collection_name: Name of the collection to store intermediate values used\n      when performing spectral normalization.\n  \"\"\"\n\n  layer_instance: Module\n  n_steps: int = 1\n  epsilon: float = 1e-12\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  error_on_non_matrix: bool = False\n  collection_name: str = 'batch_stats'\n\n  @compact\n  def __call__(self, *args, update_stats: bool, **kwargs):\n    \"\"\"Compute the largest singular value of the weights in ``self.layer_instance``\n    using power iteration and normalize the weights using this value before\n    computing the ``__call__`` output.\n\n    Args:\n      *args: positional arguments to be passed into the call method of the\n        underlying layer instance in ``self.layer_instance``.\n      update_stats: if True, update the internal ``u`` vector and ``sigma``\n        value after computing their updated values using power iteration. This\n        will help the power iteration method approximate the true singular value\n        more accurately over time.\n      **kwargs: keyword arguments to be passed into the call method of the\n        underlying layer instance in ``self.layer_instance``.\n\n    Returns:\n      Output of the layer using spectral normalized weights.\n    \"\"\"\n\n    def layer_forward(layer_instance):\n      return layer_instance(*args, **kwargs)\n\n    return transforms.map_variables(\n      layer_forward,\n      trans_in_fn=lambda vs: jax.tree_util.tree_map_with_path(\n        functools.partial(\n          self._spectral_normalize,\n          update_stats=update_stats,\n        ),\n        vs,\n      ),\n      init=self.is_initializing(),\n      mutable=True,\n    )(self.layer_instance)\n\n  def _spectral_normalize(self, path, vs, update_stats):\n    \"\"\"Compute the largest singular value using power iteration and normalize\n    the variables ``vs`` using this value. This is intended to be a helper\n    function used in this Module's ``__call__`` method in conjunction with\n    ``nn.transforms.map_variables`` and ``jax.tree_util.tree_map_with_path``.\n\n    Args:\n      path: dict key path, used for naming the ``u`` and ``sigma`` variables\n      vs: variables to be spectral normalized\n      update_stats: if True, update the ``u`` vector and ``sigma`` variables\n        after computing their updated values using power iteration. This will\n        help the power iteration method approximate the true singular value\n        more accurately over time.\n    \"\"\"\n    value = jnp.asarray(vs)\n    value_shape = value.shape\n\n    # Skip and return value if input is scalar, vector or if number of power\n    # iterations is less than 1\n    if value.ndim <= 1 or self.n_steps < 1:\n      return value\n    # Handle higher-order tensors.\n    elif value.ndim > 2:\n      if self.error_on_non_matrix:\n        raise ValueError(\n          f'Input is {value.ndim}D but error_on_non_matrix is True'\n        )\n      else:\n        value = jnp.reshape(value, (-1, value.shape[-1]))\n\n    u_var_name = (\n      self.layer_instance.name\n      + '/'\n      + '/'.join(dict_key.key for dict_key in path[1:])\n      + '/u'\n    )\n    u_var = self.variable(\n      self.collection_name,\n      u_var_name,\n      jax.random.normal,\n      self.make_rng('params')\n      if not self.has_variable(self.collection_name, u_var_name)\n      else None,\n      (1, value.shape[-1]),\n      self.param_dtype,\n    )\n    u0 = u_var.value\n    sigma_var_name = (\n      self.layer_instance.name\n      + '/'\n      + '/'.join(dict_key.key for dict_key in path[1:])\n      + '/sigma'\n    )\n    sigma_var = self.variable(\n      self.collection_name, sigma_var_name, jnp.ones, (), self.param_dtype\n    )\n\n    # Power iteration for the weight's singular value.\n    for _ in range(self.n_steps):\n      v0 = _l2_normalize(\n        jnp.matmul(u0, value.transpose([1, 0])), eps=self.epsilon\n      )\n      u0 = _l2_normalize(jnp.matmul(v0, value), eps=self.epsilon)\n\n    u0 = jax.lax.stop_gradient(u0)\n    v0 = jax.lax.stop_gradient(v0)\n\n    sigma = jnp.matmul(jnp.matmul(v0, value), jnp.transpose(u0))[0, 0]\n\n    value /= jnp.where(sigma != 0, sigma, 1)\n    value_bar = value.reshape(value_shape)\n\n    if update_stats:\n      u_var.value = u0\n      sigma_var.value = sigma\n\n    dtype = dtypes.canonicalize_dtype(vs, u0, v0, sigma, dtype=self.dtype)\n    return jnp.asarray(value_bar, dtype)\n\n\nclass WeightNorm(Module):\n  \"\"\"L2 weight normalization (https://arxiv.org/abs/1602.07868).\n\n  Weight normalization normalizes the weight params so that the l2-norm of\n  the matrix is equal to 1. This is implemented as a layer wrapper where\n  each wrapped layer will have its params l2-normalized before computing\n  its ``__call__`` output.\n\n  Example usage::\n\n    >>> import flax, flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> class Baz(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     return nn.Dense(2)(x)\n\n    >>> class Bar(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     x = Baz()(x)\n    ...     x = nn.Dense(3)(x)\n    ...     x = Baz()(x)\n    ...     x = nn.Dense(3)(x)\n    ...     return x\n\n    >>> class Foo(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     x = nn.Dense(3)(x)\n    ...     # l2-normalize all params of the second Dense layer\n    ...     x = nn.WeightNorm(nn.Dense(4), variable_filter=None)(x)\n    ...     x = nn.Dense(5)(x)\n    ...     # l2-normalize all kernels in the Bar submodule and all params in\n    ...     # the Baz submodule\n    ...     x = nn.WeightNorm(Bar(), variable_filter={'kernel', 'Baz'})(x)\n    ...     return x\n\n    >>> # init\n    >>> x = jnp.ones((1, 2))\n    >>> model = Foo()\n    >>> variables = model.init(jax.random.key(0), x)\n    >>> flax.core.freeze(jax.tree_util.tree_map(jnp.shape, variables))\n    FrozenDict({\n        params: {\n            Bar_0: {\n                Baz_0: {\n                    Dense_0: {\n                        bias: (2,),\n                        kernel: (5, 2),\n                    },\n                },\n                Baz_1: {\n                    Dense_0: {\n                        bias: (2,),\n                        kernel: (3, 2),\n                    },\n                },\n                Dense_0: {\n                    bias: (3,),\n                    kernel: (2, 3),\n                },\n                Dense_1: {\n                    bias: (3,),\n                    kernel: (2, 3),\n                },\n            },\n            Dense_0: {\n                bias: (3,),\n                kernel: (2, 3),\n            },\n            Dense_1: {\n                bias: (4,),\n                kernel: (3, 4),\n            },\n            Dense_2: {\n                bias: (5,),\n                kernel: (4, 5),\n            },\n            WeightNorm_0: {\n                Dense_1/bias/scale: (4,),\n                Dense_1/kernel/scale: (4,),\n            },\n            WeightNorm_1: {\n                Bar_0/Baz_0/Dense_0/bias/scale: (2,),\n                Bar_0/Baz_0/Dense_0/kernel/scale: (2,),\n                Bar_0/Baz_1/Dense_0/bias/scale: (2,),\n                Bar_0/Baz_1/Dense_0/kernel/scale: (2,),\n                Bar_0/Dense_0/kernel/scale: (3,),\n                Bar_0/Dense_1/kernel/scale: (3,),\n            },\n        },\n    })\n\n  Attributes:\n    layer_instance: Module instance that is wrapped with WeightNorm\n    epsilon: A small float added to l2-normalization to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    use_scale: If True, creates a learnable variable ``scale`` that is\n      multiplied to the ``layer_instance`` variables after l2-normalization.\n    scale_init: Initialization function for the scaling function.\n    feature_axes: The feature axes dimension(s). The l2-norm is calculated by\n      reducing the ``layer_instance`` variables over the remaining (non-feature)\n      axes. Therefore a separate l2-norm value is calculated and a separate\n      scale (if ``use_scale=True``) is learned for each specified feature. By\n      default, the trailing dimension is treated as the feature axis.\n    variable_filter: An optional iterable that contains string items. The\n      WeightNorm layer will selectively apply l2-normalization to the\n      ``layer_instance`` variables whose key path (delimited by '/') has a match\n      with ``variable_filter``. For example, ``variable_filter={'kernel'}`` will\n      only apply l2-normalization to variables whose key path contains 'kernel'.\n      By default, ``variable_filter={'kernel'}``.\n  \"\"\"\n\n  layer_instance: Module\n  epsilon: float = 1e-12\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  use_scale: bool = True\n  scale_init: Initializer = initializers.ones\n  feature_axes: Axes | None = -1\n  variable_filter: Iterable | None = dataclasses.field(\n    default_factory=lambda: {'kernel'}\n  )\n\n  @compact\n  def __call__(self, *args, **kwargs):\n    \"\"\"Compute the l2-norm of the weights in ``self.layer_instance``\n    and normalize the weights using this value before computing the\n    ``__call__`` output.\n\n    Args:\n      *args: positional arguments to be passed into the call method of the\n        underlying layer instance in ``self.layer_instance``.\n      **kwargs: keyword arguments to be passed into the call method of the\n        underlying layer instance in ``self.layer_instance``.\n\n    Returns:\n      Output of the layer using l2-normalized weights.\n    \"\"\"\n\n    def layer_forward(layer_instance):\n      return layer_instance(*args, **kwargs)\n\n    return transforms.map_variables(\n      layer_forward,\n      trans_in_fn=lambda vs: jax.tree_util.tree_map_with_path(\n        self._l2_normalize,\n        vs,\n      ),\n      init=self.is_initializing(),\n    )(self.layer_instance)\n\n  def _l2_normalize(self, path, vs):\n    \"\"\"Compute the l2-norm and normalize the variables ``vs`` using this\n    value. This is intended to be a helper function used in this Module's\n    ``__call__`` method in conjunction with ``nn.transforms.map_variables``\n    and ``jax.tree_util.tree_map_with_path``.\n\n    Args:\n      path: dict key path, used for naming the ``scale`` variable\n      vs: variables to be l2-normalized\n    \"\"\"\n    value = jnp.asarray(vs)\n    str_path = (\n        self.layer_instance.name\n        + '/'\n        + jax.tree_util.keystr(path[1:], simple=True, separator='/')\n    )\n    if self.variable_filter:\n      for variable_name in self.variable_filter:\n        if variable_name in str_path:\n          break\n      else:\n        return value\n\n    if self.feature_axes is None:\n      feature_axes = ()\n      reduction_axes = tuple(i for i in range(value.ndim))\n    else:\n      feature_axes = _canonicalize_axes(value.ndim, self.feature_axes)\n      reduction_axes = tuple(\n        i for i in range(value.ndim) if i not in feature_axes\n      )\n\n    feature_shape = [1] * value.ndim\n    reduced_feature_shape = []\n    for ax in feature_axes:\n      feature_shape[ax] = value.shape[ax]\n      reduced_feature_shape.append(value.shape[ax])\n\n    value_bar = _l2_normalize(value, axis=reduction_axes, eps=self.epsilon)\n\n    args = [vs]\n    if self.use_scale:\n      scale = self.param(\n        str_path + '/scale',\n        self.scale_init,\n        reduced_feature_shape,\n        self.param_dtype,\n      ).reshape(feature_shape)\n      value_bar *= scale\n      args.append(scale)\n\n    dtype = dtypes.canonicalize_dtype(*args, dtype=self.dtype)\n    return jnp.asarray(value_bar, dtype)\n"
  },
  {
    "path": "flax/linen/partitioning.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Legacy utilities for working with pjit and partitioned models.\n\n**Experimental: please give feedback, and expect changes.**\n\nThis module introduces `axis_rules`, `logical_to_mesh_axes`,\n`with_sharding_constraint` for appyling pjit sharding constraints in terms of\n\"logical named axes\" rather than pjit's default mesh axes.\n\nAdditionally, flax linen methods `param_with_axes` and `variable_with_axes`\nare introduced alongside `get_axis_names` for defining variables and parameters\nand variables with logical axis name annotations that are managed as metadata.\n\nLastly, `*_with_axes` versions of `nn.scan` and `nn.vmap` are introduced to add\nlogical axis metadata to the underlying Lifted transformations.\n\"\"\"\n\nimport functools\nimport re\nfrom typing import (Any, Optional, Tuple)\nfrom collections.abc import Callable, Mapping\n\nimport flax\nfrom flax import linen as nn\nfrom flax import struct\nfrom flax.core.frozen_dict import freeze\nfrom flax.core.frozen_dict import unfreeze\nfrom flax.core.scope import (\n  CollectionFilter as CollectionFilter,\n  PRNGSequenceFilter as PRNGSequenceFilter,\n)\nfrom flax.core.spmd import logical_axis_rules as axis_rules  # pylint: disable=unused-import\nfrom flax.core.spmd import set_logical_axis_rules as set_axis_rules  # pylint: disable=unused-import\nfrom flax.core.spmd import get_logical_axis_rules as get_axis_rules  # pylint: disable=unused-import\nfrom flax.linen.spmd import _is_logical_spec\nfrom flax.linen.spmd import _with_sharding_constraint  # pylint: disable=unused-import\nfrom flax.linen.spmd import logical_to_mesh  # pylint: disable=unused-import\nfrom flax.linen.spmd import logical_to_mesh_axes  # pylint: disable=unused-import\nfrom flax.linen.spmd import RulesFallback\nfrom flax.linen.spmd import with_logical_constraint as with_sharding_constraint\nfrom flax.traverse_util import flatten_dict\nfrom flax.traverse_util import unflatten_dict\nfrom flax.typing import (\n  Array,\n  In as ScanIn,  # pylint: disable=unused-import\n  Out as ScanOut,  # pylint: disable=unused-import\n  InOutAxis,\n  InOutScanAxis,\n  LogicalRules,  # pylint: disable=unused-import\n  ArrayPytree,  # pylint: disable=unused-import\n  LogicalPartitionSpec,  # pylint: disable=unused-import\n  LogicalPartitionSpecPytree,\n  PartitionSpecPytree,  # pylint: disable=unused-import\n)\nimport jax\n\n\n# ------------------------------------------------------------------------------\n# NOTICE:  This experimental partitioning utility API is deprecated\n#\n# We intend to continue supporting it indefinitely for those using it, but\n# we encourage new users to adopt the simpler metadata handling system found\n# in \"spmd.py\".\n# ------------------------------------------------------------------------------\n\n\n# Annotated parameters and Module axis metadata handling.\n# ------------------------------------------------------------------------------\n\n\n@struct.dataclass\nclass AxisMetadata:\n  \"\"\"Contains a tuple of axis names, which is passed through FLAX.\"\"\"\n\n  names: LogicalPartitionSpecPytree = struct.field(pytree_node=False)\n\n\ndef _param_with_axes_sow_reduce_fn(x, y):\n  \"\"\"Reduction function for sow() calls.\n\n  Args:\n    x: Existing value, or () if there was none.\n    y: New axis names sown.\n\n  Returns:\n    New axis names.\n\n  Raises:\n    TypeError: If the newly sown value is not an AxisMetadata.\n    ValueError: If the newly sown axis names don't match previously sown axis\n      names.\n    AssertionError: If a previously sown value was truthy and not an\n      AxisMetadata.\n  \"\"\"\n  if not isinstance(y, AxisMetadata):\n    raise TypeError('Expected newly sown value to be an AxisMetadata')\n\n  if isinstance(x, AxisMetadata):\n    if x != y:\n      raise ValueError(\n          'If axis names are sown twice, expected them to match. '\n          f'Got {x} and {y}.'\n      )\n  elif x:\n    # Shouldn't happen, so raise a fairly internal error.\n    raise AssertionError(f'Non-initial-or-AxisMetadata value encountered: {x}')\n  return y\n\n\ndef param_with_axes(\n    name: str,\n    init_fn,\n    *init_args,\n    axes: tuple[str, ...] | None = None,\n    module: Optional['nn.Module'] = None,\n    **init_kwargs,\n):\n  \"\"\"Declares and returns a parameter with logical axes in the current Module.\n\n  See :mod:`flax.linen.module.param` for original docstring.\n\n  Args:\n    name: The parameter name.\n    init_fn: The function that will be called to compute the initial value\n      of this variable. This function will only be called the first time\n      this parameter is used in this module.\n    *init_args: The positional arguments to pass to init_fn.\n    axes: A tuple of axis names, must match the rank of the param array.\n    module: Use an explicit module instead of deriving the most recent from\n      dynamic module context.\n    **init_kwargs: The key-word arguments to pass to init_fn.\n\n  Returns:\n    The value of the initialized parameter.\n\n  Raises:\n    TypeError: if axes specification is mal-formed.\n    ValueError: if specified logical axes don't match parameter rank.\n  \"\"\"\n  # get current module if not explicitly provided\n  if module is None:\n    module = nn.module._context.module_stack[-1]  # pylint: disable=protected-access\n    assert module is not None\n  # define/fetch parameter on that module\n  module_param = module.param(name, init_fn, *init_args, **init_kwargs)\n  if axes is not None:\n    # apply logical axis constraint immediately\n    module_param = with_sharding_constraint(\n        module_param, jax.sharding.PartitionSpec(*axes)\n    )\n    # record logical axis constraint for global axis metadata\n    module.sow(\n        'params_axes',\n        f'{name}_axes',\n        AxisMetadata(axes),  # type: ignore\n        reduce_fn=_param_with_axes_sow_reduce_fn,\n    )\n  return module_param\n\n\nclass PartitionedVariable(flax.core.scope.Variable):\n  \"\"\"A PartitionedVariable object allows mutable access to a variable.\n\n  PartitionedVariables are identified by a collection (e.g., \"batch_stats\") and\n  a name (e.g., \"moving_mean\"). The value property gives access to the\n  variable's content and can be assigned to for mutation.  Additionally,\n  PartitionedVariables enforce logical sharding constraints on both retrieval\n  and assignment.\n  \"\"\"\n\n  def __init__(\n      self,\n      scope,\n      collection: str,\n      name: str,\n      axes: tuple[str, ...] | None = None,\n      fallback: RulesFallback = RulesFallback.AXIS_IS_UNSHARDED,\n  ):\n    \"\"\"Initializes a partitioned variable.\n\n    Args:\n      scope: The scope in which the variable is stored.\n      collection: The collection of the variable (e.g., \"params\").\n      name: The name of the variable (e.g., \"dense\").\n      axes: logical axes name of variable.\n      fallback: Fallback behavior if no matching rule is found.\n    \"\"\"\n    self.scope = scope\n    self.collection = collection\n    self.name = name\n    self.axes = axes\n    self.fallback = fallback\n\n  @property\n  def value(self):\n    \"\"\"Returns the value of this Variable.\"\"\"\n    value = self.scope.get_variable(self.collection, self.name)\n    if self.axes is not None:\n      value = with_sharding_constraint(value, self.axes, fallback=self.fallback)\n    return value\n\n  @value.setter\n  def value(self, value):\n    \"\"\"Updates the value of this Variable.\"\"\"\n    if self.axes is not None:\n      value = with_sharding_constraint(value, self.axes, fallback=self.fallback)\n    self.scope.put_variable(self.collection, self.name, value)\n\n\ndef _core_variable_with_axes(\n    scope,\n    col: str,\n    name: str,\n    init_fn: Callable[..., Any],\n    *init_args,\n    axes: tuple[str, ...] | None = None,\n    fallback: RulesFallback = RulesFallback.AXIS_IS_UNSHARDED,\n    **init_kwargs,\n):\n  \"\"\"Variant of flax core variable scope call with sharding constraints.\"\"\"\n  scope.reserve(name)\n  if not scope.has_variable(col, name):\n    if not scope.is_mutable_collection(col):\n      raise flax.errors.ScopeVariableNotFoundError(name, col, scope.path_text)\n    init_value = init_fn(*init_args, **init_kwargs)\n    if axes is not None:\n      init_value = with_sharding_constraint(init_value, axes, fallback=fallback)\n    scope.put_variable(col, name, init_value)\n  return PartitionedVariable(scope, col, name, axes, fallback)\n\n\ndef variable_with_axes(\n    collection: str,\n    name: str,\n    init_fn,\n    *init_args,\n    axes: tuple[str, ...] | None = None,\n    module: Optional['nn.Module'] = None,\n    fallback: RulesFallback = RulesFallback.AXIS_IS_UNSHARDED,\n    **init_kwargs,\n):\n  \"\"\"Declares and returns a variable with logical axes in the current Module.\n\n  See :mod:`flax.linen.module.variable` for original docstring.\n\n  Args:\n    collection: The name of the variable collection.\n    name: The variable name.\n    init_fn: The function that will be called to compute the initial value\n      of this variable. This function will only be called the first time\n      this parameter is used in this module.\n    *init_args: The positional arguments to pass to init_fn.\n    axes: A tuple of axis names, must match the rank of the variable array.\n    module: Use an explicit module instead of deriving the most recent from\n      dynamic module context.\n    fallback: How sharding should behave if there is no rule covering some axis.\n    **init_kwargs: The key-word arguments to pass to init_fn.\n\n  Returns:\n    A flax `PartitionedVariable` object referencing the initialized variable\n    array.\n\n  Raises:\n    TypeError: if axes specification is mal-formed.\n    ValueError: if specified logical axes don't match parameter rank.\n  \"\"\"\n  # get current module if not explicitly provided\n  if module is None:\n    module = nn.module._context.module_stack[-1]  # pylint: disable=protected-access\n    assert module is not None\n  module_var = _core_variable_with_axes(\n      module.scope,\n      collection,\n      name,\n      init_fn,\n      *init_args,\n      axes=axes,\n      fallback=fallback,\n      **init_kwargs,\n  )\n  if axes is not None:\n    # record logical axis constraint for global axis metadata\n    module.sow(\n        f'{collection}_axes',\n        f'{name}_axes',\n        AxisMetadata(axes),  # type: ignore\n        reduce_fn=_param_with_axes_sow_reduce_fn,\n    )\n  return module_var\n\n\ndef get_axis_names(axes_metadata):\n  \"\"\"Gets axis names for variables as logical PartitionSpecs.\n\n  Args:\n    axes_metadata: a single axes-metadata collection from a flax-initialized\n      set of collections.\n\n  Returns:\n    Collection of Partitionspecs with logical axis names, with the \"_axes\"\n    suffix on variable names removed to match original variable collection for\n    annotations.\n  \"\"\"\n\n  def leaf_rewrite(x):\n    return None if x is None else jax.sharding.PartitionSpec(*x)\n\n  def rewrite(tree):\n    return jax.tree_util.tree_map(leaf_rewrite, tree, is_leaf=_is_logical_spec)\n\n  axes_metadata = unfreeze(axes_metadata)  # pytype: disable=wrong-arg-types\n  flat_dict = {\n      re.sub(r'_axes$', '', '/'.join(k)): rewrite(v.names)\n      for k, v in flatten_dict(axes_metadata).items()\n  }\n  return freeze(\n      unflatten_dict({tuple(k.split('/')): v for k, v in flat_dict.items()})\n  )\n\n\n# Metadata Aware Scan\n# -----------------------------------------------------------------------------\n\n\ndef _tree_map_axes(fn, tree):\n  \"\"\"Only map over AxisMetadata leaves in pytree - identity for other leaves.\"\"\"\n  safe_fn = lambda x: fn(x) if isinstance(x, AxisMetadata) else x\n  return jax.tree_util.tree_map(\n      safe_fn, tree, is_leaf=lambda x: isinstance(x, AxisMetadata)\n  )\n\n\ndef _is_mutable(axis_col: str) -> bool:\n  \"\"\"Determines whether a collection is mutable.\n\n  For example, when a module is called with `module.apply(..., mutable=['z'])`,\n  this function will return True for `axis_col='z'` and False otherwise.\n\n  If there is no module in scope, this function will return True.\n\n  Args:\n    axis_col: Name of the collection in question.\n\n  Returns:\n    Whether it is currently mutable.\n  \"\"\"\n  last = nn.module._context.module_stack[-1]  # pylint: disable=protected-access\n  if last:\n    return last.is_mutable_collection(axis_col)\n  else:\n    return True\n\n\n# uses this variable_transform to change 'params_axes' pytree as it bubbles\n# up / out from scan.\ndef _add_axis_to_metadata(fn, axis_pos, axis_name, axis_col='params_axes'):\n  \"\"\"Insert a named axis to axes metadata.\"\"\"\n  # Handle In() / Out() scan axis marker types.\n  if hasattr(axis_pos, 'axis'):\n    axis_pos = axis_pos.axis\n\n  def insert_fn_leaf(names):\n    if names is None:\n      return names\n    names = list(names)\n    names.insert(axis_pos, axis_name)\n    return tuple(names)\n\n  def insert_fn(x):\n    new_names = jax.tree_util.tree_map(\n        insert_fn_leaf, x.names, is_leaf=_is_logical_spec\n    )\n    return x.replace(names=new_names)\n\n  def remove_fn_leaf(names):\n    if names is None:\n      return names\n    names = list(names)\n    if names[axis_pos] != axis_name:\n      raise ValueError(\n          f'Expected axis {axis_name} at position {axis_pos} in '\n          f'axis metadata {names}.'\n      )\n    names.pop(axis_pos)\n    return tuple(names)\n\n  def remove_fn(x):\n    new_names = jax.tree_util.tree_map(\n        remove_fn_leaf, x.names, is_leaf=_is_logical_spec\n    )\n    return x.replace(names=new_names)\n\n  return nn.transforms.map_variables(\n      fn,\n      axis_col,\n      mutable=_is_mutable(axis_col),\n      trans_in_fn=lambda tree: _tree_map_axes(remove_fn, tree),\n      trans_out_fn=lambda tree: _tree_map_axes(insert_fn, tree),\n  )\n\n\n# pylint: disable=dangerous-default-value\ndef scan_with_axes(\n    target: 'flax.linen.transforms.Target',\n    variable_axes: Mapping[\n        CollectionFilter, InOutScanAxis\n    ] = {},\n    variable_broadcast: CollectionFilter = False,\n    variable_carry: CollectionFilter = False,\n    split_rngs: Mapping[PRNGSequenceFilter, bool] = {},\n    in_axes=0,\n    out_axes=0,\n    length: int | None = None,\n    reverse: bool = False,\n    unroll: int = 1,\n    axis_name: str = 'layers',\n    axes_collections: tuple[str, ...] = ('params',),\n    data_transform: Callable[..., Any] | None = None,\n    methods=None,\n) -> 'flax.linen.transforms.Target':\n  \"\"\"Wrapped version of nn.scan that handles logical axis metadata.\"\"\"\n\n  # we broadcast the static metadata collections.\n  axes_filters = tuple(f'{col}_axes' for col in axes_collections)\n  variable_broadcast = flax.core.scope.union_filters(\n      variable_broadcast, axes_filters\n  )\n\n  # perform usual lifted scan\n  scanned = flax.linen.transforms.lift_transform(\n      flax.core.lift.scan,\n      target,\n      variable_axes=variable_axes,\n      variable_broadcast=variable_broadcast,\n      variable_carry=variable_carry,\n      split_rngs=split_rngs,\n      in_axes=in_axes,\n      out_axes=out_axes,\n      length=length,\n      reverse=reverse,\n      unroll=unroll,\n      data_transform=data_transform,\n      methods=methods,\n  )\n\n  # add scan axis to logical axes metadata\n  for col in axes_collections:\n    if col in variable_axes:\n      scanned = _add_axis_to_metadata(\n          scanned,\n          axis_pos=variable_axes[col],\n          axis_name=axis_name,\n          axis_col=f'{col}_axes',\n      )\n  return scanned\n\n\n# pylint: disable=dangerous-default-value\ndef vmap_with_axes(\n    target: 'flax.linen.transforms.Target',\n    variable_axes: Mapping[\n        CollectionFilter, InOutAxis\n    ],\n    split_rngs: Mapping[PRNGSequenceFilter, bool] = {},\n    in_axes=0,\n    out_axes=0,\n    axis_size: int | None = None,\n    axis_name: str | None = None,\n    partitioning_axis_names: Mapping[Any, str] = {},\n    spmd_axis_name: str | None = None,\n    methods=None,\n) -> 'flax.linen.transforms.Target':\n  \"\"\"Wrapped version of nn.vmap that handles logical axis metadata.\"\"\"\n\n  # tell normal vmap to broadcast axis metadata.\n  variable_axes = dict(variable_axes)  # shallow copy\n  for name in partitioning_axis_names:\n    variable_axes[f'{name}_axes'] = None\n\n  # perform usual lifted vmap\n  vmapped = flax.linen.transforms.lift_transform(\n      flax.core.lift.vmap,\n      target,\n      variable_axes=variable_axes,\n      split_rngs=split_rngs,\n      in_axes=in_axes,\n      out_axes=out_axes,\n      axis_size=axis_size,\n      axis_name=axis_name,\n      spmd_axis_name=spmd_axis_name,\n      methods=methods,\n  )\n\n  for collection_name, axis in variable_axes.items():\n    if collection_name in partitioning_axis_names:\n      vmapped = _add_axis_to_metadata(  # pylint: disable=protected-access\n          vmapped,\n          axis_pos=axis,\n          axis_name=partitioning_axis_names[collection_name],\n          axis_col=f'{collection_name}_axes',\n      )\n\n  return vmapped\n\n\n# Remat abstraction bug hotfix\n# ------------------------------------------------------------------------------\n# TODO(levskaya): upstream this fix into main flax.core.lift.remat.\n# Workaround a scan(remat(...)) abstraction bug by manually implementing a\n# static_argnums behavior for flax remat via closure before applying jax remat.\n\n\ndef core_remat_static(\n    fn,\n    variables=True,\n    rngs=True,\n    concrete=False,\n    prevent_cse=True,\n    static_argnums=(),\n    policy=None,\n):\n  \"\"\"Flax functional core remat version with static_argnums.\"\"\"\n\n  static_argnums = tuple(sorted(static_argnums))\n\n  def _repack_remat_args(dyn_args, static_args):\n    \"\"\"Remake arg list from static and dynamic args given static_argnums.\"\"\"\n    args = []\n    s_cnt, d_cnt = 0, 0\n    for i in range(len(dyn_args) + len(static_args)):\n      if i in static_argnums:\n        args.append(static_args[s_cnt])\n        s_cnt += 1\n      else:\n        args.append(dyn_args[d_cnt])\n        d_cnt += 1\n    return tuple(args)\n\n  def inner(scope_fn, repack_fn, variable_groups, rng_groups, *args):\n    static_args = tuple(x for i, x in enumerate(args) if i in static_argnums)\n    dyn_args = tuple(x for i, x in enumerate(args) if i not in static_argnums)\n\n    # After JAX v0.3.16, concrete=False is a no-op and concrete=True raises\n    # NotImplementedError. Starting in JAX v0.8.2, the concrete argument is\n    # deprecated and will be removed in the future.\n    if concrete:\n      raise NotImplementedError(\n          \"The concrete argument is deprecated. Use static_argnums instead.\"\n          \" for more information, see\"\n          \" https://docs.jax.dev/en/latest/jep/11830-new-remat-checkpoint.html\"\n      )\n\n    @functools.partial(\n        jax.remat, prevent_cse=prevent_cse, policy=policy\n    )\n    @functools.wraps(fn)\n    def rematted(variable_groups, rng_groups, *dyn_args):\n      args = _repack_remat_args(dyn_args, static_args)\n      scope = scope_fn(variable_groups, rng_groups)\n      y = fn(scope, *args)\n      return y, repack_fn(scope)\n\n    return rematted(variable_groups, rng_groups, *dyn_args)\n\n  return flax.core.lift.pack(\n      inner, (variables,), (variables,), (rngs,), name='remat'\n  )\n\n\ndef remat(\n    target,\n    variables=True,\n    rngs=True,\n    concrete=False,\n    prevent_cse=True,\n    static_argnums=(),\n    policy=None,\n    methods=None,\n):\n  \"\"\"Flax lifted remat that supports static_argnums.\"\"\"\n  return flax.linen.transforms.lift_transform(\n      core_remat_static,\n      target,\n      variables=variables,\n      rngs=rngs,\n      concrete=concrete,\n      prevent_cse=prevent_cse,\n      static_argnums=static_argnums,\n      policy=policy,\n      methods=methods,\n  )\n"
  },
  {
    "path": "flax/linen/pooling.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Pooling modules.\"\"\"\n\nimport jax.numpy as jnp\nimport numpy as np\nfrom jax import lax\n\n\ndef pool(inputs, init, reduce_fn, window_shape, strides, padding):\n  \"\"\"Helper function to define pooling functions.\n\n  Pooling functions are implemented using the ReduceWindow XLA op.\n\n  .. note::\n    Be aware that pooling is not generally differentiable.\n    That means providing a reduce_fn that is differentiable does not imply that\n    pool is differentiable.\n\n  Args:\n    inputs: input data with dimensions (batch, window dims..., features).\n    init: the initial value for the reduction\n    reduce_fn: a reduce function of the form ``(T, T) -> T``.\n    window_shape: a shape tuple defining the window to reduce over.\n    strides: a sequence of ``n`` integers, representing the inter-window\n      strides (default: ``(1, ..., 1)``).\n    padding: either the string ``'SAME'``, the string ``'VALID'``, or a sequence\n      of ``n`` ``(low, high)`` integer pairs that give the padding to apply before\n      and after each spatial dimension.\n  Returns:\n    The output of the reduction for each window slice.\n  \"\"\"\n  num_batch_dims = inputs.ndim - (len(window_shape) + 1)\n  strides = strides or (1,) * len(window_shape)\n  assert len(window_shape) == len(\n    strides\n  ), f'len({window_shape}) must equal len({strides})'\n  strides = (1,) * num_batch_dims + strides + (1,)\n  dims = (1,) * num_batch_dims + window_shape + (1,)\n\n  is_single_input = False\n  if num_batch_dims == 0:\n    # add singleton batch dimension because lax.reduce_window always\n    # needs a batch dimension.\n    inputs = inputs[None]\n    strides = (1,) + strides\n    dims = (1,) + dims\n    is_single_input = True\n\n  assert inputs.ndim == len(dims), f'len({inputs.shape}) != len({dims})'\n  if not isinstance(padding, str):\n    padding = tuple(map(tuple, padding))\n    assert len(padding) == len(window_shape), (\n      f'padding {padding} must specify pads for same number of dims as '\n      f'window_shape {window_shape}'\n    )\n    assert all(\n      [len(x) == 2 for x in padding]\n    ), f'each entry in padding {padding} must be length 2'\n    padding = ((0, 0),) + padding + ((0, 0),)\n  y = lax.reduce_window(inputs, init, reduce_fn, dims, strides, padding)\n  if is_single_input:\n    y = jnp.squeeze(y, axis=0)\n  return y\n\n\ndef avg_pool(\n  inputs, window_shape, strides=None, padding='VALID', count_include_pad=True\n):\n  \"\"\"Pools the input by taking the average over a window.\n\n  Args:\n    inputs: input data with dimensions (batch, window dims..., features).\n    window_shape: a shape tuple defining the window to reduce over.\n    strides: a sequence of ``n`` integers, representing the inter-window\n      strides (default: ``(1, ..., 1)``).\n    padding: either the string ``'SAME'``, the string ``'VALID'``, or a sequence\n      of ``n`` ``(low, high)`` integer pairs that give the padding to apply before\n      and after each spatial dimension (default: ``'VALID'``).\n    count_include_pad: a boolean whether to include padded tokens\n      in the average calculation (default: ``True``).\n  Returns:\n    The average for each window slice.\n  \"\"\"\n  y = pool(inputs, 0.0, lax.add, window_shape, strides, padding)\n  if count_include_pad:\n    y = y / np.prod(window_shape)\n  else:\n    div_shape = inputs.shape[:-1] + (1,)\n    if len(div_shape) - 2 == len(window_shape):\n      div_shape = (1,) + div_shape[1:]\n    y = y / pool(\n      jnp.ones(div_shape), 0.0, lax.add, window_shape, strides, padding\n    )\n  return y\n\n\ndef max_pool(inputs, window_shape, strides=None, padding='VALID'):\n  \"\"\"Pools the input by taking the maximum of a window slice.\n\n  Args:\n    inputs: input data with dimensions (batch, window dims..., features).\n    window_shape: a shape tuple defining the window to reduce over.\n    strides: a sequence of ``n`` integers, representing the inter-window\n      strides (default: ``(1, ..., 1)``).\n    padding: either the string ``'SAME'``, the string ``'VALID'``, or a sequence\n      of ``n`` ``(low, high)`` integer pairs that give the padding to apply before\n      and after each spatial dimension (default: ``'VALID'``).\n  Returns:\n    The maximum for each window slice.\n  \"\"\"\n  y = pool(inputs, -jnp.inf, lax.max, window_shape, strides, padding)\n  return y\n\n\ndef min_pool(inputs, window_shape, strides=None, padding='VALID'):\n  \"\"\"Pools the input by taking the minimum of a window slice.\n\n  Args:\n    inputs: Input data with dimensions (batch, window dims..., features).\n    window_shape: A shape tuple defining the window to reduce over.\n    strides: A sequence of ``n`` integers, representing the inter-window strides\n      (default: ``(1, ..., 1)``).\n    padding: Either the string ``'SAME'``, the string ``'VALID'``, or a sequence of\n      ``n`` ``(low, high)`` integer pairs that give the padding to apply before and\n      after each spatial dimension (default: ``'VALID'``).\n\n  Returns:\n    The minimum for each window slice.\n  \"\"\"\n  return pool(inputs, jnp.inf, lax.min, window_shape, strides, padding)\n"
  },
  {
    "path": "flax/linen/recurrent.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Recurrent neural network modules.\n\nTHe RNNCell modules can be scanned using lifted transforms. For more information\nsee: https://flax.readthedocs.io/en/latest/developer_notes/lift.html.\n\"\"\"\n\nfrom functools import partial  # pylint: disable=g-importing-member\nfrom typing import (\n  Any,\n  TypeVar,\n)\nfrom collections.abc import Callable, Mapping, Sequence\n\nimport jax\nimport numpy as np\nfrom absl import logging\nfrom jax import numpy as jnp\nfrom jax import random\nfrom typing_extensions import Protocol\n\nfrom flax.core.frozen_dict import FrozenDict\nfrom flax.core.scope import CollectionFilter, PRNGSequenceFilter\nfrom flax.linen import initializers, transforms\nfrom flax.linen.activation import sigmoid, tanh\nfrom flax.linen.dtypes import promote_dtype\nfrom flax.linen.linear import Conv, Dense, default_kernel_init\nfrom flax.linen.module import Module, compact, nowrap\nfrom flax.typing import (\n  Array,\n  PRNGKey,\n  Dtype,\n  InOutScanAxis,\n  Initializer,\n  PrecisionLike,\n)\n\nA = TypeVar('A')\nCarry = Any\nCarryHistory = Any\nOutput = Any\n\n\nclass RNNCellBase(Module):\n  \"\"\"RNN cell base class.\"\"\"\n\n  @nowrap\n  def initialize_carry(\n    self, rng: PRNGKey, input_shape: tuple[int, ...]\n  ) -> Carry:\n    \"\"\"Initialize the RNN cell carry.\n\n    Args:\n      rng: random number generator passed to the init_fn.\n      input_shape: a tuple providing the shape of the input to the cell.\n\n    Returns:\n      An initialized carry for the given RNN cell.\n    \"\"\"\n    raise NotImplementedError\n\n  @property\n  def num_feature_axes(self) -> int:\n    \"\"\"Returns the number of feature axes of the RNN cell.\"\"\"\n    raise NotImplementedError\n\n\nclass LSTMCell(RNNCellBase):\n  r\"\"\"LSTM cell.\n\n  The mathematical definition of the cell is as follows\n\n  .. math::\n      \\begin{array}{ll}\n      i = \\sigma(W_{ii} x + W_{hi} h + b_{hi}) \\\\\n      f = \\sigma(W_{if} x + W_{hf} h + b_{hf}) \\\\\n      g = \\tanh(W_{ig} x + W_{hg} h + b_{hg}) \\\\\n      o = \\sigma(W_{io} x + W_{ho} h + b_{ho}) \\\\\n      c' = f * c + i * g \\\\\n      h' = o * \\tanh(c') \\\\\n      \\end{array}\n\n  where x is the input, h is the output of the previous time step, and c is\n  the memory.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> x = jax.random.normal(jax.random.key(0), (2, 3))\n    >>> layer = nn.LSTMCell(features=4)\n    >>> carry = layer.initialize_carry(jax.random.key(1), x.shape)\n    >>> variables = layer.init(jax.random.key(2), carry, x)\n    >>> new_carry, out = layer.apply(variables, carry, x)\n\n  Attributes:\n    features: number of output features.\n    gate_fn: activation function used for gates (default: sigmoid).\n    activation_fn: activation function used for output and memory update\n      (default: tanh).\n    kernel_init: initializer function for the kernels that transform\n      the input (default: lecun_normal).\n    recurrent_kernel_init: initializer function for the kernels that transform\n      the hidden state (default: initializers.orthogonal()).\n    bias_init: initializer for the bias parameters (default: initializers.zeros_init())\n    dtype: the dtype of the computation (default: infer from inputs and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n  \"\"\"\n\n  features: int\n  gate_fn: Callable[..., Any] = sigmoid\n  activation_fn: Callable[..., Any] = tanh\n  kernel_init: Initializer = default_kernel_init\n  recurrent_kernel_init: Initializer = initializers.orthogonal()\n  bias_init: Initializer = initializers.zeros_init()\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  carry_init: Initializer = initializers.zeros_init()\n\n  @compact\n  def __call__(self, carry, inputs):\n    r\"\"\"A long short-term memory (LSTM) cell.\n\n    Args:\n      carry: the hidden state of the LSTM cell,\n        initialized using ``LSTMCell.initialize_carry``.\n      inputs: an ndarray with the input for the current time step.\n        All dimensions except the final are considered batch dimensions.\n\n    Returns:\n      A tuple with the new carry and the output.\n    \"\"\"\n    c, h = carry\n    hidden_features = h.shape[-1]\n    # input and recurrent layers are summed so only one needs a bias.\n    dense_h = partial(\n      Dense,\n      features=hidden_features,\n      use_bias=True,\n      kernel_init=self.recurrent_kernel_init,\n      bias_init=self.bias_init,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n    )\n    dense_i = partial(\n      Dense,\n      features=hidden_features,\n      use_bias=False,\n      kernel_init=self.kernel_init,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n    )\n    i = self.gate_fn(dense_i(name='ii')(inputs) + dense_h(name='hi')(h))\n    f = self.gate_fn(dense_i(name='if')(inputs) + dense_h(name='hf')(h))\n    g = self.activation_fn(dense_i(name='ig')(inputs) + dense_h(name='hg')(h))\n    o = self.gate_fn(dense_i(name='io')(inputs) + dense_h(name='ho')(h))\n    new_c = f * c + i * g\n    new_h = o * self.activation_fn(new_c)\n    return (new_c, new_h), new_h\n\n  @nowrap\n  def initialize_carry(\n    self, rng: PRNGKey, input_shape: tuple[int, ...]\n  ) -> tuple[Array, Array]:\n    \"\"\"Initialize the RNN cell carry.\n\n    Args:\n      rng: random number generator passed to the init_fn.\n      input_shape: a tuple providing the shape of the input to the cell.\n    Returns:\n      An initialized carry for the given RNN cell.\n    \"\"\"\n    batch_dims = input_shape[:-1]\n    key1, key2 = random.split(rng)\n    mem_shape = batch_dims + (self.features,)\n    c = self.carry_init(key1, mem_shape, self.param_dtype)\n    h = self.carry_init(key2, mem_shape, self.param_dtype)\n    return (c, h)\n\n  @property\n  def num_feature_axes(self) -> int:\n    return 1\n\n\nclass DenseParams(Module):\n  \"\"\"Dummy module for creating parameters matching ``flax.linen.Dense``.\"\"\"\n\n  features: int\n  use_bias: bool = True\n  param_dtype: Dtype = jnp.float32\n  precision: PrecisionLike = None\n  kernel_init: Initializer = default_kernel_init\n  bias_init: Initializer = initializers.zeros_init()\n\n  @compact\n  def __call__(self, inputs: Array) -> tuple[Array, Array | None]:\n    k = self.param(\n      'kernel',\n      self.kernel_init,\n      (inputs.shape[-1], self.features),\n      self.param_dtype,\n    )\n    if self.use_bias:\n      b = self.param('bias', self.bias_init, (self.features,), self.param_dtype)\n    else:\n      b = None\n    return k, b\n\n\nclass OptimizedLSTMCell(RNNCellBase):\n  r\"\"\"More efficient LSTM Cell that concatenates state components before matmul.\n\n  The parameters are compatible with ``LSTMCell``. Note that this cell is often\n  faster than ``LSTMCell`` as long as the hidden size is roughly <= 2048 units.\n\n  The mathematical definition of the cell is the same as ``LSTMCell`` and as\n  follows\n\n  .. math::\n\n      \\begin{array}{ll}\n      i = \\sigma(W_{ii} x + W_{hi} h + b_{hi}) \\\\\n      f = \\sigma(W_{if} x + W_{hf} h + b_{hf}) \\\\\n      g = \\tanh(W_{ig} x + W_{hg} h + b_{hg}) \\\\\n      o = \\sigma(W_{io} x + W_{ho} h + b_{ho}) \\\\\n      c' = f * c + i * g \\\\\n      h' = o * \\tanh(c') \\\\\n      \\end{array}\n\n  where x is the input, h is the output of the previous time step, and c is\n  the memory.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> x = jax.random.normal(jax.random.key(0), (2, 3))\n    >>> layer = nn.OptimizedLSTMCell(features=4)\n    >>> carry = layer.initialize_carry(jax.random.key(1), x.shape)\n    >>> variables = layer.init(jax.random.key(2), carry, x)\n    >>> new_carry, out = layer.apply(variables, carry, x)\n\n  Attributes:\n    gate_fn: activation function used for gates (default: sigmoid).\n    activation_fn: activation function used for output and memory update\n      (default: tanh).\n    kernel_init: initializer function for the kernels that transform\n      the input (default: lecun_normal).\n    recurrent_kernel_init: initializer function for the kernels that transform\n      the hidden state (default: initializers.orthogonal()).\n    bias_init: initializer for the bias parameters (default: initializers.zeros_init()).\n    dtype: the dtype of the computation (default: infer from inputs and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n  \"\"\"\n\n  features: int\n  gate_fn: Callable[..., Any] = sigmoid\n  activation_fn: Callable[..., Any] = tanh\n  kernel_init: Initializer = default_kernel_init\n  recurrent_kernel_init: Initializer = initializers.orthogonal()\n  bias_init: Initializer = initializers.zeros_init()\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  carry_init: Initializer = initializers.zeros_init()\n\n  @compact\n  def __call__(\n    self, carry: tuple[Array, Array], inputs: Array\n  ) -> tuple[tuple[Array, Array], Array]:\n    r\"\"\"An optimized long short-term memory (LSTM) cell.\n\n    Args:\n      carry: the hidden state of the LSTM cell, initialized using\n        ``LSTMCell.initialize_carry``.\n      inputs: an ndarray with the input for the current time step. All\n        dimensions except the final are considered batch dimensions.\n\n    Returns:\n      A tuple with the new carry and the output.\n    \"\"\"\n    c, h = carry\n    hidden_features = h.shape[-1]\n\n    def _concat_dense(\n      inputs: Array,\n      params: Mapping[str, tuple[Array, Array | None]],\n      use_bias: bool = True,\n    ) -> dict[str, Array]:\n      # Concatenates the individual kernels and biases, given in params, into a\n      # single kernel and single bias for efficiency before applying them using\n      # dot_general.\n      kernels = [kernel for kernel, _ in params.values()]\n      kernel = jnp.concatenate(kernels, axis=-1)\n      if use_bias:\n        biases = []\n        for _, bias in params.values():\n          if bias is None:\n            raise ValueError('bias is None but use_bias is True.')\n          biases.append(bias)\n        bias = jnp.concatenate(biases, axis=-1)\n      else:\n        bias = None\n      inputs, kernel, bias = promote_dtype(\n        inputs, kernel, bias, dtype=self.dtype\n      )\n      y = jnp.dot(inputs, kernel)\n      if use_bias:\n        # This assert is here since mypy can't infer that bias cannot be None\n        assert bias is not None\n        y += jnp.reshape(bias, (1,) * (y.ndim - 1) + (-1,))\n\n      # Split the result back into individual (i, f, g, o) outputs.\n      split_indices = np.cumsum([kernel.shape[-1] for kernel in kernels[:-1]])\n      ys = jnp.split(y, split_indices, axis=-1)\n      return dict(zip(params.keys(), ys))\n\n    # Create params with the same names/shapes as `LSTMCell` for compatibility.\n    dense_params_h = {}\n    dense_params_i = {}\n    for component in ['i', 'f', 'g', 'o']:\n      dense_params_i[component] = DenseParams(\n        features=hidden_features,\n        use_bias=False,\n        param_dtype=self.param_dtype,\n        kernel_init=self.kernel_init,\n        bias_init=self.bias_init,\n        name=f'i{component}',  # type: ignore[call-arg]\n      )(inputs)\n      dense_params_h[component] = DenseParams(\n        features=hidden_features,\n        use_bias=True,\n        param_dtype=self.param_dtype,\n        kernel_init=self.recurrent_kernel_init,\n        bias_init=self.bias_init,\n        name=f'h{component}',  # type: ignore[call-arg]\n      )(h)\n    dense_h = _concat_dense(h, dense_params_h, use_bias=True)\n    dense_i = _concat_dense(inputs, dense_params_i, use_bias=False)\n\n    i = self.gate_fn(dense_h['i'] + dense_i['i'])\n    f = self.gate_fn(dense_h['f'] + dense_i['f'])\n    g = self.activation_fn(dense_h['g'] + dense_i['g'])\n    o = self.gate_fn(dense_h['o'] + dense_i['o'])\n\n    new_c = f * c + i * g\n    new_h = o * self.activation_fn(new_c)\n    return (new_c, new_h), new_h\n\n  @nowrap\n  def initialize_carry(\n    self, rng: PRNGKey, input_shape: tuple[int, ...]\n  ) -> tuple[Array, Array]:\n    \"\"\"Initialize the RNN cell carry.\n\n    Args:\n      rng: random number generator passed to the init_fn.\n      input_shape: a tuple providing the shape of the input to the cell.\n\n    Returns:\n      An initialized carry for the given RNN cell.\n    \"\"\"\n    batch_dims = input_shape[:-1]\n    key1, key2 = random.split(rng)\n    mem_shape = batch_dims + (self.features,)\n    c = self.carry_init(key1, mem_shape, self.param_dtype)\n    h = self.carry_init(key2, mem_shape, self.param_dtype)\n    return c, h\n\n  @property\n  def num_feature_axes(self) -> int:\n    return 1\n\n\nclass SimpleCell(RNNCellBase):\n  r\"\"\"Simple cell.\n\n  The mathematical definition of the cell is as follows\n\n  .. math::\n\n      \\begin{array}{ll}\n      h' = \\tanh(W_i x + b_i + W_h h)\n      \\end{array}\n\n  where x is the input and h is the output of the previous time step.\n\n  If `residual` is `True`,\n\n  .. math::\n\n      \\begin{array}{ll}\n      h' = \\tanh(W_i x + b_i + W_h h + h)\n      \\end{array}\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> x = jax.random.normal(jax.random.key(0), (2, 3))\n    >>> layer = nn.SimpleCell(features=4)\n    >>> carry = layer.initialize_carry(jax.random.key(1), x.shape)\n    >>> variables = layer.init(jax.random.key(2), carry, x)\n    >>> new_carry, out = layer.apply(variables, carry, x)\n\n  Attributes:\n    features: number of output features.\n    activation_fn: activation function used for output and memory update\n      (default: tanh).\n    kernel_init: initializer function for the kernels that transform\n      the input (default: lecun_normal).\n    recurrent_kernel_init: initializer function for the kernels that transform\n      the hidden state (default: initializers.orthogonal()).\n    bias_init: initializer for the bias parameters (default: initializers.zeros_init())\n    dtype: the dtype of the computation (default: None).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    residual: pre-activation residual connection (https://arxiv.org/abs/1801.06105).\n  \"\"\"\n\n  features: int\n  activation_fn: Callable[..., Any] = tanh\n  kernel_init: Initializer = default_kernel_init\n  recurrent_kernel_init: Initializer = initializers.orthogonal()\n  bias_init: Initializer = initializers.zeros_init()\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  carry_init: Initializer = initializers.zeros_init()\n  residual: bool = False\n\n  @compact\n  def __call__(self, carry, inputs):\n    \"\"\"Simple cell.\n\n    Args:\n      carry: the hidden state of the Simple cell,\n        initialized using ``SimpleCell.initialize_carry``.\n      inputs: an ndarray with the input for the current time step.\n        All dimensions except the final are considered batch dimensions.\n\n    Returns:\n      A tuple with the new carry and the output.\n    \"\"\"\n    hidden_features = carry.shape[-1]\n    # input and recurrent layers are summed so only one needs a bias.\n    dense_h = partial(\n      Dense,\n      features=hidden_features,\n      use_bias=False,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      kernel_init=self.recurrent_kernel_init,\n    )\n    dense_i = partial(\n      Dense,\n      features=hidden_features,\n      use_bias=True,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      kernel_init=self.kernel_init,\n      bias_init=self.bias_init,\n    )\n    new_carry = dense_i(name='i')(inputs) + dense_h(name='h')(carry)\n    if self.residual:\n      new_carry += carry\n    new_carry = self.activation_fn(new_carry)\n    return new_carry, new_carry\n\n  @nowrap\n  def initialize_carry(self, rng: PRNGKey, input_shape: tuple[int, ...]):\n    \"\"\"Initialize the RNN cell carry.\n\n    Args:\n      rng: random number generator passed to the init_fn.\n      input_shape: a tuple providing the shape of the input to the cell.\n\n    Returns:\n      An initialized carry for the given RNN cell.\n    \"\"\"\n    batch_dims = input_shape[:-1]\n    mem_shape = batch_dims + (self.features,)\n    return self.carry_init(rng, mem_shape, self.param_dtype)\n\n  @property\n  def num_feature_axes(self) -> int:\n    return 1\n\n\nclass GRUCell(RNNCellBase):\n  r\"\"\"GRU cell.\n\n  The mathematical definition of the cell is as follows\n\n  .. math::\n\n      \\begin{array}{ll}\n      r = \\sigma(W_{ir} x + b_{ir} + W_{hr} h) \\\\\n      z = \\sigma(W_{iz} x + b_{iz} + W_{hz} h) \\\\\n      n = \\tanh(W_{in} x + b_{in} + r * (W_{hn} h + b_{hn})) \\\\\n      h' = (1 - z) * n + z * h \\\\\n      \\end{array}\n\n  where x is the input and h is the output of the previous time step.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> x = jax.random.normal(jax.random.key(0), (2, 3))\n    >>> layer = nn.GRUCell(features=4)\n    >>> carry = layer.initialize_carry(jax.random.key(1), x.shape)\n    >>> variables = layer.init(jax.random.key(2), carry, x)\n    >>> new_carry, out = layer.apply(variables, carry, x)\n\n  Attributes:\n    features: number of output features.\n    gate_fn: activation function used for gates (default: sigmoid).\n    activation_fn: activation function used for output and memory update\n      (default: tanh).\n    kernel_init: initializer function for the kernels that transform\n      the input (default: lecun_normal).\n    recurrent_kernel_init: initializer function for the kernels that transform\n      the hidden state (default: initializers.orthogonal()).\n    bias_init: initializer for the bias parameters (default: initializers.zeros_init())\n    dtype: the dtype of the computation (default: None).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n  \"\"\"\n\n  features: int\n  gate_fn: Callable[..., Any] = sigmoid\n  activation_fn: Callable[..., Any] = tanh\n  kernel_init: Initializer = default_kernel_init\n  recurrent_kernel_init: Initializer = initializers.orthogonal()\n  bias_init: Initializer = initializers.zeros_init()\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  carry_init: Initializer = initializers.zeros_init()\n\n  @compact\n  def __call__(self, carry, inputs):\n    \"\"\"Gated recurrent unit (GRU) cell.\n\n    Args:\n      carry: the hidden state of the GRU cell,\n        initialized using ``GRUCell.initialize_carry``.\n      inputs: an ndarray with the input for the current time step.\n        All dimensions except the final are considered batch dimensions.\n\n    Returns:\n      A tuple with the new carry and the output.\n    \"\"\"\n    h = carry\n    hidden_features = h.shape[-1]\n    # input and recurrent layers are summed so only one needs a bias.\n    dense_h = partial(\n      Dense,\n      features=hidden_features,\n      use_bias=False,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      kernel_init=self.recurrent_kernel_init,\n      bias_init=self.bias_init,\n    )\n    dense_i = partial(\n      Dense,\n      features=hidden_features,\n      use_bias=True,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      kernel_init=self.kernel_init,\n      bias_init=self.bias_init,\n    )\n    r = self.gate_fn(dense_i(name='ir')(inputs) + dense_h(name='hr')(h))\n    z = self.gate_fn(dense_i(name='iz')(inputs) + dense_h(name='hz')(h))\n    # add bias because the linear transformations aren't directly summed.\n    n = self.activation_fn(\n      dense_i(name='in')(inputs) + r * dense_h(name='hn', use_bias=True)(h)\n    )\n    new_h = (1.0 - z) * n + z * h\n    return new_h, new_h\n\n  @nowrap\n  def initialize_carry(self, rng: PRNGKey, input_shape: tuple[int, ...]):\n    \"\"\"Initialize the RNN cell carry.\n\n    Args:\n      rng: random number generator passed to the init_fn.\n      input_shape: a tuple providing the shape of the input to the cell.\n\n    Returns:\n      An initialized carry for the given RNN cell.\n    \"\"\"\n    batch_dims = input_shape[:-1]\n    mem_shape = batch_dims + (self.features,)\n    return self.carry_init(rng, mem_shape, self.param_dtype)\n\n  @property\n  def num_feature_axes(self) -> int:\n    return 1\n\n\nclass MGUCell(RNNCellBase):\n  r\"\"\"MGU cell (https://arxiv.org/pdf/1603.09420.pdf).\n\n  The mathematical definition of the cell is as follows\n\n  .. math::\n\n      \\begin{array}{ll}\n      f = \\sigma(W_{if} x + b_{if} + W_{hf} h) \\\\\n      n = \\tanh(W_{in} x + b_{in} + f * (W_{hn} h + b_{hn})) \\\\\n      h' = (1 - f) * n + f * h \\\\\n      \\end{array}\n\n  where x is the input and h is the output of the previous time step.\n\n  If ``reset_gate`` is false, the above becomes\n\n  .. math::\n\n      \\begin{array}{ll}\n      f = \\sigma(W_{if} x + b_{if} + W_{hf} h) \\\\\n      n = \\tanh(W_{in} x + b_{in} + W_{hn} h) \\\\\n      h' = (1 - f) * n + f * h \\\\\n      \\end{array}\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> x = jax.random.normal(jax.random.key(0), (2, 3))\n    >>> layer = nn.MGUCell(features=4)\n    >>> carry = layer.initialize_carry(jax.random.key(1), x.shape)\n    >>> variables = layer.init(jax.random.key(2), carry, x)\n    >>> new_carry, out = layer.apply(variables, carry, x)\n\n  Attributes:\n    features: number of output features.\n    gate_fn: activation function used for gates (default: sigmoid).\n    activation_fn: activation function used for output and memory update\n      (default: tanh).\n    kernel_init: initializer function for the kernels that transform\n      the input (default: lecun_normal).\n    recurrent_kernel_init: initializer function for the kernels that transform\n      the hidden state (default: initializers.orthogonal()).\n    forget_bias_init: initializer for the bias parameters of the forget gate.\n      The default is set to initializers.ones_init() because this prevents\n      vanishing gradients. See https://proceedings.mlr.press/v37/jozefowicz15.pdf,\n      section 2.2 for more details.\n    activation_bias_init: initializer for the bias parameters of the activation\n      output (default: initializers.zeros_init()).\n    dtype: the dtype of the computation (default: None).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    reset_gate: flag for applying reset gating.\n  \"\"\"\n\n  features: int\n  gate_fn: Callable[..., Any] = sigmoid\n  activation_fn: Callable[..., Any] = tanh\n  kernel_init: Initializer = default_kernel_init\n  recurrent_kernel_init: Initializer = initializers.orthogonal()\n  forget_bias_init: Initializer = initializers.ones_init()\n  activation_bias_init: Initializer = initializers.zeros_init()\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  carry_init: Initializer = initializers.zeros_init()\n  reset_gate: bool = True\n\n  @compact\n  def __call__(self, carry, inputs):\n    \"\"\"Minimal gated unit (MGU) cell.\n\n    Args:\n      carry: the hidden state of the MGU cell,\n        initialized using ``MGUCell.initialize_carry``.\n      inputs: an ndarray with the input for the current time step.\n        All dimensions except the final are considered batch dimensions.\n\n    Returns:\n      A tuple with the new carry and the output.\n    \"\"\"\n    h = carry\n    hidden_features = h.shape[-1]\n    # input and recurrent layers are summed so only one needs a bias.\n    dense_h = partial(\n      Dense,\n      features=hidden_features,\n      use_bias=False,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      kernel_init=self.recurrent_kernel_init,\n      bias_init=self.activation_bias_init,\n    )\n    dense_i = partial(\n      Dense,\n      features=hidden_features,\n      use_bias=True,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      kernel_init=self.kernel_init,\n    )\n    f = self.gate_fn(\n      dense_i(name='if', bias_init=self.forget_bias_init)(inputs)\n      + dense_h(name='hf')(h)\n    )\n    # add bias when the linear transformations aren't directly summed.\n    x = dense_h(name=\"hn\", use_bias=self.reset_gate)(h)\n    if self.reset_gate:\n      x *= f\n    n = self.activation_fn(\n      dense_i(name=\"in\", bias_init=self.activation_bias_init)(inputs) + x\n    )\n    new_h = (1.0 - f) * n + f * h\n    return new_h, new_h\n\n  @nowrap\n  def initialize_carry(self, rng: PRNGKey, input_shape: tuple[int, ...]):\n    \"\"\"Initialize the RNN cell carry.\n\n    Args:\n      rng: random number generator passed to the init_fn.\n      input_shape: a tuple providing the shape of the input to the cell.\n\n    Returns:\n      An initialized carry for the given RNN cell.\n    \"\"\"\n    batch_dims = input_shape[:-1]\n    mem_shape = batch_dims + (self.features,)\n    return self.carry_init(rng, mem_shape, self.param_dtype)\n\n  @property\n  def num_feature_axes(self) -> int:\n    return 1\n\n\nclass ConvLSTMCell(RNNCellBase):\n  r\"\"\"A convolutional LSTM cell.\n\n  The implementation is based on xingjian2015convolutional.\n  Given x_t and the previous state (h_{t-1}, c_{t-1})\n  the core computes\n\n  .. math::\n\n     \\begin{array}{ll}\n     i_t = \\sigma(W_{ii} * x_t + W_{hi} * h_{t-1} + b_i) \\\\\n     f_t = \\sigma(W_{if} * x_t + W_{hf} * h_{t-1} + b_f) \\\\\n     g_t = \\tanh(W_{ig} * x_t + W_{hg} * h_{t-1} + b_g) \\\\\n     o_t = \\sigma(W_{io} * x_t + W_{ho} * h_{t-1} + b_o) \\\\\n     c_t = f_t c_{t-1} + i_t g_t \\\\\n     h_t = o_t \\tanh(c_t)\n     \\end{array}\n\n  where * denotes the convolution operator;\n  i_t, f_t, o_t are input, forget and output gate activations,\n  and g_t is a vector of cell updates.\n\n  .. note::\n    Forget gate initialization:\n      Following jozefowicz2015empirical we add 1.0 to b_f\n      after initialization in order to reduce the scale of forgetting in\n      the beginning of the training.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> x = jax.random.normal(jax.random.key(0), (3, 5, 5))\n    >>> layer = nn.ConvLSTMCell(features=4, kernel_size=(2, 2))\n    >>> carry = layer.initialize_carry(jax.random.key(1), x.shape)\n    >>> variables = layer.init(jax.random.key(2), carry, x)\n    >>> new_carry, out = layer.apply(variables, carry, x)\n\n  Attributes:\n    features: number of convolution filters.\n    kernel_size: shape of the convolutional kernel.\n    strides: a sequence of ``n`` integers, representing the inter-window\n      strides.\n    padding: either the string ``'SAME'``, the string ``'VALID'``, or a sequence\n      of ``n`` ``(low, high)`` integer pairs that give the padding to apply before\n      and after each spatial dimension.\n    bias: whether to add a bias to the output (default: True).\n    dtype: the dtype of the computation (default: None).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n  \"\"\"\n\n  features: int\n  kernel_size: Sequence[int]\n  strides: Sequence[int] | None = None\n  padding: str | Sequence[tuple[int, int]] = 'SAME'\n  use_bias: bool = True\n  dtype: Dtype | None = None\n  param_dtype: Dtype = jnp.float32\n  carry_init: Initializer = initializers.zeros_init()\n\n  @compact\n  def __call__(self, carry, inputs):\n    \"\"\"Constructs a convolutional LSTM.\n\n    Args:\n      carry: the hidden state of the Conv2DLSTM cell,\n        initialized using ``Conv2DLSTM.initialize_carry``.\n      inputs: input data with dimensions (batch, spatial_dims..., features).\n    Returns:\n      A tuple with the new carry and the output.\n    \"\"\"\n    c, h = carry\n    input_to_hidden = partial(\n      Conv,\n      features=4 * self.features,\n      kernel_size=self.kernel_size,\n      strides=self.strides,\n      padding=self.padding,\n      use_bias=self.use_bias,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      name='ih',\n    )\n\n    hidden_to_hidden = partial(\n      Conv,\n      features=4 * self.features,\n      kernel_size=self.kernel_size,\n      strides=self.strides,\n      padding=self.padding,\n      use_bias=self.use_bias,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      name='hh',\n    )\n\n    gates = input_to_hidden()(inputs) + hidden_to_hidden()(h)\n    i, g, f, o = jnp.split(gates, indices_or_sections=4, axis=-1)\n\n    f = sigmoid(f + 1)\n    new_c = f * c + sigmoid(i) * jnp.tanh(g)\n    new_h = sigmoid(o) * jnp.tanh(new_c)\n    return (new_c, new_h), new_h\n\n  @nowrap\n  def initialize_carry(self, rng: PRNGKey, input_shape: tuple[int, ...]):\n    \"\"\"Initialize the RNN cell carry.\n\n    Args:\n      rng: random number generator passed to the init_fn.\n      input_shape: a tuple providing the shape of the input to the cell.\n\n    Returns:\n      An initialized carry for the given RNN cell.\n    \"\"\"\n    # (*batch_dims, *signal_dims, features)\n    signal_dims = input_shape[-self.num_feature_axes : -1]\n    batch_dims = input_shape[: -self.num_feature_axes]\n    key1, key2 = random.split(rng)\n    mem_shape = batch_dims + signal_dims + (self.features,)\n    c = self.carry_init(key1, mem_shape, self.param_dtype)\n    h = self.carry_init(key2, mem_shape, self.param_dtype)\n    return c, h\n\n  @property\n  def num_feature_axes(self) -> int:\n    return len(self.kernel_size) + 1\n\n\nclass RNN(Module):\n  \"\"\"The ``RNN`` module takes any :class:`RNNCellBase` instance and applies it over a sequence\n\n  using :func:`flax.linen.scan`.\n\n  Example::\n\n    >>> import jax.numpy as jnp\n    >>> import jax\n    >>> import flax.linen as nn\n\n    >>> x = jnp.ones((10, 50, 32)) # (batch, time, features)\n    >>> lstm = nn.RNN(nn.LSTMCell(64))\n    >>> variables = lstm.init(jax.random.key(0), x)\n    >>> y = lstm.apply(variables, x)\n    >>> y.shape # (batch, time, cell_size)\n    (10, 50, 64)\n\n  As shown above, RNN uses the ``cell_size`` argument to set the ``size``\n  argument for the cell's\n  ``initialize_carry`` method, in practice this is typically the number of\n  hidden units you want\n  for the cell. However, this may vary depending on the cell you are using, for\n  example the\n  :class:`ConvLSTMCell` requires a ``size`` argument of the form\n  ``(kernel_height, kernel_width, features)``::\n\n    >>> x = jnp.ones((10, 50, 32, 32, 3)) # (batch, time, height, width, features)\n    >>> conv_lstm = nn.RNN(nn.ConvLSTMCell(64, kernel_size=(3, 3)))\n    >>> y, variables = conv_lstm.init_with_output(jax.random.key(0), x)\n    >>> y.shape # (batch, time, height, width, features)\n    (10, 50, 32, 32, 64)\n\n  By default RNN expect the time dimension after the batch dimension (``(*batch,\n  time, *features)``),\n  if you set ``time_major=True`` RNN will instead expect the time dimension to be\n  at the beginning\n  (``(time, *batch, *features)``)::\n\n    >>> x = jnp.ones((50, 10, 32)) # (time, batch, features)\n    >>> lstm = nn.RNN(nn.LSTMCell(64), time_major=True)\n    >>> variables = lstm.init(jax.random.key(0), x)\n    >>> y = lstm.apply(variables, x)\n    >>> y.shape # (time, batch, cell_size)\n    (50, 10, 64)\n\n  The output is an array of shape ``(*batch, time, *cell_size)`` by default\n  (typically), however\n  if you set ``return_carry=True`` it will instead return a tuple of the final\n  carry and the output::\n\n    >>> x = jnp.ones((10, 50, 32)) # (batch, time, features)\n    >>> lstm = nn.RNN(nn.LSTMCell(64), return_carry=True)\n    >>> variables = lstm.init(jax.random.key(0), x)\n    >>> carry, y = lstm.apply(variables, x)\n    >>> jax.tree_util.tree_map(jnp.shape, carry) # ((batch, cell_size), (batch, cell_size))\n    ((10, 64), (10, 64))\n    >>> y.shape # (batch, time, cell_size)\n    (10, 50, 64)\n\n  To support variable length sequences, you can pass a ``seq_lengths`` which is\n  an integer\n  array of shape ``(*batch)`` where each element is the length of the sequence\n  in the batch.\n  For example::\n\n    >>> seq_lengths = jnp.array([3, 2, 5])\n\n  The output elements corresponding to padding elements are NOT zeroed out. If\n  ``return_carry``\n  is set to ``True`` the carry will be the state of the last valid element of\n  each sequence.\n\n  RNN also accepts some of the arguments of :func:`flax.linen.scan`, by default\n  they are set to\n  work with cells like :class:`LSTMCell` and :class:`GRUCell` but they can be\n  overridden as needed.\n  Overriding default values to scan looks like this::\n\n    >>> lstm = nn.RNN(\n    ...   nn.LSTMCell(64),\n    ...   unroll=1, variable_axes={}, variable_broadcast='params',\n    ...   variable_carry=False, split_rngs={'params': False})\n\n  Attributes:\n    cell: an instance of :class:`RNNCellBase`.\n    time_major: if ``time_major=False`` (default) it will expect inputs with\n      shape ``(*batch, time, *features)``, else it will expect inputs with shape\n      ``(time, *batch, *features)``.\n    return_carry: if ``return_carry=False`` (default) only the output sequence\n      is returned, else it will return a tuple of the final carry and the output\n      sequence.\n    reverse: if ``reverse=False`` (default) the sequence is processed from left\n      to right and returned in the original order, else it will be processed\n      from right to left, and returned in reverse order. If ``seq_lengths`` is\n      passed, padding will always remain at the end of the sequence.\n    keep_order: if ``keep_order=True``, when ``reverse=True`` the output will be\n      reversed back to the original order after processing, this is useful to\n      align sequences in bidirectional RNNs. If ``keep_order=False`` (default),\n      the output will remain in the order specified by ``reverse``.\n    unroll: how many scan iterations to unroll within a single iteration of a\n      loop, defaults to 1. This argument will be passed to ``nn.scan``.\n    variable_axes: a dictionary mapping each collection to either an integer\n      ``i`` (meaning we scan over dimension ``i``) or ``None`` (replicate rather\n      than scan). This argument is forwarded to ``nn.scan``.\n    variable_broadcast: Specifies the broadcasted variable collections. A\n      broadcasted variable should not depend on any computation that cannot be\n      lifted out of the loop. This is typically used to define shared parameters\n      inside the fn. This argument is forwarded to ``nn.scan``.\n    variable_carry: Specifies the variable collections that are carried through\n      the loop. Mutations to these variables are carried to the next iteration\n      and will be preserved when the scan finishes. This argument is forwarded\n      to ``nn.scan``.\n    split_rngs: a mapping from PRNGSequenceFilter to bool specifying whether a\n      collection's PRNG key should be split such that its values are different\n      at each step, or replicated such that its values remain the same at each\n      step. This argument is forwarded to ``nn.scan``.\n  \"\"\"\n\n  cell: RNNCellBase\n  time_major: bool = False\n  return_carry: bool = False\n  reverse: bool = False\n  keep_order: bool = False\n  unroll: int = 1\n  variable_axes: Mapping[\n    CollectionFilter, InOutScanAxis\n  ] = FrozenDict()\n  variable_broadcast: CollectionFilter = 'params'\n  variable_carry: CollectionFilter = False\n  split_rngs: Mapping[PRNGSequenceFilter, bool] = FrozenDict(\n    {'params': False}\n  )\n\n  def __call__(\n    self,\n    inputs: jax.Array,\n    *,\n    initial_carry: Carry | None = None,\n    init_key: PRNGKey | None = None,\n    seq_lengths: Array | None = None,\n    return_carry: bool | None = None,\n    time_major: bool | None = None,\n    reverse: bool | None = None,\n    keep_order: bool | None = None,\n  ) -> Output | tuple[Carry, Output]:\n    \"\"\"\n    Applies the RNN to the inputs.\n\n    ``__call__`` allows you to optionally override some attributes like ``return_carry``\n    and ``time_major`` defined in the constructor.\n\n    Arguments:\n      inputs: the input sequence.\n      initial_carry: the initial carry, if not provided it will be initialized\n        using the cell's :meth:`RNNCellBase.initialize_carry` method.\n      init_key: a PRNG key used to initialize the carry, if not provided\n        ``jax.random.key(0)`` will be used. Most cells will ignore this\n        argument.\n      seq_lengths: an optional integer array of shape ``(*batch)`` indicating\n        the length of each sequence, elements whose index in the time dimension\n        is greater than the corresponding length will be considered padding and\n        will be ignored.\n      return_carry: if ``return_carry=False`` (default) only the output sequence is returned,\n        else it will return a tuple of the final carry and the output sequence.\n      time_major: if ``time_major=False`` (default) it will expect inputs with shape\n        ``(*batch, time, *features)``, else it will expect inputs with shape ``(time, *batch, *features)``.\n      reverse: overrides the ``reverse`` attribute, if ``reverse=False`` (default) the sequence is\n        processed from left to right and returned in the original order, else it will be processed\n        from right to left, and returned in reverse order. If ``seq_lengths`` is passed,\n        padding will always remain at the end of the sequence.\n      keep_order: overrides the ``keep_order`` attribute, if ``keep_order=True``, when ``reverse=True``\n        the output will be reversed back to the original order after processing, this is\n        useful to align sequences in bidirectional RNNs. If ``keep_order=False`` (default),\n        the output will remain in the order specified by ``reverse``.\n    Returns:\n      if ``return_carry=False`` (default) only the output sequence is returned,\n      else it will return a tuple of the final carry and the output sequence.\n    \"\"\"\n\n    if return_carry is None:\n      return_carry = self.return_carry\n    if time_major is None:\n      time_major = self.time_major\n    if reverse is None:\n      reverse = self.reverse\n    if keep_order is None:\n      keep_order = self.keep_order\n\n    # Infer the number of batch dimensions from the input shape.\n    # Cells like ConvLSTM have additional spatial dimensions.\n    time_axis = (\n      0 if time_major else inputs.ndim - (self.cell.num_feature_axes + 1)\n    )\n\n    # make time_axis positive\n    if time_axis < 0:\n      time_axis += inputs.ndim\n\n    if time_major:\n      # we add +1 because we moved the time axis to the front\n      batch_dims = inputs.shape[1 : -self.cell.num_feature_axes]\n    else:\n      batch_dims = inputs.shape[:time_axis]\n\n    # maybe reverse the sequence\n    if reverse:\n      inputs = jax.tree_util.tree_map(\n          lambda x: flip_sequences(\n              x,\n              seq_lengths,\n              num_batch_dims=len(batch_dims),\n              time_major=time_major,  # type: ignore\n          ),\n          inputs,\n      )\n\n    carry: Carry\n    if initial_carry is None:\n      if init_key is None:\n        init_key = random.key(0)\n\n      input_shape = inputs.shape[:time_axis] + inputs.shape[time_axis + 1 :]\n      carry = self.cell.initialize_carry(init_key, input_shape)\n    else:\n      carry = initial_carry\n\n    slice_carry = seq_lengths is not None and return_carry\n\n    def scan_fn(\n      cell: RNNCellBase, carry: Carry, x: Array\n    ) -> tuple[Carry, Array] | tuple[Carry, tuple[Carry, Array]]:\n      carry, y = cell(carry, x)\n      # When we have a segmentation mask we return the carry as an output\n      # so that we can select the last carry for each sequence later.\n      # This uses more memory but is faster than using jnp.where at each\n      # iteration. As a small optimization do this when we really need it.\n      if slice_carry:\n        return carry, (carry, y)\n      else:\n        return carry, y\n\n    scan = transforms.scan(\n      scan_fn,\n      in_axes=time_axis,\n      out_axes=(0, time_axis) if slice_carry else time_axis,\n      unroll=self.unroll,\n      variable_axes=self.variable_axes,\n      variable_broadcast=self.variable_broadcast,\n      variable_carry=self.variable_carry,\n      split_rngs=self.split_rngs,\n    )\n\n    scan_output = scan(self.cell, carry, inputs)\n\n    # Next we select the final carry. If a segmentation mask was provided and\n    # return_carry is True we slice the carry history and select the last valid\n    # carry for each sequence. Otherwise we just use the last carry.\n    if slice_carry:\n      assert seq_lengths is not None\n      _, (carries, outputs) = scan_output\n      # seq_lengths[None] expands the shape of the mask to match the\n      # number of dimensions of the carry.\n      carry = _select_last_carry(carries, seq_lengths)\n    else:\n      carry, outputs = scan_output\n\n    if reverse and keep_order:\n      outputs = jax.tree_util.tree_map(\n          lambda x: flip_sequences(\n              x,\n              seq_lengths,\n              num_batch_dims=len(batch_dims),\n              time_major=time_major,  # type: ignore\n          ),\n          outputs,\n      )\n\n    if return_carry:\n      return carry, outputs\n    else:\n      return outputs\n\n\ndef _select_last_carry(sequence: A, seq_lengths: jnp.ndarray) -> A:\n  last_idx = seq_lengths - 1\n\n  def _slice_array(x: jnp.ndarray):\n    return x[last_idx, jnp.arange(x.shape[1])]\n\n  return jax.tree_util.tree_map(_slice_array, sequence)\n\n\ndef _expand_dims_like(x, target):\n  \"\"\"Expands the shape of `x` to match `target`'s shape by adding singleton dimensions.\"\"\"\n  return x.reshape(list(x.shape) + [1] * (target.ndim - x.ndim))\n\n\ndef flip_sequences(\n  inputs: Array,\n  seq_lengths: Array | None,\n  num_batch_dims: int,\n  time_major: bool,\n) -> Array:\n  \"\"\"Flips a sequence of inputs along the time axis.\n\n  This function can be used to prepare inputs for the reverse direction of a\n  bidirectional LSTM. It solves the issue that, when naively flipping multiple\n  padded sequences stored in a matrix, the first elements would be padding\n  values for those sequences that were padded. This function keeps the padding\n  at the end, while flipping the rest of the elements.\n\n  Example:\n  ```python\n  inputs = [[1, 0, 0],\n            [2, 3, 0]\n            [4, 5, 6]]\n  lengths = [1, 2, 3]\n  flip_sequences(inputs, lengths) = [[1, 0, 0],\n                                     [3, 2, 0],\n                                     [6, 5, 4]]\n  ```\n\n  Args:\n    inputs: An array of input IDs <int>[batch_size, seq_length].\n    lengths: The length of each sequence <int>[batch_size].\n\n  Returns:\n    An ndarray with the flipped inputs.\n  \"\"\"\n  # Compute the indices to put the inputs in flipped order as per above example.\n  time_axis = 0 if time_major else num_batch_dims\n  max_steps = inputs.shape[time_axis]\n\n  if seq_lengths is None:\n    # reverse inputs and return\n    inputs = jnp.flip(inputs, axis=time_axis)\n    return inputs\n\n  seq_lengths = jnp.expand_dims(seq_lengths, axis=time_axis)\n\n  # create indexes\n  idxs = jnp.arange(max_steps - 1, -1, -1)  # [max_steps]\n  if time_major:\n    idxs = jnp.reshape(idxs, [max_steps] + [1] * num_batch_dims)\n  else:\n    idxs = jnp.reshape(\n      idxs, [1] * num_batch_dims + [max_steps]\n    )  # [1, ..., max_steps]\n  idxs = (idxs + seq_lengths) % max_steps  # [*batch, max_steps]\n  idxs = _expand_dims_like(\n    idxs, target=inputs\n  )  # [*batch, max_steps, *features]\n  # Select the inputs in flipped order.\n  outputs = jnp.take_along_axis(inputs, idxs, axis=time_axis)\n\n  return outputs\n\n\ndef _concatenate(a: Array, b: Array) -> Array:\n  \"\"\"Concatenates two arrays along the last dimension.\"\"\"\n  return jnp.concatenate([a, b], axis=-1)\n\n\nclass RNNBase(Protocol):\n  def __call__(\n    self,\n    inputs: jax.Array,\n    *,\n    initial_carry: Carry | None = None,\n    init_key: PRNGKey | None = None,\n    seq_lengths: Array | None = None,\n    return_carry: bool | None = None,\n    time_major: bool | None = None,\n    reverse: bool | None = None,\n    keep_order: bool | None = None,\n  ) -> Output | tuple[Carry, Output]:\n    ...\n\n\nclass Bidirectional(Module):\n  \"\"\"Processes the input in both directions and merges the results.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> layer = nn.Bidirectional(nn.RNN(nn.GRUCell(4)), nn.RNN(nn.GRUCell(4)))\n    >>> x = jnp.ones((2, 3))\n    >>> variables = layer.init(jax.random.key(0), x)\n    >>> out = layer.apply(variables, x)\n  \"\"\"\n\n  forward_rnn: RNNBase\n  backward_rnn: RNNBase\n  merge_fn: Callable[[Array, Array], Array] = _concatenate\n  time_major: bool = False\n  return_carry: bool = False\n\n  def __call__(\n    self,\n    inputs: jax.Array,\n    *,\n    initial_carry: Carry | None = None,\n    init_key: PRNGKey | None = None,\n    seq_lengths: Array | None = None,\n    return_carry: bool | None = None,\n    time_major: bool | None = None,\n    reverse: bool | None = None,\n    keep_order: bool | None = None,\n  ) -> Output | tuple[Carry, Output]:\n    if time_major is None:\n      time_major = self.time_major\n    if return_carry is None:\n      return_carry = self.return_carry\n    if init_key is not None:\n      key_forward, key_backward = random.split(init_key)\n    else:\n      key_forward = key_backward = None\n    if initial_carry is not None:\n      initial_carry_forward, initial_carry_backward = initial_carry\n    else:\n      initial_carry_forward = initial_carry_backward = None\n    # Throw a warning in case the user accidentally re-uses the forward RNN\n    # for the backward pass and does not intend for them to share parameters.\n    if self.forward_rnn is self.backward_rnn:\n      logging.warning(\n          'forward_rnn and backward_rnn is the same object, so '\n          'they will share parameters.'\n      )\n\n    # Encode in the forward direction.\n    carry_forward, outputs_forward = self.forward_rnn(\n      inputs,\n      initial_carry=initial_carry_forward,\n      init_key=key_forward,\n      seq_lengths=seq_lengths,\n      return_carry=True,\n      time_major=time_major,\n      reverse=False,\n    )\n\n    carry_backward, outputs_backward = self.backward_rnn(\n      inputs,\n      initial_carry=initial_carry_backward,\n      init_key=key_backward,\n      seq_lengths=seq_lengths,\n      return_carry=True,\n      time_major=time_major,\n      reverse=True,\n      keep_order=True,\n    )\n\n    carry = (carry_forward, carry_backward)\n    outputs = jax.tree_util.tree_map(\n        self.merge_fn, outputs_forward, outputs_backward\n    )\n\n    if return_carry:\n      return carry, outputs\n    else:\n      return outputs\n"
  },
  {
    "path": "flax/linen/spmd.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Utilities for working with jit and partitioned models.\n\nThis module introduces ``axis_rules``, ``logical_to_mesh_axes``,\n``logical_to_mesh``, ``with_logical_constraint`` for appyling jit sharding\nconstraints in terms of \"logical named axes\" rather than jit's default mesh\naxes.\n\nAdditionally the ``LogicallyPartitioned`` metadata wrapper is defined as\nwell as the initializer function wrapper ``with_logical_partitioning``for\nintroducing logical axis metadata into a model's variables.\n\"\"\"\n\nimport collections\nimport dataclasses\nimport enum\nimport functools\nfrom typing import Any\nfrom collections.abc import Callable, Sequence\n\nimport jax\nfrom jax import lax\n\nfrom flax import struct\nfrom flax.core import meta\nfrom flax.core.spmd import (\n    get_logical_axis_rules,\n)\nfrom flax.typing import (\n  Array,\n  LogicalNames,\n  LogicalRules,\n  ArrayPytree,  # pylint: disable=invalid-name\n  LogicalPartitionSpec,  # pylint: disable=unused-import\n  LogicalPartitionSpecPytree,  # pylint: disable=invalid-name\n  )\n\n\nclass _UnassignedAxis:\n  \"\"\"Sentinel class for unassigned logical axis name.\"\"\"\n\n  def __repr__(self):\n    return 'UnassignedAxis'\n\n  def __bool__(self):\n    return False\n\n\n_unassigned_axis = _UnassignedAxis()\n\n\ndef _mesh_assignment_free(new_assignment, existing_assignments):\n  \"\"\"Determines if a given mesh axis has already been assigned.\"\"\"\n  new = set(jax.tree_util.tree_leaves(new_assignment))\n  existing = set(jax.tree_util.tree_leaves(existing_assignments))\n  new.discard(jax.sharding.PartitionSpec.UNCONSTRAINED)\n  new.discard(None)\n  if existing.intersection(new):\n    return False\n  return True\n\n\ndef _logical_to_mesh_axes(\n    array_dim_names: Sequence[str | None] | None,\n    rules: LogicalRules | None = None,\n) -> list[_UnassignedAxis | None | str | tuple[str, ...]] | None:\n  \"\"\"Same as logical_to_mesh_axes, but doesn't fill in _unassigned_axis.\"\"\"\n  if array_dim_names is None:\n    return None\n  if rules is None:\n    rules = get_logical_axis_rules()\n  axis_name_counts = collections.Counter(array_dim_names)\n  # None and special values such as PartitionSpec.UNCONSTRAINED can appear more\n  # then once.\n  dups = tuple(\n      k for k, v in axis_name_counts.items() if v > 1 and isinstance(k, str)\n  )\n  if dups:\n    raise ValueError(\n      f'Unsupported: Dimensions {dups} occur more than once in array names.'\n    )\n  if not isinstance(rules, (tuple, list)):\n    raise ValueError('Unknown axis rule specification type.')\n  # We assign mesh axes using a priority based ruleset over logical axis names.\n  result: list[_UnassignedAxis | None | str | tuple[str, ...]]\n  result = [\n      (_unassigned_axis if isinstance(name, str) else name)\n      for name in array_dim_names\n  ]\n  for rule_model_name, rule_mesh_names in rules:\n    if rule_model_name in array_dim_names:\n      pos = array_dim_names.index(rule_model_name)\n      if (\n        _mesh_assignment_free(rule_mesh_names, result)\n        and result[pos] == _unassigned_axis\n      ):\n        result[pos] = rule_mesh_names\n  return result\n\n\ndef logical_to_mesh_axes(\n  array_dim_names: Sequence[str | None] | None,\n  rules: LogicalRules | None = None,\n) -> jax.sharding.PartitionSpec | None:\n  \"\"\"Compute layout for an array.\n\n  The rules are in order of precedence, and consist of pairs:\n  ``(ArrayDimensionName, MeshDimensionName)``, meaning that the given array\n  dimension (if present and unused) should be sharded across the given\n  mesh dimension (if present and unused).\n\n  A Layout of an Array is expressed as a tuple with one element for each\n  dimension in the Array. The element is either None, or is the name of a\n  mesh-dimension, meaning that this dimension of the array is sharded across\n  this dimension of the mesh.\n\n  For example, given an array with::\n\n    array_dim_names = ('batch', 'length', 'heads', 'features')\n\n  and the layout rules are::\n\n    rules = (('batch', 'X'),\n             ('features', 'X'),\n             ('heads', 'Y'),\n             ('batch', 'Z'))\n\n  then this function will return::\n\n    PartitionSpec('X', None, 'Y', None)\n\n  Args:\n    array_dim_names: Tuple of array dimension names or None.\n    rules: Optional logical to mesh rules override.  Defaults to using the\n      rules defined in the dynamic context set from the ``axis_rules`` function.\n\n  Returns:\n    PartitionSpec for the parameter.\n  \"\"\"\n  result = _logical_to_mesh_axes(array_dim_names, rules)\n  if result is None:\n    return None\n  # We default to None - ie unsharded along the dimension.\n  result = [None if x is _unassigned_axis else x for x in result]\n  return jax.sharding.PartitionSpec(*result)\n\n\ndef logical_to_mesh(tree: Any, rules: LogicalRules | None = None) -> Any:\n  \"\"\"Applies logical_to_mesh_axes to pytrees of logical PartitionSpecs.\"\"\"\n  return jax.tree_util.tree_map(\n      lambda x: logical_to_mesh_axes(x, rules),\n      tree,\n      is_leaf=lambda x: isinstance(x, jax.sharding.PartitionSpec),\n  )\n\n\ndef logical_to_mesh_sharding(\n  tree: Any,\n  mesh: jax.sharding.Mesh,\n  rules: LogicalRules | None = None,\n) -> Any:\n  \"\"\"Convert pytrees of logical PartitionSpecs to shardings.\"\"\"\n  return jax.tree_util.tree_map(\n      lambda x: jax.sharding.NamedSharding(mesh, x),\n      logical_to_mesh(tree, rules),\n      is_leaf=lambda x: isinstance(x, jax.sharding.PartitionSpec),\n  )\n\n\nclass RulesFallback(enum.Enum):\n  \"\"\"How a sharding constraint should behave when no matching rule is found.\"\"\"\n\n  AXIS_IS_UNSHARDED = 'axis_is_unsharded'\n  RAISE_ERROR = 'raise_error'\n  NO_CONSTRAINT = 'no_constraint'\n\n\ndef _with_sharding_constraint(\n  x: Array,\n  axis_resources: jax.sharding.PartitionSpec | None,\n  mesh: jax.sharding.Mesh | None = None,\n):\n  \"\"\"Wrapper for lax.with_sharding_constraint, no-op on cpu or outside jit.\"\"\"\n  if not meta.global_mesh_defined() and mesh is None:\n    return x\n  else:\n    if mesh is not None and axis_resources is not None:\n      sharding = jax.sharding.NamedSharding(mesh, axis_resources)\n      return lax.with_sharding_constraint(x, sharding)\n    return lax.with_sharding_constraint(x, axis_resources)\n\n\ndef _with_sharding_constraint_one_fallback(\n  axis_resources: LogicalPartitionSpec,\n  x: Array,\n  fallback: RulesFallback = RulesFallback.AXIS_IS_UNSHARDED,\n  rules: LogicalRules | None = None,\n  mesh: jax.sharding.Mesh | None = None,\n):\n  \"\"\"Either imposes a sharding constraint or applies fallback.\"\"\"\n  mesh_axes = _logical_to_mesh_axes(axis_resources, rules)\n  if mesh_axes is None:\n    return _with_sharding_constraint(x, None, mesh=mesh)\n\n  if fallback == RulesFallback.AXIS_IS_UNSHARDED:\n    mesh_axes = [None if x is _unassigned_axis else x for x in mesh_axes]\n  else:\n    if any(x is _unassigned_axis for x in mesh_axes):\n      if fallback == RulesFallback.RAISE_ERROR:\n        raise ValueError(f'Axis names {axis_resources} did not match a rule')\n      else:\n        return x\n  return _with_sharding_constraint(\n    x, jax.sharding.PartitionSpec(*mesh_axes), mesh=mesh\n  )\n\n\ndef _is_axis_spec(x):\n  return (\n      isinstance(x, str)\n      or x is jax.sharding.PartitionSpec.UNCONSTRAINED\n      or x is None\n  )\n\n\ndef _is_logical_spec(x):\n  return x is None or (\n      isinstance(x, tuple) and all(_is_axis_spec(e) for e in x)\n  )\n\n\ndef with_logical_constraint(\n  x: ArrayPytree,\n  logical_axis_resources: LogicalPartitionSpecPytree,\n  rules: LogicalRules | None = None,\n  mesh: jax.sharding.Mesh | None = None,\n  fallback: RulesFallback = RulesFallback.AXIS_IS_UNSHARDED,\n):\n  \"\"\"Version of jit's with_sharding_constraint that uses logical axis names.\"\"\"\n  # If no axis binding is set, this is a no-op.\n  if rules is None:\n    rules = get_logical_axis_rules()\n  if not rules or logical_axis_resources is None:\n    return x\n  # Translate logical names to mesh assignments.\n  return jax.tree_util.tree_map(\n    functools.partial(\n      _with_sharding_constraint_one_fallback,\n      fallback=fallback,\n      rules=rules,\n      mesh=mesh,\n    ),\n    logical_axis_resources,\n    x,\n    is_leaf=_is_logical_spec,\n  )\n\n\n# Logical Partitioning Axis Metadata\n# ------------------------------------------------------------------------------\n\n\nclass LogicallyPartitioned(meta.Partitioned):\n  rules: LogicalRules | None = struct.field(default=None, pytree_node=False)\n\n  # Directly comparing members of the type `jax.Array` can throw an error:\n  # \"The truth value of an array with more than one element is ambiguous.\"\n  # So we bring back an explicit implementation of __eq__ like it was prior to\n  # Python 3.13 in order work around this possibility.\n  def __eq__(self, other):\n    if self is other:\n      return True\n    if other.__class__ is self.__class__:\n      return (self.value,) == (other.value,)\n    return NotImplemented\n\n  def unbox(self, apply_constraint=True) -> Any:\n    \"\"\"Returns the wrapped value with the partitioning constraint applied.\"\"\"\n    if apply_constraint and (meta.global_mesh_defined() or self.mesh is not None):\n      return with_logical_constraint(\n        self.value,\n        self.get_partition_spec(),\n        rules=self.rules,\n        mesh=self.mesh,\n      )\n    else:\n      return self.value\n\n  def to_nnx_metadata(self) -> dict[str, Any]:\n    \"\"\"Return a dict of metadata that can translate into an `nnx.Variable`.\"\"\"\n    metadata = vars(self)\n    if 'names' in metadata:\n      metadata['out_sharding'] = metadata.pop('names')\n    if 'rules' in metadata:\n      metadata['sharding_rules'] = metadata.pop('rules')\n    return metadata\n\n  @classmethod\n  def from_nnx_metadata(cls, metadata: dict[str, Any]):\n    \"\"\"Given a dict of `nnx.Variable` format metadata, create a `nn.LogicallyPartitioned`.\"\"\"\n    metadata['names'] = metadata.pop('out_sharding')\n    metadata['rules'] = metadata.pop('sharding_rules')\n    fields = {x.name for x in dataclasses.fields(cls)}\n    return cls(**{k: v for k, v in metadata.items() if k in fields})\n\n\ndef with_logical_partitioning(\n  fn: Callable[..., Any],\n  names: LogicalNames,\n  mesh: jax.sharding.Mesh | None = None,\n  rules: LogicalRules | None = None,\n) -> Callable[..., LogicallyPartitioned]:\n  \"\"\"Wraps a function's return value with LogicallyPartitioned.\n\n  Example::\n\n    >>> import flax.linen as nn\n    >>> kernel_init = nn.with_logical_partitioning(\n    ...     nn.initializers.lecun_normal(), (None, \"data\"))\n    >>> partitioned_dense = nn.Dense(features=3, kernel_init=kernel_init)\n\n  Args:\n    fn: The function to be wrapped. Typically this is an initializer.\n    names: The logical axis passed to ``LogicallyPartitioned``.\n    mesh: The mesh to use for the partitioning. If None, the global mesh\n      resource is used if available.\n    rules: Optional logical to mesh rules use. If None, the global rules\n      are used if available.\n  Returns:\n    A function wrapping ``fn`` that will return an instance of\n    ``LogicallyPartitioned``.\n  \"\"\"\n\n  @functools.wraps(fn)\n  def wrapper(*args, **kwargs):\n    return LogicallyPartitioned(\n      fn(*args, **kwargs), names, mesh=mesh, rules=rules\n    )  # pytype: disable=wrong-keyword-args\n\n  return wrapper\n"
  },
  {
    "path": "flax/linen/stochastic.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Stochastic modules.\"\"\"\n\nfrom collections.abc import Sequence\n\nimport jax.numpy as jnp\nfrom jax import lax, random\n\nfrom flax.linen.module import Module, compact, merge_param\nfrom flax.typing import PRNGKey\n\n\nclass Dropout(Module):\n  \"\"\"Create a dropout layer.\n\n  .. note::\n    When using :meth:`Module.apply() <flax.linen.Module.apply>`, make sure\n    to include an RNG seed named ``'dropout'``. Dropout isn't necessary for\n    variable initialization.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> class MLP(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x, train):\n    ...     x = nn.Dense(4)(x)\n    ...     x = nn.Dropout(0.5, deterministic=not train)(x)\n    ...     return x\n\n    >>> model = MLP()\n    >>> x = jnp.ones((1, 3))\n    >>> variables = model.init(jax.random.key(0), x, train=False) # don't use dropout\n    >>> model.apply(variables, x, train=False) # don't use dropout\n    Array([[-0.17875527,  1.6255447 , -1.2431065 , -0.02554005]], dtype=float32)\n    >>> model.apply(variables, x, train=True, rngs={'dropout': jax.random.key(1)}) # use dropout\n    Array([[-0.35751054,  3.2510893 ,  0.        ,  0.        ]], dtype=float32)\n\n  Attributes:\n    rate: the dropout probability.  (_not_ the keep rate!)\n    broadcast_dims: dimensions that will share the same dropout mask\n    deterministic: if false the inputs are scaled by ``1 / (1 - rate)`` and\n      masked, whereas if true, no mask is applied and the inputs are returned as\n      is.\n    rng_collection: the rng collection name to use when requesting an rng key.\n  \"\"\"\n\n  rate: float\n  broadcast_dims: Sequence[int] = ()\n  deterministic: bool | None = None\n  rng_collection: str = 'dropout'\n\n  @compact\n  def __call__(\n    self,\n    inputs,\n    deterministic: bool | None = None,\n    rng: PRNGKey | None = None,\n  ):\n    \"\"\"Applies a random dropout mask to the input.\n\n    Args:\n      inputs: the inputs that should be randomly masked.\n      deterministic: if false the inputs are scaled by ``1 / (1 - rate)`` and\n        masked, whereas if true, no mask is applied and the inputs are returned\n        as is.\n      rng: an optional PRNGKey used as the random key, if not specified, one\n        will be generated using ``make_rng`` with the ``rng_collection`` name.\n\n    Returns:\n      The masked inputs reweighted to preserve mean.\n    \"\"\"\n    deterministic = merge_param(\n      'deterministic', self.deterministic, deterministic\n    )\n\n    if (self.rate == 0.0) or deterministic:\n      return inputs\n\n    # Prevent gradient NaNs in 1.0 edge-case.\n    if self.rate == 1.0:\n      return jnp.zeros_like(inputs)\n\n    keep_prob = 1.0 - self.rate\n    if rng is None:\n      rng = self.make_rng(self.rng_collection)\n    broadcast_shape = list(inputs.shape)\n    for dim in self.broadcast_dims:\n      broadcast_shape[dim] = 1\n    mask = random.bernoulli(rng, p=keep_prob, shape=broadcast_shape)\n    mask = jnp.broadcast_to(mask, inputs.shape)\n    return lax.select(mask, inputs / keep_prob, jnp.zeros_like(inputs))\n"
  },
  {
    "path": "flax/linen/summary.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Flax Module summary library.\"\"\"\n\nimport dataclasses\nimport enum\nimport io\nfrom abc import ABC, abstractmethod\nfrom types import MappingProxyType\nfrom typing import (\n  Any,\n)\nfrom collections.abc import Callable, Iterable, Mapping, Sequence\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nimport rich.console\nimport rich.table\nimport rich.text\nimport yaml\n\nimport flax.linen.module as module_lib\nfrom flax.core import meta, unfreeze\nfrom flax.core.scope import (\n  CollectionFilter,\n  DenyList,\n  LazyRng,\n)\nfrom flax.typing import (\n  Array,\n  PRNGKey,\n  RNGSequences,\n  FrozenVariableDict,\n  MutableVariableDict,\n  LogicalNames,\n)\n\n\nclass _ValueRepresentation(ABC):\n  \"\"\"A class that represents a value in the summary table.\"\"\"\n\n  @abstractmethod\n  def render(self) -> str:\n    ...\n\n\n@dataclasses.dataclass\nclass _ArrayRepresentation(_ValueRepresentation):\n  shape: tuple[int, ...]\n  dtype: Any\n\n  @classmethod\n  def from_array(cls, x: Array) -> '_ArrayRepresentation':\n    return cls(jnp.shape(x), jnp.result_type(x))\n\n  @classmethod\n  def render_array(cls, x) -> str:\n    return cls.from_array(x).render()\n\n  def render(self):\n    shape_repr = ','.join(str(x) for x in self.shape)\n    return f'[dim]{self.dtype}[/dim][{shape_repr}]'\n\n\n@dataclasses.dataclass\nclass _PartitionedArrayRepresentation(_ValueRepresentation):\n  array_representation: _ArrayRepresentation\n  names: LogicalNames\n\n  @classmethod\n  def from_partitioned(\n    cls, partitioned: meta.Partitioned\n  ) -> '_PartitionedArrayRepresentation':\n    return cls(\n      _ArrayRepresentation.from_array(partitioned.value), partitioned.names\n    )\n\n  def render(self):\n    return self.array_representation.render() + f' [dim]P[/dim]{self.names}'\n\n\n@dataclasses.dataclass\nclass _ObjectRepresentation(_ValueRepresentation):\n  obj: Any\n\n  def render(self):\n    return repr(self.obj)\n\n\n@dataclasses.dataclass\nclass Row:\n  \"\"\"Contains the information about a single row in the summary table.\n\n  Attributes:\n    path: A tuple of strings that represents the path to the module.\n    module_copy: A copy of the module being summarized.\n    method: method of the module called.\n    inputs: inputs to the module.\n    outputs: Output of the Module as reported by `capture_intermediates`.\n    module_variables: Dictionary of variables in the module (no submodules\n      included).\n    counted_variables: Dictionary of variables that should be counted for this\n      row, if no summarization is done (e.g. `depth=None` in `module_summary`)\n      then this field is the same as `module_variables`, however if a\n      summarization is done then this dictionary potentially contains parameters\n      from submodules depending on the depth of the Module in question.\n    flops: FLOPs cost of calling the module method.\n    vjp_flops: FLOPs cost of calling the VJP of the module method.\n  \"\"\"\n\n  path: tuple[str, ...]\n  module_copy: module_lib.Module\n  method: str\n  inputs: Any\n  outputs: Any\n  module_variables: dict[str, dict[str, Any]]\n  counted_variables: dict[str, dict[str, Any]]\n  flops: int\n  vjp_flops: int\n\n  def __post_init__(self):\n    self.inputs = self.inputs\n    self.outputs = self.outputs\n    self.module_variables = self.module_variables\n    self.counted_variables = self.counted_variables\n\n  def size_and_bytes(\n    self, collections: Iterable[str]\n  ) -> dict[str, tuple[int, int]]:\n    return {\n      col: (\n        _size_and_bytes(self.counted_variables[col])\n        if col in self.counted_variables\n        else (0, 0)\n      )\n      for col in collections\n    }\n\n\nclass Table(list[Row]):\n  \"\"\"A list of Row objects.\n\n  Table inherits from `List[Row]` so it has all the methods of a list, however\n  it also contains some additional fields:\n\n  * `module`: the module that this table is summarizing\n  * `collections`: a list containing the parameter collections (e.g. 'params', 'batch_stats', etc)\n  \"\"\"\n\n  def __init__(\n    self,\n    module: module_lib.Module,\n    collections: Sequence[str],\n    rows: Iterable[Row],\n  ):\n    super().__init__(rows)\n    self.module = module\n    self.collections = collections\n\n\ndef tabulate(\n  module: module_lib.Module,\n  rngs: PRNGKey | RNGSequences,\n  depth: int | None = None,\n  show_repeated: bool = False,\n  mutable: CollectionFilter = DenyList('intermediates'),\n  console_kwargs: Mapping[str, Any] | None = None,\n  table_kwargs: Mapping[str, Any] = MappingProxyType({}),\n  column_kwargs: Mapping[str, Any] = MappingProxyType({}),\n  compute_flops: bool = False,\n  compute_vjp_flops: bool = False,\n  **kwargs,\n) -> Callable[..., str]:\n  \"\"\"Returns a function that creates a summary of the Module represented as a table.\n\n  This function accepts most of the same arguments and internally calls\n  `Module.init`, except that it returns a function of the form\n  `(*args, **kwargs) -> str` where `*args` and `**kwargs` are passed to\n  `method` (e.g. `__call__`) during the forward pass.\n\n  `tabulate` uses `jax.eval_shape` under the hood to run the forward computation\n  without consuming any FLOPs or allocating memory.\n\n  Additional arguments can be passed into the `console_kwargs` argument, for\n  example, `{'width': 120}`. For a full list of `console_kwargs` arguments, see:\n  https://rich.readthedocs.io/en/stable/reference/console.html#rich.console.Console\n\n  Example::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> class Foo(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     h = nn.Dense(4)(x)\n    ...     return nn.Dense(2)(h)\n\n    >>> x = jnp.ones((16, 9))\n    >>> tabulate_fn = nn.tabulate(\n    ...     Foo(), jax.random.key(0), compute_flops=True, compute_vjp_flops=True)\n\n    >>> # print(tabulate_fn(x))\n\n  This gives the following output::\n\n                                           Foo Summary\n    ┏━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓\n    ┃ path    ┃ module ┃ inputs        ┃ outputs       ┃ flops ┃ vjp_flops ┃ params          ┃\n    ┡━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩\n    │         │ Foo    │ float32[16,9] │ float32[16,2] │ 1504  │ 4460      │                 │\n    ├─────────┼────────┼───────────────┼───────────────┼───────┼───────────┼─────────────────┤\n    │ Dense_0 │ Dense  │ float32[16,9] │ float32[16,4] │ 1216  │ 3620      │ bias:           │\n    │         │        │               │               │       │           │ float32[4]      │\n    │         │        │               │               │       │           │ kernel:         │\n    │         │        │               │               │       │           │ float32[9,4]    │\n    │         │        │               │               │       │           │                 │\n    │         │        │               │               │       │           │ 40 (160 B)      │\n    ├─────────┼────────┼───────────────┼───────────────┼───────┼───────────┼─────────────────┤\n    │ Dense_1 │ Dense  │ float32[16,4] │ float32[16,2] │ 288   │ 840       │ bias:           │\n    │         │        │               │               │       │           │ float32[2]      │\n    │         │        │               │               │       │           │ kernel:         │\n    │         │        │               │               │       │           │ float32[4,2]    │\n    │         │        │               │               │       │           │                 │\n    │         │        │               │               │       │           │ 10 (40 B)       │\n    ├─────────┼────────┼───────────────┼───────────────┼───────┼───────────┼─────────────────┤\n    │         │        │               │               │       │     Total │ 50 (200 B)      │\n    └─────────┴────────┴───────────────┴───────────────┴───────┴───────────┴─────────────────┘\n\n                                   Total Parameters: 50 (200 B)\n\n\n  **Note**: rows order in the table does not represent execution order,\n  instead it aligns with the order of keys in `variables` which are sorted\n  alphabetically.\n\n  **Note**: `vjp_flops` returns `0` if the module is not differentiable.\n\n  Args:\n    module: The module to tabulate.\n    rngs: The rngs for the variable collections as passed to `Module.init`.\n    depth: controls how many submodule deep the summary can go. By default its\n      `None` which means no limit. If a submodule is not shown because of the\n      depth limit, its parameter count and bytes will be added to the row of its\n      first shown ancestor such that the sum of all rows always adds up to the\n      total number of parameters of the Module.\n    show_repeated: If `True`, repeated calls to the same module will be shown\n      in the table, otherwise only the first call will be shown. Default is\n      `False`.\n    mutable: Can be bool, str, or list. Specifies which collections should be\n      treated as mutable: ``bool``: all/no collections are mutable. ``str``: The\n      name of a single mutable collection. ``list``: A list of names of mutable\n      collections. By default all collections except 'intermediates' are\n      mutable.\n    console_kwargs: An optional dictionary with additional keyword arguments\n      that are passed to `rich.console.Console` when rendering the table.\n      Default arguments are `{'force_terminal': True, 'force_jupyter': False}`.\n    table_kwargs: An optional dictionary with additional keyword arguments that\n      are passed to `rich.table.Table` constructor.\n    column_kwargs: An optional dictionary with additional keyword arguments that\n      are passed to `rich.table.Table.add_column` when adding columns to the\n      table.\n    compute_flops: whether to include a `flops` column in the table listing the\n      estimated FLOPs cost of each module forward pass. Does incur actual\n      on-device computation / compilation / memory allocation, but still\n      introduces overhead for large modules (e.g. extra 20 seconds for a\n      Stable Diffusion's UNet, whereas otherwise tabulation would finish in 5\n      seconds).\n    compute_vjp_flops: whether to include a `vjp_flops` column in the table\n      listing the estimated FLOPs cost of each module backward pass. Introduces\n      a compute overhead of about 2-3X of `compute_flops`.\n    **kwargs: Additional arguments passed to `Module.init`.\n\n  Returns:\n    A function that accepts the same `*args` and `**kwargs` of the forward pass\n    (`method`) and returns a string with a tabular representation of the\n    Modules.\n  \"\"\"\n  # add non-default arguments to kwargs, this prevents some issue we overloading init\n  # see: https://github.com/google/flax/issues/3299\n  if mutable != DenyList('intermediates'):\n    kwargs['mutable'] = mutable\n\n  def _tabulate_fn(*fn_args, **fn_kwargs):\n    table_fn = _get_module_table(\n      module,\n      depth=depth,\n      show_repeated=show_repeated,\n      compute_flops=compute_flops,\n      compute_vjp_flops=compute_vjp_flops,\n    )\n\n    table = table_fn(rngs, *fn_args, **fn_kwargs, **kwargs)\n\n    non_param_cols = [\n      'path',\n      'module',\n      'inputs',\n      'outputs',\n    ]\n\n    if compute_flops:\n      non_param_cols.append('flops')\n    if compute_vjp_flops:\n      non_param_cols.append('vjp_flops')\n\n    return _render_table(\n      table, console_kwargs, table_kwargs, column_kwargs, non_param_cols\n    )\n\n  return _tabulate_fn\n\n\ndef _get_flops(fn, *args, **kwargs):\n  e = jax.jit(fn).lower(*args, **kwargs)\n  cost = e.cost_analysis()\n  if cost is None:\n    return 0\n  flops = int(cost['flops']) if 'flops' in cost else 0\n  return flops\n\n\ndef _get_call_flops(\n  c: module_lib._CallInfo,\n  compute_flops: bool,\n  compute_vjp_flops: bool,\n) -> tuple[int, int]:\n  \"\"\"Return the FLOPs of executing the call `c` in the call stack.\n\n  Does not perform actual computation / compilation / memory allocation, but\n  still introduces overhead for large modules.\n\n  Args:\n    c: ``_CallInfo``.\n    compute_flops: whether to compute forward pass FLOPs. Return `-1` otherwise.\n    compute_vjp_flops: whether to compute backward pass FLOPs. Return `-1`\n      otherwise.\n\n  Returns:\n    FLOPs of executing forward pass of `c`, and its VJP.\n  \"\"\"\n\n  if not compute_flops and not compute_vjp_flops:\n    return -1, -1\n\n  rngs = jax.tree_util.tree_map(\n      lambda x: x.rng, c.rngs, is_leaf=lambda x: isinstance(x, LazyRng)\n  )\n\n  args = jax.tree_util.tree_map(_from_value_representation, c.args)\n  kwargs = jax.tree_util.tree_map(_from_value_representation, c.kwargs)\n\n  leaves, treedef = jax.tree_util.tree_flatten((args, kwargs))\n  dynamic_leaves = []\n  dynamic_idxs = []\n  for i, arg in enumerate(leaves):\n    if isinstance(arg, jax.ShapeDtypeStruct):\n      dynamic_leaves.append(arg)\n      dynamic_idxs.append(i)\n\n  def _get_inputs(dynamic_leaves):\n    new_leaves: list[Any] = leaves.copy()\n    for i, arg in zip(dynamic_idxs, dynamic_leaves):\n      new_leaves[i] = arg\n    return treedef.unflatten(new_leaves)\n\n  def init(rngs, dynamic_leaves):\n    \"\"\"`c.module.init` closed over static keyword arguments.\"\"\"\n    args, kwargs = _get_inputs(dynamic_leaves)\n    return c.module.init(\n      rngs,\n      *args,\n      method=c.method,\n      mutable=c.mutable,\n      **kwargs,\n    )\n\n  variables = jax.eval_shape(init, rngs, dynamic_leaves)\n\n  def apply(variables, rngs, dynamic_leaves):\n    \"\"\"`c.module.apply` closed over static keyword arguments.\"\"\"\n    args, kwargs = _get_inputs(dynamic_leaves)\n    return c.module.apply(\n      variables,\n      *args,\n      rngs=rngs,\n      method=c.method,\n      mutable=c.mutable,\n      **kwargs,\n    )\n\n  # Forward pass FLOPs\n  if compute_flops:\n    flops = _get_flops(apply, variables, rngs, dynamic_leaves)\n  else:\n    flops = -1\n\n  if compute_vjp_flops:\n    # Backward pass FLOPs\n    def apply_vjp(variables, rngs, dynamic_leaves):\n      \"\"\"VJP of `c.module.apply` closed over static keyword arguments.\"\"\"\n      out, vjp_fn = jax.vjp(apply, variables, rngs, dynamic_leaves)\n      return vjp_fn(out)\n\n    vjp_flops = _get_flops(apply_vjp, variables, rngs, dynamic_leaves)\n  else:\n    vjp_flops = -1\n\n  return flops, vjp_flops\n\n\ndef _get_module_table(\n  module: module_lib.Module,\n  depth: int | None,\n  show_repeated: bool,\n  compute_flops: bool,\n  compute_vjp_flops: bool,\n) -> Callable[..., Table]:\n  \"\"\"A function that takes a Module and returns function with the same signature\n  as `init` but returns the Table representation of the Module.\"\"\"\n\n  def _get_table_fn(*args, **kwargs):\n    with module_lib._tabulate_context():\n\n      def _get_variables():\n        return module.init(*args, **kwargs)\n      # TODO(cgarciae): is it possible to avoid leaking tracers for summaries?\n      with jax.check_tracer_leaks(False):\n        variables = jax.eval_shape(_get_variables)\n      calls = module_lib._context.call_info_stack[-1].calls\n      calls.sort(key=lambda c: c.index)\n\n    collections: set[str] = set(variables.keys())\n    rows = []\n    all_paths: set[tuple[str, ...]] = {call.path for call in calls}\n    visited_paths: set[tuple[str, ...]] = set()\n\n    for c in calls:\n      call_depth = len(c.path)\n      inputs = _process_inputs(c.args, c.kwargs)\n\n      if c.path in visited_paths or not hasattr(c.module, c.method):\n        if not show_repeated:\n          continue\n        module_vars = {}\n        counted_vars = {}\n      elif depth is not None:\n        if call_depth > depth:\n          continue\n        module_vars, _ = _get_module_variables(c.path, variables, all_paths)\n        if call_depth == depth:\n          counted_vars = _get_path_variables(c.path, variables)\n        else:\n          counted_vars = module_vars\n      else:\n        module_vars, _ = _get_module_variables(c.path, variables, all_paths)\n        counted_vars = module_vars\n\n      visited_paths.add(c.path)\n      rows.append(\n        Row(\n          c.path,\n          c.module.copy(parent=None),\n          c.method,\n          inputs,\n          c.outputs,\n          module_vars,\n          counted_vars,\n          *_get_call_flops(c, compute_flops, compute_vjp_flops),\n        )\n      )\n\n    return Table(module, tuple(collections), rows)\n\n  return _get_table_fn\n\n\ndef _get_module_variables(\n  path: tuple[str, ...],\n  variables: FrozenVariableDict,\n  all_paths: set[tuple[str, ...]],\n) -> tuple[MutableVariableDict, Any]:\n  \"\"\"A function that takes a path and variables structure and returns a\n\n  (module_variables, submodule_variables) tuple for that path.\n  _get_module_variables\n  uses the `all_paths` set to determine if a variable belongs to a submodule or\n  not.\n  \"\"\"\n  module_variables = _get_path_variables(path, variables)\n  submodule_variables: Any = {collection: {} for collection in module_variables}\n  all_keys = {\n    key for collection in module_variables.values() for key in collection\n  }\n\n  for key in all_keys:\n    submodule_path = path + (key,)\n    if submodule_path in all_paths:\n      for collection in module_variables:\n        if key in module_variables[collection]:\n          submodule_variables[collection][key] = module_variables[\n            collection\n          ].pop(key)\n\n  return module_variables, submodule_variables\n\n\ndef _get_path_variables(\n  path: tuple[str, ...], variables: FrozenVariableDict\n) -> MutableVariableDict:\n  \"\"\"A function that takes a path and a variables structure and returns the\n  variable structure at that path.\n  \"\"\"\n  path_variables = {}\n\n  for collection in variables:\n    collection_variables = variables[collection]\n    for name in path:\n      if name not in collection_variables:\n        collection_variables = None\n        break\n      collection_variables = collection_variables[name]\n\n    if collection_variables is not None:\n      path_variables[collection] = unfreeze(collection_variables)\n\n  return path_variables\n\n\ndef _process_inputs(args, kwargs) -> Any:\n  \"\"\"A function that normalizes the representation of the ``args`` and\n  ``kwargs`` for the ``inputs`` column.\n  \"\"\"\n  if args and kwargs:\n    input_values = (*args, kwargs)\n  elif args and not kwargs:\n    input_values = args[0] if len(args) == 1 else args\n  elif kwargs and not args:\n    input_values = kwargs\n  else:\n    input_values = ()\n\n  return input_values\n\n\ndef _render_table(\n  table: Table,\n  console_extras: Mapping[str, Any] | None,\n  table_kwargs: Mapping[str, Any],\n  column_kwargs: Mapping[str, Any],\n  non_params_cols: list[str],\n) -> str:\n  \"\"\"A function that renders a Table to a string representation using rich.\"\"\"\n  console_kwargs = {'force_terminal': True, 'force_jupyter': False}\n  if console_extras is not None:\n    console_kwargs.update(console_extras)\n\n  rich_table = rich.table.Table(\n    show_header=True,\n    show_lines=True,\n    show_footer=True,\n    title=f'{table.module.__class__.__name__} Summary',\n    **table_kwargs,\n  )\n\n  for c in non_params_cols:\n    rich_table.add_column(c, **column_kwargs)\n\n  for col in table.collections:\n    rich_table.add_column(col, **column_kwargs)\n\n  for row in table:\n    collections_size_repr = []\n\n    for collection, size_bytes in row.size_and_bytes(table.collections).items():\n      col_repr = ''\n\n      if collection in row.module_variables:\n        module_variables = _represent_tree(row.module_variables[collection])\n        module_variables = _normalize_structure(module_variables)\n        col_repr += _as_yaml_str(\n          _summary_tree_map(_maybe_render, module_variables)\n        )\n        if col_repr:\n          col_repr += '\\n\\n'\n\n      col_repr += f'[bold]{_size_and_bytes_repr(*size_bytes)}[/bold]'\n      collections_size_repr.append(col_repr)\n\n    no_show_methods = {'__call__', '<lambda>'}\n    path_repr = '/'.join(row.path)\n    method_repr = (\n      f' [dim]({row.method})[/dim]' if row.method not in no_show_methods else ''\n    )\n    rich_table.add_row(\n      path_repr,\n      type(row.module_copy).__name__ + method_repr,\n      *(\n        _as_yaml_str(\n          _summary_tree_map(\n            _maybe_render, _normalize_structure(getattr(row, c))\n          )\n        )\n        for c in non_params_cols[2:]\n      ),\n      *collections_size_repr,\n    )\n\n  # add footer with totals\n  n_non_params_cols = len(non_params_cols)\n  rich_table.columns[n_non_params_cols - 1].footer = rich.text.Text.from_markup(\n    'Total', justify='right'\n  )\n\n  # get collection totals\n  collection_total = {col: (0, 0) for col in table.collections}\n  for row in table:\n    for col, size_bytes in row.size_and_bytes(table.collections).items():\n      collection_total[col] = (\n        collection_total[col][0] + size_bytes[0],\n        collection_total[col][1] + size_bytes[1],\n      )\n\n  # add totals to footer\n  for i, col in enumerate(table.collections):\n    rich_table.columns[n_non_params_cols + i].footer = _size_and_bytes_repr(\n      *collection_total[col]\n    )\n\n  # add final totals to caption\n  caption_totals = (0, 0)\n  for size, num_bytes in collection_total.values():\n    caption_totals = (\n      caption_totals[0] + size,\n      caption_totals[1] + num_bytes,\n    )\n\n  rich_table.caption_style = 'bold'\n  rich_table.caption = (\n    f'\\nTotal Parameters: {_size_and_bytes_repr(*caption_totals)}'\n  )\n\n  return '\\n' + _get_rich_repr(rich_table, console_kwargs) + '\\n'\n\n\ndef _summary_tree_map(f, tree, *rest):\n  return jax.tree_util.tree_map(f, tree, *rest, is_leaf=lambda x: x is None)\n\n\ndef _size_and_bytes_repr(size: int, num_bytes: int) -> str:\n  if not size:\n    return ''\n  bytes_repr = _bytes_repr(num_bytes)\n  return f'{size:,} [dim]({bytes_repr})[/dim]'\n\n\ndef _size_and_bytes(pytree: Any) -> tuple[int, int]:\n  leaves = jax.tree_util.tree_leaves(pytree)\n  size = sum(x.size for x in leaves if hasattr(x, 'size'))\n  num_bytes = sum(\n    x.size * x.dtype.itemsize for x in leaves if hasattr(x, 'size')\n  )\n  return size, num_bytes\n\n\ndef _get_rich_repr(obj, console_kwargs):\n  f = io.StringIO()\n  console = rich.console.Console(file=f, **console_kwargs)\n  console.print(obj)\n  return f.getvalue()\n\n\ndef _as_yaml_str(value) -> str:\n  if (hasattr(value, '__len__') and len(value) == 0) or value is None:\n    return ''\n\n  file = io.StringIO()\n  yaml.safe_dump(\n    value,\n    file,\n    default_flow_style=False,\n    indent=2,\n    sort_keys=False,\n    explicit_end=False,\n  )\n  return file.getvalue().replace('\\n...', '').replace(\"'\", '').strip()\n\n\ndef _normalize_structure(obj):\n  if isinstance(obj, _ValueRepresentation):\n    return obj\n  if isinstance(obj, (tuple, list)):\n    return tuple(map(_normalize_structure, obj))\n  elif isinstance(obj, Mapping):\n    return {\n      _normalize_structure(k): _normalize_structure(v) for k, v in obj.items()\n    }\n  elif dataclasses.is_dataclass(obj):\n    return {\n      f.name: _normalize_structure(getattr(obj, f.name))\n      for f in dataclasses.fields(obj)\n    }\n  elif isinstance(obj, enum.Enum):\n    # `yaml.safe_dump` does not support Enum key types so extract the underlying value\n    return obj.value\n  else:\n    return obj\n\n\ndef _bytes_repr(num_bytes):\n  count, units = (\n    (f'{num_bytes / 1e9 :,.1f}', 'GB')\n    if num_bytes > 1e9\n    else (f'{num_bytes / 1e6 :,.1f}', 'MB')\n    if num_bytes > 1e6\n    else (f'{num_bytes / 1e3 :,.1f}', 'KB')\n    if num_bytes > 1e3\n    else (f'{num_bytes:,}', 'B')\n  )\n\n  return f'{count} {units}'\n\n\ndef _get_value_representation(x: Any) -> _ValueRepresentation:\n  if isinstance(x, (int, float, bool, type(None))) or (\n    isinstance(x, np.ndarray) and np.isscalar(x)\n  ):\n    return _ObjectRepresentation(x)\n  elif isinstance(x, meta.Partitioned):\n    return _PartitionedArrayRepresentation.from_partitioned(x)\n  try:\n    return _ArrayRepresentation.from_array(x)\n  except:\n    return _ObjectRepresentation(x)\n\n\ndef _from_value_representation(x: _ValueRepresentation) -> Any:\n  if isinstance(x, _ArrayRepresentation):\n    return jax.ShapeDtypeStruct(x.shape, x.dtype)\n\n  elif isinstance(x, _PartitionedArrayRepresentation):\n    return jax.ShapeDtypeStruct(\n      x.array_representation.shape, x.array_representation.dtype\n    )\n\n  elif isinstance(x, _ObjectRepresentation):\n    return x.obj\n\n  raise TypeError(x, type(x))\n\n\ndef _represent_tree(x):\n  \"\"\"Returns a tree with the same structure as `x` but with each leaf replaced\n  by a `_ValueRepresentation` object.\"\"\"\n  return jax.tree_util.tree_map(\n    _get_value_representation,\n    x,\n    is_leaf=lambda x: x is None or isinstance(x, meta.Partitioned),\n  )\n\n\ndef _maybe_render(x):\n  return x.render() if hasattr(x, 'render') else repr(x)\n"
  },
  {
    "path": "flax/linen/transforms.py",
    "content": "# Copyright 2023 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"JAX transformations on Modules.\n\nJax functional transformations operate on pure functions.\nFlax extends these transformations to also operate on Module's which\nhave stateful variables and PRNG sequences. We refer to these extended\nversions as \"lifted transformations\".\n\nA lifted transformation can be applied to a ``Module`` class or a\nfunction that takes a ``Module`` instance as its first argument.\n\"\"\"\n\nfrom collections.abc import Callable, Iterable, Mapping, Sequence\nimport contextlib\nimport dataclasses\nimport functools\nimport inspect\nfrom typing import (\n  Any,\n  TypeVar,\n  Union,\n)\nimport weakref\n\nfrom flax import core\nfrom flax import errors, struct, traceback_util\nfrom flax import serialization\nfrom flax.core import Scope, lift, meta\nfrom flax.core.frozen_dict import FrozenDict\nfrom flax.core.scope import (\n  CollectionFilter,\n  LazyRng,\n  PRNGSequenceFilter,\n)\nfrom flax.ids import FlaxId\nfrom flax.linen import module as linen_module\nfrom flax.linen.module import (\n  Module,\n  Variable,\n  _derive_profiling_name,\n  _get_unbound_fn,\n  wrap_method_once,\n)\nfrom flax.typing import (\n  InOutAxis,\n  InOutScanAxis,\n)\nimport jax\n\ntraceback_util.register_exclusion(__file__)\n\n# pylint: disable=protected-access,dangerous-default-value\n\n\n# Utils\n# -----------------------------------------------------------------------------\ndef clean_clone(x):\n  \"\"\"Remove scopes and tracers from children.\"\"\"\n  if isinstance(x, Module):\n    object.__setattr__(\n      x, 'children', {k: clean_clone(v) for k, v in x.children.items()}\n    )\n    object.__setattr__(x, 'scope', None)\n  return x\n\n\n@struct.dataclass\nclass VariablePlaceholder:\n  \"\"\"Used to mark Variables in a JAX-compatible way when lifting arguments.\"\"\"\n\n  collection: str = struct.field(pytree_node=False)\n  name: str = struct.field(pytree_node=False)\n  unbox: bool = struct.field(pytree_node=False)\n  id: int = struct.field(pytree_node=False)\n\n\n@struct.dataclass\nclass InstancePlaceholder:\n  \"\"\"Marks module instances in a JAX-compatible way when lifting arguments.\"\"\"\n\n  cls: type[Any] = struct.field(pytree_node=False)\n  attrs: dict[Any, Any] = struct.field(pytree_node=False)\n  id: int = struct.field(pytree_node=False)\n\n\ndef _memoize_by_id(fn, refs):\n  \"\"\"Memoization by module/variable id to handle aliasing in traversal.\"\"\"\n\n  @functools.wraps(fn)\n  def wrapped_fn(x):\n    nonlocal refs\n    if isinstance(x, (VariablePlaceholder, InstancePlaceholder)):\n      x_id = x.id\n    elif isinstance(x, (Variable, Module)):\n      x_id = x._id\n    else:\n      return fn(x)\n    if x_id not in refs:\n      refs[x_id] = fn(x)\n    return refs[x_id]\n\n  return wrapped_fn\n\n\ndef get_module_scopes(module, args=None, kwargs=None):\n  \"\"\"Get all scopes on module, including constructor Module arguments.\n\n  To properly functionalize a Module that has other bound Modules passed in\n  \"from the outside\" as dataclass attributes, we need to traverse all dataclass\n  fields to find the Scopes associated with the Module.  Additionally, because\n  we allow Modules to be passed inside pytrees on the dataclass attributes, we\n  must traverse all dataclass attributes as pytrees to find all Modules.  We\n  additionally handle lifting Variables (which are just references to data in\n  particular scopes) and Module instances that are passed as arguments to\n  methods.\n\n  Args:\n    module: a bound flax Module.\n    args: an *args list possibly containing Variables or Module instances\n      referencing a scope.\n    kwargs: a **kwargs dict possibly containing Variables or Module instances\n      referencing a scope.\n\n  Returns:\n    A list of all functional-core Scopes bound on self and inside dataclass\n    fields as well as any Scopes passed via argument Variables, an updated args\n    list, and an updated kwargs dict that have both had Variables replaced with\n    VariablePlaceholders and Module instances replaced with InstancePlaceholders\n    that are compatible with jax functions.\n  \"\"\"\n  scopes: list[Scope] = []\n  refs = {}\n\n  # Gather scopes associated with Variables and Module instances passed as\n  # positional and keyword arguments.\n  @functools.partial(_memoize_by_id, refs=refs)\n  def get_arg_scope(x):\n    nonlocal scopes\n    if isinstance(x, Variable) and isinstance(x.scope, Scope):\n      scopes.append(x.scope)\n      return VariablePlaceholder(x.collection, x.name, x.unbox, x._id)\n    elif isinstance(x, Module) and isinstance(x.scope, Scope):\n      x._try_setup(shallow=True)\n      scopes.append(x.scope)\n      attrs = {\n        f.name: getattr(x, f.name)\n        for f in dataclasses.fields(x)\n        if f.name != 'parent' and f.init\n      }\n      attrs = jax.tree_util.tree_map(get_arg_scope, attrs)\n      return InstancePlaceholder(x.__class__, attrs, x._id)\n    return x\n\n  new_args, new_kwargs = jax.tree_util.tree_map(get_arg_scope, (args, kwargs))\n\n  # Gather scopes in Variables and Submodules passed as Module attributes.\n  @functools.partial(_memoize_by_id, refs=refs)\n  def get_scopes(module):\n    nonlocal scopes\n    module._try_setup(shallow=True)\n\n    def get_scopes_inner(x):\n      nonlocal scopes\n      if isinstance(x, Module) and isinstance(x.scope, Scope):\n        get_scopes(x)\n      elif isinstance(x, Variable) and isinstance(x.scope, Scope):\n        scopes.append(x.scope)\n\n    attrs = {\n      f.name: getattr(module, f.name)\n      for f in dataclasses.fields(module)\n      if f.name != 'parent' and f.init\n    }\n    for leaf in jax.tree_util.tree_leaves(attrs):\n      get_scopes_inner(leaf)\n    scopes.append(module.scope)\n\n  get_scopes(module)\n  return scopes, new_args, new_kwargs\n\n\ndef set_module_scopes(module, args, kwargs, scopes):\n  \"\"\"Set all scopes on module, including those on Modules in dataclass fields.\n\n  To properly functionalize a Module we must also \"rehydrate\" it with Scopes\n  from `get_module_scopes`.  We need to set scopes not just on the Module but\n  also on any Module living inside dataclass attributes or even pytrees in its\n  dataclass attributes.  We additionally handle restoring Variables and Module\n  instances from their placeholders in the method positional and keyword\n  arguments.  The order of traversal through this method is the same as in\n  `get_module_scopes`, guaranteeing the correct Scopes are applied to each\n  Module.\n\n  Args:\n    module: a flax Module.\n    args: an *args list possibly containing VariablePlaceholder or\n      InstancePlaceholder members.\n    kwargs: a **kwargs dict possibly containing VariablePlaceholder or\n      InstancePlaceholder members.\n    scopes: a list of Scopes corresponding to this Module and its arguments that\n      was created by the `get_module_scopes` function.\n\n  Returns:\n    A copy of the module with it and its attributes bound to the scopes passed\n    to this function, an updated args list, and an updated kwargs dict with\n    updated Variable and Module instance references.\n  \"\"\"\n  idx = 0\n  refs = {}\n\n  # Set scopes associated with Variables and Module instances passed as\n  # positional and keyword arguments.\n  @functools.partial(_memoize_by_id, refs=refs)\n  def set_arg_scope(x):\n    nonlocal idx\n    if isinstance(x, VariablePlaceholder):\n      new_x = Variable(\n        scope=scopes[idx], collection=x.collection, name=x.name, unbox=x.unbox\n      )\n      idx += 1\n      return new_x\n    elif isinstance(x, InstancePlaceholder):\n      instance_scope = scopes[idx]\n      idx += 1\n      instance_attrs = jax.tree_util.tree_map(set_arg_scope, x.attrs)\n      return x.cls(parent=instance_scope, **instance_attrs)\n    return x\n\n  def is_placeholder(x):\n    return isinstance(x, (VariablePlaceholder, InstancePlaceholder))\n\n  new_args, new_kwargs = jax.tree_util.tree_map(\n    set_arg_scope, (args, kwargs), is_leaf=is_placeholder\n  )\n\n  # set scopes in Variables and Submodules passed as Module attributes\n  @functools.partial(_memoize_by_id, refs=refs)\n  def set_scopes(module):\n    nonlocal idx\n\n    def set_scopes_inner(x):\n      nonlocal idx\n      if isinstance(x, Module) and isinstance(x.scope, Scope):\n        return set_scopes(x)\n      elif isinstance(x, Variable) and isinstance(x.scope, Scope):\n        new_x = Variable(\n          scope=scopes[idx],\n          collection=x.collection,\n          name=x.name,\n          unbox=x.unbox,\n        )\n        idx += 1\n        return new_x\n      else:\n        return x\n\n    attrs = {\n      f.name: getattr(module, f.name)\n      for f in dataclasses.fields(module)\n      if f.name != 'parent' and f.init\n    }\n    new_attrs = jax.tree_util.tree_map(set_scopes_inner, attrs)\n    new_module = module.clone(parent=scopes[idx], **new_attrs)\n    idx += 1\n    return new_module\n\n  new_module = set_scopes(module)\n  assert len(scopes) == idx, f'scope list mismatch {len(scopes)} != {idx}'\n  return new_module, new_args, new_kwargs\n\n\ndef _test_transformed_return_values(tree, method_name):\n  \"\"\"Tests whether the return value contains any Modules or Variables.\"\"\"\n  impure = any(\n    map(\n      lambda x: isinstance(x, (Module, Variable)),\n      jax.tree_util.tree_leaves(tree),\n    )\n  )\n  if impure:\n    raise errors.TransformedMethodReturnValueError(method_name)\n\n\n# Class lifting\n# -----------------------------------------------------------------------------\ndef module_class_lift_transform(\n  transform, module_class, *trafo_args, methods=None, **trafo_kwargs\n):\n  \"\"\"Module class lift transform.\"\"\"\n  # TODO(marcvanzee): Improve docstrings (#1977).\n  # TODO(levskaya): find nicer argument convention for multi-method case?\n\n  # Prepare per-method transform args, kwargs.\n  if methods is None:\n    # Default case, just transform __call__\n    class_trafo_args = {'__call__': (trafo_args, trafo_kwargs)}\n  elif isinstance(methods, (list, tuple)):\n    # Transform every method in methods with given args, kwargs.\n    class_trafo_args = {m: (trafo_args, trafo_kwargs) for m in methods}\n  elif isinstance(methods, dict):\n    # Pass different trafo args per each method.\n    class_trafo_args = {k: ((), v) for k, v in methods.items()}\n  else:\n    raise ValueError(\n      'transform methods argument must be None, tuple, list, or dict.'\n    )\n\n  # Handle partially initialized module class constructors.\n  if isinstance(module_class, functools.partial) and issubclass(\n    module_class.func, Module\n  ):\n    partial_object = module_class\n    module_class = module_class.func\n  else:\n    partial_object = None\n\n  def create_trans_fn(fn_name, fn_trafo_args):\n    # get existing unbound method from class\n    fn = getattr(module_class, fn_name)\n    trafo_args, trafo_kwargs = fn_trafo_args\n\n    # we need to create a scope-function from our class for the given method\n    @functools.wraps(fn)\n    def wrapped_fn(self, *args, **kwargs):\n      state = self._state.export()\n\n      # make a scope-function to transform\n      def core_fn(scopes, *args, **kwargs):\n        # make a clone of self using its arguments\n        attrs = {\n          f.name: getattr(self, f.name)\n          for f in dataclasses.fields(self)\n          if f.name != 'parent' and f.init\n        }\n        # we reference module_class, not self.__class__ to avoid infinite loop\n        cloned = module_class(parent=None, **attrs)\n        cloned, args, kwargs = set_module_scopes(cloned, args, kwargs, scopes)\n        object.__setattr__(cloned, '_state', state.export())\n        res = fn(cloned, *args, **kwargs)\n        self._state.reimport(cloned._state)\n        _test_transformed_return_values(res, fn_name)\n        return res\n\n      # here we apply the given lifting transform to the scope-ingesting fn\n      trafo_fn = transform(core_fn, *trafo_args, **trafo_kwargs)\n      module_scopes, args, kwargs = get_module_scopes(self, args, kwargs)\n      ret = trafo_fn(module_scopes, *args, **kwargs)\n      return ret\n\n    return wrapped_fn\n\n  transformed_fns = {\n    fn_name: create_trans_fn(fn_name, fn_trafo_args)\n    for fn_name, fn_trafo_args in class_trafo_args.items()\n  }\n  # construct new dynamic class w. transformed methods\n  transformed_cls = type(\n    transform.__name__.capitalize() + module_class.__name__,\n    (module_class,),\n    transformed_fns,\n  )\n  # Handle partially initialized module class constructors.\n  if partial_object is not None:\n    transformed_cls = functools.partial(\n      transformed_cls, *partial_object.args, **partial_object.keywords\n    )\n  return transformed_cls\n\n\n# Function lifting as decorator on methods __inside__ class definition.\n# -----------------------------------------------------------------------------\ndef decorator_lift_transform(\n  transform, class_fn, *trafo_args, multi_scope=True, **trafo_kwargs\n):\n  \"\"\"Decorator for lifted transform.\"\"\"\n  # TODO(marcvanzee): Improve docstrings (#1977).\n  # Due to the ordering of method decorators, we must wrap the class_fn\n  # with the module state management wrapper first to maintain Module state\n  # correctly.\n  if isinstance(class_fn, tuple):\n    class_fns = class_fn\n  else:\n    class_fns = (class_fn,)\n  prewrapped_fns = [wrap_method_once(class_fn) for class_fn in class_fns]\n\n  @functools.wraps(prewrapped_fns[0])\n  def wrapped_fn(self: Module, *args, **kwargs):\n    state = self._state.export()\n\n    # make a scope-function to transform\n    def core_fn(prewrapped_fn, class_fn, scopes, *args, **kwargs):\n      if not multi_scope:\n        scopes = [scopes]\n      cloned, args, kwargs = set_module_scopes(self, args, kwargs, scopes)\n      object.__setattr__(cloned, '_state', state.export())\n      res = prewrapped_fn(cloned, *args, **kwargs)\n      self._state.reimport(cloned._state)\n      _test_transformed_return_values(res, getattr(class_fn, '__name__', None))\n      return res\n\n    core_fns = [\n      functools.partial(core_fn, prewrapped_fn, class_fn)\n      for prewrapped_fn, class_fn in zip(prewrapped_fns, class_fns)\n    ]\n    # here we apply the given lifting transform to the scope-ingesting fn\n    trafo_fn = transform(*core_fns, *trafo_args, **trafo_kwargs)\n    module_scopes, args, kwargs = get_module_scopes(self, args, kwargs)\n    if not multi_scope:\n      if len(module_scopes) != 1:\n        # TODO(levskaya): transforms like jvp & vjp have args that follow the\n        # pytree structure of scopes. The user doesn't explicitly control shared\n        # modules passed as arguments to methods or as attributes to Module\n        # constructors. Therefore, there is no obvious API for specifying\n        # arguments per lifted Module.\n        raise NotImplementedError(\n          'This transform does not yet support'\n          ' Modules that include other Modules passed as arguments.'\n        )\n      module_scopes = module_scopes[0]\n    return trafo_fn(module_scopes, *args, **kwargs)\n\n  return wrapped_fn\n\n\n@dataclasses.dataclass(frozen=True)\nclass _HashableProxy:\n  \"\"\"A hashable proxy object that is use to define a hash for Modules.\n\n  The hash produced by _HashableProxy is useful for nn.jit to decide if a\n  function should be retraced or not\n  \"\"\"\n\n  module_ref: weakref.ref\n  hash_key: int\n\n  @classmethod\n  def from_module(cls, module: Module) -> '_HashableProxy':\n    fingerprint = _module_fingerprint(module)\n    hash_key = hash(fingerprint)\n    return cls(weakref.ref(module), hash_key)\n\n  def __hash__(self):\n    return self.hash_key\n\n  def __eq__(self, other):\n    return isinstance(other, _HashableProxy) and self.hash_key == other.hash_key\n\n  @property\n  def module(self):\n    return self.module_ref()\n\n\ndef _module_fingerprint(module: Module) -> tuple[type[Any], Any]:\n  return _fingerprint_recursive(module, (), {})\n\n\ndef _fingerprint_recursive(\n  obj: Any, path: tuple[str, ...], seen_modules: dict[FlaxId, int]\n) -> Any:\n  \"\"\"Creates a hashable representation for a Module by traversing its structure recursively.\"\"\"\n\n  def _get_fingerprint(name: str, value: Any) -> tuple[str, Any]:\n    return name, _fingerprint_recursive(value, (*path, name), seen_modules)\n\n  if isinstance(obj, str):\n    return obj\n  elif hasattr(obj, '__fn_or_cls__'):  # support PaxConfig objects\n    return _fingerprint_recursive(obj.__fn_or_cls__, path, seen_modules)\n  elif isinstance(obj, Module):\n    fingerprint: Any\n    if obj._id in seen_modules:\n      # if we have already seen the module we just use the index\n      # as its static component\n      fingerprint = seen_modules[obj._id]\n      return type(obj), fingerprint\n    else:\n      # if its a new module we add it to the cache and give it\n      # a new index\n      seen_modules[obj._id] = len(seen_modules)\n      # TODO(cgarciae): define a way for the user of nn.jit to define\n      # what fields it wants to ignore per Module instance.\n      fingerprints = []\n      for field in dataclasses.fields(obj):\n        if not hasattr(obj, field.name):\n          continue\n        if field.name not in ('parent', 'name'):\n          value = getattr(obj, field.name)\n          fingerprints.append(_get_fingerprint(field.name, value))\n      # add state fingerprint\n      state_fingerprint = (\n        _get_fingerprint('in_compact_method', obj._state.in_compact_method),\n        _get_fingerprint('in_setup', obj._state.in_setup),\n        _get_fingerprint('setup_called', obj._state.setup_called),\n        _get_fingerprint('is_initialized', obj._state.is_initialized),\n        _get_fingerprint('autoname_cursor', obj._state.autoname_cursor),\n      )\n      fingerprints.append(('_state', state_fingerprint))\n      # add scope fingerprint\n      scope = obj.scope\n      if scope is not None:\n        static_scope = (\n          _get_fingerprint('mutable', scope.mutable),\n          _get_fingerprint('flags', scope.flags),\n          _get_fingerprint('rng_counts', scope.rng_counters),\n          _get_fingerprint('reservations', scope.reservations),\n        )\n        _check_field_is_hashable((*path, 'scope'), static_scope)\n        fingerprints.append(('scope', static_scope))\n      fingerprint = tuple(fingerprints)\n      return type(obj), fingerprint\n  elif dataclasses.is_dataclass(obj):\n    fingerprints = []\n    for field in dataclasses.fields(obj):\n      if not hasattr(obj, field.name):\n        continue\n      value = getattr(obj, field.name)\n      value_fingerprint = _get_fingerprint(field.name, value)\n      fingerprints.append((field.name, value_fingerprint))\n    return type(obj), tuple(fingerprints)\n  elif isinstance(obj, core.DenyList):\n    return type(obj), _get_fingerprint('deny', obj.deny)\n  elif isinstance(obj, dict):\n    fingerprint = tuple((k, _get_fingerprint(k, v)) for k, v in obj.items())\n    return fingerprint\n  elif serialization.is_serializable(obj):\n    state = serialization.to_state_dict(obj)\n    fingerprint = _fingerprint_recursive(state, path, seen_modules)\n    return type(obj), fingerprint\n  elif isinstance(obj, Mapping):\n    return tuple((k, _get_fingerprint(k, v)) for k, v in obj.items())\n  elif isinstance(obj, Iterable):\n    return tuple(_get_fingerprint(str(i), v) for i, v in enumerate(obj))\n  else:\n    _check_field_is_hashable(path, obj)\n    return obj\n\n\ndef _check_field_is_hashable(path: tuple[str, ...], x: Any):\n  \"\"\"Checks if a field is hashable.\"\"\"\n  try:\n    hash(x)\n  except Exception as e:\n    path_name = '/'.join(path)\n    raise ValueError(f\"Value at '{path_name}' is not hashable: {e}\") from e\n\n\ndef decorator_lift_transform_cached(transform, class_fn, **trafo_kwargs):\n  \"\"\"Decorator for lifted transform.\n\n  Similar to `decorator_lift_transform` but specialized for `jit`, it reuses the\n  previous transform when available to avoid retracing.\n  \"\"\"\n  # TODO(marcvanzee): Improve docstrings (#1977).\n  # Due to the ordering of method decorators, we must wrap the class_fn\n  # with the module state management wrapper first to maintain Module state\n  # correctly.\n  multi_scope = True\n\n  if isinstance(class_fn, tuple):\n    class_fns = class_fn\n  else:\n    class_fns = (class_fn,)\n  prewrapped_fns = [wrap_method_once(class_fn) for class_fn in class_fns]\n  trafo_fn = None\n\n  @functools.wraps(prewrapped_fns[0])\n  def wrapped_fn(self: Module, *args, **kwargs):\n    nonlocal trafo_fn\n    state = self._state.export()\n\n    # increment rng counters for all rngs in scope\n    with fork_rngs(self):\n      # make a scope-function to transform\n      def core_fn(\n          prewrapped_fn,\n          class_fn,\n          scopes,\n          module_hash,\n          *args,\n          **kwargs,\n      ):\n        # self = hash_key.obj\n        self: Module = module_hash.module\n        if not multi_scope:\n          scopes = [scopes]\n        cloned, args, kwargs = set_module_scopes(self, args, kwargs, scopes)\n        object.__setattr__(cloned, '_state', state.export())\n        res = prewrapped_fn(cloned, *args, **kwargs)\n        self._state.reimport(cloned._state)\n        _test_transformed_return_values(\n            res, getattr(class_fn, '__name__', None)\n        )\n        return res\n\n      core_fns = [\n          functools.wraps(class_fn)(\n              functools.partial(core_fn, prewrapped_fn, class_fn)\n          )\n          for prewrapped_fn, class_fn in zip(prewrapped_fns, class_fns)\n      ]\n\n      # here we apply the given lifting transform to the scope-ingesting fn\n      if trafo_fn is None:\n        trafo_fn = transform(*core_fns, **trafo_kwargs)\n\n      module_scopes, args, kwargs = get_module_scopes(self, args, kwargs)\n\n      if not multi_scope:\n        if len(module_scopes) != 1:\n          # TODO(levskaya): transforms like jvp & vjp have args that follow the\n          # pytree structure of scopes. The user doesn't explicitly control shared\n          # modules passed as arguments to methods or as attributes to Module\n          # constructors. Therefore, there is no obvious API for specifying\n          # arguments per lifted Module.\n          raise NotImplementedError(\n              'This transform does not yet support'\n              ' Modules that include other Modules passed as arguments.'\n          )\n        module_scopes = module_scopes[0]\n\n      # get a hashable proxy object for the Module\n      hash_key = _HashableProxy.from_module(self)\n\n      return trafo_fn(module_scopes, hash_key, *args, **kwargs)\n\n  return wrapped_fn\n\n\n@contextlib.contextmanager\ndef fork_rngs(module: Module):\n  \"\"\"Context manager to fork rngs in a module.\"\"\"\n  if module.scope is None:\n    yield\n    return\n\n  current_rngs = module.scope.rngs.copy()\n  module.scope.rngs = {\n      name: LazyRng.create(module.make_rng(name)) for name in current_rngs\n  }\n\n  try:\n    yield\n  finally:\n    module.scope.rngs = current_rngs\n\n\ndef module_class_lift_transform_cached(\n    transform, module_class, methods=None, **trafo_kwargs\n):\n  \"\"\"Module class lift transform.\"\"\"\n  # TODO(marcvanzee): Improve docstrings (#1977).\n  # TODO(levskaya): find nicer argument convention for multi-method case?\n  trafo_args = ()\n\n  # Prepare per-method transform args, kwargs.\n  if methods is None:\n    # Default case, just transform __call__\n    class_trafo_args = {'__call__': (trafo_args, trafo_kwargs)}\n  elif isinstance(methods, (list, tuple)):\n    # Transform every method in methods with given args, kwargs.\n    class_trafo_args = {m: (trafo_args, trafo_kwargs) for m in methods}\n  elif isinstance(methods, dict):\n    # Pass different trafo args per each method.\n    class_trafo_args = {k: ((), v) for k, v in methods.items()}\n  else:\n    raise ValueError(\n      'transform methods argument must be None, tuple, list, or dict.'\n    )\n\n  # Handle partially initialized module class constructors.\n  if isinstance(module_class, functools.partial) and issubclass(\n    module_class.func, Module\n  ):\n    partial_object = module_class\n    module_class = module_class.func\n  else:\n    partial_object = None\n\n  def create_trans_fn(fn_name, fn_trafo_args):\n    # get existing unbound method from class\n    fn = getattr(module_class, fn_name)\n    trafo_args, trafo_kwargs = fn_trafo_args\n    trafo_fn = None\n\n    # we need to create a scope-function from our class for the given method\n    @functools.wraps(fn)\n    def wrapped_fn(self: Module, *args, **kwargs):\n      assert self.scope is not None\n      nonlocal trafo_fn\n      state = self._state.export()\n\n      # increment rng counters for all rngs in scope\n      with fork_rngs(self):\n        # make a scope-function to transform\n        def core_fn(scopes, module_hash, *args, **kwargs):\n          self: Module = module_hash.module\n          # make a clone of self using its arguments\n          attrs = {\n              f.name: getattr(self, f.name)\n              for f in dataclasses.fields(self)\n              if f.name != 'parent' and f.init\n          }\n          # we reference module_class, not self.__class__ to avoid infinite loop\n          cloned = module_class(parent=None, **attrs)\n          cloned, args, kwargs = set_module_scopes(cloned, args, kwargs, scopes)\n          object.__setattr__(cloned, '_state', state.export())\n          res = fn(cloned, *args, **kwargs)\n          self._state.reimport(cloned._state)\n          _test_transformed_return_values(res, fn_name)\n          return res\n\n        # here we apply the given lifting transform to the scope-ingesting fn\n        trafo_fn = trafo_fn or transform(core_fn, *trafo_args, **trafo_kwargs)\n        module_scopes, args, kwargs = get_module_scopes(self, args, kwargs)\n\n        # get a hash for the Module by using its repr as a proxy\n        hash_key = _HashableProxy.from_module(self)\n\n        ret = trafo_fn(module_scopes, hash_key, *args, **kwargs)\n        return ret\n\n    return wrapped_fn\n\n  transformed_fns = {\n    fn_name: create_trans_fn(fn_name, fn_trafo_args)\n    for fn_name, fn_trafo_args in class_trafo_args.items()\n  }\n  # construct new dynamic class w. transformed methods\n  transformed_cls = type(\n    transform.__name__.capitalize() + module_class.__name__,\n    (module_class,),\n    transformed_fns,\n  )\n  # Handle partially initialized module class constructors.\n  if partial_object is not None:\n    transformed_cls = functools.partial(\n      transformed_cls, *partial_object.args, **partial_object.keywords\n    )\n  return transformed_cls\n\n\n# Utility to wrap a class or to use as decorator in def of class method.\n# -----------------------------------------------------------------------------\n\nTransformTarget = Union[type[Module], Callable[..., Any]]\nTarget = TypeVar('Target', bound=TransformTarget)\n\n\ndef _is_module_class(target: TransformTarget) -> bool:\n  return (\n    inspect.isclass(target)\n    and issubclass(target, Module)\n    or (isinstance(target, functools.partial))\n    and _is_module_class(target.func)\n  )\n\n\ndef lift_transform(\n  transform, target, *trafo_args, methods=None, **trafo_kwargs\n):\n  \"\"\"Applies to class or as a decorator on class fns.\"\"\"\n  # TODO(marcvanzee): Improve docstrings (#1977).\n  if _is_module_class(target):\n    return module_class_lift_transform(\n      transform, target, *trafo_args, methods=methods, **trafo_kwargs\n    )\n  # we presume this is being used as a function decorator in class definition\n  elif callable(target) and not isinstance(target, Module):\n    return decorator_lift_transform(\n      transform, target, *trafo_args, **trafo_kwargs\n    )\n  else:\n    raise errors.TransformTargetError(target)\n\n\ndef lift_transform_cached(\n    transform, target, *trafo_args, methods=None, **trafo_kwargs\n):\n  \"\"\"Applies to class or as a decorator on class fns.\"\"\"\n  # TODO(marcvanzee): Improve docstrings (#1977).\n  if _is_module_class(target):\n    return module_class_lift_transform_cached(\n        transform, target, *trafo_args, methods=methods, **trafo_kwargs\n    )\n  # we presume this is being used as a function decorator in class definition\n  elif callable(target) and not isinstance(target, Module):\n    return decorator_lift_transform_cached(\n        transform, target, *trafo_args, **trafo_kwargs\n    )\n  else:\n    raise errors.TransformTargetError(target)\n\n\ndef lift_direct_transform(\n  transform: Callable[..., Any],\n  targets: tuple[Callable[..., Any], ...],\n  mdl: Module,\n  *args,\n  multi_scope=True,\n  **kwargs,\n):\n  \"\"\"Lift direct transform.\"\"\"\n  # TODO(marcvanzee): Improve docstrings (#1977).\n  for target in targets:\n    if _is_module_class(target):\n      raise ValueError(\n        f'The {transform.__name__} transform can only be applied on a Module'\n        ' method. That is function that takes a Module instance as its first'\n        ' arg.'\n      )\n    elif not callable(target):\n      raise ValueError('transform target must be callable')\n  # normalize self.foo bound methods to class.foo unbound methods.\n  targets = tuple(_get_unbound_fn(target) for target in targets)\n  aug_transform = lambda *fns: functools.partial(transform, *fns)\n  return decorator_lift_transform(\n    aug_transform, targets, multi_scope=multi_scope\n  )(mdl, *args, **kwargs)\n\n\ndef vmap(\n  target: Target,\n  variable_axes: Mapping[CollectionFilter, InOutAxis] = FrozenDict(),\n  split_rngs: Mapping[PRNGSequenceFilter, bool] = FrozenDict(),\n  in_axes=0,\n  out_axes=0,\n  axis_size: int | None = None,\n  axis_name: str | None = None,\n  spmd_axis_name: str | None = None,\n  metadata_params: Mapping[Any, Any] = {},\n  methods=None,\n) -> Target:\n  \"\"\"A lifted version of ``jax.vmap``.\n\n  See ``jax.vmap`` for the unlifted batch transform in Jax.\n\n  ``vmap`` can be used to add a batch axis to a ``Module``.\n  For example we could create a version of ``Dense`` with\n  a batch axis that does not share parameters::\n\n    >>> import flax.linen as nn\n    >>> BatchDense = nn.vmap(\n    ...     nn.Dense,\n    ...     in_axes=0, out_axes=0,\n    ...     variable_axes={'params': 0},\n    ...     split_rngs={'params': True})\n\n  By using ``variable_axes={'params': 0}``, we indicate that the\n  parameters themselves are mapped over and therefore not shared along\n  the mapped axis. Consequently, we also split the 'params' RNG,\n  otherwise the parameters would be initialized identically along\n  the mapped axis.\n\n  Similarly, ``vmap`` could be used to add a batch axis with parameter\n  sharing::\n\n    >>> import flax.linen as nn\n    >>> BatchDense = nn.vmap(\n    ...     nn.Dense,\n    ...     in_axes=0, out_axes=0,\n    ...     variable_axes={'params': None},\n    ...     split_rngs={'params': False})\n\n  Here we use ``variable_axes={'params': None}`` to indicate the parameter\n  variables are shared along the mapped axis. Consequently, the 'params'\n  RNG must also be shared.\n\n  Args:\n    target: a ``Module`` or a function taking a ``Module`` as its first\n      argument.\n    variable_axes: the variable collections that are lifted into the batching\n      transformation. Use ``None`` to indicate a broadcasted collection or an\n      integer to map over an axis. For example, passing in\n      ``variable_axes={'params': None}`` will indicate that the\n      parameter variables should be shared along the mapped axis.\n    split_rngs: Split PRNG sequences will be different for each index of the\n      batch dimension. Unsplit PRNGs will be broadcasted.\n    in_axes: Specifies the mapping of the input arguments (see ``jax.vmap``).\n    out_axes: Specifies the mapping of the return value (see ``jax.vmap``).\n    axis_size: Specifies the size of the batch axis. This only needs to be\n      specified if it cannot be derived from the input arguments.\n    axis_name: Specifies a name for the batch axis. Can be used together with\n      parallel reduction primitives (e.g. ``jax.lax.pmean``, ``jax.lax.ppermute``,\n      etc.). Note, this is only used for pmap and shard map. For SPMD jit, you\n      do not need to manually synchronize. Just make sure that the axes are\n      correctly annotated and XLA:SPMD will insert the necessary collectives.\n    methods: If ``target`` is a ``Module``, the methods of ``Module`` to vmap over.\n    spmd_axis_name: Axis name added to any pjit sharding constraints appearing\n      in ``fn``. See also\n      https://github.com/google/flax/blob/main/flax/linen/partitioning.py.\n    metadata_params: arguments dict passed to AxisMetadata instances in the\n      variable tree.\n\n  Returns:\n    A batched/vectorized version of ``target``, with the same arguments but with\n    extra axes at positions indicated by ``in_axes``, and the same return value,\n    but with extra axes at positions indicated by ``out_axes``.\n  \"\"\"\n  return lift_transform(\n    lift.vmap,\n    target,\n    variable_axes,\n    split_rngs,\n    methods=methods,\n    in_axes=in_axes,\n    out_axes=out_axes,\n    axis_size=axis_size,\n    axis_name=axis_name,\n    metadata_params=metadata_params,\n    spmd_axis_name=spmd_axis_name,\n  )\n\n\ndef jit(\n  target: Target,\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n  static_argnums: int | Iterable[int] = (),\n  static_argnames: str | Iterable[str] = (),\n  donate_argnums: int | Iterable[int] = (),\n  device=None,\n  backend: str | None = None,\n  methods=None,\n) -> Target:\n  \"\"\"Lifted version of ``jax.jit``.\n\n  Args:\n    target: a ``Module`` or a function taking a ``Module`` as its first\n      argument.\n    variables: The variable collections that are lifted. By default all\n      collections are lifted.\n    rngs: The PRNG sequences that are lifted. By default all PRNG sequences are\n      lifted.\n    static_argnums: An int or collection of ints specifying which positional\n      arguments to treat as static (compile-time constant). Operations that only\n      depend on static arguments will be constant-folded in Python (during\n      tracing), and so the corresponding argument values can be any Python\n      object. Static arguments should be hashable, meaning both ``__hash__`` and\n      ``__eq__`` are implemented, and immutable. Calling the jitted function\n      with different values for these constants will trigger recompilation. If\n      the jitted function is called with fewer positional arguments than\n      indicated by ``static_argnums`` then an error is raised. Arguments that\n      are not arrays or containers thereof must be marked as static. Defaults to\n      ().\n    static_argnames: An optional string or collection of strings specifying\n      which named arguments to treat as static (compile-time constant). See the\n      comment on ``static_argnums`` for details. If not provided but\n      ``static_argnums`` is set, the default is based on calling\n      ``inspect.signature(fun)`` to find corresponding named arguments.\n    donate_argnums: Specify which arguments are \"donated\" to the computation. It\n      is safe to donate arguments if you no longer need them once the\n      computation has finished. In some cases XLA can make use of donated\n      buffers to reduce the amount of memory needed to perform a computation,\n      for example recycling one of your input buffers to store a result. You\n      should not reuse buffers that you donate to a computation, JAX will raise\n      an error if you try to.\n    device: This is an experimental feature and the API is likely to change.\n      Optional, the Device the jitted function will run on. (Available devices\n      can be retrieved via :py:func:`jax.devices`.) The default is inherited\n      from XLA's DeviceAssignment logic and is usually to use\n      ``jax.devices()[0]``.\n    backend: a string representing the XLA backend: ``'cpu'``, ``'gpu'``, or\n      ``'tpu'``.\n    methods: If ``target`` is a ``Module``, the methods of ``Module`` to jit.\n\n  Returns:\n    A wrapped version of target, set up for just-in-time compilation.\n  \"\"\"\n  # TODO(marcvanzee): Improve docstrings (#1977).\n  return lift_transform_cached(\n      lift.jit,\n      target,\n      variables=variables,\n      rngs=rngs,\n      static_argnums=static_argnums,\n      static_argnames=static_argnames,\n      donate_argnums=donate_argnums,\n      device=device,\n      backend=backend,\n      methods=methods,\n  )\n\n\ndef checkpoint(\n  target: Target,\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n  concrete: bool = False,\n  prevent_cse: bool = True,\n  static_argnums: int | tuple[int, ...] = (),\n  policy: Callable[..., bool] | None = None,\n  methods=None,\n) -> Target:\n  \"\"\"Lifted version of ``jax.checkpoint``.\n\n  Checkpointing is a technique for reducing memory usage by recomputing\n  activations during backpropagation. When training large models, it can be\n  helpful to checkpoint parts of the model to trade off memory usage for\n  additional computation.\n\n  Example::\n\n    >>> import jax\n    >>> import jax.numpy as jnp\n    >>> import flax.linen as nn\n    ...\n    >>> class CheckpointedMLP(nn.Module):\n    ...   @nn.checkpoint\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     x = nn.Dense(128)(x)\n    ...     x = nn.relu(x)\n    ...     x = nn.Dense(1)(x)\n    ...     return x\n    ...\n    >>> model = CheckpointedMLP()\n    >>> variables = model.init(jax.random.key(0), jnp.ones((1, 16)))\n\n  This function is aliased to ``remat`` just like ``jax.remat``.\n\n  Args:\n    target: a ``Module`` or a function taking a ``Module``\n      as its first argument. intermediate computations will be\n      re-computed when computing gradients for the target.\n    variables: The variable collections that are lifted. By default all\n      collections are lifted.\n    rngs: The PRNG sequences that are lifted. By default all PRNG sequences\n      are lifted.\n    concrete: Optional, boolean indicating whether ``fun`` may involve\n      value-dependent Python control flow (default ``False``). Support for such\n      control flow is optional, and disabled by default, because in some\n      edge-case compositions with :func:`jax.jit` it can lead to some extra\n      computation.\n    prevent_cse: Optional, boolean indicating whether to prevent common\n      subexpression elimination (CSE) optimizations in the HLO generated from\n      differentiation. This CSE prevention has costs because it can foil other\n      optimizations, and because it can incur high overheads on some backends,\n      especially GPU. The default is True because otherwise, under a ``jit`` or\n      ``pmap``, CSE can defeat the purpose of this decorator. But in some\n      settings, like when used inside a ``scan``, this CSE prevention mechanism\n      is unnecessary, in which case ``prevent_cse`` should be set to False.\n    static_argnums: Optional, int or sequence of ints, indicates which argument\n      values on which to specialize for tracing and caching purposes. Specifying\n      arguments as static can avoid ConcretizationTypeErrors when tracing, but\n      at the cost of more retracing overheads.\n    policy: Experimental checkpoint policy, see ``jax.checkpoint``.\n    methods: An optional list of method names that will be lifted, if ``methods``\n      is None (default) only the ``__call__`` method will be lifted. If``target``\n      is a function, ``methods`` is ignored.\n\n  Returns:\n    A wrapped version of ``target``. When computing gradients intermediate\n    computations will be re-computed on the backward pass.\n  \"\"\"\n  # subtract 1 from each static_argnums because 'self' is not passed to the\n  # lifted function\n  static_argnums = jax.tree_util.tree_map(lambda x: x - 1, static_argnums)\n  return lift_transform(\n      lift.checkpoint,\n      target,\n      variables=variables,\n      rngs=rngs,\n      concrete=concrete,\n      static_argnums=static_argnums,\n      prevent_cse=prevent_cse,\n      policy=policy,\n      methods=methods,\n  )\n\n\nremat = checkpoint\n\n\ndef remat_scan(\n  target: Target,\n  lengths: Sequence[int] | None = (),\n  policy: Callable[..., bool] | None = None,\n  variable_broadcast: CollectionFilter = False,\n  variable_carry: CollectionFilter = False,\n  variable_axes: Mapping[CollectionFilter, InOutScanAxis] = FrozenDict(\n    {True: 0}\n  ),\n  split_rngs: Mapping[PRNGSequenceFilter, bool] = FrozenDict({True: True}),\n) -> Target:\n  \"\"\"Combines remat and scan for memory efficiency and constant time compilation.\n\n  ``remat_scan`` allows for constant compile times and sublinear\n  memory usage with respect to model depth. At a small constant\n  penalty. This is typically beneficial for very deep models.\n\n  Example::\n\n    >>> import flax.linen as nn\n\n    >>> class BigModel(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     DenseStack = nn.remat_scan(nn.Dense, lengths=(10, 10))\n    ...     # 100x dense with O(sqrt(N)) memory for gradient computation\n    ...     return DenseStack(8, name=\"dense_stack\")(x)\n\n  Args:\n    target: a ``Module`` or a function taking a ``Module`` as its first\n      argument.\n    lengths: number of loop iterations at the given level. The total number of\n      iterations ``n = prod(lengths)``. each loop is rematerialized. This way the\n      memory consumption is proportional to ``n^(1 / d)`` where ``d =\n      len(lengths)``. Minimal memory consumptions requires tuning the lengths\n      such that the same amount of memory is consumed at each level of the\n      nested loop.\n    policy: Experimental checkpoint policy, see ``jax.checkpoint``.\n    variable_broadcast: Specifies the broadcasted variable collections. A\n      broadcasted variable should not depend on any computation that cannot be\n      lifted out of the loop. This is typically used to define shared parameters\n      inside the fn.\n    variable_carry: Specifies the variable collections that are carried through\n      the loop. Mutations to these variables are carried to the next iteration\n      and will be preserved when the scan finishes.\n    variable_axes: the variable collections that are scanned over. Defaults to\n      ``{True: 0}``.\n    split_rngs: Split PRNG sequences will be different for each loop iterations.\n      If split is False the PRNGs will be the same across iterations. Defaults\n      to ``{True: True}``.\n\n  Returns:\n    A wrapped version of ``target`` that repeats itself prod(lengths) times.\n  \"\"\"\n  return lift_transform(\n    lift.remat_scan,\n    target,\n    lengths=lengths,\n    variable_broadcast=variable_broadcast,\n    variable_carry=variable_carry,\n    variable_axes=variable_axes,\n    split_rngs=split_rngs,\n    policy=policy,\n  )\n\n\ndef scan(\n  target: Target,\n  variable_axes: Mapping[CollectionFilter, InOutScanAxis] = FrozenDict(),\n  variable_broadcast: CollectionFilter = False,\n  variable_carry: CollectionFilter = False,\n  split_rngs: Mapping[PRNGSequenceFilter, bool] = FrozenDict(),\n  in_axes=0,\n  out_axes=0,\n  length: int | None = None,\n  reverse: bool = False,\n  unroll: int = 1,\n  data_transform: Callable[..., Any] | None = None,\n  metadata_params: Mapping[Any, Any] = {},\n  methods=None,\n  _split_transpose: bool = False,\n  check_constancy_invariants: bool = True,\n) -> Target:\n  \"\"\"A lifted version of ``jax.lax.scan``.\n\n  See ``jax.lax.scan`` for the unlifted scan in Jax.\n\n  To improve consistency with ``vmap``, this version of scan\n  uses ``in_axes`` and ``out_axes`` to determine which arguments\n  are scanned over and along which axis.\n\n  ``scan`` distinguishes between 3 different types of values inside the loop:\n\n  #. **scan**: a value that is iterated over in a loop. All scan values must\n     have the same size in the axis they are scanned over. Scanned outputs\n     will be stacked along the scan axis.\n\n  #. **carry**: A carried value is updated at each loop iteration. It must\n     have the same shape and dtype throughout the loop.\n\n  #. **broadcast**: a value that is closed over by the loop. When a variable\n     is broadcasted they are typically initialized inside the loop body but\n     independent of the loop variables.\n\n  The ``target`` should have the signature\n  ``(module, carry, *xs) -> (carry, ys)``, where ``xs`` and ``ys``\n  are the scan values that go in and out of the loop.\n\n  Example::\n\n    >>> import flax.linen as nn\n    >>> import jax\n    >>> import jax.numpy as jnp\n    ...\n    >>> class LSTM(nn.Module):\n    ...   features: int\n    ...\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     ScanLSTM = nn.scan(\n    ...       nn.LSTMCell, variable_broadcast=\"params\",\n    ...       split_rngs={\"params\": False}, in_axes=1, out_axes=1)\n    ...\n    ...     lstm = ScanLSTM(self.features)\n    ...     input_shape =  x[:, 0].shape\n    ...     carry = lstm.initialize_carry(jax.random.key(0), input_shape)\n    ...     carry, x = lstm(carry, x)\n    ...     return x\n    ...\n    >>> x = jnp.ones((4, 12, 7))\n    >>> module = LSTM(features=32)\n    >>> y, variables = module.init_with_output(jax.random.key(0), x)\n\n  Note that when providing a function to ``nn.scan``, the scanning happens over\n  all arguments starting from the third argument, as specified by ``in_axes``.\n  The previous example could also be written using the functional form as::\n\n    >>> class LSTM(nn.Module):\n    ...   features: int\n    ...\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...\n    ...     cell = nn.LSTMCell(self.features)\n    ...     def body_fn(cell, carry, x):\n    ...       carry, y = cell(carry, x)\n    ...       return carry, y\n    ...     scan = nn.scan(\n    ...       body_fn, variable_broadcast=\"params\",\n    ...       split_rngs={\"params\": False}, in_axes=1, out_axes=1)\n    ...\n    ...     input_shape =  x[:, 0].shape\n    ...     carry = cell.initialize_carry(\n    ...       jax.random.key(0), input_shape)\n    ...     carry, x = scan(cell, carry, x)\n    ...     return x\n    ...\n    >>> module = LSTM(features=32)\n    >>> variables = module.init(jax.random.key(0), jnp.ones((4, 12, 7)))\n\n  You can also use ``scan`` to reduce the compilation time of your JAX program\n  by merging multiple layers into a single scan loop, you can do this when\n  you have a sequence of identical layers that you want to apply iteratively\n  to an input. For example::\n\n    >>> class ResidualMLPBlock(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x, _):\n    ...     h = nn.Dense(features=2)(x)\n    ...     h = nn.relu(h)\n    ...     return x + h, None\n    ...\n    >>> class ResidualMLP(nn.Module):\n    ...   n_layers: int = 4\n    ...\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     ScanMLP = nn.scan(\n    ...       ResidualMLPBlock, variable_axes={'params': 0},\n    ...       variable_broadcast=False, split_rngs={'params': True},\n    ...       length=self.n_layers)\n    ...     x, _ = ScanMLP()(x, None)\n    ...     return x\n    ...\n    >>> model = ResidualMLP(n_layers=4)\n    >>> variables = model.init(jax.random.key(42), jnp.ones((1, 2)))\n\n  To reduce both compilation and memory usage, you can use :func:`remat_scan`\n  which will in addition checkpoint each layer in the scan loop.\n\n  Args:\n    target: a ``Module`` or a function taking a ``Module`` as its first\n      argument.\n    variable_axes: the variable collections that are scanned over.\n    variable_broadcast: Specifies the broadcasted variable collections. A\n      broadcasted variable should not depend on any computation that cannot be\n      lifted out of the loop. This is typically used to define shared parameters\n      inside the fn.\n    variable_carry: Specifies the variable collections that are carried through\n      the loop. Mutations to these variables are carried to the next iteration\n      and will be preserved when the scan finishes.\n    split_rngs: Split PRNG sequences will be different for each loop iterations.\n      If split is False the PRNGs will be the same across iterations.\n    in_axes: Specifies the axis to scan over for the arguments. Should be a\n      prefix tree of the arguments. Use ``flax.core.broadcast`` to feed an entire\n      input to each iteration of the scan body.\n    out_axes: Specifies the axis to scan over for the return value. Should be a\n      prefix tree of the return value.\n    length: Specifies the number of loop iterations. This only needs to be\n      specified if it cannot be derived from the scan arguments.\n    reverse: If true, scan from end to start in reverse order.\n    unroll: how many scan iterations to unroll within a single iteration of a\n      loop (default: 1).\n    data_transform: optional function to transform raw functional-core variable\n      and rng groups inside lifted scan body_fn, intended for inline SPMD\n      annotations.\n    metadata_params: arguments dict passed to AxisMetadata instances in the\n      variable tree.\n    methods: If ``target`` is a ``Module``, the methods of ``Module`` to scan over.\n    _split_transpose: An experimental feature to split the transpose of a scan\n       into a scan and a map, backed by an experimental Jax lax.scan() feature.\n    check_constancy_invariants: If true, the scan will verify that the\n      broadcast constants are true loop invariants, and further supports\n      broadcast function (non-carry) outputs.  This requires an extra jax\n      tracing step however, so setting to false can reduce trace time on larger\n      models.\n\n  Returns:\n    The scan function with the signature ``(module, carry, *xs) -> (carry,\n    ys)``, where ``xs`` and ``ys`` are the scan values that go in and out of\n    the loop.\n  \"\"\"\n  return lift_transform(\n    lift.scan,\n    target,\n    variable_axes=variable_axes,\n    variable_broadcast=variable_broadcast,\n    variable_carry=variable_carry,\n    split_rngs=split_rngs,\n    in_axes=in_axes,\n    out_axes=out_axes,\n    length=length,\n    reverse=reverse,\n    unroll=unroll,\n    _split_transpose=_split_transpose,\n    data_transform=data_transform,\n    metadata_params=metadata_params,\n    methods=methods,\n    check_constancy_invariants=check_constancy_invariants,\n  )\n\n\ndef map_variables(\n  target: Target,\n  mapped_collections: CollectionFilter = True,\n  trans_in_fn: Callable[..., Any] = lift.id_fn,\n  trans_out_fn: Callable[..., Any] = lift.id_fn,\n  init: bool = False,\n  mutable: bool = False,\n  rngs: PRNGSequenceFilter = True,\n  variables: CollectionFilter = True,\n  methods=None,\n) -> Target:\n  \"\"\"Map Variables inside a module.\n\n  ``map_variables`` can be used to transform the variables inside a module\n  both before and after the module is applied. This is useful among other\n  things for masking the weights of a module without having to modify the\n  module itself.\n\n  Example::\n\n    >>> import jax\n    >>> import jax.numpy as jnp\n    >>> import flax.linen as nn\n    ...\n    >>> class CausalDense(nn.Module):\n    ...   '''A dense layer that masks the weights such that the output is\n    ...   causal, i.e. output i only depends on input <= i.\n    ...   '''\n    ...   features: int\n    ...\n    ...   def apply_mask(self, variables):\n    ...     return (jax.tree_util.tree_map(jnp.triu, variables)\n    ...             if not self.is_initializing() else variables)\n    ...\n    ...   def setup(self):\n    ...     # temporary class\n    ...     _CausalDense = nn.map_variables(\n    ...       nn.Dense, 'params', self.apply_mask, init=self.is_initializing())\n    ...     self.dense = _CausalDense(features=self.features, use_bias=False)\n    ...\n    ...   def __call__(self, x):\n    ...     return self.dense(x)\n    ...\n    >>> module = CausalDense(features=5)\n    >>> variables = module.init(jax.random.key(0), jnp.ones((1, 5)))\n\n  Args:\n    target: the module or function to be transformed.\n    mapped_collections: the collection(s) to be transformed.\n    trans_in_fn: modifies the variables before applying the module or function.\n    trans_out_fn: modifies the variables after applying the module or function,\n      it is only applied if either ``init`` or ``mutable`` are not False.\n    init: If True, variables are initialized before transformation.\n    mutable: If True, the mapped variable collections will be mutable.\n    rngs: PRNGSequences added to the transformed scope (default: all).\n    variables: Additional Variable collections added to the transformed scope.\n      Besides those specified by ``target`` (default: all).\n    methods: If ``target`` is a ``Module``, the methods of ``Module`` to map\n      variables for.\n\n  Returns:\n    a wrapped version of ``target`` that will map the specified collections.\n  \"\"\"\n\n  return lift_transform(\n    lift.map_variables,\n    target,\n    mapped_collections,\n    trans_in_fn,\n    trans_out_fn,\n    init,\n    mutable,\n    rngs,\n    variables,\n    methods=methods,\n  )\n\n\ndef vjp(\n  fn: Callable[..., Any],\n  mdl: Module,\n  *primals,\n  has_aux: bool = False,\n  reduce_axes=(),\n  vjp_variables: CollectionFilter = 'params',\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n  multi_scope: bool = False,\n):\n  \"\"\"A lifted version of ``jax.vjp``.\n\n  See ``jax.vjp`` for the unlifted vector-Jacobian product (backward gradient).\n\n  Note that a gradient is returned for all variables in the collections\n  specified by ``vjp_variables``. However, the backward function only expects\n  a cotangent for the return value of ``fn``. If variables require a co-tangent\n  as well they can be returned from ``fn`` using ``Module.variables``.\n\n  Example::\n\n    >>> import flax.linen as nn\n    >>> import jax.numpy as jnp\n\n    >>> class LearnScale(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x, y):\n    ...     p = self.param('scale', nn.initializers.zeros_init(), ())\n    ...     return p * x * y\n\n    >>> class Foo(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x, y):\n    ...     z, bwd = nn.vjp(lambda mdl, x, y: mdl(x, y), LearnScale(), x, y)\n    ...     params_grad, x_grad, y_grad = bwd(jnp.ones(z.shape))\n    ...     return z, params_grad, x_grad, y_grad\n\n  Args:\n    fn: Function to be differentiated. Its arguments should be arrays, scalars,\n      or standard Python containers of arrays or scalars. It should return an\n      array, scalar, or standard Python container of arrays or scalars. It will\n      receive the scope and primals as arguments.\n    mdl: The module of which the variables will be differentiated.\n    *primals: A sequence of primal values at which the Jacobian of ``fn``\n      should be evaluated. The length of ``primals`` should be equal to the\n      number of positional parameters to ``fn``. Each primal value should be a\n      tuple of arrays, scalar, or standard Python containers thereof.\n    has_aux: Optional, bool. Indicates whether ``fn`` returns a pair where the\n     first element is considered the output of the mathematical function to be\n     differentiated and the second element is auxiliary data. Default ``False``.\n    vjp_variables: The vjpfun will return a cotangent vector for all\n      variable collections specified by this filter.\n    variables: other variables collections that are available inside ``fn`` but\n      do not receive a cotangent.\n    rngs: the prngs that are available inside ``fn``.\n    multi_scope: for Modules containing multiple scopes from outside modules passed in,\n      allow for variable gradients to be returned for multiple scopes instead of erroring.\n  Returns:\n    If ``has_aux`` is ``False``, returns a ``(primals_out, vjpfun)`` pair, where\n    ``primals_out`` is ``fn(*primals)``.\n    ``vjpfun`` is a function from a cotangent vector with the same shape as\n    ``primals_out`` to a tuple of cotangent vectors with the same shape as\n    ``primals``, representing the vector-Jacobian product of ``fn`` evaluated at\n    ``primals``. If ``has_aux`` is ``True``, returns a\n    ``(primals_out, vjpfun, aux)`` tuple where ``aux`` is the auxiliary data\n    returned by ``fn``.\n  \"\"\"\n  if reduce_axes:\n    raise NotImplementedError('reduce_axes argument to vjp is deprecated')\n  del reduce_axes\n\n  return lift_direct_transform(\n    lift.vjp,\n    (fn,),\n    mdl,\n    *primals,\n    multi_scope=multi_scope,\n    has_aux=has_aux,\n    vjp_variables=vjp_variables,\n    variables=variables,\n    rngs=rngs,\n  )\n\n\ndef value_and_grad(\n  fn: Callable[..., Any],\n  mdl: Module,\n  *primals,\n  has_aux: bool = False,\n  reduce_axes=(),\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n):\n  \"\"\"A limited, lifted equivalent of ``jax.value_and_grad``.\n\n  Note that for this convenience function, gradients are only calculated for\n  the function inputs, and not with respect to any module variables. The\n  target function must return a scalar-valued output.  For a more general\n  lifted vjp, see ``nn.vjp`` for the lifted vector-Jacobian product.\n\n  Example::\n\n    class LearnScale(nn.Module):\n      @nn.compact\n      def __call__(self, x, y):\n        p = self.param('scale', nn.initializers.zeros_init(), ())\n        return p * x * y\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x, y):\n        z, (x_grad, y_grad) = nn.value_and_grad(\n            lambda mdl, x, y: mdl(x, y), LearnScale(), x, y)\n        return z, x_grad, y_grad\n\n  Args:\n    fn: Function to be differentiated. Its arguments should be arrays, scalars,\n      or standard Python containers of arrays or scalars. It should return an\n      array, scalar, or standard Python container of arrays or scalars. It will\n      receive the scope and primals as arguments.\n    mdl: The module of which the variables will be differentiated.\n    *primals: A sequence of primal values at which the Jacobian of ``fn``\n      should be evaluated. The length of ``primals`` should be equal to the\n      number of positional parameters to ``fn``. Each primal value should be a\n      tuple of arrays, scalar, or standard Python containers thereof.\n    has_aux: Optional, bool. Indicates whether ``fn`` returns a pair where the\n     first element is considered the output of the mathematical function to be\n     differentiated and the second element is auxiliary data. Default ``False``.\n    variables: variables collections that are available inside ``fn`` but\n      do not receive a cotangent.\n    rngs: the prngs that are available inside ``fn``.\n  Returns:\n    If ``has_aux`` is ``False``, returns a ``primals_out, grads`` pair, where\n    ``primals_out`` is ``fn(*primals)``.  ``grads`` are the gradients for the\n    corresponding primals and do not include the gradients for module variables.\n    If ``has_aux`` is ``True``, returns a\n    ``(primals_out, aux), grads`` tuple where ``aux`` is the auxiliary data\n    returned by ``fn``.\n  \"\"\"\n  if reduce_axes:\n    raise NotImplementedError(\n        'reduce_axes argument to value_and_grad is deprecated')\n  del reduce_axes\n\n  grad_partial = functools.partial(\n    lift_direct_transform,\n    lift.value_and_grad,\n    (fn,),\n    mdl,\n    *primals,\n    has_aux=has_aux,\n    variables=variables,\n    rngs=rngs,\n  )\n\n  if has_aux:\n    out, aux, argument_grads = grad_partial()\n    if out.shape != ():\n      raise ValueError(\n        'grad can only work on functions with '\n        f'scalar-valued outputs. out shape={out.shape}'\n      )\n    return (out, aux), argument_grads\n  else:\n    out, argument_grads = grad_partial()\n    if out.shape != ():\n      raise ValueError(\n        'grad can only work on functions with '\n        f'scalar-valued outputs. out shape={out.shape}'\n      )\n    return out, argument_grads\n\n\ndef grad(\n  fn: Callable[..., Any],\n  mdl: Module,\n  *primals,\n  has_aux: bool = False,\n  reduce_axes=(),\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n):\n  \"\"\"A limited, lifted equivalent of ``jax.grad``.\n\n  Note that for this convenience function, gradients are only calculated for\n  the function inputs, and not with respect to any module variables. The\n  target function must return a scalar-valued output.  For a more general\n  lifted vjp, see ``nn.vjp`` for the lifted vector-Jacobian product.\n\n  Example::\n\n    class LearnScale(nn.Module):\n      @nn.compact\n      def __call__(self, x, y):\n        p = self.param('scale', nn.initializers.zeros_init(), ())\n        return p * x * y\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x, y):\n        x_grad, y_grad = nn.grad(\n            lambda mdl, x, y: mdl(x, y), LearnScale(), x, y)\n        return x_grad, y_grad\n\n  Args:\n    fn: Function to be differentiated. Its arguments should be arrays, scalars,\n      or standard Python containers of arrays or scalars. It should return an\n      array, scalar, or standard Python container of arrays or scalars. It will\n      receive the scope and primals as arguments.\n    mdl: The module of which the variables will be differentiated.\n    *primals: A sequence of primal values at which the Jacobian of ``fn``\n      should be evaluated. The length of ``primals`` should be equal to the\n      number of positional parameters to ``fn``. Each primal value should be a\n      tuple of arrays, scalar, or standard Python containers thereof.\n    has_aux: Optional, bool. Indicates whether ``fn`` returns a pair where the\n     first element is considered the output of the mathematical function to be\n     differentiated and the second element is auxiliary data. Default ``False``.\n    variables: variables collections that are available inside ``fn`` but\n      do not receive a cotangent.\n    rngs: the prngs that are available inside ``fn``.\n  Returns:\n    If ``has_aux`` is ``False``, returns ``grads``, where ``grads`` are the\n    gradients for the corresponding primals and do not include the gradients\n    for module variables.\n    If ``has_aux`` is ``True``, returns a\n    ``(grads, aux)`` tuple where ``aux`` is the auxiliary data\n    returned by ``fn``.\n  \"\"\"\n  if reduce_axes:\n    raise NotImplementedError('reduce_axes argument to grad is deprecated')\n  del reduce_axes\n\n  value_and_grad_partial = functools.partial(\n    value_and_grad,\n    fn,\n    mdl,\n    *primals,\n    has_aux=has_aux,\n    variables=variables,\n    rngs=rngs,\n  )\n\n  if has_aux:\n    (_, aux), argument_grads = value_and_grad_partial()\n    return argument_grads, aux\n  else:\n    _, argument_grads = value_and_grad_partial()\n    return argument_grads\n\n\ndef jvp(\n  fn: Callable[..., Any],\n  mdl: Module,\n  primals,\n  tangents,\n  variable_tangents,\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n) -> tuple[Any, Callable[..., Any]] | tuple[Any, Callable[..., Any], Any]:\n  \"\"\"A lifted version of ``jax.jvp``.\n\n  See ``jax.jvp`` for the unlifted Jacobian-vector product (forward gradient).\n\n  Note that no tangents are returned for variables. When variable tangents\n  are required their value should be returned explicitly by ``fn``\n  using ``Module.variables``::\n\n    >>> import flax.linen as nn\n    >>> import jax.numpy as jnp\n\n    >>> class LearnScale(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     p = self.param('test', nn.initializers._init(), ())\n    ...     return p * x\n\n    >>> class Foo(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     scale = LearnScale()\n    ...     vars_t = jax.tree_util.tree_map(jnp.ones_like,\n    ...                                     scale.variables.get('params', {}))\n    ...     _, out_t = nn.jvp(\n    ...         lambda mdl, x: mdl(x), scale, (x,), (jnp.zeros_like(x),),\n    ...         variable_tangents={'params': vars_t})\n    ...     return out_t\n\n  Example::\n\n    >>> def learn_scale(scope, x):\n    ...   p = scope.param('scale', nn.initializers.zeros_init(), ())\n    ...   return p * x\n\n    >>> def f(scope, x):\n    ...   vars_t = jax.tree_util.tree_map(jnp.ones_like, scope.variables().get('params', {}))\n    ...   x, out_t = lift.jvp(\n    ...       learn_scale, scope, (x,), (jnp.zeros_like(x),),\n    ...       variable_tangents={'params': vars_t})\n    ...   return out_t\n\n  Args:\n    fn: Function to be differentiated. Its arguments should be arrays, scalars,\n      or standard Python containers of arrays or scalars. It should return an\n      array, scalar, or standard Python container of arrays or scalars. It will\n      receive the scope and primals as arguments.\n    mdl: The module of which the variables will be differentiated.\n    primals: The primal values at which the Jacobian of ``fun`` should be\n      evaluated. Should be either a tuple or a list of arguments,\n      and its length should be equal to the number of positional parameters of\n      ``fun``.\n    tangents: The tangent vector for which the Jacobian-vector product should be\n      evaluated. Should be either a tuple or a list of tangents, with the same\n      tree structure and array shapes as ``primals``.\n    variable_tangents: A dict or PyTree fo dicts with the same structure as\n      scopes. Each entry in the dict specifies the tangents for a variable\n      collection. Not specifying a collection in variable_tangents is\n      equivalent to passing a zero vector as the tangent.\n    variables: other variables collections that are available in ``fn`` but\n      do not receive a tangent.\n    rngs: the prngs that are available inside ``fn``.\n\n  Returns:\n    A ``(primals_out, tangents_out)`` pair, where ``primals_out`` is\n    ``fun(*primals)``, and ``tangents_out`` is the Jacobian-vector product of\n    ``function`` evaluated at ``primals`` with ``tangents``. The\n    ``tangents_out`` value has the same Python tree structure and shapes as\n    ``primals_out``.\n  \"\"\"\n  return lift_direct_transform(\n    lift.jvp,\n    (fn,),\n    mdl,\n    primals,\n    tangents,\n    variable_tangents,\n    multi_scope=False,\n    variables=variables,\n    rngs=rngs,\n  )\n\n\nModuleT = TypeVar('ModuleT', bound=Module)\nC = TypeVar('C')\n\n\ndef while_loop(\n  cond_fn: Callable[[ModuleT, C], bool],\n  body_fn: Callable[[ModuleT, C], C],\n  mdl: ModuleT,\n  init: C,\n  carry_variables: CollectionFilter = False,\n  broadcast_variables: CollectionFilter = True,\n  split_rngs: Mapping[PRNGSequenceFilter, bool] = FrozenDict(),\n) -> C:\n  \"\"\"Lifted version of jax.lax.while_loop.\n\n  The lifted scope is passed to ``cond_fn`` and ``body_fn``.\n  Broadcasted variables are immutable. The carry variable are\n  mutable but cannot change shape and dtype.\n  This also means you cannot initialize variables inside\n  the body. Consider calling ``body_fn`` once manually before\n  calling ``while_loop`` if variable initialization is required.\n\n  Example::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> class WhileLoopExample(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     def cond_fn(mdl, c):\n    ...       return mdl.variables['state']['acc'] < 10\n    ...     def body_fn(mdl, c):\n    ...       acc = mdl.variable('state', 'acc', lambda: jnp.array(0))\n    ...       acc.value += 1\n    ...       y = nn.Dense(c.shape[-1])(c)\n    ...       return y\n    ...     c = x\n    ...     if self.is_mutable_collection('params'):\n    ...       return body_fn(self, c)\n    ...     else:\n    ...       return nn.while_loop(cond_fn, body_fn, self, c,\n    ...                             carry_variables='state')\n\n    >>> k = jax.random.key(0)\n    >>> x = jnp.ones((2, 2))\n    >>> initial_vars = WhileLoopExample().init(k, x)\n    >>> result, state = WhileLoopExample().apply(initial_vars, x, mutable=['state'])\n\n  Args:\n    cond_fn: Should return True as long as the loop should continue.\n    body_fn: The body of the while loop.\n    mdl: The Module which should be lifted into the loop.\n    init: The initial state passed to the loop\n    carry_variables: collections that are carried through the loop\n      and are therefore mutable (default: none).\n    broadcast_variables: collections that are closed over and are\n      therefore read-only (default: all collections)\n    split_rngs: Split PRNG sequences will be different for each loop iterations.\n      If split is False the PRNGs will be the same across iterations.\n  Returns:\n    The final state after executing the while loop.\n  \"\"\"\n  return lift_direct_transform(\n    lift.while_loop,\n    (cond_fn, body_fn),\n    mdl,\n    init,\n    carry_variables,\n    broadcast_variables,\n    split_rngs,\n  )\n\n\ndef _cond_wrapper(t_fn, f_fn, scope, pred, *ops, variables, rngs):\n  return lift.cond(\n    pred, t_fn, f_fn, scope, *ops, variables=variables, rngs=rngs\n  )\n\n\ndef cond(\n  pred: Any,\n  true_fun: Callable[..., C],\n  false_fun: Callable[..., C],\n  mdl: Module,\n  *operands,\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n) -> C:\n  \"\"\"Lifted version of ``jax.lax.cond``.\n\n  The returned values from ``true_fun`` and ``false_fun``\n  must have the same Pytree structure, shapes, and dtypes.\n  The variables created or updated inside the\n  branches must also have the same structure.\n  Note that this constraint is violated when\n  creating variables or submodules in only one branch.\n  Because initializing variables in just one branch\n  causes the parameter structure to be different.\n\n  Example::\n\n    >>> import flax.linen as nn\n\n    >>> class CondExample(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x, pred):\n    ...     self.variable('state', 'true_count', lambda: 0)\n    ...     self.variable('state', 'false_count', lambda: 0)\n    ...     def true_fn(mdl, x):\n    ...       mdl.variable('state', 'true_count').value += 1\n    ...       return nn.Dense(2, name='dense')(x)\n    ...     def false_fn(mdl, x):\n    ...       mdl.variable('state', 'false_count').value += 1\n    ...       return -nn.Dense(2, name='dense')(x)\n    ...     return nn.cond(pred, true_fn, false_fn, self, x)\n\n  Args:\n    pred: determines if true_fun or false_fun is evaluated.\n    true_fun: The function evaluated when ``pred`` is ``True``.\n      The signature is (module, *operands) -> T.\n    false_fun: The function evaluated when ``pred`` is ``False``.\n      The signature is (module, *operands) -> T.\n    mdl: A Module target to pass.\n    *operands: The arguments passed to ``true_fun`` and ``false_fun``\n    variables: The variable collections passed to the conditional\n      branches (default: all)\n    rngs: The PRNG sequences passed to the conditionals (default: all)\n  Returns:\n    The result of the evaluated branch (``true_fun`` or ``false_fun``).\n  \"\"\"\n  return lift_direct_transform(\n    _cond_wrapper,\n    (true_fun, false_fun),\n    mdl,\n    pred,\n    *operands,\n    variables=variables,\n    rngs=rngs,\n  )\n\n\ndef _switch_wrapper(*args, variables, rngs, n_branches):\n  # first n_branches arguments are branches.\n  # then scope, index, and the rest are *operands\n  branches = args[:n_branches]\n  scope, index, *operands = args[n_branches:]\n  return lift.switch(\n    index, branches, scope, *operands, variables=variables, rngs=rngs\n  )\n\n\ndef switch(\n  index: Any,\n  branches: Sequence[Callable[..., C]],\n  mdl: Module,\n  *operands,\n  variables: CollectionFilter = True,\n  rngs: PRNGSequenceFilter = True,\n) -> C:\n  \"\"\"Lifted version of ``jax.lax.switch``.\n\n  The returned values from ``branches``\n  must have the same Pytree structure, shapes, and dtypes.\n  The variables created or updated inside the\n  branches must also have the same structure.\n  Note that this constraint is violated when\n  creating variables or submodules in only one branch.\n  Because initializing variables in just one branch\n  causes the parameter structure to be different.\n\n  Example::\n\n    >>> import flax.linen as nn\n\n    >>> class SwitchExample(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x, index):\n    ...     self.variable('state', 'a_count', lambda: 0)\n    ...     self.variable('state', 'b_count', lambda: 0)\n    ...     self.variable('state', 'c_count', lambda: 0)\n    ...     def a_fn(mdl, x):\n    ...       mdl.variable('state', 'a_count').value += 1\n    ...       return nn.Dense(2, name='dense')(x)\n    ...     def b_fn(mdl, x):\n    ...       mdl.variable('state', 'b_count').value += 1\n    ...       return -nn.Dense(2, name='dense')(x)\n    ...     def c_fn(mdl, x):\n    ...       mdl.variable('state', 'c_count').value += 1\n    ...       return nn.Dense(2, name='dense')(x)\n    ...     return nn.switch(index, [a_fn, b_fn, c_fn], self, x)\n\n  If you want to have a different parameter structure for each branch\n  you should run all branches on initialization before calling switch::\n\n    >>> class MultiHeadSwitchExample(nn.Module):\n    ...   def setup(self) -> None:\n    ...     self.heads = [\n    ...       nn.Sequential([nn.Dense(10), nn.Dense(7), nn.Dense(5)]),\n    ...       nn.Sequential([nn.Dense(11), nn.Dense(5)]),\n    ...       nn.Dense(5),\n    ...     ]\n    ...\n    ...   @nn.compact\n    ...   def __call__(self, x, index):\n    ...     def head_fn(i):\n    ...       return lambda mdl, x: mdl.heads[i](x)\n    ...     branches = [head_fn(i) for i in range(len(self.heads))]\n    ...\n    ...     # run all branches on init\n    ...     if self.is_mutable_collection('params'):\n    ...       for branch in branches:\n    ...         _ = branch(self, x)\n    ...\n    ...     return nn.switch(index, branches, self, x)\n\n  Args:\n    index: Integer scalar type, indicating which branch function to apply.\n    branches: Sequence of functions to be applied based on index.\n      The signature of each function is (module, *operands) -> T.\n    mdl: A Module target to pass.\n    *operands: The arguments passed to the branches.\n    variables: The variable collections passed to the conditional\n      branches (default: all)\n    rngs: The PRNG sequences passed to the conditionals (default: all)\n  Returns:\n    The result of the evaluated branch.\n  \"\"\"\n  return lift_direct_transform(\n    _switch_wrapper,\n    tuple(branches),\n    mdl,\n    index,\n    *operands,\n    variables=variables,\n    rngs=rngs,\n    n_branches=len(branches),\n  )\n\n\n# a version of lift.custom_vjp with a single scope function\n# this avoids having to lift multiple functions in\n# lift_transform.\ndef _custom_vjp_single_scope_fn(\n  fn: Callable[..., Any],\n  backward_fn: Callable[..., Any],\n  grad_vars: CollectionFilter = 'params',\n  nondiff_argnums=(),\n):\n  nodiff_fn = functools.partial(fn, needs_residual=False)\n  forward_fn = functools.partial(fn, needs_residual=True)\n  return lift.custom_vjp(\n    nodiff_fn, forward_fn, backward_fn, grad_vars, nondiff_argnums\n  )\n\n\ndef custom_vjp(\n  fn: Callable[..., Any],\n  forward_fn: Callable[..., Any],\n  backward_fn: Callable[..., Any],\n  grad_vars: CollectionFilter = 'params',\n  nondiff_argnums=(),\n):\n  \"\"\"Lifted version of ``jax.custom_vjp``.\n\n  ``forward_fn`` and ``backward_fn`` together define a custom vjp for ``fn``.\n  The original ``fn`` will run in case a vjp (backward gradient) is not computed.\n\n  The ``forward_fn`` receives the same arguments as ``fn`` but is expected to return\n  a tuple containing the output of ``fn(mdl, *args)`` and the residuals that are\n  passed to ``backward_fn``.\n\n  The ``backward_fn`` receives the nondiff arguments, residuals, and the output\n  tangents. It should return a tuple containing the variable and input tangents.\n\n  Note that the vjp function returned by ``nn.vjp`` can be passed as residual and\n  used in the ``backward_fn``. The scope is unavailable during the backward pass.\n  If the module is required in ``backward_fn``, a snapshot of the variables can\n  be taken and returned as a residual in the ``forward_fn``.\n\n  Example::\n\n    >>> import flax.linen as nn\n    >>> import jax, jax.numpy as jnp\n\n    >>> class Foo(nn.Module):\n    ...   @nn.compact\n    ...   def __call__(self, x):\n    ...     def f(mdl, x):\n    ...       return mdl(x)\n    ...\n    ...     def fwd(mdl, x):\n    ...       return nn.vjp(f, mdl, x)\n    ...\n    ...     def bwd(vjp_fn, y_t):\n    ...       params_t, *inputs_t = vjp_fn(y_t)\n    ...       params_t = jax.tree_util.tree_map(jnp.sign, params_t)\n    ...       return (params_t, *inputs_t)\n    ...\n    ...     sign_grad = nn.custom_vjp(\n    ...         f, forward_fn=fwd, backward_fn=bwd)\n    ...     return sign_grad(nn.Dense(1), x).reshape(())\n\n    >>> x = jnp.ones((2,))\n    >>> variables = Foo().init(jax.random.key(0), x)\n    >>> grad = jax.grad(Foo().apply)(variables, x)\n\n  Args:\n    fn: The function to define a custom_vjp for.\n    forward_fn: A function with the same arguments as ``fn`` returning an tuple\n      with the original output and the residuals that will be passed to\n      ``backward_fn``.\n    backward_fn: arguments are passed as\n      ``(*nondiff_args, residuals, tangents)`` The function should return a\n      tuple containing the tangents for the variable in the collections\n      specified by ``grad_vars`` and the input arguments (except the module and\n      nondiff args).\n    grad_vars: The collections for which a vjp will be computed\n      (default: \"params\").\n    nondiff_argnums: arguments for which no vjp is computed.\n  Returns:\n    A function with the same signature as ``fn`` with the custom vjp.\n  \"\"\"\n\n  def shared_forward_fn(*args, needs_residual, **kwargs):\n    if needs_residual:\n      return forward_fn(*args, **kwargs)\n    else:\n      return fn(*args, **kwargs)\n\n  return decorator_lift_transform(\n    _custom_vjp_single_scope_fn,\n    shared_forward_fn,\n    backward_fn=backward_fn,\n    grad_vars=grad_vars,\n    nondiff_argnums=nondiff_argnums,\n    multi_scope=False,\n  )\n\n\ndef named_call(class_fn, force=True):\n  \"\"\"Labels a method for labelled traces in profiles.\n\n  Note that it is better to use the `jax.named_scope` context manager directly\n  to add names to JAX's metadata name stack.\n\n  Args:\n    class_fn: The class method to label.\n    force: If True, the named_call transform is applied even if it is globally\n      disabled. (e.g.: by calling `flax.linen.disable_named_call()`)\n  Returns:\n    A wrapped version of ``class_fn`` that is labeled.\n  \"\"\"\n\n  # We use JAX's dynamic name-stack named_call. No transform boundary needed!\n  @functools.wraps(class_fn)\n  def wrapped_fn(self, *args, **kwargs):\n    if (not force and not linen_module._use_named_call) or self._state.in_setup:  # pylint: disable=protected-access  # pylint: disable=protected-access\n      return class_fn(self, *args, **kwargs)\n    full_name = _derive_profiling_name(self, class_fn)\n    return jax.named_call(class_fn, name=full_name)(self, *args, **kwargs)\n\n  return wrapped_fn\n\n\ndef add_metadata_axis(\n  target: Target,\n  variable_axes: Mapping[CollectionFilter, InOutAxis] = FrozenDict(),\n  metadata_params: dict[Any, Any] = {},\n) -> Target:\n  \"\"\"A helper to manipulate boxed axis metadata.\n\n  This is a helper to manipulate the *metadata* in boxed variables, similar\n  to how lifted ``vmap`` and ``scan`` will handle the introduction and stripping\n  of the new metadata axis across a transform boundary.\n\n  Args:\n    target: a ``Module`` or a function taking a ``Module``\n      as its first argument.\n    variable_axes: the variable collections whose axis metadata is being\n      transformed. Use `None` to indicate a broadcasted collection or an integer\n      to specify an axis index for an introduced axis.\n    methods: If `target` is a `Module`, the methods of `Module` to vmap over.\n    metadata_params: arguments dict passed to AxisMetadata instances in the\n      variable tree.\n  Returns:\n    A transformed version of ``target`` that performs a transform of the\n    axis metadata on its variables.\n  \"\"\"\n\n  def add_fn(axis):\n    return lambda x: meta.add_axis(x, axis, metadata_params)\n\n  def remove_fn(axis):\n    return lambda x: meta.remove_axis(x, axis, metadata_params)\n\n  for col_name, axis in variable_axes.items():\n    target = map_variables(\n      target,\n      col_name,\n      trans_in_fn=remove_fn(axis),\n      trans_out_fn=add_fn(axis),\n      mutable=True,\n    )\n  return target\n\n\ndef fold_rngs(\n    target: Target,\n    variables: CollectionFilter = True,\n    rngs: PRNGSequenceFilter = True,\n) -> Target:\n  return lift_transform_cached(\n      lift.fold_rngs,\n      target,\n      variables=variables,\n      rngs=rngs,\n  )\n"
  },
  {
    "path": "flax/metrics/__init__.py",
    "content": "# Copyright 2023 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "flax/metrics/tensorboard.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Write Summaries from JAX for use with Tensorboard.\"\"\"\n\nimport contextlib\nimport functools\nimport os\n\nimport numpy as np\nimport tensorflow as tf  # pytype: disable=import-error\nfrom tensorboard.plugins.hparams import api as hparams_api\n\n# pylint: disable=g-import-not-at-top\nfrom flax import io\n\n\ndef _flatten_dict(input_dict, parent_key='', sep='.'):\n  \"\"\"Flattens and simplifies dict such that it can be used by hparams.\n\n  Args:\n    input_dict: Input dict, e.g., from ConfigDict.\n    parent_key: String used in recursion.\n    sep: String used to separate parent and child keys.\n\n  Returns:\n   Flattened dict.\n  \"\"\"\n  items = []\n  for k, v in input_dict.items():\n    new_key = parent_key + sep + k if parent_key else k\n\n    # Valid types according to https://github.com/tensorflow/tensorboard/blob/1204566da5437af55109f7a4af18f9f8b7c4f864/tensorboard/plugins/hparams/summary_v2.py\n    valid_types = (\n      bool,\n      int,\n      float,\n      str,\n      np.bool_,\n      np.integer,\n      np.floating,\n      np.character,\n    )\n\n    if isinstance(v, dict):\n      # Recursively flatten the dict.\n      items.extend(_flatten_dict(v, new_key, sep=sep).items())\n      continue\n    elif not isinstance(v, valid_types):\n      # Cast any incompatible values as strings such that they can be handled by hparams\n      v = str(v)\n    items.append((new_key, v))\n  return dict(items)\n\n\n@contextlib.contextmanager\ndef _as_default(summary_writer: tf.summary.SummaryWriter, auto_flush: bool):\n  \"\"\"No-flush variation of summary_writer.as_default().\"\"\"\n  context_manager = summary_writer.as_default()\n  try:\n    context_manager.__enter__()\n    yield summary_writer\n  finally:\n    old_flush = summary_writer.flush\n    new_flush = old_flush if auto_flush else lambda: None\n    summary_writer.flush = new_flush\n    context_manager.__exit__(None, None, None)\n    summary_writer.flush = old_flush\n\n\nclass SummaryWriter:\n  \"\"\"Saves data in event and summary protos for tensorboard.\"\"\"\n\n  def __init__(self, log_dir, auto_flush=True):\n    \"\"\"Create a new SummaryWriter.\n\n    Args:\n      log_dir: path to record tfevents files in.\n      auto_flush: if true, flush after every reported metric.\n    \"\"\"\n    log_dir = os.fspath(log_dir)\n\n    # If needed, create log_dir directory as well as missing parent directories.\n    if not io.isdir(log_dir):\n      io.makedirs(log_dir)\n\n    self._event_writer = tf.summary.create_file_writer(log_dir)\n    self._as_default = functools.partial(_as_default, auto_flush=auto_flush)\n    self._closed = False\n\n  def close(self):\n    \"\"\"Close SummaryWriter. Final!\"\"\"\n    if not self._closed:\n      self._event_writer.close()\n      self._closed = True\n      del self._event_writer\n\n  def flush(self):\n    self._event_writer.flush()\n\n  def scalar(self, tag, value, step):\n    \"\"\"Saves scalar value.\n\n    Args:\n      tag: str: label for this data\n      value: int/float: number to log\n      step: int: training step\n    \"\"\"\n    value = float(np.array(value))\n    with self._as_default(self._event_writer):\n      tf.summary.scalar(name=tag, data=value, step=step)\n\n  def image(self, tag, image, step, max_outputs=3):\n    \"\"\"Saves RGB image summary from np.ndarray [H,W], [H,W,1], or [H,W,3].\n\n    Args:\n      tag: str: label for this data\n      image: ndarray: [H,W], [H,W,1], [H,W,3], [K,H,W], [K,H,W,1], [K,H,W,3]\n        Save image in greyscale or colors.\n        Pixel values could be either uint8 or float.\n        Floating point values should be in range [0, 1).\n      step: int: training step\n      max_outputs: At most this many images will be emitted at each step.\n        Defaults to 3.\n    \"\"\"\n    image = np.array(image)\n    # tf.summary.image expects image to have shape [k, h, w, c] where,\n    # k = number of samples, h = height, w = width, c = number of channels.\n    if len(np.shape(image)) == 2:\n      image = image[np.newaxis, :, :, np.newaxis]\n    elif len(np.shape(image)) == 3:\n      # this could be either [k, h, w] or [h, w, c]\n      if np.shape(image)[-1] in (1, 3):\n        image = image[np.newaxis, :, :, :]\n      else:\n        image = image[:, :, :, np.newaxis]\n    if np.shape(image)[-1] == 1:\n      image = np.repeat(image, 3, axis=-1)\n\n    # Convert to tensor value as tf.summary.image expects data to be a tensor.\n    image = tf.convert_to_tensor(image)\n    with self._as_default(self._event_writer):\n      tf.summary.image(name=tag, data=image, step=step, max_outputs=max_outputs)\n\n  def audio(self, tag, audiodata, step, sample_rate=44100, max_outputs=3):\n    \"\"\"Saves audio as wave.\n\n    NB: single channel only right now.\n\n    Args:\n      tag: str: label for this data\n      audiodata: ndarray [Nsamples, Nframes, Nchannels]: audio data to\n        be saved as wave. The data will be clipped to [-1.0, 1.0].\n      step: int: training step\n      sample_rate: sample rate of passed in audio buffer\n      max_outputs: At most this many audio clips will be emitted at each\n        step. Defaults to 3.\n    \"\"\"\n    # tf.summary.audio expects the audio data to have floating values in\n    # [-1.0, 1.0].\n    audiodata = np.clip(np.array(audiodata), -1, 1)\n\n    # Convert to tensor value as tf.summary.audio expects data to be a tensor.\n    audio = tf.convert_to_tensor(audiodata, dtype=tf.float32)\n    with self._as_default(self._event_writer):\n      tf.summary.audio(\n        name=tag,\n        data=audio,\n        sample_rate=sample_rate,\n        step=step,\n        max_outputs=max_outputs,\n        encoding='wav',\n      )\n\n  def histogram(self, tag, values, step, bins=None):\n    \"\"\"Saves histogram of values.\n\n    Args:\n      tag: str: label for this data\n      values: ndarray: will be flattened by this routine\n      step: int: training step\n      bins: number of bins in histogram\n    \"\"\"\n    values = np.array(values)\n    values = np.reshape(values, -1)\n    with self._as_default(self._event_writer):\n      tf.summary.histogram(name=tag, data=values, step=step, buckets=bins)\n\n  def text(self, tag, textdata, step):\n    \"\"\"Saves a text summary.\n\n    Args:\n      tag: str: label for this data\n      textdata: string\n      step: int: training step\n    Note: markdown formatting is rendered by tensorboard.\n    \"\"\"\n    if not isinstance(textdata, (str, bytes)):\n      raise ValueError('`textdata` should be of the type `str` or `bytes`.')\n    with self._as_default(self._event_writer):\n      tf.summary.text(name=tag, data=tf.constant(textdata), step=step)\n\n  def write(self, tag, tensor, step, metadata=None):\n    \"\"\"Saves an arbitrary tensor summary.\n\n    Useful when working with custom plugins or constructing a summary directly.\n\n    Args:\n      tag: str: label for this data\n      tensor: ndarray: tensor data to save.\n      step: int: training step\n      metadata: Optional SummaryMetadata, as a proto or serialized bytes.\n    Note: markdown formatting is rendered by tensorboard.\n    \"\"\"\n    with self._as_default(self._event_writer):\n      tf.summary.write(tag=tag, tensor=tensor, step=step, metadata=metadata)\n\n  def hparams(self, hparams):\n    \"\"\"Saves hyper parameters.\n\n    Args:\n      hparams: Flat mapping from hyper parameter name to value.\n    \"\"\"\n\n    with self._as_default(self._event_writer):\n      hparams_api.hparams(hparams=_flatten_dict(hparams))\n"
  },
  {
    "path": "flax/nnx/README.md",
    "content": "[![codecov](https://codecov.io/gh/cgarciae/nnx/branch/main/graph/badge.svg?token=VqJjL474Z7)](https://codecov.io/gh/cgarciae/nnx)\n\n# NNX\n\n_**N**eural **N**etworks for JA**X**_ - | [docs](https://flax.readthedocs.io/en/latest/nnx/index.html) |\n\nNNX is a JAX-based neural network library that focuses on providing the best development experience to make\nbuilding and experimenting with neural networks as easy and intuitive as possible.\n\n* **Pythonic**: Modules are standard Python classes, promoting ease of use and a more familiar\n  development experience.\n* **Easy-to-use**: NNX provides a set of transforms that take care of state management, allowing\n  users to focus on building their models and training loops.\n* **Expressive**: NNX allows fine-grained over the Module state with lifted transforms, enabling\n  users to define complex architectures.\n* **Compatible**: NNX allows functionalizing Module state, making it possible to directly use JAX\n  transformations when needed.\n\n## What does NNX look like?\n\nNNX removes most of the friction from building and training neural networks in JAX. It provides\na Module system that uses standard Python classes, and a set of transforms that extend\nJAX to handle objects.\n\n```python\nfrom flax import nnx\nimport optax\n\nclass Model(nnx.Module):\n  def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n    self.linear = nnx.Linear(din, dmid, rngs=rngs)\n    self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n    self.dropout = nnx.Dropout(0.2, rngs=rngs)\n    self.linear_out = nnx.Linear(dmid, dout, rngs=rngs)\n\n  def __call__(self, x):\n    x = nnx.relu(self.dropout(self.bn(self.linear(x))))\n    return self.linear_out(x)\n\n\nmodel = Model(2, 64, 3, rngs=nnx.Rngs(0))  # eager initialization\noptimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)  # reference sharing\n\n@nnx.jit  # automatic state management\ndef train_step(model, optimizer, x, y):\n  def loss_fn(model):\n    y_pred = model(x)  # call methods directly\n    return ((y_pred - y) ** 2).mean()\n\n  loss, grads = nnx.value_and_grad(loss_fn)(model)\n  optimizer.update(model, grads)  # inplace updates\n\n  return loss\n```\n\nTo learn more about the `Module` abstraction, check out our [NNX Basics](https://flax.readthedocs.io/en/latest/nnx/nnx_basics.html#) guide.\n\n## Installation\n\nTo get started with `nnx`, install Flax from GitHub:\n```\npip install git+https://github.com/google/flax.git\n```\n\n### Examples\n\n* [LM1B](https://github.com/google/flax/tree/main/examples/lm1b_nnx): A language model trained on the 1 Billion Word Benchmark dataset.\n\n#### Toy Examples\n* [Basic Example](https://github.com/google/flax/tree/main/examples/nnx_toy_examples/02_lifted_transforms.py): Shows how to train a simple model using NNX.\n* [Using the Functional API](https://github.com/google/flax/tree/main/examples/nnx_toy_examples/01_functional_api.py): Shows how to train a simple model using the functional API.\n* [Training a VAE](https://github.com/google/flax/tree/main/examples/nnx_toy_examples/05_vae.py): Shows how to train a VAE on the binarized MNIST dataset.\n* [Scan over layers](https://github.com/google/flax/tree/main/examples/nnx_toy_examples/06_scan_over_layers.py): An contrived example that implements scan over layers with dropout and a share BatcNorm layer to showcase how lifted transforms can be implemented. It uses the functional API along with `jax.vmap` and `jax.lax.scan`.\n"
  },
  {
    "path": "flax/nnx/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom flax.core.spmd import logical_axis_rules as logical_axis_rules\nfrom flax.linen.pooling import avg_pool as avg_pool\nfrom flax.linen.pooling import max_pool as max_pool\nfrom flax.linen.pooling import min_pool as min_pool\nfrom flax.linen.pooling import pool as pool\nfrom flax.typing import Initializer as Initializer\n\nfrom .bridge import wrappers as wrappers\nfrom .filterlib import WithTag as WithTag\nfrom .filterlib import PathContains as PathContains\nfrom .filterlib import OfType as OfType\nfrom .filterlib import Any as Any\nfrom .filterlib import All as All\nfrom .filterlib import Not as Not\nfrom .filterlib import Everything as Everything\nfrom .filterlib import Nothing as Nothing\nfrom .graphlib import GraphDef as GraphDef\nfrom .graphlib import GraphState as GraphState\nfrom .graphlib import PureState as PureState\nfrom . import pytreelib as object\nfrom .pytreelib import Pytree as Pytree\nfrom .pytreelib import Object as Object\nfrom .pytreelib import Data as Data\nfrom .pytreelib import Static as Static\nfrom .pytreelib import dataclass as dataclass\nfrom .pytreelib import data as data\nfrom .pytreelib import static as static\nfrom .pytreelib import register_data_type as register_data_type\nfrom .pytreelib import is_data as is_data\nfrom .pytreelib import has_data as has_data\nfrom .pytreelib import check_pytree as check_pytree\nfrom .helpers import Dict as Dict\nfrom .helpers import List as List\nfrom .helpers import Sequential as Sequential\nfrom .helpers import TrainState as TrainState\nfrom .module import M as M\nfrom .module import Module as Module\nfrom .module import capture as capture\nfrom .module import view as view\nfrom .module import view_info as view_info\nfrom .module import with_attributes as with_attributes\nfrom .module import iter_children as iter_children, iter_modules as iter_modules\nfrom .graphlib import merge as merge\nfrom .graphlib import UpdateContext as UpdateContext\nfrom .graphlib import update_context as update_context\nfrom .graphlib import current_update_context as current_update_context\nfrom .graphlib import split as split\nfrom .graphlib import update as update\nfrom .graphlib import clone as clone\nfrom .graphlib import pop as pop\nfrom .graphlib import state as state\nfrom .graphlib import graphdef as graphdef\nfrom .graphlib import iter_graph as iter_graph\nfrom .graphlib import recursive_map as recursive_map\nfrom .graphlib import find_duplicates as find_duplicates\nfrom .graphlib import map as map\nfrom .graphlib import call as call\nfrom .graphlib import set_metadata as set_metadata\nfrom .graphlib import SplitContext as SplitContext\nfrom .graphlib import split_context as split_context\nfrom .graphlib import MergeContext as MergeContext\nfrom .graphlib import merge_context as merge_context\nfrom .graphlib import variables as variables\nfrom .graphlib import vars_as as vars_as\nfrom .graphlib import pure as pure\nfrom .graphlib import cached_partial as cached_partial\nfrom .graphlib import flatten as flatten\nfrom .graphlib import unflatten as unflatten\nfrom .graphlib import set_graph_mode as set_graph_mode\nfrom .graphlib import set_graph_updates as set_graph_updates\nfrom .nn import initializers as initializers\nfrom .nn.activations import celu as celu\nfrom .nn.activations import elu as elu\nfrom .nn.activations import gelu as gelu\nfrom .nn.activations import glu as glu\nfrom .nn.activations import hard_sigmoid as hard_sigmoid\nfrom .nn.activations import hard_silu as hard_silu\nfrom .nn.activations import hard_swish as hard_swish\nfrom .nn.activations import hard_tanh as hard_tanh\nfrom .nn.activations import leaky_relu as leaky_relu\nfrom .nn.activations import log_sigmoid as log_sigmoid\nfrom .nn.activations import log_softmax as log_softmax\nfrom .nn.activations import logsumexp as logsumexp\nfrom .nn.activations import one_hot as one_hot\nfrom .nn.activations import relu as relu\nfrom .nn.activations import relu6 as relu6\nfrom .nn.activations import selu as selu\nfrom .nn.activations import sigmoid as sigmoid\nfrom .nn.activations import identity as identity\nfrom .nn.activations import silu as silu\nfrom .nn.activations import soft_sign as soft_sign\nfrom .nn.activations import softmax as softmax\nfrom .nn.activations import softplus as softplus\nfrom .nn.activations import standardize as standardize\nfrom .nn.activations import swish as swish\nfrom .nn.activations import tanh as tanh\nfrom .nn.activations import PReLU as PReLU\nfrom .nn.attention import MultiHeadAttention as MultiHeadAttention\nfrom .nn.attention import combine_masks as combine_masks\nfrom .nn.attention import dot_product_attention as dot_product_attention\nfrom .nn.attention import make_attention_mask as make_attention_mask\nfrom .nn.attention import make_causal_mask as make_causal_mask\nfrom .nn.recurrent import RNNCellBase as RNNCellBase\nfrom .nn.recurrent import LSTMCell as LSTMCell\nfrom .nn.recurrent import GRUCell as GRUCell\nfrom .nn.recurrent import OptimizedLSTMCell as OptimizedLSTMCell\nfrom .nn.recurrent import SimpleCell as SimpleCell\nfrom .nn.recurrent import RNN as RNN\nfrom .nn.recurrent import Bidirectional as Bidirectional\nfrom .nn.linear import Conv as Conv\nfrom .nn.linear import ConvTranspose as ConvTranspose\nfrom .nn.linear import Embed as Embed\nfrom .nn.linear import Linear as Linear\nfrom .nn.linear import LinearGeneral as LinearGeneral\nfrom .nn.linear import Einsum as Einsum\nfrom .nn.lora import LoRA as LoRA\nfrom .nn.lora import LoRALinear as LoRALinear\nfrom .nn.lora import LoRAParam as LoRAParam\nfrom .nn.normalization import BatchNorm as BatchNorm\nfrom .nn.normalization import LayerNorm as LayerNorm\nfrom .nn.normalization import RMSNorm as RMSNorm\nfrom .nn.normalization import GroupNorm as GroupNorm\nfrom .nn.normalization import InstanceNorm as InstanceNorm\nfrom .nn.normalization import SpectralNorm as SpectralNorm\nfrom .nn.normalization import WeightNorm as WeightNorm\nfrom .nn.stochastic import Dropout as Dropout\nfrom .rnglib import Rngs as Rngs\nfrom .rnglib import RngStream as RngStream\nfrom .rnglib import RngState as RngState\nfrom .rnglib import RngKey as RngKey\nfrom .rnglib import RngCount as RngCount\nfrom .rnglib import fork_rngs as fork_rngs\nfrom .rnglib import reseed as reseed\nfrom .rnglib import split_rngs as split_rngs\nfrom .rnglib import restore_rngs as restore_rngs\nfrom .spmd import PARTITION_NAME as PARTITION_NAME\nfrom .spmd import get_partition_spec as get_partition_spec\nfrom .spmd import get_named_sharding as get_named_sharding\nfrom .spmd import with_partitioning as with_partitioning\nfrom .spmd import get_abstract_model as get_abstract_model\nfrom .spmd import abstract_with_sharding as abstract_with_sharding\nfrom .statelib import FlatState as FlatState\nfrom .statelib import State as State\nfrom .statelib import to_flat_state as to_flat_state\nfrom .statelib import from_flat_state as from_flat_state\nfrom .statelib import to_pure_dict as to_pure_dict\nfrom .statelib import replace_by_pure_dict as replace_by_pure_dict\nfrom .statelib import restore_int_paths as restore_int_paths\nfrom .statelib import filter_state as filter_state\nfrom .statelib import merge_state as merge_state\nfrom .statelib import split_state as split_state\nfrom .statelib import map_state as map_state\nfrom .training import metrics as metrics\nfrom .variablelib import Param as Param\n# this needs to be imported before optimizer to prevent circular import\nfrom .training import optimizer as optimizer\nfrom .training.metrics import Metric as Metric\nfrom .training.metrics import MultiMetric as MultiMetric\nfrom .training.optimizer import OptState as OptState\nfrom .training.optimizer import OptArray as OptArray\nfrom .training.optimizer import OptVariable as OptVariable\nfrom .training.optimizer import Optimizer as Optimizer\nfrom .training.optimizer import ModelAndOptimizer as ModelAndOptimizer\nfrom .training.optimizer import OptState as OptState\nfrom .transforms.autodiff import DiffState as DiffState\nfrom .transforms.autodiff import grad as grad\nfrom .transforms.autodiff import value_and_grad as value_and_grad\nfrom .transforms.autodiff import custom_vjp as custom_vjp\nfrom .transforms.autodiff import vjp as vjp\nfrom .transforms.autodiff import jvp as jvp\nfrom .transforms.autodiff import remat as remat\nfrom .transforms.compilation import jit as jit\nfrom .transforms.compilation import jit_partial as jit_partial\nfrom .transforms.compilation import shard_map as shard_map\nfrom .transforms.compilation import StateSharding as StateSharding\nfrom .transforms.iteration import Carry as Carry\nfrom .transforms.iteration import scan as scan\nfrom .transforms.iteration import vmap as vmap\nfrom .transforms.iteration import pmap as pmap\nfrom .transforms.transforms import eval_shape as eval_shape\nfrom .transforms.transforms import cond as cond\nfrom .transforms.transforms import switch as switch\nfrom .transforms.transforms import checkify as checkify\nfrom .transforms.iteration import while_loop as while_loop\nfrom .transforms.iteration import fori_loop as fori_loop\nfrom .transforms.iteration import StateAxes as StateAxes\nfrom .transforms.iteration import transform_metadata as transform_metadata\nfrom .variablelib import A as A\nfrom .variablelib import BatchStat as BatchStat\nfrom .variablelib import Cache as Cache\nfrom .variablelib import Intermediate as Intermediate\nfrom .variablelib import Perturbation as Perturbation\nfrom .variablelib import Variable as Variable\nfrom .variablelib import VariableMetadata as VariableMetadata\nfrom .variablelib import with_metadata as with_metadata\nfrom .variablelib import variable_type_from_name as variable_type_from_name\nfrom .variablelib import variable_name_from_type as variable_name_from_type\nfrom .variablelib import register_variable_name as register_variable_name\nfrom .variablelib import use_eager_sharding as use_eager_sharding, using_eager_sharding as using_eager_sharding, var_defaults as var_defaults\nfrom .visualization import display as display\nfrom .extract import to_tree as to_tree\nfrom .extract import from_tree as from_tree\nfrom .extract import NodeStates as NodeStates\nfrom .summary import tabulate as tabulate\nfrom . import traversals as traversals\nfrom . import graphlib as graphlib\n# import last to prevent potential import cycles\nfrom . import graph as graph\nfrom . import compat as compat\n\nimport typing as _tp\n\nif _tp.TYPE_CHECKING:\n  VariableState = Variable\nelse:\n  def __getattr__(name):\n    if name == \"VariableState\":\n      import warnings\n      warnings.warn(\n          \"'VariableState' was removed, this is just an alias to 'Variable'. \"\n          \"Plase use 'Variable' directly instead.\",\n          DeprecationWarning,\n          stacklevel=2,\n      )\n      return Variable\n    raise AttributeError(f\"Module {__name__} has no attribute '{name}'\")\n"
  },
  {
    "path": "flax/nnx/bridge/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nfrom .wrappers import functional as functional\nfrom .wrappers import Functional as Functional\nfrom .wrappers import ToNNX as ToNNX\nfrom .wrappers import lazy_init as lazy_init\nfrom .wrappers import ToLinen as ToLinen\nfrom .wrappers import to_linen as to_linen\nfrom .variables import NNXMeta as NNXMeta\nfrom .variables import with_partitioning as with_partitioning\nfrom .module import Module as Module\nfrom .module import Scope as Scope\nfrom .module import AttrPriority as AttrPriority\nfrom .module import compact as compact\nfrom .module import current_context as current_context\nfrom .module import current_module as current_module\nfrom .interop import nnx_in_bridge_mdl as nnx_in_bridge_mdl\nfrom .interop import linen_in_bridge_mdl as linen_in_bridge_mdl\nfrom flax.nnx.nn import initializers as initializers\n"
  },
  {
    "path": "flax/nnx/bridge/interop.py",
    "content": "# Copyright 2025 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing as tp\n\nfrom flax.linen import module as nn_module\nfrom flax.nnx import graphlib, rnglib\nfrom flax.nnx.bridge import wrappers\nfrom flax.nnx.bridge import module as bdg_module\nimport flax.nnx.module as nnx_module\nfrom flax.nnx.transforms.transforms import eval_shape as nnx_eval_shape\nfrom flax.nnx.transforms.compilation import jit as nnx_jit\n\n\ndef nnx_in_bridge_mdl(factory: tp.Callable[[rnglib.Rngs], nnx_module.Module],\n                      name: str | None = None) -> nnx_module.Module:\n  \"\"\"Make pure NNX modules a submodule of a bridge module.\n\n  Create module at init time, or make abstract module and let parent bind\n  it with its state.\n  Use current bridge module scope for RNG generation.\n\n  Args:\n    factory: a function that takes an `nnx.Rngs` arg and returns an NNX module.\n    name: the name of the module. Only used during `bridge.compact` functions;\n      in setup() function the user will set it to an attribute explicitly.\n  Returns:\n    A submodule (`nnx.Module`) of the bridge module.\n  \"\"\"\n  parent_ctx, parent = bdg_module.current_context(), bdg_module.current_module()\n  assert parent_ctx is not None and parent is not None, 'nnx_in_bridge_mdl() only needed inside bridge Module'\n  parent = parent_ctx.module\n  assert parent.scope is not None\n\n  if parent.is_initializing():\n    module = factory(parent.scope.rngs)\n  else:\n    rngs = parent.scope.rngs if parent.scope.rngs else rnglib.Rngs(7)  # dummy\n    module = nnx_eval_shape(factory, rngs, graph=True)\n\n    @nnx_jit\n    def rng_state(rngs):\n      return graphlib.state(factory(rngs), rnglib.RngState, graph=True)\n\n    # Make sure the internal rng state is not abstract - other vars shall be\n    if parent.scope.rngs:\n      graphlib.update(module, rng_state(parent.scope.rngs))\n\n  # Automatically set the attribute if compact. If setup, user is responsible\n  # for setting the attribute of the superlayer.\n  if parent_ctx.in_compact:\n    if name is None:\n      name = bdg_module._auto_submodule_name(parent_ctx, type(module))\n    setattr(parent, name, module)\n  return module\n\n\ndef linen_in_bridge_mdl(linen_module: nn_module.Module,\n                        name: str | None = None) -> nnx_module.Module:\n  \"\"\"Make Linen modules a submodule of a bridge module using wrappers.ToNNX().\n\n  Args:\n    linen_module: the underlying Linen module instance.\n    name: the name of the module. Only used during `bridge.compact` functions;\n      in setup() function the user will set it to an attribute explicitly.\n  Returns:\n    A submodule (`nnx.Module`) of the bridge module.\n  \"\"\"\n  parent_ctx, parent = bdg_module.current_context(), bdg_module.current_module()\n  assert parent_ctx is not None and parent is not None, 'linen_in_bridge_mdl() only needed inside bridge Module'\n  assert parent.scope is not None\n  module = wrappers.ToNNX(linen_module, parent.scope.rngs)\n  wrappers._set_initializing(module, parent.is_initializing())\n  if parent_ctx.in_compact:\n    if name is None:\n      name = bdg_module._auto_submodule_name(parent_ctx, type(linen_module))\n    setattr(parent, name, module)\n  return module\n"
  },
  {
    "path": "flax/nnx/bridge/module.py",
    "content": "# Copyright 2025 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom __future__ import annotations\n\nfrom collections import defaultdict\nimport dataclasses\nimport enum\nimport functools\nimport inspect\nimport threading\nimport typing as tp\nimport jax\nimport typing_extensions as tpe\n\nfrom flax import errors\nfrom flax.core import meta\nfrom flax.core.scope import CollectionFilter\nfrom flax.core.frozen_dict import FrozenDict\nfrom flax.nnx import graphlib, rnglib, statelib, traversals\nfrom flax.nnx import variablelib\nimport flax.nnx.module as nnx_module\nfrom flax.nnx.pytreelib import Pytree\nfrom flax.nnx import variablelib\nfrom flax.nnx.bridge import variables as bridge_variables\nimport numpy as np\n\nA = tp.TypeVar('A')\nM = tp.TypeVar('M', bound='Module')\nF = tp.TypeVar('F', bound=tp.Callable[..., tp.Any])\n\n\n@dataclasses.dataclass\nclass ModuleStackEntry:\n  module: Module\n  in_compact: bool\n  type_counter: defaultdict[type, int] = dataclasses.field(\n    default_factory=lambda: defaultdict(int)\n  )\n\n\n@dataclasses.dataclass\nclass ModuleContext(threading.local):\n  module_stack: list[ModuleStackEntry | None] = dataclasses.field(\n    default_factory=lambda: [None]\n  )\n\n\nMODULE_CONTEXT = ModuleContext()\n\n\nclass ModuleState(statelib.State):\n  pass\n\nfrom flax.nnx.pytreelib import register_data_type\nregister_data_type(ModuleState)\n\n\nclass Scope(Pytree):\n  def __init__(self, rngs: rnglib.Rngs, mutable: CollectionFilter):\n    self.rngs = rngs\n    self.mutable = mutable\n\n  def copy(self):\n    return Scope(self.rngs, self.mutable)\n\n\nclass _HasSetup(tp.Protocol):\n  def setup(self) -> None: ...\n\n\ndef has_setup(x: tp.Any) -> tp.TypeGuard[_HasSetup]:\n  return hasattr(x, 'setup')\n\n\ndef _maybe_call_setup(module: Module):\n  if (\n    has_setup(module)\n    and isinstance(module, Module)\n    and not module._pytree__state.is_setup\n  ):\n    # void parent context\n    MODULE_CONTEXT.module_stack.append(\n      ModuleStackEntry(module, in_compact=False)\n    )\n    try:\n      module.setup()  # type: ignore[attribute-error]\n      module._pytree__state._is_setup = True\n    finally:\n      MODULE_CONTEXT.module_stack.pop()\n\n\ndef _bind_module(parent: Module, module: Module) -> Module:\n  assert parent.scope is not None\n\n  for _, value in reversed(list(graphlib.iter_graph(module, graph=True))):\n    if isinstance(value, Module):\n      if module.scope is None:\n        value.scope = parent.scope.copy()  # type: ignore[attribute-error]\n      _maybe_call_setup(value)\n  return module\n\n\ndef current_context() -> ModuleStackEntry | None:\n  return MODULE_CONTEXT.module_stack[-1]\n\n\ndef current_module() -> Module | None:\n  \"\"\"A quick util to get the current bridge module.\"\"\"\n  ctx = current_context()\n  if ctx is None:\n    return None\n  return ctx.module\n\n\ndef _auto_submodule_name(parent_ctx, cls):\n  \"\"\"Increment type count and generate a new submodule name.\"\"\"\n  type_index = parent_ctx.type_counter[cls]\n  parent_ctx.type_counter[cls] += 1\n  return f'{cls.__name__}_{type_index}'\n\n\nclass ModuleMeta(nnx_module.ModuleMeta):\n\n  def _pytree_meta_construct(cls, self, *args, **kwargs):\n    vars(self)['scope'] = None\n    super()._pytree_meta_construct(self, *args, **kwargs)\n\n\ndef _module_meta_call(cls: type[M], *args, **kwargs) -> M:\n  # compact behavior\n  parent_ctx = MODULE_CONTEXT.module_stack[-1]\n  parent = None\n  module: M\n\n  name = None\n  if parent_ctx is not None:\n    if 'parent' in kwargs:\n      parent = kwargs.pop('parent')\n      if parent_ctx.in_compact and parent is not None:\n        raise ValueError(\n          f\"'parent' can only be set to None, got {type(parent).__name__}\"\n        )\n    else:\n      parent = parent_ctx.module\n\n    if 'name' in kwargs:\n      name = kwargs['name']\n      if not 'name' in inspect.get_annotations(cls):\n         kwargs.pop('name')\n      if not isinstance(name, str):\n        raise ValueError(f\"'name' must be a 'str', got {type(name).__name__}\")\n    elif parent_ctx.in_compact:\n      name = _auto_submodule_name(parent_ctx, cls)\n\n  module = nnx_module.ModuleMeta.__call__(cls, *args, **kwargs)\n  module.scope = None\n  module.attr_priorities = {}\n\n  if parent is not None:\n    assert parent.scope is not None\n    # compact, or setup if `name` exists\n    if name is not None:\n      setattr(parent, name, module)\n      parent.set_attr_priority(name, AttrPriority.INIT_PARENT)\n\n  return module  # type: ignore\n\n# set ModuleMeta.__call__ like this because pytype doesn't understand\n# the use of TYPE_CHECKING conditionals for metaclass methods\nModuleMeta.__call__ = _module_meta_call  # type: ignore\n\nclass AttrPriority(enum.IntEnum):\n  HIGH = 0\n  INIT_PARENT = 20\n  DEFAULT = 50\n  LOW = 100\n\n\nclass PriorityStr(str):\n  _priority: AttrPriority\n\n  def __new__(cls, priority: AttrPriority, value: str):\n    obj = super().__new__(cls, value)\n    obj._priority = priority\n    return obj\n\n  def _check_and_get_priority(self, other) -> AttrPriority:\n    if not isinstance(other, (str, PriorityStr)):\n      raise NotImplementedError(\n        f'Cannot compare {type(self)} with {type(other)}'\n      )\n    if isinstance(other, PriorityStr):\n      return other._priority\n    return AttrPriority.DEFAULT\n\n  def __lt__(self, other) -> bool:\n    other_priority = self._check_and_get_priority(other)\n    if self._priority == other_priority:\n      return super().__lt__(other)\n    return self._priority < other_priority\n\n  def __gt__(self, other) -> bool:\n    other_priority = self._check_and_get_priority(other)\n    if self._priority == other_priority:\n      return super().__gt__(other)\n    return self._priority > other_priority\n\nclass ModuleBase:\n  if tp.TYPE_CHECKING:\n    scope: Scope | None\n    attr_priorities: dict[str, AttrPriority]\n\n\n@tpe.dataclass_transform(field_specifiers=(dataclasses.field,))  # type: ignore[not-supported-yet]\nclass Module(nnx_module.Module, ModuleBase, metaclass=ModuleMeta):\n  def __init_subclass__(cls) -> None:\n    super().__init_subclass__(pytree=False)\n\n    cls = dataclasses.dataclass(repr=False)(cls)\n    cls.__hash__ = object.__hash__  # type: ignore[method-assign]\n\n  def __getattribute__(self, name: str):\n    return type(self)._getattr(self, name)\n\n  def _getattr(self, name: str) -> tp.Any:\n    value = super().__getattribute__(name)\n    if isinstance(value, ModuleState):\n      raise AttributeError\n    return value\n\n  def _setattr(self, name: str, value: tp.Any) -> None:\n    if self.scope is not None:\n      if name in vars(self) and isinstance(\n        state := vars(self)[name], ModuleState\n      ):\n        graphlib.update(value, state)\n      for leaf in jax.tree.leaves(value, is_leaf=graphlib.is_graph_node):\n        if isinstance(leaf, Module):\n          leaf._pytree__state._initializing = self.is_initializing()\n          _bind_module(self, leaf)\n    super()._setattr(name, value)\n\n  def _graph_node_flatten(self):\n    nodes = vars(self).copy()\n    keys = (\n      PriorityStr(self.attr_priorities.get(k, AttrPriority.DEFAULT), k)\n      for k in nodes.keys()\n    )\n    sorted_nodes = list((k, nodes[k]) for k in sorted(keys))\n    return sorted_nodes, type(self)\n\n  def set_attr_priority(self, name: str, value: AttrPriority):\n    self.attr_priorities[name] = value\n\n  def make_rng(self, name: str = 'default') -> jax.Array:\n    if self.scope is None:\n      raise ValueError(\"Can't use RNGs on unbound modules\")\n    return self.scope.rngs[name]()  # type: ignore[attribute-error]\n\n  def param(  # type: ignore[invalid-annotation]\n    self,\n    name: str,\n    init_fn: tp.Callable[..., A],\n    *init_args,\n    unbox: bool = True,\n    **init_kwargs,\n  ) -> variablelib.Param[A]:\n    # TODO(cgarciae): implement same condition as linen\n    if self.scope is None:\n      raise ValueError(\n        'Parameters must be initialized in `setup()` or in a method '\n        'wrapped in `@compact`'\n      )\n    if hasattr(self, name):\n      value = getattr(self, name)\n      # TODO(cgarciae): implement reservations\n      # if self._name_taken(name):\n      #   raise errors.NameInUseError('param', name, self.__class__.__name__)\n\n      if isinstance(value, variablelib.Variable):\n        if not isinstance(value, variablelib.Param):\n          raise ValueError(\n            f\"Expected '{name}' to be a Param, got {type(value).__name__}\"\n          )\n        return value\n\n      abs_value = jax.eval_shape(\n        lambda: init_fn(jax.random.key(0), *init_args, **init_kwargs)\n      )\n      abs_value_flat = jax.tree.leaves(abs_value)\n      value_flat = jax.tree.leaves(value)\n      for val, abs_val in zip(value_flat, abs_value_flat):\n        if np.shape(val) != np.shape(abs_val):\n          raise errors.ScopeParamShapeError(\n            name, '', np.shape(abs_val), np.shape(val)\n          )\n\n      if isinstance(abs_value, variablelib.VariableMetadata):\n        abs_value.raw_value = value\n        value = abs_value\n\n      variable = variablelib.Param(value)\n    else:\n      value = init_fn(self.make_rng('params'), *init_args, **init_kwargs)\n      variable = variablelib.Param(value)\n\n    setattr(self, name, variable)\n    return variable\n\n  def variable(  # type: ignore[invalid-annotation]\n    self,\n    col: str,\n    name: str,\n    init_fn: tp.Callable[..., A] | None = None,\n    *init_args,\n    unbox: bool = True,\n    **init_kwargs,\n  ) -> variablelib.Variable[A]:\n    variable_type = variablelib.variable_type_from_name(\n      col, allow_register=True\n    )\n    if self.scope is None:\n      raise ValueError(\n        'Variables must be initialized in `setup()` or in a method '\n        'wrapped in `@compact`'\n      )\n\n    if hasattr(self, name):\n      value = getattr(self, name)\n      # TODO(cgarciae): implement reservations\n      # if self._name_taken(name):\n      #   raise errors.NameInUseError('param', name, self.__class__.__name__)\n\n      if isinstance(value, variablelib.Variable):\n        return value\n\n      if init_fn is None:\n        raise ValueError(f\"Expected 'init_fn' to be a callable, got None\")\n\n      abs_value = jax.eval_shape(lambda: init_fn(*init_args, **init_kwargs))\n      abs_value_flat = jax.tree.leaves(abs_value)\n      value_flat = jax.tree.leaves(value)\n      for val, abs_val in zip(value_flat, abs_value_flat):\n        if np.shape(val) != np.shape(abs_val):\n          raise errors.ScopeParamShapeError(\n            name, '', np.shape(abs_val), np.shape(val)\n          )\n\n      if isinstance(abs_value, variablelib.VariableMetadata):\n        abs_value.raw_value = value\n        value = abs_value\n\n      variable = variable_type(value)\n    else:\n      if init_fn is None:\n        raise ValueError(f\"Expected 'init_fn' to be a callable, got None\")\n\n      value = init_fn(*init_args, **init_kwargs)\n      variable = variable_type(value)\n\n    setattr(self, name, variable)\n    return variable\n\n  def _get_variables(self) -> tp.Mapping:\n    state = graphlib.state(self, graph=True)\n    _variables: dict = {}\n\n    variable: variablelib.Variable\n    for path, variable in statelib.to_flat_state(state):\n      if isinstance(variable, rnglib.RngState):\n        # Don't return RNG states, since Linen doesn't have them.\n        continue\n\n      try:\n        collection = variablelib.variable_name_from_type(type(variable))\n      except ValueError:\n        collection = type(variable).__name__\n\n      if collection not in _variables:\n        _variables[collection] = {}\n\n      if isinstance(\n        variable, variablelib.Variable\n      ) and bridge_variables.is_vanilla_variable(variable):\n        leaf = variable.get_value()\n      else:\n        leaf = bridge_variables.to_linen_var(variable)\n\n      _variables[collection][path] = leaf\n\n    _variables = {\n      collection: traversals.unflatten_mapping(flat_state)\n      for collection, flat_state in _variables.items()\n    }\n\n    return _variables\n\n  @property\n  def variables(self):\n    _variables = FrozenDict(self._get_variables())\n    return _variables\n\n  def apply(\n    self,\n    variables: dict[str, tp.Mapping],\n    *args,\n    rngs: int | jax.Array | dict[str, jax.Array] | rnglib.Rngs | None = None,\n    method: tp.Callable[..., tp.Any] | str = '__call__',\n    mutable: CollectionFilter = False,\n    _initialize: bool = False,\n    **kwargs,\n  ) -> tp.Any:\n    module = graphlib.clone(self, graph=True)\n\n    # create variables\n    real_variables = dict(variables)\n    for col_name, linen_collection in variables.items():\n\n      def to_variable(value):\n        return bridge_variables.to_nnx_var(col_name, value)\n\n      linen_collection = jax.tree.map(\n        to_variable,\n        linen_collection,\n        is_leaf=lambda x: isinstance(x, meta.AxisMetadata),\n      )\n      real_variables[col_name] = linen_collection\n\n    states = ({},) if not real_variables else real_variables.values()\n    state = statelib.merge_state(*states, cls=ModuleState)\n    graphlib.update(module, state)\n\n    if rngs is None:\n      rngs = rnglib.Rngs()\n    elif isinstance(rngs, jax.Array | int):\n      rngs = rnglib.Rngs(rngs)\n    elif isinstance(rngs, dict):\n      rngs = rnglib.Rngs(**rngs)\n\n    # get method\n    _method: tp.Callable[..., tp.Any]\n    if isinstance(method, str):\n      attribute_name = method\n      _method = getattr(module, attribute_name)\n      if not callable(_method):\n        class_name = type(module).__name__\n        raise TypeError(\n          f\"'{class_name}.{attribute_name}' must be a callable, got\"\n          f' {type(_method)}.'\n        )\n      # if the `method` string is a submodule, we create a lambda function\n      # that calls the submodule, forwarding all arguments.\n      if isinstance(_method, Module):\n        _method = lambda module, *args, **kwargs: getattr(\n          module, attribute_name\n        )(*args, **kwargs)\n    else:\n      _method = method\n    _method = _get_unbound_fn(_method)\n\n    # set temporary state\n    for _, value in graphlib.iter_graph(module, graph=True):\n      if isinstance(value, Pytree):\n        value._pytree__state._initializing = _initialize\n      if isinstance(value, Module):\n        value.scope = Scope(rngs, mutable)\n        _maybe_call_setup(value)\n\n    MODULE_CONTEXT.module_stack.append(\n      ModuleStackEntry(module, in_compact=False)\n    )\n    try:\n      out = _method(module, *args, **kwargs)\n    finally:\n      MODULE_CONTEXT.module_stack.pop()\n      # reset temporary state\n      for _, value in graphlib.iter_graph(module, graph=True):\n        if isinstance(value, Pytree):\n          value._pytree__state._initializing = False\n        if isinstance(value, Module):\n          value.scope = None\n\n    _variables: tp.Mapping = module._get_variables()\n\n    if mutable is False:\n      return out\n    else:\n      return out, _variables\n\n  def init(\n    self,\n    rngs: int | jax.Array | dict[str, jax.Array] | rnglib.Rngs | None = None,\n    *args,\n    method: tp.Callable[..., tp.Any] | str = '__call__',\n    **kwargs,\n  ):\n    out, variables = self.apply(\n      {},\n      *args,\n      _initialize=True,\n      mutable=True,\n      rngs=rngs,\n      method=method,\n      **kwargs,\n    )\n    return variables\n\n  def init_with_output(\n    self,\n    rngs: int | jax.Array | dict[str, jax.Array] | rnglib.Rngs | None = None,\n    *args,\n    method: tp.Callable[..., tp.Any] | str = '__call__',\n    mutable: tp.Any = False,\n    # capture_intermediates: bool | Callable[['Module', str], bool] = False,\n    **kwargs,\n  ) -> tuple[tp.Any, dict[str, tp.Mapping]]:\n    return self.apply(\n      {},\n      *args,\n      rngs=rngs,\n      method=method,\n      mutable=True,\n      _initialize=True,\n      **kwargs,\n    )\n\n  def is_initializing(self) -> bool:\n    return self._pytree__state._initializing\n\n\ndef compact(f: F) -> F:\n  @functools.wraps(f)\n  def compact_wrapper(self, *args, **kwargs):\n    if not isinstance(self, Module):\n      raise ValueError(\n        f\"Expected 'self' to be a nnx.bridge.Module, got {type(self).__name__}\"\n      )\n\n    MODULE_CONTEXT.module_stack.append(ModuleStackEntry(self, in_compact=True))\n\n    try:\n      return f(self, *args, **kwargs)\n    finally:\n      MODULE_CONTEXT.module_stack.pop()\n\n  return compact_wrapper  # type: ignore\n\n\ndef _get_unbound_fn(method_or_fn: tp.Callable) -> tp.Callable:\n  if inspect.ismethod(method_or_fn) and isinstance(\n    method_or_fn.__self__, Module\n  ):  # pytype: disable=attribute-error\n    method_or_fn = method_or_fn.__func__  # pytype: disable=attribute-error\n  if (\n    not callable(method_or_fn)\n    or len(inspect.signature(method_or_fn).parameters) < 1\n  ):\n    raise errors.ApplyModuleInvalidMethodError(method_or_fn)\n\n  return method_or_fn\n\n"
  },
  {
    "path": "flax/nnx/bridge/variables.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom typing import Any, TypeVar\nimport typing as tp\n\nimport jax\nfrom flax import struct\nfrom flax.core import meta\nfrom flax.nnx import spmd\nfrom flax.nnx import traversals\nfrom flax.nnx import variablelib\nfrom flax.typing import LogicalNames\n\n\nA = TypeVar('A')\nB = TypeVar('B')\n\n\ndef sort_variable_types(types: tp.Iterable[type]):\n  def _variable_parents_count(t: type):\n    return sum(1 for p in t.mro() if issubclass(p, variablelib.Variable))\n  parent_count = {t: _variable_parents_count(t) for t in types}\n  return sorted(types, key=lambda t: -parent_count[t])\n\n\n#############################################\n### NNX Variable <-> Linen metadata boxes ###\n#############################################\n\n\nclass NNXMeta(struct.PyTreeNode, meta.AxisMetadata[A]):\n  \"\"\"Default Flax metadata class for `nnx.Variable`.\"\"\"\n\n  var_type: type[variablelib.Variable[tp.Any]] = struct.field(pytree_node=False)\n  value: Any = struct.field(pytree_node=True)\n  metadata: dict[str, tp.Any] = struct.field(pytree_node=False)\n\n  def unbox(self) -> A:\n    return self.value\n\n  def replace_boxed(self, val: B) -> 'NNXMeta[B]':\n    return self.replace(value=val)  # type: ignore\n\n  def add_axis(self, index: int, params: dict[Any, Any]) -> 'NNXMeta[A]':\n    # TODO: implement this, supporting hooks\n    return self\n\n  def remove_axis(self, index: int, params: dict[Any, Any]) -> 'NNXMeta[A]':\n    # TODO: implement this, supporting hooks\n    return self\n\n  def get_partition_spec(self) -> jax.sharding.PartitionSpec:\n    \"\"\"Returns the ``Partitionspec`` for this partitioned value.\"\"\"\n    nnx_var = self.to_nnx_variable()\n    spec = spmd.get_partition_spec(nnx_var).get_raw_value()\n    assert isinstance(spec, jax.sharding.PartitionSpec)\n    return spec\n\n  def to_nnx_variable(self) -> variablelib.Variable:\n    return self.var_type(self.value, **self.metadata)\n\n\ndef is_vanilla_variable(vs: variablelib.Variable) -> bool:\n  \"\"\"A variable is vanilla if its metadata is essentially blank.\n\n  Returns False only if it has non-empty hooks or any non-built-in attribute.\n  \"\"\"\n  for key, value in vs.get_metadata().items():\n    if key in variablelib.Variable.required_metadata:\n      continue\n    if key.endswith('_hooks') and value == ():\n      continue\n    return False\n  return True\n\n\ndef to_linen_var(vs: variablelib.Variable) -> meta.AxisMetadata:\n  metadata = vs.get_metadata()\n  if 'linen_meta_type' in metadata:\n    linen_type = metadata['linen_meta_type']\n    if hasattr(linen_type, 'from_nnx_metadata'):\n      return linen_type.from_nnx_metadata({'value': vs.get_value(), **metadata})\n    return linen_type(vs.get_value(), **metadata)\n  if is_vanilla_variable(vs):\n    return vs.get_value()\n  return NNXMeta(type(vs), vs.get_value(), metadata)\n\n\ndef get_col_name(keypath: tp.Sequence[Any]) -> str:\n  \"\"\"Given the keypath of a Flax variable type, return its Linen collection name.\"\"\"\n  # Infer variable type from the leaf's path, which contains its Linen collection name\n  assert isinstance(keypath[0], jax.tree_util.DictKey)\n  return str(keypath[0].key)\n\n\ndef to_nnx_var(col: str, x: meta.AxisMetadata | Any) -> variablelib.Variable:\n  \"\"\"Convert a Linen variable to an NNX variable.\"\"\"\n  vtype = variablelib.variable_type_from_name(col, allow_register=True)\n  if isinstance(x, NNXMeta):\n    assert vtype == x.var_type, f'Type stored in NNXMeta {x.var_type} != type inferred from collection name {vtype}'\n    return x.to_nnx_variable()\n  if isinstance(x, meta.AxisMetadata):\n    x_metadata = vars(x)\n    if hasattr(x, 'to_nnx_metadata'):\n      x_metadata = x.to_nnx_metadata()\n    assert hasattr(x, 'value')\n    return vtype(**x_metadata, linen_meta_type=type(x))\n  return vtype(x)\n\n\ndef _recursive_merge(dict1, dict2):\n  \"\"\"Recursively merge two dicts.\"\"\"\n  flat_map = traversals.flatten_mapping(dict1)\n  flat_map |= traversals.flatten_mapping(dict2)\n  return traversals.unflatten_mapping(flat_map)\n\n\ndef linen_vars_to_nnx_attrs(variables: tp.Mapping[str, Any]) -> dict[str, Any]:\n  \"\"\"Convert a dict of Linen-style variables to NNX variables.\"\"\"\n  nnx_vars = jax.tree_util.tree_map_with_path(\n      lambda kp, x: to_nnx_var(get_col_name(kp), x),\n      variables,\n      is_leaf=lambda x: not isinstance(x, dict),\n  )\n  flat_paths: dict[tuple, tp.Any] = {}\n  for col_name, col_variables in nnx_vars.items():  # pylint: disable=unused-variable\n    for path, variable in traversals.flatten_mapping(col_variables).items():\n      if path in flat_paths:\n        raise ValueError(\n            f\"Found duplicate variable path {path} with variables \"\n            f\"{flat_paths[path]} and {variable}. \"\n            \"This is not allowed in NNX.\"\n        )\n      flat_paths[path] = variable\n\n  nnx_vars = traversals.unflatten_mapping(flat_paths)\n  return nnx_vars\n\n\ndef nnx_attrs_to_linen_vars(nnx_attrs: dict) -> dict:\n  \"\"\"Convert a dict of NNX variables to Linen-style variables.\"\"\"\n  linen_structured = {}\n  for kp, v in traversals.flatten_mapping(nnx_attrs).items():\n    if isinstance(v, variablelib.Variable):\n      col_name = variablelib.variable_name_from_type(type(v))\n      v = to_linen_var(v.to_state())\n    else:\n      raise ValueError(f'Cannot infer collection name from value: {v}')\n    linen_structured[(col_name, *kp)] = v\n  variables = traversals.unflatten_mapping(linen_structured)\n  return variables\n\n\n\ndef with_partitioning(\n    fn: tp.Callable[..., tp.Any],\n    names: LogicalNames,\n    mesh: jax.sharding.Mesh | None = None,\n) -> tp.Callable[..., meta.Partitioned[tp.Any]]:\n  \"\"\"Same interface as Linen, but calls NNX `with_partitioning` within.\"\"\"\n  return spmd.with_partitioning(fn, names, mesh,\n                                linen_meta_type=meta.Partitioned)"
  },
  {
    "path": "flax/nnx/bridge/wrappers.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom functools import partial\nimport typing as tp\nfrom typing import Any\nimport warnings\nimport dataclasses\n\nfrom flax import linen\nfrom flax import core\nfrom flax import nnx\nfrom flax.core import FrozenDict\nfrom flax.core import meta\nfrom flax.nnx import graphlib\nfrom flax.nnx import variablelib\nfrom flax.nnx.bridge import variables as bv\nfrom flax.nnx.bridge import module as bdg_module\nfrom flax.nnx.module import Module\nfrom flax.nnx.statelib import State\nfrom flax.nnx.pytreelib import Pytree\nfrom flax.nnx.rnglib import Rngs\nimport jax\nfrom jax import tree_util as jtu\n\nM = tp.TypeVar('M', bound=Module)\n\n# Flax-like style is NNX\n@dataclasses.dataclass\nclass Functional(tp.Generic[M]):\n  module_type: tp.Type[M]\n  graphdef: tp.Optional[graphlib.GraphDef[M]]\n  args: tuple[tp.Any, ...]\n  kwargs: dict[str, tp.Any]\n\n  def init(self, *, rngs: tp.Optional[Rngs] = None) -> State:\n    kwargs = {}\n    if rngs is not None:\n      kwargs['rngs'] = rngs\n    module = self.module_type(*self.args, **self.kwargs, **kwargs)\n    graphdef, state = nnx.split(module)\n    self.graphdef = graphdef\n    return state  # type: ignore\n\n  def apply(self, *states: tp.Any):\n    assert self.graphdef is not None\n    return self.graphdef.apply(*states)\n\n\ndef functional(cls: tp.Type[M]) -> tp.Callable[..., Functional[M]]:\n  def _functional_constructor(*args: tp.Any, **kwargs: tp.Any) -> Functional[M]:\n    return Functional(cls, None, args, kwargs)\n\n  return _functional_constructor\n\n\ndef _set_initializing(module: Module, initializing: bool):\n  for _, value in graphlib.iter_graph(module, graph=True):\n    if isinstance(value, Pytree):\n      value._pytree__state._initializing = initializing\n\n\ndef lazy_init(fn: Module | tp.Callable[..., tp.Any], *args, **kwargs):\n  \"\"\"To run through an arbitrary nnx.Module method and initialize all its needed state.\n\n  Here used to trigger initialization of all `LinenToNNX` module variables.\"\"\"\n  if isinstance(fn, Module):\n    module = fn\n    assert callable(fn)\n  else:\n    if not (hasattr(fn, '__self__') and isinstance(fn.__self__, Module)):\n      raise ValueError(f'{fn = } needs to be a method of an NNX Module.')\n    module = fn.__self__\n  _set_initializing(module, True)\n  try:\n    _ = fn(*args, **kwargs)\n  finally:\n    _set_initializing(module, False)\n  return fn\n\ndef current_linen_module() -> linen.Module | None:\n  \"\"\"Get the current Linen module from the Linen context.\"\"\"\n  if linen.module._context.module_stack:  # pylint: disable=W0212\n    return linen.module._context.module_stack[-1]  # pylint: disable=W0212\n  return None\n\nclass ToNNX(Module):\n  \"\"\"A wrapper to turn any Linen module into an NNX module.\n\n  The result NNX module can be used standalone with all NNX APIs, or as a submodule of\n  another NNX module.\n\n  Since Linen module initialization requires a sample input, you need to call `lazy_init`\n  with an argument to initialize the variables.\n\n  Example::\n\n    >>> from flax import linen as nn, nnx\n    >>> import jax\n    >>> linen_module = nn.Dense(features=64)\n    >>> x = jax.numpy.ones((1, 32))\n    >>> # Like Linen init(), initialize with a sample input\n    >>> model = nnx.bridge.ToNNX(linen_module, rngs=nnx.Rngs(0)).lazy_init(x)\n    >>> # Like Linen apply(), but using NNX's direct call method\n    >>> y = model(x)\n    >>> model.kernel.shape\n    (32, 64)\n\n  Args:\n    module: The Linen Module instance.\n    rngs: The `nnx.Rngs` instance being passed to any NNX module.\n\n  Returns:\n    A stateful NNX module that behaves the same as the wrapped Linen module.\n  \"\"\"\n\n  def __init__(\n      self,\n      module: linen.Module,\n      rngs: Rngs | jax.Array | None = None,\n  ):\n    self.to_nnx__module = module\n\n    self.to_nnx__rngs: Rngs | None\n    if isinstance(rngs, jax.Array):\n      self.to_nnx__rngs = Rngs(params=rngs)\n    elif isinstance(rngs, nnx.Rngs):\n      self.to_nnx__rngs = rngs.fork()\n    else:\n      self.to_nnx__rngs = rngs\n\n  @property\n  def rngs(self) -> Rngs | None:\n    warnings.warn(\n        '`ToNNX.rngs` is deprecated. Please use `to_nnx__rngs` instead.',\n        DeprecationWarning,\n    )\n    return self.to_nnx__rngs\n\n  @property\n  def module(self) -> linen.Module:\n    warnings.warn(\n        '`ToNNX.module` is deprecated. Please use `to_nnx__module` instead.',\n        DeprecationWarning,\n    )\n    return self.to_nnx__module\n\n  def _setattr(self, name, value):\n    if not nnx.is_data(value) and nnx.has_data(value):\n      value = nnx.data(value)\n    super()._setattr(name, value)\n\n  def lazy_init(self, *args, **kwargs):\n    \"\"\"A shortcut of calling `nnx.bridge.lazy_init()` upon this module.\"\"\"\n    return lazy_init(self, *args, **kwargs)\n\n  def __getattr__(self, name: str):\n    if hasattr(super(), name):\n      return super().__getattribute__(name)\n    maybe_method = getattr(type(self.to_nnx__module), name, None)\n    if callable(maybe_method):\n      method = partial(self.__call__, method=maybe_method)\n      method.__self__ = self\n      return method\n    return super().__getattribute__(name)\n\n  def __call__(\n    self,\n    *args: Any,\n    rngs: Rngs | jax.Array | None = None,\n    method: tp.Callable[..., Any] | str | None = None,\n    mutable: tp.Any = None,\n    **kwargs: Any,\n  ) -> Any:\n    # Shape-based lazy init of the flax variables\n    if rngs is None:\n      rngs = self.to_nnx__rngs\n    if isinstance(rngs, nnx.Rngs):\n      _rngs = {name: stream() for name, stream in rngs.items()}\n    elif isinstance(rngs, jax.Array):\n      _rngs = {'params': rngs}\n    else:\n      _rngs = {}\n    # rename default to params\n    if 'params' not in _rngs and 'default' in _rngs:\n      _rngs['params'] = _rngs.pop('default')\n    if self._pytree__state.initializing:\n      out, updates = self.to_nnx__module.init_with_output(_rngs, *args, method=method, **kwargs)\n    else:\n      nnx_attrs = {\n          k: v\n          for k, v in vars(self).items()\n          if not k.startswith('to_nnx__') and not k.startswith('_pytree__')\n      }\n      variables = bv.nnx_attrs_to_linen_vars(nnx_attrs)\n\n      # Get `mutable` from top level bridge.Module context if any\n      if mutable is not None:\n        pass\n      elif (m := bdg_module.current_module()) is not None:  # type: ignore[assignment]\n        assert m.scope is not None\n        mutable = m.scope.mutable\n      elif (m := current_linen_module()) is not None:  # type: ignore[assignment]\n        assert m.scope is not None\n        mutable = m.scope.mutable\n      else:\n        mutable = False\n\n      out = self.to_nnx__module.apply(\n        variables, *args, rngs=_rngs, method=method, mutable=mutable, **kwargs\n      )\n\n      # Split out the updates if `mutable` is passed into the Flax module\n      if mutable is not False:\n        out, updates = out\n      else:\n        updates = None\n\n    # Split out the updates if `mutable` is passed into the Flax module\n    if updates:\n      nnx_attrs = bv.linen_vars_to_nnx_attrs(updates)\n      # nnx.update(self, nnx_attrs)\n      # TODO(cgarciae): ideally we just do an update but currently dictionaries don't allow\n      # insertion of new keys, we need to enable this in NNX to simplify the code bellow\n      # to the simple nnx.update(self, nnx_attrs) above.\n      for attr_name, value in nnx_attrs.items():\n        if hasattr(self, attr_name) and isinstance(value, dict):\n          original_value = getattr(self, attr_name)\n          new_values = bv._recursive_merge(original_value, value)\n          setattr(self, attr_name, nnx.data(new_values))\n        else:\n          setattr(self, attr_name, nnx.data(value))\n\n    return out\n\n\ndef linen_rngs_dict(linen_module: linen.Module, add_default: bool = False):\n  \"\"\"Given a module, split out one of its every active RNG key collections.\"\"\"\n  assert linen_module.scope is not None, 'linen_rngs_dict() must be called inside a Linen module.'\n  rngs: dict[str, tp.Any] = {\n      name: linen_module.make_rng(name)\n      for name in linen_module.scope.rngs.keys()\n  }\n  if add_default and 'default' not in rngs:\n    rngs['default'] = 0\n  return rngs\n\ndef _get_module_method(module, method: tp.Callable[..., Any] | str | None):\n  \"\"\"Get a callable method from the module, or raise TypeError.\"\"\"\n  if method is None:\n    method = '__call__'\n\n  if isinstance(method, str):\n    attribute_name = method\n    method = getattr(type(module), attribute_name)\n    if not callable(method):\n      class_name = type(module).__name__\n      raise TypeError(\n        f\"'{class_name}.{attribute_name}' must be a callable, got\"\n        f' {type(method)}.'\n      )\n  if not callable(method):\n    class_name = type(module).__name__\n    raise TypeError(\n      f\"'{method}' must be a callable, got {type(method)}.\"\n    )\n\n  return method\n\nclass ToLinen(linen.Module):\n  \"\"\"A wrapper to turn any NNX module into a Linen module.\n\n  The result Linen module can be used standalone with all Linen APIs, or as a\n  submodule of\n  another Linen module.\n\n  Since NNX modules are stateful and owns the state, we only create it once\n  during init\n  time, and will track its state and static data as separate variables.\n\n  Example::\n\n    >>> from flax import linen as nn, nnx\n    >>> import jax\n    >>> model = nnx.bridge.ToLinen(nnx.Linear, args=(32, 64))\n    >>> x = jax.numpy.ones((1, 32))\n    >>> y, variables = model.init_with_output(jax.random.key(0), x)\n    >>> y.shape\n    (1, 64)\n    >>> variables['params']['kernel'].shape\n    (32, 64)\n    >>> # The static GraphDef of the underlying NNX module\n    >>> variables.keys()\n    dict_keys(['params'])\n\n  Args:\n    nnx_class: The NNX Module class (not instance!).\n    args: The arguments that normally would be passed in to create the NNX\n      module.\n    kwargs: The keyword arguments that normally would be passed in to create the\n      NNX module.\n    skip_rng: True if this NNX module doesn't need `rngs` arg during\n      initialization (not common).\n\n  Returns:\n    A stateful NNX module that behaves the same as the wrapped Linen module.\n  \"\"\"\n\n  nnx_class: tp.Callable[..., Module]\n  args: tp.Sequence = ()\n  kwargs: tp.Mapping[str, tp.Any] = FrozenDict({})\n  skip_rng: bool = False\n  metadata_fn: tp.Callable[[variablelib.Variable], tp.Any] | None = bv.to_linen_var\n\n  @linen.compact\n  def __call__(\n    self, *args, nnx_method: tp.Callable[..., Any] | str | None = None, **kwargs\n  ):\n    def _module_kwargs():\n      maybe_add_default = not self.is_initializing()\n      module_kwargs = dict(self.kwargs)\n      if not self.skip_rng:\n        module_kwargs['rngs'] = nnx.Rngs(\n            **linen_rngs_dict(self, add_default=maybe_add_default)\n        )\n      return module_kwargs\n\n    # init codepath\n    if self.is_initializing():\n      module = self.nnx_class(*self.args, **_module_kwargs())\n      # TODO: add lazy_init here in case there's an `ToNNX` submodule under `module`.\n      # update linen variables before call module to save initial state\n      self._update_variables(module)\n      method_fn = _get_module_method(module, nnx_method)\n      out = method_fn(module, *args, **kwargs)\n      return out\n\n    # create the nnx module\n    module = self.nnx_class(*self.args, **_module_kwargs())\n\n    # update nnx module from linen variables\n    def maybe_unbox(x):\n      if isinstance(x, meta.AxisMetadata):\n        return x.unbox()\n      return x\n    states = jtu.tree_map(\n        maybe_unbox,\n        list(core.unfreeze(self.variables).values()),  # type: ignore[wrong-arg-types, arg-type]\n        is_leaf=lambda x: isinstance(x, meta.AxisMetadata),\n    )\n    if not states:\n      states = ({},)\n\n    new_state = nnx.merge_state(*states)\n    new_state_flat = nnx.traversals.flatten_mapping(new_state)\n    current_state_flat = nnx.traversals.flatten_mapping(nnx.state(module))\n    unknown_state_flat = {path: v for path, v in new_state_flat.items() if path not in current_state_flat}\n\n    if unknown_state_flat:\n      paths_str = \"\"\n      for path, _ in unknown_state_flat.items():\n        paths_str += f\"\\n  - {'/'.join(map(str, path))}\"\n\n      warnings.warn(f\"Found unknown module paths in incoming state:{paths_str}\")\n\n    nnx.update(module, new_state)\n\n    method_fn = _get_module_method(module, nnx_method)\n    out = method_fn(module, *args, **kwargs)\n    self._update_variables(module)\n    return out\n\n  def __getattr__(self, name: str):\n    if hasattr(super(), name):\n      return super().__getattribute__(name)\n    if name in self.kwargs:\n      return self.kwargs[name]\n    maybe_method = getattr(self.nnx_class, name, None)\n    if callable(maybe_method):\n      method = partial(self.__call__, nnx_method=maybe_method)\n      method.__self__ = self\n      return method\n    return super().__getattribute__(name)\n\n  def _update_variables(self, module):\n    \"\"\"Store the NNX module's graph def and state inside Linen module variables.\"\"\"\n    state = nnx.state(module, nnx.Not(nnx.RngState))\n\n    collection_flat_state: dict[str, list[tuple[tuple[tp.Any, ...], tp.Any]]] = {}\n\n    # group state by collection\n    for path, leaf in nnx.to_flat_state(state):\n      type_ = type(leaf)\n      collection = variablelib.variable_name_from_type(\n          type_, allow_register=True\n      )\n      if collection not in collection_flat_state:\n        collection_flat_state[collection] = []\n      collection_flat_state[collection].append((path, leaf))\n\n    # update linen variables\n    for collection, flat_state in collection_flat_state.items():\n      if self.is_mutable_collection(collection):\n\n        def _to_linen_var(x):\n          if isinstance(x, nnx.Variable):\n            if self.metadata_fn is not None:\n              return self.metadata_fn(x)  # pylint: disable=too-many-function-args\n            else:\n              return x.get_value()\n          return x\n\n        collection_state = nnx.traversals.unflatten_mapping(flat_state)\n        collection_state = jax.tree.map(\n            _to_linen_var,\n            collection_state,\n            is_leaf=lambda x: isinstance(x, nnx.Variable),\n        )\n        for k, v in collection_state.items():\n          self.put_variable(collection, k, v)\n\n\n\nclass _Missing:\n  ...\n\n\n_MISSING = _Missing()\n\n\ndef to_linen(\n    nnx_class: tp.Callable[..., Module],\n    *args,\n    metadata_fn: (\n        tp.Callable[[variablelib.Variable], tp.Any] | None\n    ) = bv.to_linen_var,\n    name: str | None = None,\n    skip_rng: bool = False,\n    abstract_init: bool = True,\n    **kwargs,\n):\n  \"\"\"Shortcut of `nnx.bridge.ToLinen` if user is not changing any of its default fields.\"\"\"\n  return ToLinen(\n      nnx_class,\n      args=args,\n      kwargs=FrozenDict(kwargs),\n      metadata_fn=metadata_fn,\n      skip_rng=skip_rng,\n      name=name,\n  )\n\ndef to_linen_class(\n    base_nnx_class: type[M],\n    base_metadata_fn: tp.Callable[[variablelib.VariableState], tp.Any] | None = bv.to_linen_var,\n    base_skip_rng: bool = False,\n    **partial_kwargs: tp.Any,\n) -> type[ToLinen]:\n  \"\"\"Dynamically wraps an NNX module class into a Flax Linen module class.\"\"\"\n\n  class ToLinenPartial(ToLinen):\n    \"\"\"A dynamically created Linen Module that wraps a specific NNX Module.\n\n    This class is not meant to be used directly. Instead, it is created and\n    returned by the `to_linen_class` function. It acts as a \"partially applied\"\n    version of the `ToLinen` wrapper, where the NNX module to be wrapped and\n    its default arguments are pre-configured.\n\n    When you instantiate this class, it behaves like a standard Linen module.\n    The arguments you provide during instantiation can override the defaults\n    that were set when this class was created by `to_linen_class`.\n\n    For example:\n      >>> from flax import linen as nn, nnx\n      >>> from maxtext.src.maxtext.layers import linears\n      >>> # Create a specialized Linen wrapper for linears.DenseGeneral\n      >>> LinenDenseGeneral = to_linen_class(linears.DenseGeneral)\n      >>> # Now, LinenDenseGeneral can be used like a regular Linen module\n      >>> class MyModel(nn.Module):\n      ...   def setup(self):\n      ...     # Instantiate the wrapped linears.DenseGeneral with its arguments\n      ...     self.dense = LinenDenseGeneral(\n      ...         in_features_shape=10, out_features_shape=5\n      ...     )\n      ...   def __call__(self, x):\n      ...     return self.dense(x)\n\n    Attributes:\n      (The attributes are dynamically set by the `ToLinen` parent class based\n       on the arguments provided during instantiation.)\n    \"\"\"\n\n    def __init_subclass__(cls, **kwargs):\n      super().__init_subclass__(**kwargs)\n\n      def __init__(\n          self,\n          args=None,\n          kwargs=None,\n          nnx_class=None,\n          skip_rng=None,\n          metadata_fn=None,\n          name=_MISSING,\n          parent=_MISSING,\n          **other_kwargs,\n      ):\n        linen_kwargs = {}\n        if not isinstance(parent, _Missing):\n          linen_kwargs[\"parent\"] = parent\n        if not isinstance(name, _Missing):\n          linen_kwargs[\"name\"] = name\n        ToLinen.__init__(\n            self,\n            nnx_class=nnx_class or base_nnx_class,\n            args=args or (),\n            metadata_fn=metadata_fn or base_metadata_fn,\n            skip_rng=skip_rng or base_skip_rng,\n            kwargs=FrozenDict({**partial_kwargs, **(kwargs or {}), **other_kwargs}),\n            **linen_kwargs,\n        )\n\n      cls.__init__ = __init__\n\n  return ToLinenPartial\n"
  },
  {
    "path": "flax/nnx/compat.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Compat API.\n\nCompatibility wrappers for NNX APIs. Each function in this module mirrors the\ncorresponding ``nnx.*`` API but enforces ``graph=True`` (and\n``graph_updates=True`` for transforms), preserving the pre-tree-mode behavior.\n\"\"\"\n\nimport functools\n\nfrom flax.nnx import graphlib as _graphlib\nfrom flax.nnx import module as _module\nfrom flax.nnx import rnglib as _rnglib\nfrom flax.nnx.transforms import autodiff as _autodiff\nfrom flax.nnx.transforms import compilation as _compilation\nfrom flax.nnx.transforms import iteration as _iteration\nfrom flax.nnx.transforms import transforms as _transforms\nfrom flax.nnx import spmd as _spmd\n\n# graphlib\nsplit = functools.partial(_graphlib.split, graph=True)\nstate = functools.partial(_graphlib.state, graph=True)\nclone = functools.partial(_graphlib.clone, graph=True)\ngraphdef = functools.partial(_graphlib.graphdef, graph=True)\nflatten = functools.partial(_graphlib.flatten, graph=True)\niter_graph = functools.partial(_graphlib.iter_graph, graph=True)\nrecursive_map = functools.partial(_graphlib.recursive_map, graph=True)\n\n# module\nview = functools.partial(_module.view, graph=True)\nview_info = functools.partial(_module.view_info, graph=True)\niter_modules = functools.partial(_module.iter_modules, graph=True)\niter_children = functools.partial(_module.iter_children, graph=True)  # type: ignore[has-type]\n\n# rnglib\nsplit_rngs = functools.partial(_rnglib.split_rngs, graph=True)\nfork_rngs = functools.partial(_rnglib.fork_rngs, graph=True)\nreseed = functools.partial(_rnglib.reseed, graph=True)\nbackup_keys = functools.partial(_rnglib.backup_keys, graph=True)\n\n# transforms - compilation\njit = functools.partial(_compilation.jit, graph=True, graph_updates=True)\nshard_map = functools.partial(\n    _compilation.shard_map, graph=True, graph_updates=True\n)\n\n# transforms - autodiff\ngrad = functools.partial(_autodiff.grad, graph=True, graph_updates=True)\nvalue_and_grad = functools.partial(\n    _autodiff.value_and_grad, graph=True, graph_updates=True\n)\ncustom_vjp = functools.partial(\n    _autodiff.custom_vjp, graph=True, graph_updates=True\n)\nvjp = functools.partial(_autodiff.vjp, graph=True, graph_updates=True)\njvp = functools.partial(_autodiff.jvp, graph=True, graph_updates=True)\nremat = functools.partial(_autodiff.remat, graph=True, graph_updates=True)\n\n# transforms - iteration\nvmap = functools.partial(_iteration.vmap, graph=True, graph_updates=True)\nscan = functools.partial(_iteration.scan, graph=True, graph_updates=True)\npmap = functools.partial(_iteration.pmap, graph=True, graph_updates=True)\nwhile_loop = functools.partial(\n    _iteration.while_loop, graph=True, graph_updates=True\n)\nfori_loop = functools.partial(\n    _iteration.fori_loop, graph=True, graph_updates=True\n)\n\n# transforms - general\neval_shape = functools.partial(\n    _transforms.eval_shape, graph=True, graph_updates=True\n)\ncond = functools.partial(_transforms.cond, graph=True, graph_updates=True)\nswitch = functools.partial(_transforms.switch, graph=True, graph_updates=True)\ncheckify = functools.partial(\n    _transforms.checkify, graph=True, graph_updates=True\n)\n\n# spmd\nget_abstract_model = functools.partial(_spmd.get_abstract_model, graph=True)\n"
  },
  {
    "path": "flax/nnx/extract.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport abc\nimport functools\nimport typing as tp\nfrom collections import namedtuple\n\nimport jax\n\nfrom flax import struct\nfrom flax import typing\nfrom flax.nnx.pytreelib import Pytree\nfrom flax.typing import Missing, PathParts\nfrom flax.nnx import graphlib, variablelib\n\n\nA = tp.TypeVar('A')\nIndex = int\nKeyPath = tuple[tp.Hashable, ...]\nPrefix = tp.Any\nLeaf = tp.Any\n\n\nclass PrefixMapping(abc.ABC):\n  @abc.abstractmethod\n  def map_prefix(\n    self,\n    path: typing.PathParts,\n    variable: variablelib.Variable,\n    /,\n  ) -> tp.Any: ...\n\ndef check_consistent_aliasing(\n  node: tp.Any,\n  prefix: tp.Any,\n  /,\n  *,\n  node_prefixes: dict[int, list[tuple[PathParts, tp.Any]]] | None = None,\n):\n  \"\"\"Check for consistent aliasing of nodes when extracting graph.\"\"\"\n  if node_prefixes is None:\n    node_prefixes = {}\n\n  # Store variable references for error messages\n  node_id_to_variable: dict[int, tp.Any] = {}\n\n  # collect all paths and prefixes for each node\n  for path, value in graphlib.iter_graph(node, graph=True):\n    if graphlib.is_graph_node(value) or isinstance(value, graphlib.Variable):\n      if isinstance(value, Pytree):\n        value._check_valid_context(\n          lambda: f'Trying to extract graph node from different trace level, got {value!r}'\n        )\n      if isinstance(value, graphlib.Variable):\n        if not value._can_update:\n          raise ValueError(\n            f'Cannot extract graph node from different trace level, got {value!r}'\n          )\n        if isinstance(prefix, PrefixMapping):\n          variable_prefix = prefix.map_prefix(path, value)\n        else:\n          variable_prefix = prefix\n\n        value_id = id(value)\n        node_id_to_variable[value_id] = value\n        if value_id in node_prefixes:\n          paths_prefixes = node_prefixes[value_id]\n          paths_prefixes.append((path, variable_prefix))\n        else:\n          node_prefixes[value_id] = [(path, variable_prefix)]\n\n  # check for inconsistent aliasing\n  node_msgs = []\n  for node_id, paths_prefixes in node_prefixes.items():\n    unique_prefixes = {prefix for _, prefix in paths_prefixes}\n    if len(unique_prefixes) > 1:\n      path_prefix_repr = '\\n'.join(\n        f'  {\"/\".join(map(str,path)) if path else \"<root>\"}: {prefix}'\n        for path, prefix in paths_prefixes\n      )\n      # Get the variable type name if available\n      if node_id in node_id_to_variable:\n        variable = node_id_to_variable[node_id]\n        node_type_name = type(variable).__name__\n      else:\n        node_type_name = f'Node ID: {node_id}'\n\n      nodes_msg = f'Node: {node_type_name}\\n{path_prefix_repr}'\n      node_msgs.append(nodes_msg)\n\n  if node_msgs:\n    raise ValueError(\n      'Inconsistent aliasing detected. The following nodes have different prefixes:\\n'\n      + '\\n'.join(node_msgs)\n    )\n\n\ndef check_consistent_aliasing2(\n  node: tp.Any,\n  prefix: tp.Any,\n  /,\n  *,\n  base_path: tuple[tp.Any, ...] = (),\n  node_prefixes: dict[int, list[tuple[PathParts, tp.Any]]] | None = None,\n):\n  if node_prefixes is None:\n    node_prefixes = {}\n\n  node_id_to_variable: dict[int, tp.Any] = {}\n\n  for path, value in graphlib.iter_graph(node, graph=True):\n    path = base_path + path\n    if graphlib.is_graph_node(value) or isinstance(value, graphlib.Variable):\n      value_id = id(value)\n      node_id_to_variable[value_id] = value\n      if value_id in node_prefixes:\n        node_prefixes[value_id].append((path, prefix))\n      else:\n        node_prefixes[value_id] = [(path, prefix)]\n\n  node_msgs = []\n  for node_id, paths_prefixes in node_prefixes.items():\n    unique_prefixes = {p for _, p in paths_prefixes}\n    if len(unique_prefixes) > 1:\n      path_prefix_repr = '\\n'.join(\n        f'  {\"/\".join(map(str,path)) if path else \"<root>\"}: {p}'\n        for path, p in paths_prefixes\n      )\n      if node_id in node_id_to_variable:\n        variable = node_id_to_variable[node_id]\n        node_type_name = type(variable).__name__\n      else:\n        node_type_name = f'Node ID: {node_id}'\n\n      node_msgs.append(f'Node: {node_type_name}\\n{path_prefix_repr}')\n\n  if node_msgs:\n    raise ValueError(\n      'Inconsistent aliasing detected. The following nodes have different prefixes:\\n'\n      + '\\n'.join(node_msgs)\n    )\n\n# -----------------------------\n# to_tree/from_tree\n# -----------------------------\n\ndef broadcast_prefix(\n  prefix_tree: tp.Any,\n  full_tree: tp.Any,\n  prefix_is_leaf: tp.Callable[[tp.Any], bool] | None = None,\n  tree_is_leaf: tp.Callable[[tp.Any], bool] | None = None,\n) -> list[tp.Any]:\n  # If prefix_tree is not a tree prefix of full_tree, this code can raise a\n  # ValueError; use prefix_errors to find disagreements and raise more precise\n  # error messages.\n  result = []\n  num_leaves = lambda t: jax.tree_util.tree_structure(\n    t, is_leaf=tree_is_leaf\n  ).num_leaves\n  add_leaves = lambda x, subtree: result.extend([x] * num_leaves(subtree))\n  jax.tree.map(\n    add_leaves,\n    prefix_tree,\n    full_tree,\n    is_leaf=lambda x: isinstance(x, variablelib.Variable)\n    or graphlib.is_graph_node(x)\n    or (prefix_is_leaf is not None and prefix_is_leaf(x)),\n  )\n  return result\n\ndef broadcast_prefix2(\n  prefix_tree: tp.Any,\n  full_tree: tp.Any,\n  is_leaf: tp.Callable[[tp.Any], bool] | None = None,\n) -> tuple[list[KeyPath], list[tp.Any]]:\n  paths: list[KeyPath] = []\n  leaves: list[tp.Any] = []\n  num_leaves = lambda t: jax.tree_util.tree_structure(t).num_leaves\n  def add_leaves(path, x, subtree):\n    n = num_leaves(subtree)\n    paths.extend([path] * n)\n    leaves.extend([x] * n)\n  jax.tree.map_with_path(add_leaves, prefix_tree, full_tree, is_leaf=is_leaf)\n  return paths, leaves\n\ndef broadcast_prefix_map(\n  f: tp.Callable[..., tp.Any],\n  prefix_tree: tp.Any,\n  full_tree: tp.Any,\n  *rest: tp.Any,\n  is_leaf: tp.Callable[[tp.Any], bool] | None = None,\n) -> tp.Any:\n  paths, prefix_leaves = broadcast_prefix2(prefix_tree, full_tree, is_leaf=is_leaf)\n  leaves, treedef = jax.tree_util.tree_flatten(full_tree, is_leaf=is_leaf)\n  full_prefix_tree = treedef.unflatten(prefix_leaves)\n  return jax.tree.map_with_path(f, full_prefix_tree, full_tree, *rest, is_leaf=is_leaf)\n\n\nclass GraphDefState(struct.PyTreeNode):\n  graphdef: graphlib.GraphDef[tp.Any] = struct.field(pytree_node=False)\n  state: graphlib.GraphState = struct.field(pytree_node=True)\n\nS = tp.TypeVar(\n  'S', bound=graphlib.GraphState | graphlib.GraphFlatState | list[tp.Any]\n)\n\nclass NodeStates(struct.PyTreeNode):\n  _graphdef: graphlib.GraphDef[tp.Any] | None\n  states: tuple[tp.Any, ...]\n  metadata: tp.Any = struct.field(pytree_node=False)\n\n  @property\n  def graphdef(self) -> graphlib.GraphDef[tp.Any]:\n    if self._graphdef is None:\n      raise ValueError('No graphdef available')\n    return self._graphdef\n\n  @property\n  def state(self) -> tp.Any:\n    if len(self.states) != 1:\n      raise ValueError(\n        f'Expected exactly one GraphDefState, got {len(self.states)}'\n      )\n    return self.states[0]\n\n  @classmethod\n  def from_split(\n    cls,\n    graphdef: graphlib.GraphDef[tp.Any] | None,\n    state: tp.Any,\n    /,\n    *states: tp.Any,\n    metadata: tp.Any = None,\n  ):\n    return cls(_graphdef=graphdef, states=(state, *states), metadata=metadata)\n\n  @classmethod\n  def from_states(\n    cls,\n    state: tp.Any,\n    *states: tp.Any,\n  ):\n    return cls(_graphdef=None, states=(state, *states), metadata=None)\n\n  @classmethod\n  def from_prefixes(\n    cls,\n    prefixes: tp.Iterable[tp.Any],\n    /,\n    *,\n    metadata: tp.Any = None,\n  ):\n    return cls(_graphdef=None, states=tuple(prefixes), metadata=metadata)\n\n\ndef default_split_fn(\n  ctx: graphlib.SplitContext, path: KeyPath, prefix: Prefix, leaf: Leaf\n) -> tp.Any:\n  return NodeStates.from_split(*ctx.split(leaf))\n\n\ndef to_tree(\n  tree,\n  /,\n  *,\n  prefix: tp.Any = Missing,\n  split_fn: tp.Callable[\n    [graphlib.SplitContext, KeyPath, Prefix, Leaf], tp.Any\n  ] = default_split_fn,\n  map_non_graph_nodes: bool = False,\n  ctxtag: tp.Hashable | None = None,\n  check_aliasing: bool = True,\n) -> tp.Any:\n  if prefix is Missing or prefix is None:\n    # fast path, no need for prefix broadcasting or consistent aliasing checks\n    with graphlib.split_context(ctxtag) as split_ctx:\n      return jax.tree.map(\n        lambda x: split_fn(split_ctx, (), prefix, x)\n        if map_non_graph_nodes\n        or graphlib.is_graph_node(x)\n        or isinstance(x, variablelib.Variable)\n        else x,\n        tree,\n        is_leaf=lambda x: isinstance(x, variablelib.Variable)\n        or graphlib.is_graph_node(x),\n      )\n  leaf_prefixes = broadcast_prefix(\n    prefix,\n    tree,\n    prefix_is_leaf=lambda x: x is None\n    or isinstance(x, variablelib.Variable)\n    or graphlib.is_graph_node(x),\n    tree_is_leaf=lambda x: isinstance(x, variablelib.Variable)\n    or graphlib.is_graph_node(x),\n  )\n  leaf_keys, treedef = jax.tree_util.tree_flatten_with_path(\n    tree,\n    is_leaf=lambda x: isinstance(x, variablelib.Variable)\n    or graphlib.is_graph_node(x),\n  )\n\n  assert len(leaf_keys) == len(leaf_prefixes)\n  leaves_out = []\n  node_prefixes: dict[int, list[tuple[PathParts, tp.Any]]] = {}\n\n  with graphlib.split_context(ctxtag) as split_ctx:\n    for (keypath, leaf), leaf_prefix in zip(leaf_keys, leaf_prefixes):\n      if graphlib.is_graph_node(leaf) or isinstance(leaf, variablelib.Variable):\n        if check_aliasing:\n          check_consistent_aliasing(\n            leaf, leaf_prefix, node_prefixes=node_prefixes\n          )\n        tree_node = split_fn(split_ctx, keypath, leaf_prefix, leaf)\n        leaves_out.append(tree_node)\n      else:\n        if map_non_graph_nodes:\n          leaf = split_fn(split_ctx, keypath, leaf_prefix, leaf)\n        leaves_out.append(leaf)\n\n  pytree_out = jax.tree.unflatten(treedef, leaves_out)\n  return pytree_out\n\n\ndef to_tree2(\n  tree,\n  /,\n  *,\n  prefix: tp.Any = Missing,\n  check_aliasing: bool = True,\n) -> tp.Any:\n  \"\"\"to_tree2 has two main tasks:\n\n  1. Convert all graph nodes to NodeStates (a tree representation).\n  2. Check all Variables are aliased consistently given the prefix tree,\n    e.g. vmap's in/out_axes arguments.\n\n  Each NodeState contains the `GraphDef` and State for each object, these\n  are generated using `graphlib.flatten`. `extract.broadcast_prefix` is used\n  to calculate the prefix for each node, `check_consistent_aliasing2` traverses\n  the nodes subgraph and checks for Variable aliasing.\n  \"\"\"\n  ref_index: graphlib.RefMap = graphlib.RefMap()\n\n  def _to_node_states(leaf):\n    if not (graphlib.is_graph_node(leaf) or isinstance(leaf, variablelib.Variable)):\n      return leaf\n    graphdef, flat_state = graphlib.flatten(\n      leaf, ref_index=ref_index, graph=True\n    )\n    (state,) = graphlib._to_nested_state(graphdef, (flat_state,))\n    return NodeStates.from_split(graphdef, state)\n\n  is_leaf = lambda x: (\n    isinstance(x, variablelib.Variable) or graphlib.is_graph_node(x)\n  )\n\n  if prefix is Missing or prefix is None:\n    return jax.tree.map(_to_node_states, tree, is_leaf=is_leaf)\n\n  leaf_prefixes = broadcast_prefix(\n    prefix,\n    tree,\n    prefix_is_leaf=lambda x: x is None or is_leaf(x),\n    tree_is_leaf=is_leaf,\n  )\n  leaf_paths, treedef = jax.tree_util.tree_flatten_with_path(tree, is_leaf=is_leaf)\n\n  assert len(leaf_paths) == len(leaf_prefixes)\n  leaves_out = []\n  node_prefixes: dict[int, list[tuple[PathParts, tp.Any]]] = {}\n\n  for (keypath, leaf), leaf_prefix in zip(leaf_paths, leaf_prefixes):\n    if is_leaf(leaf):\n      if check_aliasing:\n        base_path = graphlib.jax_to_nnx_path(keypath)\n        check_consistent_aliasing2(\n          leaf, leaf_prefix, base_path=base_path, node_prefixes=node_prefixes\n        )\n      leaves_out.append(_to_node_states(leaf))\n    else:\n      leaves_out.append(leaf)\n\n  return jax.tree.unflatten(treedef, leaves_out)\n\n\ndef from_tree2(tree: tp.Any, /) -> tp.Any:\n  index_ref = graphlib.IndexMap()\n\n  def _from_node_states(x):\n    if not isinstance(x, NodeStates):\n      return x\n    state = graphlib._merge_to_flat_state(x.states)\n    return graphlib.unflatten(\n      x.graphdef, state, index_ref=index_ref,\n    )\n\n  return jax.tree.map(\n    _from_node_states,\n    tree,\n    is_leaf=lambda x: (\n      isinstance(x, NodeStates)\n      or graphlib.is_graph_node(x)\n      or isinstance(x, variablelib.Variable)\n    ),\n  )\n\n\ndef merge_tree_node(\n  ctx: graphlib.MergeContext, path: KeyPath, prefix: Prefix, leaf: Leaf\n) -> tp.Any:\n  if not isinstance(leaf, NodeStates):\n    raise ValueError(f'Expected TreeNode, got {type(leaf)} at path {path}')\n  return ctx.merge(leaf.graphdef, *leaf.states)\n\n\ndef is_tree_node(x):\n  return isinstance(x, NodeStates)\n\n\ndef from_tree(\n  tree: tp.Any,\n  /,\n  *,\n  prefix: tp.Any = Missing,\n  merge_fn: tp.Callable[\n    [graphlib.MergeContext, KeyPath, Prefix, Leaf], tp.Any\n  ] = merge_tree_node,\n  is_node_leaf: tp.Callable[[Leaf], bool] = is_tree_node,\n  is_leaf: tp.Callable[[Leaf], bool] = is_tree_node,\n  map_non_graph_nodes: bool = False,\n  is_inner: bool | None = None,\n  ctxtag: tp.Hashable | None = None,\n) -> tp.Any:\n  if prefix is Missing or prefix is None:\n    # fast path, no need for prefix broadcasting or consistent aliasing checks\n    with graphlib.merge_context(ctxtag, is_inner) as merge_ctx:\n\n      def maybe_split(x):\n        if (\n          map_non_graph_nodes\n          or is_node_leaf(x)\n          or isinstance(x, variablelib.Variable)\n        ):\n          return merge_fn(merge_ctx, (), prefix, x)\n        return x\n\n      return jax.tree.map(maybe_split, tree, is_leaf=is_leaf)\n  leaf_prefixes = broadcast_prefix(\n    prefix,\n    tree,\n    prefix_is_leaf=lambda x: x is None or is_leaf(x),\n    tree_is_leaf=is_leaf,\n  )\n  leaf_keys, treedef = jax.tree_util.tree_flatten_with_path(\n    tree, is_leaf=is_leaf\n  )\n  assert len(leaf_keys) == len(leaf_prefixes)\n  leaves_out = []\n\n  with graphlib.merge_context(ctxtag, is_inner) as merge_ctx:\n    for (keypath, leaf), leaf_prefix in zip(leaf_keys, leaf_prefixes):\n      if (\n        map_non_graph_nodes\n        or is_node_leaf(leaf)\n        or isinstance(leaf, variablelib.Variable)\n      ):\n        leaf = merge_fn(merge_ctx, keypath, leaf_prefix, leaf)\n      leaves_out.append(leaf)\n\n  pytree_out = jax.tree.unflatten(treedef, leaves_out)\n  return pytree_out\n\ndef clear_non_graph_nodes(tree):\n  return jax.tree.map(\n    lambda x: x\n    if graphlib.is_graph_node(x) or isinstance(x, variablelib.Variable)\n    else None,\n    tree,\n    is_leaf=lambda x: isinstance(x, variablelib.Variable)\n    or graphlib.is_graph_node(x),\n  )\n\nclass Mask(tp.NamedTuple):\n  pass\n\ndef mask_at(t: tuple, index: int | None) -> tuple:\n  if index is None:\n    return t\n  return tuple(\n    Mask() if i == index else x\n    for i, x in enumerate(t)\n  )\n\ndef replace_at(t: tuple, index: int, value: tp.Any) -> tuple:\n  return tuple(\n    value if i == index else x\n    for i, x in enumerate(t)\n  )\n\ndef updates_and_snapshot(args: A) -> tuple[A, A]:\n  is_leaf = lambda x: isinstance(x, variablelib.Variable)\n  leaves, treedef = jax.tree.flatten(args, is_leaf=is_leaf)\n  updates_leaves: list[variablelib.Variable | Mask] = []\n  snapshot_leaves: list[variablelib.Variable | Mask] = []\n  for leaf in leaves:\n    if isinstance(leaf, variablelib.Variable):\n      updates_leaves.append(leaf)\n      snapshot_leaves.append(leaf.copy())\n    else:\n      updates_leaves.append(Mask())\n      snapshot_leaves.append(Mask())\n  updates = jax.tree.unflatten(treedef, updates_leaves)\n  snapshot = jax.tree.unflatten(treedef, snapshot_leaves)\n  return updates, snapshot\n\n\ndef check_no_aliases(fn_name: str, /, **kwargs):\n  Attrs = namedtuple('Attrs', kwargs.keys())  # type: ignore[misc]\n  container = Attrs(**kwargs)\n  is_leaf = lambda x: isinstance(x, variablelib.Variable)\n  seen: dict[int, jax.tree_util.KeyPath] = {}\n  for path, leaf in jax.tree.leaves_with_path(\n    container, is_leaf=is_leaf\n  ):\n    if not isinstance(leaf, variablelib.Variable):\n      continue\n    var_id = id(leaf)\n    if var_id in seen:\n      path_str = jax.tree_util.keystr(path)\n      seen_path_str = jax.tree_util.keystr(seen[var_id])\n      raise ValueError(\n        f'Duplicate {leaf}\\nfound at paths:\\n\\n'\n        f'  - {seen_path_str}\\n'\n        f'  - {path_str}\\n\\n'\n        f'nnx.{fn_name} with graph_updates=False does not support '\n        'returning input Variables as outputs. '\n        f'Consider the following options:\\n\\n'\n        f'1. Remove the duplicate Variables.\\n'\n        f'2. Create new Variables via nnx.clone() and use those instead.\\n'\n        f'3. Enable graph mode and graph updates by passing graph=True and '\n        f'graph_updates=True to {fn_name}\\n\\n'\n        f'  nnx.{fn_name}(..., graph=True, graph_updates=True)\\n\\n'\n        f'4. Use nnx.compat.{fn_name} (sets graph and graph_updates to True '\n        f'automatically)\\n\\n'\n        f'  nnx.compat.{fn_name}(...)'\n      )\n    seen[var_id] = path\n\n\ndef check_prefix(prefix: tp.Any, prefix_name: str, fn_name: str):\n  def _check(path, leaf):\n    if graphlib.is_graph_node(leaf) or isinstance(leaf, variablelib.Variable):\n      raise ValueError(\n        f'Found graph node or Variable of type {type(leaf).__name__} '\n        f'at path {jax.tree_util.keystr(path)} in `{prefix_name}` '\n        f'for nnx.{fn_name}. Graph nodes and Variables are not allowed '\n        f'as prefixes when graph=True and graph_updates=False'\n      )\n  jax.tree.map_with_path(\n    _check, prefix,\n    is_leaf=lambda x: isinstance(x, variablelib.Variable)\n    or graphlib.is_graph_node(x),\n  )\n\n\ndef variable_changed(post: variablelib.Variable, pre: variablelib.Variable) -> bool:\n  post_leaves, post_td = jax.tree.flatten(post)\n  pre_leaves, pre_td = jax.tree.flatten(pre)\n  return post_td != pre_td or any(  # type: ignore[operator]\n    a is not b for a, b in zip(post_leaves, pre_leaves)\n  )\n\nKeepFn = tp.Callable[\n    [PathParts, tp.Any, variablelib.Variable, variablelib.Variable], bool\n]\n\ndef mask_variable_updates(\n    current_tree: A,\n    snapshot_tree: A,\n    *,\n    prefix: tp.Any = Missing,\n    keep_fn: KeepFn | None = None,\n) -> A:\n  if keep_fn is None:\n    keep_fn = lambda _, _pfx, cur, snap: variable_changed(cur, snap)\n\n  def _mask_updates(path, prefix_leaf, current, snapshot):\n    if isinstance(current, variablelib.Variable):\n      if current.hijax or current.ref:\n        return Mask()\n      if keep_fn(path, prefix_leaf, current, snapshot):\n        return current\n    return Mask()\n  is_leaf = lambda x: isinstance(x, variablelib.Variable) or x is None\n  if prefix is Missing:\n    return jax.tree.map_with_path(\n        lambda path, cur, snap: _mask_updates(path, None, cur, snap),\n        current_tree, snapshot_tree, is_leaf=is_leaf,\n    )\n  return broadcast_prefix_map(\n      _mask_updates, prefix, current_tree, snapshot_tree, is_leaf=is_leaf,\n  )\n\n\ndef apply_variable_updates(args_tree: A, updates_tree: A):\n  is_leaf = lambda x: isinstance(x, variablelib.Variable) or isinstance(x, Mask)\n  args_leaves = jax.tree.leaves(args_tree, is_leaf=is_leaf)\n  _, treedef = jax.tree.flatten(args_tree, is_leaf=is_leaf)\n  updates_leaves = treedef.flatten_up_to(updates_tree)\n  for variable, update in zip(args_leaves, updates_leaves, strict=True):\n    if isinstance(update, variablelib.Variable):\n      assert isinstance(variable, variablelib.Variable)\n      variable.update_from_state(update)\n\n\ndef treemap_copy_args(f):\n  @functools.wraps(f)\n  def wrapper(*args, **kwargs):\n    args, kwargs = jax.tree.map(lambda x: x, (args, kwargs))\n    return f(*args, **kwargs)\n  return wrapper\n\n\ndef check_same_variables(inputs, outputs, transform_name: str = ''):\n  def _check(in_leaf, out_leaf):\n    if isinstance(in_leaf, variablelib.Variable) and in_leaf is not out_leaf:\n      raise ValueError(\n        f'{transform_name} Variable identity must be preserved '\n        'across iterations.'\n      )\n  is_leaf = lambda x: isinstance(x, (Mask, variablelib.Variable))\n  jax.tree.map(\n    _check, inputs, outputs,\n    is_leaf=is_leaf,\n  )\n\n\ndef update_carry_variables(init_val, val_out):\n  def _update(in_leaf, out_leaf):\n    if isinstance(in_leaf, variablelib.Variable):\n      in_leaf.update_from_state(out_leaf)\n      return in_leaf\n    return out_leaf\n\n  return jax.tree.map(\n    _update, init_val, val_out,\n    is_leaf=lambda x: isinstance(x, variablelib.Variable),\n  )\n"
  },
  {
    "path": "flax/nnx/filterlib.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport builtins\nimport dataclasses\nfrom flax.typing import Key, PathParts\nimport typing as tp\n\nif tp.TYPE_CHECKING:\n  ellipsis = builtins.ellipsis\nelse:\n  ellipsis = tp.Any\n\nPredicate = tp.Callable[[PathParts, tp.Any], bool]\n\nFilterLiteral = tp.Union[type, str, Predicate, bool, ellipsis, None]\nFilter = tp.Union[FilterLiteral, tuple['Filter', ...], list['Filter']]\n\n\n\ndef to_predicate(filter: Filter) -> Predicate:\n  \"\"\"Converts a Filter to a predicate function.\n  See `Using Filters <https://flax.readthedocs.io/en/latest/guides/filters_guide.html>`__.\n  \"\"\"\n\n  if isinstance(filter, str):\n    return WithTag(filter)\n  elif isinstance(filter, type):\n    return OfType(filter)\n  elif isinstance(filter, bool):\n    if filter:\n      return Everything()\n    else:\n      return Nothing()\n  elif filter is Ellipsis:\n    return Everything()\n  elif filter is None:\n    return Nothing()\n  elif callable(filter):\n    return filter\n  elif isinstance(filter, (list, tuple)):\n    return Any(*filter)\n  else:\n    raise TypeError(f'Invalid collection filter: {filter:!r}. ')\n\ndef filters_to_predicates(\n  filters: tp.Sequence[Filter],\n) -> tuple[Predicate, ...]:\n  for i, filter_ in enumerate(filters):\n    if filter_ in (..., True) and i != len(filters) - 1:\n      remaining_filters = filters[i + 1 :]\n      if not all(f in (..., True) for f in remaining_filters):\n        raise ValueError(\n          '`...` or `True` can only be used as the last filters, '\n          f'got {filter_} it at index {i}.'\n        )\n  return tuple(map(to_predicate, filters))\n\n\nclass HasTag(tp.Protocol):\n  tag: str\n\n\ndef _has_tag(x: tp.Any) -> tp.TypeGuard[HasTag]:\n  return hasattr(x, 'tag')\n\n\n@dataclasses.dataclass(frozen=True)\nclass WithTag:\n  tag: str\n\n  def __call__(self, path: PathParts, x: tp.Any):\n    return _has_tag(x) and x.tag == self.tag\n\n  def __repr__(self):\n    return f'WithTag({self.tag!r})'\n\n\n@dataclasses.dataclass(frozen=True)\nclass PathContains:\n  key: Key | str\n  exact: bool = True\n\n  def __call__(self, path: PathParts, x: tp.Any):\n    if self.exact:\n      return self.key in path\n    return any(str(self.key) in str(part) for part in path)\n\n  def __repr__(self):\n    return f'PathContains({self.key!r}, exact={self.exact})'\n\n\nclass PathIn:\n  def __init__(self, *paths: PathParts):\n    self.paths = frozenset(paths)\n\n  def __call__(self, path: PathParts, x: tp.Any):\n    return path in self.paths\n\n  def __repr__(self):\n    paths_repr = ','.join(map(repr, self.paths))\n    return f'PathIn({paths_repr})'\n\n  def __eq__(self, other):\n    return isinstance(other, PathIn) and self.paths == other.paths\n\n  def __hash__(self):\n    return hash(self.paths)\n\n\n@dataclasses.dataclass(frozen=True)\nclass OfType:\n  type: type\n\n  def __call__(self, path: PathParts, x: tp.Any):\n    return isinstance(x, self.type)\n\n  def __repr__(self):\n    return f'OfType({self.type!r})'\n\n\nclass Any:\n  def __init__(self, *filters: Filter):\n    self.predicates = tuple(\n      to_predicate(collection_filter) for collection_filter in filters\n    )\n\n  def __call__(self, path: PathParts, x: tp.Any):\n    return any(predicate(path, x) for predicate in self.predicates)\n\n  def __repr__(self):\n    return f'Any({\", \".join(map(repr, self.predicates))})'\n\n  def __eq__(self, other):\n    return isinstance(other, Any) and self.predicates == other.predicates\n\n  def __hash__(self):\n    return hash(self.predicates)\n\n\nclass All:\n  def __init__(self, *filters: Filter):\n    self.predicates = tuple(\n      to_predicate(collection_filter) for collection_filter in filters\n    )\n\n  def __call__(self, path: PathParts, x: tp.Any):\n    return all(predicate(path, x) for predicate in self.predicates)\n\n  def __repr__(self):\n    return f'All({\", \".join(map(repr, self.predicates))})'\n\n  def __eq__(self, other):\n    return isinstance(other, All) and self.predicates == other.predicates\n\n  def __hash__(self):\n    return hash(self.predicates)\n\n\nclass Not:\n  def __init__(self, collection_filter: Filter, /):\n    self.predicate = to_predicate(collection_filter)\n\n  def __call__(self, path: PathParts, x: tp.Any):\n    return not self.predicate(path, x)\n\n  def __repr__(self):\n    return f'Not({self.predicate!r})'\n\n  def __eq__(self, other):\n    return isinstance(other, Not) and self.predicate == other.predicate\n\n  def __hash__(self):\n    return hash(self.predicate)\n\n\nclass Everything:\n  def __call__(self, path: PathParts, x: tp.Any):\n    return True\n\n  def __repr__(self):\n    return 'Everything()'\n\n  def __eq__(self, other):\n    return isinstance(other, Everything)\n\n  def __hash__(self):\n    return hash(Everything)\n\n\nclass Nothing:\n  def __call__(self, path: PathParts, x: tp.Any):\n    return False\n\n  def __repr__(self):\n    return 'Nothing()'\n\n  def __eq__(self, other):\n    return isinstance(other, Nothing)\n\n  def __hash__(self):\n    return hash(Nothing)\n"
  },
  {
    "path": "flax/nnx/graph.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Graph module.\n\nRe-exports APIs from ``flax.nnx.graphlib``. This module is kept for backward\ncompatibility with code that imports from ``flax.nnx.graph``.\n\"\"\"\n\nfrom flax.nnx.graphlib import *\n\n"
  },
  {
    "path": "flax/nnx/graphlib.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom __future__ import annotations\n\nimport contextlib\nimport dataclasses\nimport functools\nimport threading\nimport typing as tp\nimport builtins\n\nimport jax.core\n\nfrom flax import config\nfrom flax.nnx import filterlib, reprlib, traversals, variablelib\nfrom flax.nnx import statelib\nfrom flax.nnx.proxy_caller import (\n  ApplyCaller,\n  CallableProxy,\n  DelayedAccessor,\n)\nfrom flax.nnx.statelib import FlatState, State, map_state\nfrom flax.nnx.variablelib import Variable, is_array_ref, V\nfrom flax.typing import BaseConfigContext, HashableMapping, Key, PathParts, is_key_like\nimport jax\nimport numpy as np\nimport treescope  # type: ignore[import-not-found,import-untyped]\nimport typing_extensions as tpe\n\nA = tp.TypeVar('A')\nB = tp.TypeVar('B')\nC = tp.TypeVar('C')\nF = tp.TypeVar('F', bound=tp.Callable)\n\nHA = tp.TypeVar('HA', bound=tp.Hashable)\nHB = tp.TypeVar('HB', bound=tp.Hashable)\nKeyT = tp.TypeVar('KeyT', bound=Key)\n\nIndex = int\n\ndef _tree_mode_suggestion_api(fn_name: str) -> str:\n  return (\n    f'Consider the following options:\\n\\n'\n    '1. Remove the duplicates and guarantee a tree structure.\\n'\n    f'2. Enable graph mode by passing graph=True to {fn_name} e.g.\\n\\n'\n    f'  nnx.{fn_name}(..., graph=True)\\n\\n'\n    f'3. Use nnx.compat.{fn_name} instead e.g.\\n\\n'\n    f'  nnx.compat.{fn_name}(...)'\n  )\n\ndef _tree_mode_suggestion_transform(fn_name: str) -> str:\n  return (\n    f'Consider the following options:\\n\\n'\n    '1. Remove the duplicates.\\n'\n    f'2. Enable graph mode and graph updates by passing graph=True and '\n    f'graph_updates=True to {fn_name} e.g.\\n\\n'\n    f'  nnx.{fn_name}(..., graph=True, graph_updates=True)\\n\\n'\n    f'3. Use nnx.compat.{fn_name} instead e.g.\\n\\n'\n    f'  nnx.compat.{fn_name}(...)'\n  )\n\ndef _check_valid_pytree(\n  node: tp.Any, fn_name: str, path: str = '',\n) -> None:\n  from flax.nnx import pytreelib\n  if (\n    isinstance(node, pytreelib.Pytree)\n    and not node._pytree__is_pytree\n  ):\n    msg = (\n      f\"Cannot use '{fn_name}' with graph=False on a \"\n      f\"'{type(node).__name__}' instance that has pytree=False. \"\n    )\n    if path:\n      msg += f\"Found at path: {path}. \"\n    msg += (\n      f\"Pytree subclasses with pytree=False are not registered as \"\n      f\"JAX pytrees and cannot be used in tree-mode. \"\n      + _tree_mode_suggestion_api(fn_name)\n    )\n    raise ValueError(msg)\n\nNames = tp.Sequence[int]\nNode = tp.TypeVar('Node')\nLeaf = tp.TypeVar('Leaf')\nAuxData = tp.TypeVar('AuxData')\n\n\n@jax.tree_util.register_static\n@dataclasses.dataclass(frozen=True, slots=True)\nclass NoUpdate: ...\n\n\nNO_UPDATE = NoUpdate()\n\n\n@jax.tree_util.register_static\n@dataclasses.dataclass(frozen=True, slots=True)\nclass Repeated: ...\n\n\nREPEATED = Repeated()\n\n\n@jax.tree_util.register_dataclass\n@dataclasses.dataclass(frozen=True, slots=True, repr=False)\nclass ArrayRefOutput(reprlib.Representable):\n  value: jax.Array\n\n  def __nnx_repr__(self):\n    yield reprlib.Object(type=type(self))\n    yield reprlib.Attr('value', self.value)\n\n  def __treescope_repr__(self, path, subtree_renderer):\n    return treescope.repr_lib.render_object_constructor(\n      object_type=type(self),\n      attributes={\n        'value': self.value,\n      },\n      path=path,\n      subtree_renderer=subtree_renderer,\n    )\n\n\nLeafType = tp.Union[\n  Variable,\n  jax.Array,\n  np.ndarray,\n  variablelib.Ref,\n  ArrayRefOutput,\n  NoUpdate,\n]\nGraphState = State[Key, LeafType]\nGraphFlatState = FlatState[LeafType]\n\n\ndef is_node_leaf(x: tp.Any) -> tpe.TypeGuard[LeafType]:\n  return isinstance(x, LeafType) or variablelib.is_array_ref(x)  # type: ignore[misc, arg-type]\n\n\nclass IndexMap(dict[Index, tp.Any]):\n  @staticmethod\n  def from_refmap(refmap: RefMap) -> IndexMap:\n    return IndexMap((index, value) for value, index in refmap.items())\n\n\nif config.flax_use_flaxlib:\n  import flaxlib  # type: ignore[import]\n\n  globals()['IndexMap'] = flaxlib.IndexMap\n\n\n# RefMap = dict\nclass RefMap(tp.MutableMapping[tp.Any, int], reprlib.MappingReprMixin):\n  \"\"\"A mapping that hashes keys by their identity.\"\"\"\n\n  def __init__(\n    self,\n    mapping: tp.Mapping[tp.Any, int]\n    | tp.Iterable[tuple[tp.Any, int]]\n    | None = None,\n    /,\n  ):\n    self._mapping: dict[int, tuple[tp.Any, int]] = dict()\n    if mapping is not None:\n      self.update(mapping)\n\n  @staticmethod\n  def from_indexmap(indexmap: IndexMap) -> RefMap:\n    refmap = RefMap()\n    refmap.update((value, index) for index, value in indexmap.items())\n    return refmap\n\n  def get(self, key: tp.Any, default: int | None = None) -> int | None:  # type: ignore[override]\n    return self._mapping.get(id(key), (None, default))[1]\n\n  def __getitem__(self, key: tp.Any) -> int:\n    return self._mapping[id(key)][1]\n\n  def __setitem__(self, key: tp.Any, value: int):\n    self._mapping[id(key)] = (key, value)\n\n  def __delitem__(self, key: tp.Any):\n    del self._mapping[id(key)]\n\n  def __len__(self) -> int:\n    return len(self._mapping)\n\n  def __contains__(self, key: tp.Any) -> bool:\n    return id(key) in self._mapping\n\n  def __iter__(self) -> tp.Iterator[tp.Any]:\n    for key, _ in self._mapping.values():\n      yield key\n\n  def items(self) -> tp.ItemsView[tp.Any, int]:\n    return self._mapping.values()  # type: ignore\n\n\n# save python version\nPythonRefMap = RefMap\n\nif config.flax_use_flaxlib:\n  import flaxlib  # type: ignore[import]\n\n  globals()['RefMap'] = flaxlib.RefMap\n\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass NodeImplBase(tp.Generic[Node, Leaf, AuxData]):\n  type: type[Node]\n  flatten: tp.Callable[[Node], tuple[tp.Sequence[tuple[Key, Leaf]], AuxData]]\n\n  def node_dict(self, node: Node) -> dict[Key, tp.Any]:\n    nodes, _ = self.flatten(node)\n    return {\n      key: value.value if isinstance(value, DataElem | StaticElem) else value\n      for key, value in nodes\n    }\n\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass GraphNodeImpl(NodeImplBase[Node, Leaf, AuxData]):\n  set_key: tp.Callable[[Node, Key, Leaf], None]\n  pop_key: tp.Callable[[Node, Key], Leaf]\n  create_empty: tp.Callable[[AuxData], Node]\n  clear: tp.Callable[[Node], None]\n  init: tp.Callable[[Node, tp.Iterable[tuple[Key, Leaf]]], None]\n\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass PytreeNodeImpl(NodeImplBase[Node, Leaf, AuxData]):\n  unflatten: tp.Callable[[tp.Sequence[tuple[Key, Leaf]], AuxData], Node]\n  set_key: tp.Callable[[Node, Key, Leaf], None] | None\n  pop_key: tp.Callable[[Node, Key], Leaf] | None\n\n\nNodeImpl = tp.Union[\n  GraphNodeImpl[Node, Leaf, AuxData], PytreeNodeImpl[Node, Leaf, AuxData]\n]\n\n\nGRAPH_REGISTRY: dict[type, NodeImpl[tp.Any, tp.Any, tp.Any]] = {}\nPYTREE_REGISTRY: dict[type, PytreeNodeImpl[tp.Any, tp.Any, tp.Any]] = {}\n\n\ndef register_graph_node_type(\n  type: type,\n  flatten: tp.Callable[[Node], tuple[tp.Sequence[tuple[Key, Leaf]], AuxData]],\n  set_key: tp.Callable[[Node, Key, Leaf], None],\n  pop_key: tp.Callable[[Node, Key], Leaf],\n  create_empty: tp.Callable[[AuxData], Node],\n  clear: tp.Callable[[Node], None],\n  init: tp.Callable[[Node, tp.Iterable[tuple[Key, Leaf]]], None],\n):\n  if type in GRAPH_REGISTRY:\n    raise ValueError(f'Node type {type} is already registered.')\n\n  GRAPH_REGISTRY[type] = GraphNodeImpl(\n    type=type,\n    flatten=flatten,\n    set_key=set_key,\n    pop_key=pop_key,\n    create_empty=create_empty,\n    clear=clear,\n    init=init,\n  )\n\n\ndef register_pytree_node_type(\n  type: type,\n  flatten: tp.Callable[[Node], tuple[tp.Sequence[tuple[Key, Leaf]], AuxData]],\n  unflatten: tp.Callable[[tp.Sequence[tuple[Key, Leaf]], AuxData], Node],\n  *,\n  set_key: tp.Callable[[Node, Key, Leaf], None] | None = None,\n  pop_key: tp.Callable[[Node, Key], Leaf] | None = None,\n):\n  if type in PYTREE_REGISTRY:\n    raise ValueError(f'Node type {type} is already registered.')\n\n  PYTREE_REGISTRY[type] = PytreeNodeImpl(\n    type=type,\n    flatten=flatten,\n    unflatten=unflatten,\n    set_key=set_key,\n    pop_key=pop_key,\n  )\n\n\ndef is_node(x: tp.Any) -> bool:\n  if isinstance(x, Variable):\n    return False\n  if type(x) in GRAPH_REGISTRY:\n    return True\n  return is_pytree_node(x)\n\n\ndef is_graph_node(x: tp.Any) -> bool:\n  return (\n    type(x) in GRAPH_REGISTRY\n    or variablelib.is_array_ref(x)\n    or isinstance(x, Variable)\n  )\n\n\ndef is_node_type(x: type[tp.Any]) -> bool:\n  return x in GRAPH_REGISTRY or x in PYTREE_REGISTRY or x is GenericPytree\n\n\ndef get_node_impl(x: Node) -> NodeImpl[Node, tp.Any, tp.Any] | None:\n  if isinstance(x, Variable):\n    return None\n\n  node_type = type(x)\n\n  if node_type in GRAPH_REGISTRY:\n    return GRAPH_REGISTRY[node_type]\n  elif node_type in PYTREE_REGISTRY:\n    return PYTREE_REGISTRY[node_type]\n  elif node_type in JAX_PYTREE_REGISTRY or issubclass(node_type, tuple):\n    return PYTREE_NODE_IMPL  # type: ignore\n  else:\n    return None\n\n\ndef get_node_impl_for_type(\n  x: type[Node],\n) -> NodeImpl[Node, tp.Any, tp.Any] | None:\n  if x is GenericPytree:\n    return PYTREE_NODE_IMPL  # type: ignore\n  elif x in PYTREE_REGISTRY:\n    return PYTREE_REGISTRY[x]\n  elif x in GRAPH_REGISTRY:\n    return GRAPH_REGISTRY[x]\n  else:\n    return None\n\n# use type-aware sorting to support int keys\ndef _type_aware_sort(item: tuple[tp.Any, tp.Any]) -> tuple[int, tp.Any]:\n  key, _ = item\n  if isinstance(key, int):\n    return (0, key)\n  elif isinstance(key, str):\n    return (1, key)\n  else:\n    raise ValueError(f'Unsupported key type: {type(key)!r}')\n\n\n@jax.tree_util.register_static\n@dataclasses.dataclass(frozen=True, repr=False)\nclass NodeRef(tp.Generic[Node], reprlib.Representable):\n  index: int\n\n  def __nnx_repr__(self):\n    yield reprlib.Object(type=type(self))\n    yield reprlib.Attr('index', self.index)\n\n  def __treescope_repr__(self, path, subtree_renderer):\n    return treescope.repr_lib.render_object_constructor(\n      object_type=type(self),\n      attributes={'index': self.index},\n      path=path,\n      subtree_renderer=subtree_renderer,\n    )\n\n\nif config.flax_use_flaxlib:\n  import flaxlib  # type: ignore[import]\n\n  jax.tree_util.register_static(flaxlib.NodeRef)\n  globals()['NodeRef'] = flaxlib.NodeRef\n\n\n@dataclasses.dataclass(frozen=True, repr=False)\nclass VariableDef(reprlib.Representable, tp.Generic[Node]):\n  type: type[Node]\n  index: int\n  outer_index: int | None\n  metadata: HashableMapping[str, tp.Any]\n  array_refdef: ArrayRefDef | NodeRef | None\n\n  def with_no_outer_index(self) -> VariableDef:\n    return VariableDef(\n      type=self.type,\n      index=self.index,\n      outer_index=None,\n      metadata=self.metadata,\n      array_refdef=self.array_refdef.with_no_outer_index()\n      if isinstance(self.array_refdef, ArrayRefDef)\n      else self.array_refdef,\n    )\n\n  def with_same_outer_index(self) -> VariableDef:\n    return VariableDef(\n      type=self.type,\n      index=self.index,\n      outer_index=self.index,\n      metadata=self.metadata,\n      array_refdef=self.array_refdef.with_same_outer_index()\n      if isinstance(self.array_refdef, ArrayRefDef)\n      else self.array_refdef,\n    )\n\n  def with_matching_outer_index(self, other) -> VariableDef:\n    return VariableDef(\n      type=self.type,\n      index=self.index,\n      outer_index=other.outer_index,\n      metadata=self.metadata,\n      array_refdef=self.array_refdef.with_matching_outer_index(other.array_refdef)\n      if isinstance(self.array_refdef, ArrayRefDef)\n      else self.array_refdef\n    )\n\n  def __nnx_repr__(self):\n    yield reprlib.Object(type=type(self))\n    yield reprlib.Attr('type', self.type.__name__)\n    yield reprlib.Attr('index', self.index)\n    yield reprlib.Attr('outer_index', self.outer_index)\n    yield reprlib.Attr('metadata', reprlib.PrettyMapping(self.metadata))\n\n  def __treescope_repr__(self, path, subtree_renderer):\n    return treescope.repr_lib.render_object_constructor(\n      object_type=type(self),\n      attributes={\n        'type': self.type,\n        'index': self.index,\n        'outer_index': self.outer_index,\n        'metadata': self.metadata,\n      },\n      path=path,\n      subtree_renderer=subtree_renderer,\n    )\n\n\nif config.flax_use_flaxlib:\n  import flaxlib  # type: ignore[import]\n\n  jax.tree_util.register_static(flaxlib.VariableDef)\n  globals()['VariableDef'] = flaxlib.VariableDef\n\n\n@dataclasses.dataclass(frozen=True, repr=False)\nclass ArrayRefDef(reprlib.Representable):\n  index: int\n  outer_index: int | None\n\n  def with_no_outer_index(self):\n    return ArrayRefDef(\n      index=self.index,\n      outer_index=None,\n    )\n\n  def with_same_outer_index(self):\n    return ArrayRefDef(\n      index=self.index,\n      outer_index=self.index,\n    )\n\n  def with_matching_outer_index(self, other):\n    return ArrayRefDef(\n      index=self.index,\n      outer_index=other.outer_index,\n    )\n\n  def __nnx_repr__(self):\n    yield reprlib.Object(type=type(self))\n    yield reprlib.Attr('index', self.index)\n    yield reprlib.Attr('outer_index', self.outer_index)\n\n  def __treescope_repr__(self, path, subtree_renderer):\n    return treescope.repr_lib.render_object_constructor(\n      object_type=type(self),\n      attributes={\n        'index': self.index,\n        'outer_index': self.outer_index,\n      },\n      path=path,\n      subtree_renderer=subtree_renderer,\n    )\n\n\n@jax.tree_util.register_static\n@dataclasses.dataclass(frozen=True, repr=False, slots=True)\nclass NodeDef(tp.Generic[Node], reprlib.Representable):\n  \"\"\"A dataclass that denotes the tree structure of a\n  :class:`Module`. A ``GraphDef`` can be generated by either\n  calling :func:`split` or :func:`graphdef` on the :class:`Module`.\"\"\"\n\n  type: tp.Type[Node]\n  index: int | None\n  outer_index: int | None\n  num_attributes: int\n  metadata: tp.Any\n\n  def with_no_outer_index(self) -> NodeDef[Node]:\n    return NodeDef(\n      type=self.type,\n      index=self.index,\n      outer_index=None,\n      num_attributes=self.num_attributes,\n      metadata=self.metadata,\n    )\n\n  def with_same_outer_index(self) -> NodeDef[Node]:\n    return NodeDef(\n      type=self.type,\n      index=self.index,\n      outer_index=self.index,\n      num_attributes=self.num_attributes,\n      metadata=self.metadata,\n    )\n\n  def with_matching_outer_index(self, other) -> NodeDef[Node]:\n    return NodeDef(\n      type=self.type,\n      index=self.index,\n      outer_index=other.outer_index,\n      num_attributes=self.num_attributes,\n      metadata=self.metadata,\n    )\n\n  def __nnx_repr__(self):\n    yield reprlib.Object(type=type(self))\n\n    yield reprlib.Attr('type', self.type.__name__)\n    yield reprlib.Attr('index', self.index)\n    yield reprlib.Attr('outer_index', self.outer_index)\n    yield reprlib.Attr('num_attributes', self.num_attributes)\n    yield reprlib.Attr('metadata', self.metadata)\n\n  def __treescope_repr__(self, path, subtree_renderer):\n    return treescope.repr_lib.render_object_constructor(\n      object_type=type(self),\n      attributes={\n        'type': self.type,\n        'index': self.index,\n        'outer_index': self.outer_index,\n        'num_attributes': self.num_attributes,\n        'metadata': self.metadata,\n      },\n      path=path,\n      subtree_renderer=subtree_renderer,\n    )\n\n\nif config.flax_use_flaxlib:\n  import flaxlib  # type: ignore[import]\n\n  jax.tree_util.register_static(flaxlib.NodeDef)\n  globals()['NodeDef'] = flaxlib.NodeDef\n\n@jax.tree_util.register_static\n@dataclasses.dataclass(frozen=True, slots=True)\nclass TreeNodeDef(tp.Generic[Node]):\n  type: tp.Type[Node]\n  treedef: jax.tree_util.PyTreeDef\n  path_index: tuple[tuple[PathParts, int], ...]\n\n  def with_no_outer_index(self) -> TreeNodeDef[Node]:\n    return self\n\n  def with_same_outer_index(self) -> TreeNodeDef[Node]:\n    return self\n\n  def with_matching_outer_index(self, other) -> TreeNodeDef[Node]:\n    return self\n\nNodeDefType = tp.Union[\n  NodeDef[Node],\n  NodeRef[Node],\n  VariableDef[Node],\n  ArrayRefDef,\n  TreeNodeDef[Node],\n]\n\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass NodeAttr:\n  pass\n\n\nNODE_ATTR = NodeAttr()\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass LeafAttr:\n  pass\n\nLEAF_ATTR = LeafAttr()\n\nAttrType = tp.Union[\n  NodeAttr,\n  LeafAttr,\n  'Static[tp.Any]',\n]\n\n\n# GraphDef = tp.Union[NodeDef[Node], NodeRef[Node], VariableDef[Node]]\n@jax.tree_util.register_static\n@dataclasses.dataclass(frozen=True, slots=True)\nclass GraphDef(tp.Generic[Node]):\n  nodes: list[NodeDefType[tp.Any]]\n  attributes: list[tuple[Key, AttrType]]\n  num_leaves: int\n\n  def __hash__(self) -> int:\n    return hash((tuple(self.nodes), tuple(self.attributes)))\n\n  def with_no_outer_index(self) -> GraphDef[Node]:\n    return GraphDef(\n      nodes=[\n        node.with_no_outer_index() if not isinstance(node, NodeRef) else node\n        for node in self.nodes\n      ],\n      attributes=self.attributes,\n      num_leaves=self.num_leaves,\n    )\n\n  def with_matching_outer_index(self, other) -> GraphDef[Node]:\n    return GraphDef(\n      nodes=[\n        node.with_matching_outer_index(other_node) if not isinstance(node, NodeRef) else node\n        for node, other_node in zip(self.nodes, other.nodes)\n      ],\n      attributes=self.attributes,\n      num_leaves=self.num_leaves,\n    )\n\n  def with_same_outer_index(self) -> GraphDef[Node]:\n    return GraphDef(\n      nodes=[\n        node.with_same_outer_index() if not isinstance(node, NodeRef) else node\n        for node in self.nodes\n      ],\n      attributes=self.attributes,\n      num_leaves=self.num_leaves,\n    )\n\n  # TODO(cgarciae): remove this method\n  def apply(\n    self, state: GraphState, *states: GraphState,\n    graph: bool | None = None,\n  ) -> ApplyCaller[tuple[GraphDef[Node], GraphState]]:\n    accessor = DelayedAccessor()\n\n    def _apply(\n      accessor: DelayedAccessor, *args, **kwargs\n    ) -> tuple[tp.Any, tuple[GraphDef[Node], GraphState]]:\n      module = merge(self, state, *states)\n      fn = accessor(module)\n      out = fn(*args, **kwargs)\n      if graph is None:\n        _graph = config.nnx_graph_mode\n      else:\n        _graph = graph\n      graphdef, flat_state = flatten(module, graph=_graph)\n      state_ = statelib.from_flat_state(flat_state)\n      return out, (graphdef, state_)\n\n    return CallableProxy(_apply, accessor)  # type: ignore\n\n\nPureState = tuple[GraphDef[Node], GraphState]\n\n\ndef _tree_flatten(\n  node: tp.Any,\n  nodes: list[NodeDefType[tp.Any]],\n  leaves: list[tp.Any],\n  paths: list[PathParts] | None,\n) -> None:\n  seen_variables: dict[int, str] = {}\n  seen_refs: dict[int, str] = {}\n  def _is_leaf(path, x):\n    if isinstance(x, Variable):\n      var_id = id(x)\n      str_path = jax.tree_util.keystr(path)\n      if var_id in seen_variables:\n        raise ValueError(\n          f'Duplicate {x}\\nfound at paths:\\n\\n'\n          f'  - {seen_variables[var_id]}\\n'\n          f'  - {str_path}\\n\\n'\n          'Tree mode (graph=False) does not support shared references. '\n          + _tree_mode_suggestion_api('split')\n        )\n      seen_variables[var_id] = str_path\n      return True\n    if variablelib.is_array_ref(x):\n      ref_id = id(x)\n      str_path = jax.tree_util.keystr(path)\n      if ref_id in seen_refs:\n        raise ValueError(\n          f'Duplicate {x}\\nfound at paths:\\n\\n'\n          f'  - {seen_refs[ref_id]}\\n'\n          f'  - {str_path}\\n\\n'\n          'Tree mode (graph=False) does not support shared references. '\n          + _tree_mode_suggestion_api('split')\n        )\n      seen_refs[ref_id] = str_path\n    _check_valid_pytree(x, 'flatten', jax.tree_util.keystr(path))\n    return False\n  jax_leaves, treedef = jax.tree_util.tree_flatten_with_path(\n    node, is_leaf=_is_leaf, is_leaf_takes_path=True\n  )\n  nnx_paths_and_leaves: list[tuple[PathParts, tp.Any]] = [\n    (jax_to_nnx_path(jax_path), value) for jax_path, value in jax_leaves\n  ]\n  original_indices = {p: i for i, (p, _) in enumerate(nnx_paths_and_leaves)}\n  nnx_paths_and_leaves.sort()\n  path_index = tuple(\n    (p, original_indices[p]) for p, _ in nnx_paths_and_leaves\n  )\n\n  tree_nodedef: TreeNodeDef[tp.Any] = TreeNodeDef(\n    type=type(node),\n    treedef=treedef,\n    path_index=path_index,\n  )\n  nodes.append(tree_nodedef)\n\n  sorted_leaf_index = 0\n  for nnx_path, value in nnx_paths_and_leaves:\n    if isinstance(value, Variable):\n      nodes.append(VariableDef(\n        type=value.var_type,\n        index=sorted_leaf_index,\n        outer_index=None,\n        metadata=HashableMapping(value.get_metadata()),\n        array_refdef=None,\n      ))\n    leaves.append(value)\n    if paths is not None:\n      paths.append(nnx_path)\n    sorted_leaf_index += 1\n\n\n@tp.overload\ndef flatten(  # type: ignore[invalid-annotation]\n  node: Node,\n  /,\n  *,\n  ref_index: RefMap | None = ...,\n  ref_outer_index: RefMap | None = ...,\n  graph: bool = ...,\n) -> tuple[GraphDef[Node], FlatState[tp.Any]]: ...\n@tp.overload\ndef flatten(  # type: ignore[invalid-annotation]\n  node: Node,\n  /,\n  *,\n  with_paths: tp.Literal[True],\n  ref_index: RefMap | None = ...,\n  ref_outer_index: RefMap | None = ...,\n  graph: bool = ...,\n) -> tuple[\n  GraphDef[Node],\n  FlatState[tp.Any],\n]: ...\n@tp.overload\ndef flatten(  # type: ignore[invalid-annotation]\n  node: Node,\n  /,\n  *,\n  with_paths: tp.Literal[False],\n  ref_index: RefMap | None = ...,\n  ref_outer_index: RefMap | None = ...,\n  graph: bool = ...,\n) -> tuple[\n  GraphDef[Node],\n  list[tp.Any],\n]: ...\n@tp.overload\ndef flatten(  # type: ignore[invalid-annotation]\n  node: Node,\n  /,\n  *,\n  with_paths: bool,\n  ref_index: RefMap | None = ...,\n  ref_outer_index: RefMap | None = ...,\n  graph: bool = ...,\n) -> tuple[\n  GraphDef[Node],\n  FlatState[tp.Any] | list[tp.Any],\n]: ...\ndef flatten(  # type: ignore[invalid-annotation]\n  node: Node,\n  /,\n  *,\n  with_paths: bool = True,\n  ref_index: RefMap | None = None,\n  ref_outer_index: RefMap | None = None,\n  graph: bool | None = None,\n) -> tuple[\n  GraphDef[Node],\n  FlatState[tp.Any] | list[tp.Any],\n]:\n  \"\"\"Flattens a graph node into a (graphdef, state) pair.\n\n  Args:\n    x: A graph node.\n    ref_index: A mapping from nodes to indexes, defaults to None. If not provided, a new\n      empty dictionary is created. This argument can be used to flatten a sequence of graph\n      nodes that share references.\n    with_paths: A boolean that indicates whether to return a FlatState object that includes\n      the paths, or just a list of the Variable's inner values.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n  \"\"\"\n  if graph is None:\n    graph = set_graph_mode.current_value()\n  if ref_index is None:\n    ref_index = RefMap()\n  leaves: list[tp.Any] = []\n  path: list[Key] | None = [] if with_paths else None\n  paths: list[PathParts] | None = [] if with_paths else None\n  nodes: list[NodeDefType[tp.Any]] = []\n  attributes: list[tuple[Key, AttrType]] = []\n  if graph:\n    node_impl = get_node_impl(node)\n    _graph_flatten(\n      node,\n      node_impl,\n      path,\n      ref_index,\n      ref_outer_index,\n      nodes,\n      attributes,\n      leaves,\n      paths,\n    )\n  else:\n    _tree_flatten(\n      node,\n      nodes,\n      leaves,\n      paths,\n    )\n  graphdef: GraphDef = GraphDef(\n    nodes=nodes, attributes=attributes, num_leaves=len(leaves)\n  )\n\n  if paths is not None:\n    return graphdef, FlatState.from_sorted_keys_values(tuple(paths), leaves)  # type: ignore[return-value]\n  else:\n    return graphdef, leaves\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass DataElem:\n  value: tp.Any\n\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass StaticElem:\n  value: tp.Any\n\ndef _graph_flatten(\n  node: Node,\n  node_impl: NodeImpl[Node, Leaf, AuxData] | None,\n  path: list[Key] | None,\n  ref_index: RefMap,\n  ref_outer_index: RefMap | None,\n  nodes: list[NodeDefType[tp.Any]],\n  attributes: list[tuple[Key, AttrType]],\n  leaves: list[tp.Any],\n  paths: list[PathParts] | None,\n) -> None:\n  is_pytree_node_ = type(node_impl) is PytreeNodeImpl\n\n  index: int | None\n  if not is_pytree_node_ and node in ref_index:\n    nodes.append(NodeRef(index := ref_index[node]))\n    return\n\n  is_graph_node_ = type(node_impl) is GraphNodeImpl\n  is_variable = isinstance(node, Variable)\n  is_array_ref = variablelib.is_array_ref(node)\n\n  # only cache graph nodes, we don't add array refs here\n  # as they are added in the make_mutable_arraydef function\n  if is_graph_node_ or is_variable:\n    index = len(ref_index)\n    ref_index[node] = index\n  else:\n    index = None\n\n  def make_mutable_arraydef(value: variablelib.Ref):\n    if value in ref_index:\n      index = ref_index[value]\n      return NodeRef(index), REPEATED\n    else:\n      index = len(ref_index)\n      ref_index[value] = index\n    output_value: NoUpdate | ArrayRefOutput | variablelib.Ref\n    if ref_outer_index is not None:\n      if value in ref_outer_index:\n        outer_index = ref_outer_index[value]\n        output_value = NO_UPDATE\n        array_refdef = ArrayRefDef(index=index, outer_index=outer_index)\n      else:\n        output_value = ArrayRefOutput(value[...])\n        array_refdef = ArrayRefDef(index=index, outer_index=None)\n    else:\n      output_value = value\n      array_refdef = ArrayRefDef(index=index, outer_index=None)\n    return array_refdef, output_value\n\n  if is_variable:\n    assert isinstance(node, Variable)\n    assert index is not None\n    prev_inner_value = node.get_raw_value()\n    if variablelib.is_array_ref(prev_inner_value):\n      array_refdef, inner_value = make_mutable_arraydef(prev_inner_value)\n    else:\n      array_refdef = None\n      inner_value = prev_inner_value\n    if path is None:\n      leaf = inner_value\n    else:\n      leaf = node  # type: ignore[assignment]\n      if inner_value is not prev_inner_value:\n        leaf.set_raw_value(inner_value)\n\n    variabledef = VariableDef(\n      type=node.var_type,  # type: ignore\n      index=index,\n      outer_index=ref_outer_index.get(node, None) if ref_outer_index else None,\n      metadata=HashableMapping(node.get_metadata()),\n      array_refdef=array_refdef,\n    )\n    if type(inner_value) is not Repeated:\n      assert not isinstance(leaf, Repeated)\n      leaves.append(leaf)\n      if path is not None:\n        assert paths is not None\n        paths.append(tuple(path))\n    nodes.append(variabledef)\n    return\n  elif is_array_ref:\n    array_refdef, leaf = make_mutable_arraydef(node)  # type: ignore[arg-type]\n    if not isinstance(leaf, Repeated):\n      leaves.append(leaf)\n      if path is not None:\n        assert paths is not None\n        paths.append(tuple(path))\n    nodes.append(array_refdef)\n    return\n  elif not is_pytree_node_ and not is_graph_node_:\n    # unkown leaf\n    leaves.append(node)\n    if path is not None:\n      assert paths is not None\n      paths.append(tuple(path))\n    return\n\n  if node_impl is None:\n    raise RuntimeError(f'Unsupported type: {type(node)}, this is a bug.')\n\n  values, metadata = node_impl.flatten(node)\n  num_attributes = len(values)\n  nodedef = NodeDef(\n    node_impl.type,\n    index,\n    ref_outer_index[node]\n    if is_graph_node_ and ref_outer_index and node in ref_outer_index\n    else None,\n    num_attributes,\n    metadata,\n  )\n  nodes.append(nodedef)\n\n  for key, value in values:\n    is_data = None\n    if isinstance(value, DataElem):\n      value = value.value\n      is_data = True\n    elif isinstance(value, StaticElem):\n      value = value.value\n      is_data = False\n\n    if is_data is False:\n      attributes.append((key, Static(value)))\n      continue\n\n    value_node_impl = get_node_impl(value)\n    if path is not None:\n      path.append(key)\n    if value_node_impl is not None or isinstance(value, Variable):\n      attributes.append((key, NODE_ATTR))\n      _graph_flatten(\n        value,\n        value_node_impl,\n        path,\n        ref_index,\n        ref_outer_index,\n        nodes,\n        attributes,\n        leaves,\n        paths,\n      )\n    elif variablelib.is_array_ref(value):\n      attributes.append((key, NODE_ATTR))\n      array_refdef, leaf = make_mutable_arraydef(value)\n      if not isinstance(leaf, Repeated):\n        leaves.append(leaf)\n        if paths is not None:\n          paths.append(tuple(path))  # type: ignore\n      nodes.append(array_refdef)\n    elif isinstance(value, (jax.Array, np.ndarray)) or is_data:\n      attributes.append((key, LEAF_ATTR))\n      if paths is not None:\n        paths.append(tuple(path))  # type: ignore\n      leaves.append(value)\n    else:\n      attributes.append((key, Static(value)))\n\n    if path is not None:\n      path.pop()\n\n  return\n\n\ndef _get_sorted_leaves(\n  xs: tp.Mapping[tp.Any, tp.Any],\n) -> list[tp.Any]:\n  if not isinstance(xs, tp.Mapping):  # type: ignore\n    raise TypeError(f'expected Mapping; got {type(xs).__qualname__}')\n  leaves: list[tp.Any] = []\n\n  def _flatten(xs):\n    if not isinstance(xs, tp.Mapping):\n      leaves.append(xs)\n    else:\n      for _, value in sorted(xs.items()):\n        _flatten(value)\n\n  _flatten(xs)\n  return leaves\n\n\ndef _tree_unflatten(\n  graphdef: GraphDef[tp.Any],\n  leaves: list[tp.Any],\n  copy_variables: bool,\n) -> tp.Any:\n  tree_nodedef = graphdef.nodes[0]\n  assert isinstance(tree_nodedef, TreeNodeDef)\n  variable_defs_iter = iter(\n    node for node in graphdef.nodes[1:] if isinstance(node, VariableDef)\n  )\n  variabledef = next(variable_defs_iter, None)\n\n  original_leaves: list[tp.Any] = [None] * len(leaves)\n  for i, (path, original_index) in enumerate(tree_nodedef.path_index):\n    leaf = leaves[i]\n    if variabledef is not None and variabledef.index == i:\n      if isinstance(leaf, Variable):\n        if copy_variables:\n          leaf = leaf.copy()\n      else:\n        leaf = variabledef.type.from_metadata(\n          leaf, dict(variabledef.metadata)\n        )\n      variabledef = next(variable_defs_iter, None)\n    original_leaves[original_index] = leaf\n\n  return tree_nodedef.treedef.unflatten(original_leaves)\n\n\ndef unflatten(  # type: ignore[invalid-annotation]\n  graphdef: GraphDef[Node],\n  state: State[Key, tp.Any] | FlatState[tp.Any] | list[tp.Any],\n  /,\n  *,\n  index_ref: IndexMap | None = None,\n  outer_index_outer_ref: IndexMap | None = None,\n  copy_variables: bool = False,\n) -> Node:\n  \"\"\"Unflattens a graphdef into a node with the given state.\n\n  Args:\n    graphdef: A GraphDef instance.\n    state: A State instance.\n    index_ref: A mapping from indexes to nodes references found during the graph\n      traversal, defaults to None. If not provided, a new empty dictionary is\n      created. This argument can be used to unflatten a sequence of (graphdef,\n      state) pairs that share the same index space.\n    index_ref_cache: A mapping from indexes to existing nodes that can be\n      reused. When an reference is reused, ``GraphNodeImpl.clear`` is called to\n      leave the object in an empty state and then filled by the unflatten\n      process, as a result existing graph nodes are mutated to have the new\n      content/topology specified by the graphdef.\n    copy_variables: If True variables in the state will be copied onto the new\n      new structure, else variables will be shared. Default is False.\n  \"\"\"\n  if isinstance(state, (State, dict)):\n    leaves = _get_sorted_leaves(state)\n  elif isinstance(state, FlatState):\n    leaves = state.leaves\n  elif isinstance(state, list):  # type: ignore\n    leaves = state\n  else:\n    raise ValueError(f'Unsupported state type: {type(state)}')\n\n  if len(leaves) != graphdef.num_leaves:\n    raise ValueError(\n      f'Incorrect number of leaves, expected {graphdef.num_leaves} leaves, but got {len(leaves)}.'\n    )\n\n  if graphdef.nodes and isinstance(graphdef.nodes[0], TreeNodeDef):\n    return _tree_unflatten(graphdef, leaves, copy_variables)\n\n  if index_ref is None:\n    index_ref = IndexMap()\n\n  if len(graphdef.nodes) == 0:\n    return leaves[0]\n  elif isinstance(nodedef := graphdef.nodes[0], NodeRef):\n    node = index_ref[nodedef.index]\n  else:\n    node_iter = iter(graphdef.nodes)\n    attribute_iter = iter(graphdef.attributes)\n    leaves_iter = iter(leaves)\n    nodedef = next(node_iter)\n    assert not isinstance(nodedef, NodeRef)\n    if isinstance(nodedef, ArrayRefDef):\n      node_impl = None\n    else:\n      node_impl = get_node_impl_for_type(nodedef.type)\n    node = _graph_unflatten(\n      nodedef,\n      node_impl,\n      node_iter,\n      attribute_iter,\n      leaves_iter,\n      index_ref,\n      outer_index_outer_ref,\n      copy_variables,\n    )\n\n    try:\n      next(leaves_iter)\n    except StopIteration:\n      pass\n    else:\n      raise ValueError('Incorrect number of leaves in state.')\n\n  return node\n\n\ndef _graph_unflatten(\n  nodedef: NodeDefType[Node],\n  node_impl: NodeImpl[Node, Leaf, AuxData] | None,\n  node_iter: tp.Iterator[NodeDefType[Node]],\n  attribute_iter: tp.Iterator[tuple[Key, AttrType]],\n  leaves_iter: tp.Iterator[tp.Any],\n  index_ref: IndexMap,\n  outer_index_outer_ref: IndexMap | None,\n  copy_variables: bool,\n) -> Node:\n  \"\"\"Recursive helper for graph_unflatten.\n\n    Args:\n      nodedef: A GraphDef instance or an index to a node in the cache.\n      state: A mapping from attribute names to variables or subgraphs.\n      index_ref: A mapping from indexes to nodes that have been traversed.\n        If a node is already in the cache, it won't be traversed again.\n      outer_index_outer_ref: A mapping from indexes to existing nodes that can be reused.\n        When an reference is reused, ``GraphNodeImpl.clear`` is called to leave the\n        object in an empty state and then filled by the unflatten process, as a result\n        existing graph nodes are mutated to have the new content/topology\n        specified by the nodedef.\n  \"\"\"\n\n  def get_mutable_array(array_refdef: ArrayRefDef, leaf):\n    assert type(array_refdef) is ArrayRefDef\n    if (\n      outer_index_outer_ref is not None\n      and array_refdef.outer_index is not None\n      and array_refdef.outer_index in outer_index_outer_ref\n    ):\n      # if array ref exists, update it\n      array_ref = outer_index_outer_ref[array_refdef.outer_index]\n      if not variablelib.is_array_ref(array_ref):\n        raise RuntimeError(f'Expected a ArrayRef type but got {array_ref}.')\n      if type(leaf) is not NoUpdate:\n        raise RuntimeError(f'Expected a no update for ArrayRef but got {leaf}.')\n    elif type(leaf) in (NoUpdate, Repeated):\n      raise ValueError(\n        f\"Expected a ArrayRefOutput type but got '{leaf}.'\"\n      )\n    elif type(leaf) is ArrayRefOutput:\n      array_ref = jax.new_ref(leaf.value)\n    elif variablelib.is_array_ref(leaf):\n      array_ref = leaf\n    else:\n      # here we allow merging frozen arrays and will not create a new array ref\n      array_ref = leaf\n\n    index_ref[array_refdef.index] = array_ref\n    return array_ref\n\n  if type(nodedef) is NodeRef:\n    return index_ref[nodedef.index]\n\n  if type(nodedef) is VariableDef:\n    variabledef = tp.cast(VariableDef[Variable], nodedef)\n    # its a unseen variable, create a new one\n\n    if variabledef.array_refdef is not None:\n      if type(variabledef.array_refdef) is NodeRef:\n        value = index_ref[variabledef.array_refdef.index]\n      else:\n        value = next(leaves_iter)\n        assert type(variabledef.array_refdef) is ArrayRefDef\n        if isinstance(value, Variable):\n          copy_ref = not isinstance(\n            value.get_raw_value(), (NoUpdate, Repeated, ArrayRefOutput)\n          )\n          value = value.copy(_copy_ref=copy_ref) if copy_variables else value\n          inner_value = value.get_raw_value()\n          array_ref = get_mutable_array(variabledef.array_refdef, inner_value)\n          if array_ref is not inner_value:\n            value.set_raw_value(array_ref)\n        else:\n          # if value is an array or array ref, we need call get_mutable_array\n          # to register it in the index_ref\n          value = get_mutable_array(variabledef.array_refdef, value)\n    else:\n      value = next(leaves_iter)\n      if isinstance(value, Variable) and copy_variables:\n        copy_ref = not isinstance(\n          value.get_raw_value(), (NoUpdate, Repeated, ArrayRefOutput)\n        )\n        value = value.copy(_copy_ref=copy_ref)\n\n    # when idxmap is present, check if the Varable exists there\n    # and update existing variables if it does\n    if (\n      outer_index_outer_ref is not None\n      and variabledef.outer_index is not None\n      and variabledef.outer_index in outer_index_outer_ref\n    ):\n      # if variable exists, update it\n      variable = outer_index_outer_ref[variabledef.outer_index]\n      if not isinstance(variable, Variable):\n        raise ValueError(f'Expected a Variable type but got {type(variable)}.')\n      elif isinstance(value, Variable):\n        variable.update_from_state(value)\n      else:\n        variable.set_raw_value(value)\n    else:  # variabledef.index not in index_ref_cache\n      # variable reference does not exist outside, create a new one\n      if isinstance(value, Variable):\n        variable = value\n      else:\n        variable = variabledef.type.from_metadata(\n          value, dict(variabledef.metadata)\n        )\n    index_ref[variabledef.index] = variable\n    return variable  # type: ignore[return-value]\n\n  if type(nodedef) is ArrayRefDef:\n    leaf = next(leaves_iter)\n    array_ref = get_mutable_array(nodedef, leaf)\n    return array_ref  # type: ignore[return-value]\n\n  assert type(nodedef) is NodeDef\n  if node_impl is None:\n    raise RuntimeError(f'Unsupported type: {nodedef.type}, this is a bug.')\n  if nodedef.index is not None and nodedef.index in index_ref:\n    raise RuntimeError(f'GraphDef index {nodedef.index} already used.')\n\n  def _get_children() -> list[tuple[Key, tp.Any]]:\n    children: list[tuple[Key, LeafType | Node]] = []  # type: ignore[invalid-annotation]\n\n    assert type(nodedef) is NodeDef\n    for _ in range(nodedef.num_attributes):\n      key, value = next(attribute_iter)\n      if type(value) is Static:\n        children.append((key, value.value))  # type: ignore[attribute-error]\n      elif type(value) is LeafAttr:\n        leaf = next(leaves_iter)\n        children.append((key, leaf))\n      elif type(value) is NodeAttr:\n        node_def = next(node_iter)\n        if isinstance(node_def, NodeRef):\n          node = index_ref[node_def.index]\n        elif isinstance(node_def, ArrayRefDef):\n          leaf = next(leaves_iter)\n          node = get_mutable_array(node_def, leaf)\n        elif isinstance(node_def, NodeDef | VariableDef):\n          value_node_impl = get_node_impl_for_type(node_def.type)\n          node = _graph_unflatten(\n            node_def,\n            value_node_impl,\n            node_iter,\n            attribute_iter,\n            leaves_iter,\n            index_ref,\n            outer_index_outer_ref,\n            copy_variables,\n          )\n        else:\n          raise RuntimeError(f'Unknown node definition: {node_def!r}')\n        children.append((key, node))\n      elif type(value) is NodeRef:\n        children.append((key, index_ref[value.index]))  # type: ignore[attribute-error]\n      else:\n        raise RuntimeError(f'Unknown static field: {key!r}')\n\n    return children\n\n  if isinstance(node_impl, GraphNodeImpl):\n    # we create an empty node first and add it to the index\n    # this avoids infinite recursion when there is a reference cycle\n    assert type(nodedef) is NodeDef\n    if (\n      outer_index_outer_ref is not None\n      and nodedef.outer_index is not None\n      and nodedef.outer_index in outer_index_outer_ref\n    ):\n      node = outer_index_outer_ref[nodedef.outer_index]\n      if type(node) != nodedef.type:\n        raise ValueError(\n          f'Expected a node of type {nodedef.type} for index '\n          f'{nodedef.index}, but got a node of type {type(node)}.'\n        )\n      node_impl.clear(node)\n    else:\n      node = node_impl.create_empty(nodedef.metadata)\n    assert nodedef.index is not None\n    index_ref[nodedef.index] = node\n    node_impl.init(node, _get_children())\n  else:\n    # if the node type does not support the creation of an empty object it means\n    # that it cannot reference itself, so we can create its children first\n    node = node_impl.unflatten(_get_children(), nodedef.metadata)\n\n  return node\n\n\ndef graph_pop(\n  node: tp.Any,\n  filters: tuple[filterlib.Filter, ...],\n) -> tuple[GraphState, ...]:\n  id_to_index: dict[int, Index] = {}\n  path_parts: PathParts = ()\n  predicates = tuple(filterlib.to_predicate(filter) for filter in filters)\n  flat_states: tuple[dict[PathParts, LeafType], ...] = tuple(\n    {} for _ in predicates\n  )\n  _graph_pop(node, id_to_index, path_parts, flat_states, predicates)\n  return tuple(\n    statelib.from_flat_state(flat_state) for flat_state in flat_states\n  )\n\n\ndef _graph_pop(\n  node: tp.Any,\n  id_to_index: dict[int, Index],\n  path_parts: PathParts,\n  flat_states: tuple[dict[PathParts, LeafType], ...],\n  predicates: tuple[filterlib.Predicate, ...],\n) -> None:\n  if not is_node(node):\n    raise RuntimeError(f'Unsupported type: {type(node)}, this is a bug.')\n\n  if id(node) in id_to_index:\n    return\n\n  id_to_index[id(node)] = len(id_to_index)\n  node_impl = get_node_impl(node)\n  if node_impl is None:\n    raise TypeError(f'Unknown node type: {type(node)}')\n  node_dict = node_impl.node_dict(node)\n\n  for name, value in node_dict.items():\n    if is_node(value):\n      _graph_pop(\n        node=value,\n        id_to_index=id_to_index,\n        path_parts=(*path_parts, name),\n        flat_states=flat_states,\n        predicates=predicates,\n      )\n      continue\n    elif not is_node_leaf(value):\n      continue\n    elif id(value) in id_to_index:\n      continue\n\n    node_path = (*path_parts, name)\n    node_impl = get_node_impl(node)\n    if node_impl is None:\n      raise TypeError(f'Unknown node type: {type(node)}')\n\n    for state, predicate in zip(flat_states, predicates):\n      if predicate(node_path, value):\n        if node_impl.pop_key is None:\n          raise ValueError(\n            f'Cannot pop key {name!r} from node of type {type(node).__name__}'\n          )\n        id_to_index[id(value)] = len(id_to_index)\n        node_impl.pop_key(node, name)\n        if isinstance(value, Variable):\n          value = value\n        state[node_path] = value  # type: ignore[index] # mypy is wrong here?\n        break\n    else:\n      # NOTE: should we raise an error here?\n      pass\n\n\ndef _graph_update_dynamic(node: tp.Any, state: tp.Mapping[KeyT, tp.Any]):\n  def _update_variable(node: Variable, value):\n    if isinstance(value, Variable):\n      # updated from Variable\n      node.update_from_state(value)\n    else:\n      # updated from raw value\n      if isinstance(value, State) and not value:\n        # NOTE: this is a special case when trying to update a Variable from state\n        # created when flattening into a NodeRef, which creates an empty State. This\n        # can happen when using standalone Variables with `grad`\n        pass\n      else:\n        if is_array_ref(node.get_raw_value()) and (\n          isinstance(value, jax.Array) or is_array_ref(value)\n        ):\n          node[...] = value[...]\n        else:\n          node.set_raw_value(value, _unsafe_bypass_check=True)\n\n  if isinstance(node, Variable):\n    _update_variable(node, state)\n    return\n\n  if not is_node(node):\n    raise RuntimeError(f'Unsupported type: {type(node)}')\n\n  node_impl = get_node_impl(node)\n  if node_impl is None:\n    raise TypeError(f'Unknown node type: {type(node)}')\n  node_dict = node_impl.node_dict(node)\n  for key, value in state.items():\n    # case 1: new state is being added\n    if key not in node_dict:\n      if node_impl.set_key is None:\n        raise ValueError(\n          f'Cannot set key {key!r} on immutable node of '\n          f'type {type(node).__name__}'\n        )\n      if isinstance(value, Variable):\n        copy_ref = not isinstance(\n          value.get_raw_value(), (NoUpdate, Repeated, ArrayRefOutput)\n        )\n        value = value.copy(_copy_ref=copy_ref)\n      node_impl.set_key(node, key, value)\n      continue\n\n    current_value = node_dict[key]\n\n    # case 2: subgraph is being updated\n    if is_array_ref(current_value):\n      current_value[...] = value\n    elif is_node(current_value):\n      if is_node_leaf(value):\n        raise ValueError(f'Expected a subgraph for {key!r}, but got: {value!r}')\n      _graph_update_dynamic(current_value, value)\n    elif isinstance(current_value, Variable):\n      _update_variable(current_value, value)\n    elif node_impl.set_key is not None:\n      node_impl.set_key(node, key, value)\n    else:\n      raise ValueError(\n        f'Cannot set key {key!r} on immutable node of '\n        f'type {type(node).__name__}'\n      )\n\n\n# --------------------------------------------------------\n# UpdateContext\n# --------------------------------------------------------\n\n\nclass StaticCache(tp.NamedTuple):\n  graphdef: GraphDef[tp.Any]\n  final_graphdef: GraphDef[tp.Any]\n  paths: tuple[PathParts, ...]\n  variables: list[Variable[tp.Any]]\n  new_ref_index: RefMap\n  new_index_ref: IndexMap\n\n  @staticmethod\n  def create(\n    graphdef: GraphDef[tp.Any],\n    paths: tuple[PathParts, ...],\n    variables: list[Variable[tp.Any]],\n    new_ref_index: RefMap,\n  ):\n    new_index_ref = IndexMap.from_refmap(new_ref_index)\n    final_graphdef: GraphDef[tp.Any]\n    final_graphdef = graphdef.with_same_outer_index()\n    return StaticCache(\n      graphdef=graphdef,\n      final_graphdef=final_graphdef,\n      paths=paths,\n      variables=variables,\n      new_ref_index=new_ref_index,\n      new_index_ref=new_index_ref,\n    )\n\n\n@dataclasses.dataclass\nclass GraphContext(threading.local):\n  update_context_stacks: dict[tp.Hashable, list[UpdateContext]] = (\n    dataclasses.field(default_factory=dict)\n  )\n  ref_index_stack: list[SplitContext] = dataclasses.field(default_factory=list)\n  index_ref_stack: list[MergeContext] = dataclasses.field(default_factory=list)\n  tmp_static_cache: tp.MutableMapping[tp.Any, StaticCache] | None = None\n  caching: bool = False\n  graph_mode_stack: list[bool] = dataclasses.field(default_factory=list)\n  graph_updates_stack: list[bool] = dataclasses.field(default_factory=list)\n\n\nGRAPH_CONTEXT = GraphContext()\n\n\nclass set_graph_mode(BaseConfigContext):\n  get_default = classmethod(lambda cls: config.nnx_graph_mode)\n  get_stack = classmethod(lambda cls: GRAPH_CONTEXT.graph_mode_stack)\n\n\nclass set_graph_updates(BaseConfigContext):\n  get_default = classmethod(lambda cls: config.nnx_graph_updates)\n  get_stack = classmethod(lambda cls: GRAPH_CONTEXT.graph_updates_stack)\n\n\n@contextlib.contextmanager\ndef static_cache(static_cache: tp.MutableMapping[tp.Any, StaticCache]):\n  if GRAPH_CONTEXT.caching:\n    yield\n    return\n\n  GRAPH_CONTEXT.tmp_static_cache = static_cache\n\n  try:\n    yield\n  finally:\n    if GRAPH_CONTEXT.tmp_static_cache is not None:\n      raise ValueError(\n        'GRAPH_CONTEXT.tmp_static_cache should be None, no context consumed it.'\n      )\n\n\ndef _cached_partial(f: tp.Callable[..., tp.Any], *cached_args, graph: bool | None = None):\n  \"\"\"Create a partial from a NNX transformed function alog with some cached input arguments\n  and reduces the python overhead by caching the traversal of NNX graph nodes. This is useful\n  for speed up function that are called repeatedly with the same subset of inputs e.g. a\n  ``train_step`` with a ``model`` and ``optimizer``::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n    >>> import optax\n    ...\n    >>> model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    >>> optimizer = nnx.Optimizer(model, optax.adamw(1e-3), wrt=nnx.Param)\n    ...\n    >>> @nnx.jit\n    ... def train_step(model, optimizer, x, y):\n    ...   def loss_fn(model):\n    ...     return jnp.mean((model(x) - y) ** 2)\n    ...\n    ...   loss, grads = nnx.value_and_grad(loss_fn)(model)\n    ...   optimizer.update(model, grads)\n    ...   return loss\n    ...\n    >>> cached_train_step = nnx.cached_partial(train_step, model, optimizer)\n    ...\n    >>> for step in range(total_steps:=2):\n    ...   x, y = jnp.ones((10, 2)), jnp.ones((10, 3))\n    ...   # loss = train_step(model, optimizer, x, y)\n    ...   loss = cached_train_step(x, y)\n    ...   print(f'Step {step}: loss={loss:.3f}')\n    Step 0: loss=2.669\n    Step 1: loss=2.660\n\n  Note that ``cached_partial`` will clone all cached graph nodes to gurantee the validity\n  of the cache, and these clones will contain references to the same Variable objects\n  which guarantees that state is propagated correctly back to the original graph nodes.\n  Because of the previous, the final structure of all graph nodes must be the same\n  after each call to the cached function, otherwise an error will be raised. Temporary\n  mutations are allowed (e.g. the use of ``Module.sow``) as long as they are cleaned up before\n  the function returns (e.g. via ``nnx.pop``).\n\n  Args:\n    f: A function to cache.\n    *cached_args: A subset of the input arguments containing the graph nodes to cache.\n\n  Returns:\n    A partial function expecting the remaining arguments to the original function.\n  \"\"\"\n  if graph is None:\n    graph = set_graph_mode.current_value()\n  if not graph:\n    raise ValueError(\n      'cached_partial is a graph-mode-only API and does not support '\n      'tree-mode (graph=False).'\n    )\n  cache: tp.MutableMapping[tp.Any, StaticCache] = PythonRefMap()  # type: ignore\n  original_ref_index: RefMap = RefMap()\n  index_ref: IndexMap = IndexMap()\n  cached_ref_index: RefMap = RefMap()\n\n  def create_static_cache(x):\n    # TODO(cgarciae): support Array attribute updates for graph nodes\n    if is_graph_node(x) or isinstance(x, Variable):\n      graphdef, flat_state = flatten(\n        x, with_paths=True, ref_index=original_ref_index, graph=True\n      )\n      paths = flat_state.paths\n      variables = flat_state.leaves\n      # clone but keep the same variable references\n      node_cache = unflatten(\n        graphdef, flat_state, index_ref=index_ref, copy_variables=False,\n      )\n      start_index = len(cached_ref_index)\n      flatten(\n        node_cache,\n        ref_index=cached_ref_index,\n        with_paths=False,\n        graph=True,\n      )\n      cached_new_ref_index = RefMap(\n        (key, value)\n        for key, value in cached_ref_index.items()\n        if value >= start_index\n      )\n      cache[node_cache] = StaticCache.create(\n        graphdef, paths, variables, cached_new_ref_index\n      )\n      return node_cache\n    return x\n\n  cached_args = jax.tree.map(\n    create_static_cache,\n    cached_args,\n    is_leaf=lambda x: is_graph_node(x) or isinstance(x, Variable),\n  )\n\n  @functools.wraps(f)\n  def cache_args_wrapper(*args, **kwargs):\n    with static_cache(cache):\n      return f(*cached_args, *args, **kwargs)\n\n  return cache_args_wrapper\n\n\nif tp.TYPE_CHECKING:\n  cached_partial = functools.partial\nelse:\n  cached_partial = _cached_partial\n\n\n@dataclasses.dataclass\nclass SplitContext:\n  ctxtag: tp.Hashable | None\n  ref_index: RefMap\n  is_inner: bool | None\n\n  @tp.overload\n  def split(self, graph_node: A, /) -> tuple[GraphDef[A], GraphState]: ...  # type: ignore[invalid-annotation]\n\n  @tp.overload\n  def split(  # type: ignore[invalid-annotation]\n    self, graph_node: A, first: filterlib.Filter, /\n  ) -> tuple[GraphDef[A], GraphState]: ...\n\n  @tp.overload\n  def split(\n    self,\n    graph_node: A,\n    first: filterlib.Filter,\n    second: filterlib.Filter,\n    /,\n    *filters: filterlib.Filter,\n  ) -> tuple[GraphDef[A], GraphState, tpe.Unpack[tuple[GraphState, ...]]]: ...  # type: ignore[not-supported-yet]\n\n  def split(\n    self, node: A, *filters: filterlib.Filter\n  ) -> tuple[GraphDef[A], tpe.Unpack[tuple[GraphState, ...]]]:  # type: ignore[not-supported-yet]\n    ctx = (\n      current_update_context(self.ctxtag) if self.ctxtag is not None else None\n    )\n    inner_ref_outer_index = (\n      ctx.inner_ref_outer_index if ctx and ctx.inner_ref_outer_index else None\n    )\n    graphdef, flat_state = flatten(\n      node, ref_index=self.ref_index, ref_outer_index=inner_ref_outer_index, graph=True\n    )\n    flat_states = _split_state(flat_state, filters)\n    states = _to_nested_state(graphdef, flat_states)\n\n    return graphdef, *states\n\n  @tp.overload\n  def flatten(  # type: ignore[invalid-annotation]\n    self,\n    graph_node: A,\n    /,\n    *,\n    with_paths: tp.Literal[False],\n  ) -> tuple[GraphDef[A], list[tp.Any]]: ...\n\n  @tp.overload\n  def flatten(  # type: ignore[invalid-annotation]\n    self,\n    graph_node: A,\n    /,\n  ) -> tuple[GraphDef[A], FlatState[tp.Any]]: ...\n\n  @tp.overload\n  def flatten(  # type: ignore[invalid-annotation]\n    self,\n    graph_node: A,\n    first: filterlib.Filter,\n    /,\n  ) -> tuple[GraphDef[A], FlatState[tp.Any]]: ...\n\n  @tp.overload\n  def flatten(  # type: ignore[invalid-annotation]\n    self,\n    graph_node: A,\n    first: filterlib.Filter,\n    second: filterlib.Filter,\n    /,\n    *filters: filterlib.Filter,\n  ) -> tuple[\n    GraphDef[A],\n    FlatState[tp.Any],\n    tpe.Unpack[tuple[FlatState[tp.Any], ...]],\n  ]: ...\n\n  def flatten(  # type: ignore[invalid-annotation]\n    self,\n    node: A,\n    *filters: filterlib.Filter,\n    with_paths: bool = True,\n  ) -> tuple[\n    GraphDef[A],\n    FlatState[tp.Any] | list[tp.Any],\n    tpe.Unpack[tuple[FlatState[tp.Any], ...]],\n  ]:\n    if not with_paths and filters:\n      raise ValueError('Cannot use filters with with_paths=False')\n\n    ctx = (\n      current_update_context(self.ctxtag) if self.ctxtag is not None else None\n    )\n    static_cache = (\n      ctx.static_cache if ctx is not None and self.is_inner is False else None\n    )\n    ref_outer_index = (\n      ctx.inner_ref_outer_index if ctx and ctx.inner_ref_outer_index else None\n    )\n    flat_state: FlatState[tp.Any] | list[tp.Any]\n    leaves: list[tp.Any]\n    if node in self.ref_index:\n      # node is already in the ref_index, call flatten which will return a NodeRef\n      graphdef, flat_state = flatten(\n        node,\n        ref_index=self.ref_index,\n        ref_outer_index=ref_outer_index,\n        with_paths=with_paths,\n        graph=True,\n      )\n      if with_paths:\n        assert isinstance(flat_state, FlatState)\n        paths = flat_state.paths\n        leaves = flat_state.leaves\n      else:\n        assert isinstance(flat_state, list)\n        paths = None\n        leaves = flat_state\n    elif static_cache is not None and node in static_cache:\n      node_static_cache = static_cache[node]\n      graphdef = node_static_cache.graphdef\n      # add the new references to the ref_index\n      self.ref_index.update(node_static_cache.new_ref_index)\n\n      if with_paths:\n        paths = node_static_cache.paths\n        leaves = node_static_cache.variables\n      else:\n        paths = None\n        leaves = [\n          variable.get_raw_value() for variable in node_static_cache.variables\n        ]\n    else:\n      graphdef, flat_state = flatten(\n        node,\n        ref_index=self.ref_index,\n        ref_outer_index=ref_outer_index,\n        with_paths=with_paths,\n        graph=True,\n      )\n      if with_paths:\n        assert isinstance(flat_state, FlatState)\n        paths = flat_state.paths\n        leaves = flat_state.leaves\n      else:\n        assert isinstance(flat_state, list)\n        paths = None\n        leaves = flat_state\n\n    if with_paths:\n      assert paths is not None\n      flat_state = FlatState.from_sorted_keys_values(paths, leaves)\n      flat_states = _split_state(flat_state, filters)\n      return graphdef, *flat_states  # type: ignore[bad-return-type]\n    else:\n      return graphdef, leaves\n\n\n@contextlib.contextmanager\ndef split_context(ctxtag: tp.Hashable | None = None):\n  ctx = current_update_context(ctxtag) if ctxtag is not None else None\n  is_inner = ctx.outer_ref_outer_index is not None if ctx is not None else None\n  GRAPH_CONTEXT.ref_index_stack.append(SplitContext(ctxtag, RefMap(), is_inner))\n\n  try:\n    yield GRAPH_CONTEXT.ref_index_stack[-1]\n  finally:\n    flatten_ctx = GRAPH_CONTEXT.ref_index_stack.pop()\n    if ctxtag is not None:\n      ctx = current_update_context(ctxtag)\n      ctx.flatten_end(flatten_ctx.ref_index)\n    del flatten_ctx.ref_index\n    del flatten_ctx.ctxtag\n\n\n@dataclasses.dataclass\nclass MergeContext:\n  ctxtag: tp.Hashable | None\n  index_ref: IndexMap\n  is_inner: bool | None\n\n  def merge(  # type: ignore[invalid-annotation]\n    self,\n    graphdef: GraphDef[A],\n    state: GraphState,\n    /,\n    *states: GraphState,\n  ) -> A:\n    ctx = (\n      current_update_context(self.ctxtag) if self.ctxtag is not None else None\n    )\n    outer_index_outer_ref = (\n      ctx.outer_index_outer_ref if ctx and ctx.outer_index_outer_ref else None\n    )\n\n    _state = _merge_to_flat_state((state, *states))\n    node = unflatten(\n      graphdef,\n      _state,\n      index_ref=self.index_ref,\n      outer_index_outer_ref=outer_index_outer_ref,\n      copy_variables=True,\n    )\n    return node\n\n  def unflatten(  # type: ignore[invalid-annotation]\n    self,\n    graphdef: GraphDef[A],\n    flat_state: GraphFlatState | list[tp.Any],\n    /,\n    *flat_states: GraphFlatState,\n  ) -> A:\n    ctx = (\n      current_update_context(self.ctxtag) if self.ctxtag is not None else None\n    )\n    static_cache = (\n      ctx.static_cache if ctx is not None and self.is_inner is False else None\n    )\n    state: FlatState[tp.Any] | list[tp.Any]\n    if type(flat_state) is list:\n      if flat_states:\n        raise ValueError(\n          'Cannot use multiple flat_states when flat_state is a list, '\n          f'got flat_state: {flat_state!r}, flat_states: {flat_states!r}'\n        )\n      state = flat_state\n    else:\n      state = FlatState.merge(flat_state, *flat_states)\n\n    if type(graphdef.nodes[0]) is NodeRef:\n      node = unflatten(\n        graphdef,\n        state,\n        index_ref=self.index_ref,\n      )\n\n    elif static_cache is not None:\n      assert isinstance(graphdef.nodes[0], NodeDef) or isinstance(graphdef.nodes[0], VariableDef)\n      assert ctx is not None\n      if (outer_index := graphdef.nodes[0].outer_index) is not None:\n        outer_index_outer_ref = ctx.outer_index_outer_ref\n        assert outer_index_outer_ref is not None\n        node = outer_index_outer_ref[outer_index]\n\n        if node in static_cache:\n          static_cache_node = static_cache[node]\n          if static_cache_node.final_graphdef != graphdef:\n            raise ValueError(\n              'The graph structure of a node added to cached_partial was mutated inside the transformation, '\n              f'this is not allowed.\\nNode: {node}\\nOuput graphdef: {graphdef}\\nExpected graphdef: {static_cache_node.final_graphdef}'\n            )\n          if type(state) is list:\n            leaves = state\n          elif type(state) is FlatState:\n            leaves = state.leaves\n          else:\n            raise ValueError(f'Unsupported state type: {type(state)}')\n\n          if len(leaves) != len(static_cache_node.variables):\n            raise ValueError(\n              f'Incorrect number of leaves: expected {len(static_cache_node.variables)} '\n              f'leaves in the state, got {len(leaves)}'\n            )\n          for variable, leaf in zip(static_cache_node.variables, leaves):\n            if isinstance(leaf, Variable):\n              variable.update_from_state(leaf)\n            else:\n              variable.set_raw_value(leaf)\n          self.index_ref.update(static_cache_node.new_index_ref)\n        else:\n          # uncached node, create it\n          node = unflatten(\n            graphdef,\n            state,\n            index_ref=self.index_ref,\n            outer_index_outer_ref=outer_index_outer_ref,\n          )\n      else:  # graphdef.outer_index is None\n        # its a new node, create it\n        node = unflatten(\n          graphdef,\n          state,\n          index_ref=self.index_ref,\n        )\n    else:\n      outer_index_outer_ref = (\n        ctx.outer_index_outer_ref if ctx and ctx.outer_index_outer_ref else None\n      )\n      node = unflatten(\n        graphdef,\n        state,\n        index_ref=self.index_ref,\n        outer_index_outer_ref=outer_index_outer_ref,\n      )\n    return node\n\n\n@tp.overload\n@contextlib.contextmanager\ndef merge_context() -> tp.Generator[MergeContext, None, None]: ...  # type: ignore[bad-return-type]\n@tp.overload\n@contextlib.contextmanager\ndef merge_context(\n  ctxtag: tp.Hashable | None, inner: bool | None\n) -> tp.Generator[MergeContext, None, None]: ...  # type: ignore[bad-return-type]\n@contextlib.contextmanager\ndef merge_context(ctxtag: tp.Hashable | None = None, inner: bool | None = None):\n  GRAPH_CONTEXT.index_ref_stack.append(MergeContext(ctxtag, IndexMap(), inner))\n\n  try:\n    yield GRAPH_CONTEXT.index_ref_stack[-1]\n  finally:\n    unflatten_ctx = GRAPH_CONTEXT.index_ref_stack.pop()\n    index_ref = unflatten_ctx.index_ref\n    if ctxtag is not None:\n      if inner is None:\n        raise ValueError('inner_merge must be specified when using ctxtag')\n      ctx = current_update_context(ctxtag)\n      ctx.unflatten_end(index_ref, inner)\n    del unflatten_ctx.index_ref\n    del unflatten_ctx.ctxtag\n\n\n@jax.tree_util.register_static\n@dataclasses.dataclass\nclass UpdateContext:\n  \"\"\"A context manager for handling complex state updates.\"\"\"\n\n  tag: tp.Hashable\n  outer_ref_outer_index: RefMap | None\n  outer_index_inner_ref: IndexMap | None\n  # reverse caches\n  outer_index_outer_ref: IndexMap | None\n  inner_ref_outer_index: RefMap | None\n  static_cache: tp.MutableMapping[tp.Any, StaticCache] | None\n\n  # define hash and eq to make this an opaque object\n  def __hash__(self):\n    return 0\n\n  def __eq__(self, other):\n    return isinstance(other, UpdateContext)\n\n  def flatten_end(self, ref_index: RefMap):\n    if self.outer_ref_outer_index is None:\n      # outer split (1), store the references\n      self.outer_ref_outer_index = ref_index\n      self.outer_index_outer_ref = IndexMap.from_refmap(\n        self.outer_ref_outer_index\n      )\n    else:\n      # inner split (3), clear index_ref\n      self.outer_index_inner_ref = None\n      self.inner_ref_outer_index = None\n\n  def unflatten_end(self, index_ref: IndexMap, inner_merge: bool):\n    if inner_merge:\n      # inner merge (2)\n      self.outer_index_inner_ref = index_ref\n      self.inner_ref_outer_index = RefMap.from_indexmap(index_ref)\n\n\n@dataclasses.dataclass\nclass UpdateContextManager:\n  tag: tp.Hashable\n\n  def __enter__(self):\n    if GRAPH_CONTEXT.tmp_static_cache is not None:\n      # take current static cache\n      static_cache = GRAPH_CONTEXT.tmp_static_cache\n      GRAPH_CONTEXT.tmp_static_cache = None\n    else:\n      static_cache = None\n    ctx = UpdateContext(\n      tag=self.tag,\n      outer_ref_outer_index=None,\n      outer_index_inner_ref=None,\n      outer_index_outer_ref=None,\n      inner_ref_outer_index=None,\n      static_cache=static_cache,\n    )\n    if self.tag not in GRAPH_CONTEXT.update_context_stacks:\n      GRAPH_CONTEXT.update_context_stacks[self.tag] = [ctx]\n    else:\n      GRAPH_CONTEXT.update_context_stacks[self.tag].append(ctx)\n    return ctx\n\n  def __exit__(self, *args):\n    if self.tag not in GRAPH_CONTEXT.update_context_stacks:\n      raise RuntimeError(\n        f'No update context found for tag {self.tag!r}, this is a bug.'\n      )\n    stack = GRAPH_CONTEXT.update_context_stacks[self.tag]\n\n    ctx = stack.pop()\n    # clear references\n    del ctx.outer_ref_outer_index\n    del ctx.outer_index_inner_ref\n    del ctx.outer_index_outer_ref\n    del ctx.inner_ref_outer_index\n\n    if not stack:\n      del GRAPH_CONTEXT.update_context_stacks[self.tag]\n\n  def __call__(self, f: F) -> F:\n    @functools.wraps(f)\n    def update_context_manager_wrapper(*args, **kwargs):\n      with self:\n        return f(*args, **kwargs)\n\n    return update_context_manager_wrapper  # type: ignore\n\n\ndef update_context(tag: tp.Hashable):\n  \"\"\"Creates an :class:`UpdateContext` context manager which can be used to handle\n  more complex state updates beyond what ``nnx.update`` can handle, including\n  updates to static properties and graph structure.\n\n  UpdateContext exposes a ``split`` and ``merge`` API with the same\n  signature as ``nnx.split`` / ``nnx.merge`` but performs some bookkeeping\n  to have the necessary information in order to perfectly update the input\n  objects based on the changes made inside the transform. The UpdateContext\n  must call split and merge a total of 4 times, the first\n  and last calls happen outside the transform and the second and third calls\n  happen inside the transform as shown in the diagram below::\n\n\n                          idxmap\n    (2) merge ─────────────────────────────► split (3)\n          ▲                                    │\n          │               inside               │\n          │. . . . . . . . . . . . . . . . . . │ index_mapping\n          │               outside              │\n          │                                    ▼\n    (1) split──────────────────────────────► merge (4)\n                          refmap\n\n\n  The first call to split ``(1)`` creates a ``refmap`` which keeps track of the\n  outer references, and the first call to merge ``(2)`` creates an ``idxmap`` which\n  keeps track of the inner references. The second call to split ``(3)`` combines\n  the refmap and idxmap to produce the ``index_mapping`` which indicates\n  how the outer references map to the inner references. Finally, the last call to\n  merge ``(4)`` uses the index_mapping and the refmap to reconstruct the\n  output of the transform while reusing/updating the inner references. To avoid\n  memory leaks, the idxmap is cleared after ``(3)`` and the refmap is\n  cleared after ``(4)``, and both are cleared after the context manager exits.\n\n  Here is a simple example showing the use of ``update_context``::\n\n    >>> from flax import nnx\n    ...\n    >>> class Foo(nnx.Module): pass\n    ...\n    >>> m1 = Foo()\n    >>> with nnx.update_context('example'):\n    ...   with nnx.split_context('example') as ctx:\n    ...     graphdef, state = ctx.split(m1)\n    ...   @jax.jit\n    ...   def f(graphdef, state):\n    ...     with nnx.merge_context('example', inner=True) as ctx:\n    ...       m2 = ctx.merge(graphdef, state)\n    ...     m2.a = 1\n    ...     m2.ref = m2  # create a reference cycle\n    ...     with nnx.split_context('example') as ctx:\n    ...       return ctx.split(m2)\n    ...   graphdef_out, state_out = f(graphdef, state)\n    ...   with nnx.merge_context('example', inner=False) as ctx:\n    ...     m3 = ctx.merge(graphdef_out, state_out)\n    ...\n    >>> assert m1 is m3\n    >>> assert m1.a == 1\n    >>> assert m1.ref is m1\n\n  Note that ``update_context`` takes in a ``tag`` argument which is used\n  primarily as a safety mechanism reduce the risk of accidentally using the\n  wrong UpdateContext when using :func:`current_update_context` to access the\n  current active context. ``update_context`` can also be used as a\n  decorator that creates/activates an UpdateContext context for the\n  duration of the function::\n\n    >>> from flax import nnx\n    ...\n    >>> class Foo(nnx.Module): pass\n    ...\n    >>> m1 = Foo()\n    >>> @jax.jit\n    ... def f(graphdef, state):\n    ...   with nnx.merge_context('example', inner=True) as ctx:\n    ...     m2 = ctx.merge(graphdef, state)\n    ...   m2.a = 1     # insert static attribute\n    ...   m2.ref = m2  # create a reference cycle\n    ...   with nnx.split_context('example') as ctx:\n    ...     return ctx.split(m2)\n    ...\n    >>> @nnx.update_context('example')\n    ... def g(m1):\n    ...   with nnx.split_context('example') as ctx:\n    ...     graphdef, state = ctx.split(m1)\n    ...   graphdef_out, state_out = f(graphdef, state)\n    ...   with nnx.merge_context('example', inner=False) as ctx:\n    ...     return ctx.merge(graphdef_out, state_out)\n    ...\n    >>> m3 = g(m1)\n    >>> assert m1 is m3\n    >>> assert m1.a == 1\n    >>> assert m1.ref is m1\n\n  The context can be accessed using :func:`current_update_context`.\n\n  Args:\n    tag: A string tag to identify the context.\n  \"\"\"\n  return UpdateContextManager(tag=tag)\n\n\ndef current_update_context(tag: tp.Hashable) -> UpdateContext:\n  \"\"\"Returns the current active :class:`UpdateContext` for the given tag.\"\"\"\n  if tag not in GRAPH_CONTEXT.update_context_stacks:\n    raise ValueError(f'No update context found for tag {tag!r}.')\n  return GRAPH_CONTEXT.update_context_stacks[tag][-1]\n\n\n# --------------------------------------------------------\n# Functional API\n# --------------------------------------------------------\n\n\ndef _split_state(\n  state: FlatState[tp.Any],\n  filters: tuple[filterlib.Filter, ...],\n) -> tuple[FlatState[tp.Any], tpe.Unpack[tuple[FlatState[tp.Any], ...]]]:\n  if not filters:\n    return (state,)  # type: ignore[bad-return-type]\n  states = state.split(*filters)\n  if not isinstance(states, tuple):\n    return (states,)  # type: ignore[bad-return-type]\n  assert len(states) > 0\n  return states  # type: ignore[return-value]\n\n\n@tp.overload\ndef split(  # type: ignore[invalid-annotation]\n  graph_node: A, /, *, graph: bool | None = None,\n) -> tuple[GraphDef[A], GraphState]: ...\n@tp.overload\ndef split(  # type: ignore[invalid-annotation]\n  graph_node: A, first: filterlib.Filter, /, *, graph: bool | None = None,\n) -> tuple[GraphDef[A], GraphState]: ...\n@tp.overload\ndef split(  # type: ignore[invalid-annotation]\n  graph_node: A,\n  first: filterlib.Filter,\n  second: filterlib.Filter,\n  /,\n  *filters: filterlib.Filter,\n  graph: bool | None = None,\n) -> tuple[\n  GraphDef[A],\n  GraphState,\n  tpe.Unpack[tuple[GraphState, ...]],\n]: ...\ndef split(  # type: ignore[invalid-annotation]\n  node: A, *filters: filterlib.Filter, graph: bool | None = None,\n) -> tuple[\n  GraphDef[A],\n  GraphState,\n  tpe.Unpack[tuple[GraphState, ...]],\n]:\n  \"\"\"Split a graph node into a :class:`GraphDef` and one or more :class:`State`s. State is\n  a ``Mapping`` from strings or integers to ``Variables``, Arrays or nested States. GraphDef\n  contains all the static information needed to reconstruct a ``Module`` graph, it is analogous\n  to JAX's ``PyTreeDef``. :func:`split` is used in conjunction with :func:`merge` to switch\n  seamlessly between stateful and stateless representations of the graph.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n    ...\n    >>> class Foo(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.batch_norm = nnx.BatchNorm(2, rngs=rngs)\n    ...     self.linear = nnx.Linear(2, 3, rngs=rngs)\n    ...\n    >>> node = Foo(nnx.Rngs(0))\n    >>> graphdef, params, batch_stats = nnx.split(node, nnx.Param, nnx.BatchStat)\n    ...\n    >>> jax.tree.map(jnp.shape, params)\n    State({\n      'batch_norm': {\n        'bias': Param(\n          value=(2,)\n        ),\n        'scale': Param(\n          value=(2,)\n        )\n      },\n      'linear': {\n        'bias': Param(\n          value=(3,)\n        ),\n        'kernel': Param(\n          value=(2, 3)\n        )\n      }\n    })\n    >>> jax.tree.map(jnp.shape, batch_stats)\n    State({\n      'batch_norm': {\n        'mean': BatchStat(\n          value=(2,)\n        ),\n        'var': BatchStat(\n          value=(2,)\n        )\n      }\n    })\n\n  :func:`split` and :func:`merge` are primarily used to interact directly with JAX\n  transformations, see\n  `Functional API <https://flax.readthedocs.io/en/latest/nnx/nnx_basics.html#the-functional-api>`__\n  for more information.\n\n  Arguments:\n    node: graph node to split.\n    *filters: some optional filters to group the state into mutually exclusive substates.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n  Returns:\n    ``GraphDef`` and one or more ``States`` equal to the number of filters passed. If no\n    filters are passed, a single ``State`` is returned.\n  \"\"\"\n  if graph is None:\n    graph = set_graph_mode.current_value()\n  graphdef, flat_state = flatten(node, graph=graph)\n  flat_states = _split_state(flat_state, filters)\n  states = _to_nested_state(graphdef, flat_states)\n  return graphdef, *states  # type: ignore[return-value]\n\n\ndef _to_nested_state(\n  graphdef: GraphDef[A], flat_states: tp.Iterable[tp.Any]\n) -> tuple[tp.Any, ...]:\n  def _nested_or_leaf(flat_state):\n    if not flat_state:\n      return State({})\n    if len(flat_state) == 1 and flat_state[0][0] == ():\n      return flat_state[0][1]\n    return statelib.from_flat_state(flat_state)\n  states = tuple(\n    _nested_or_leaf(flat_state) for flat_state in flat_states\n  )\n  return states\n\n\ndef _merge_to_flat_state(states: tp.Iterable[tp.Any]):\n  flat_state: list[tuple[PathParts, tp.Any]] = []\n\n  for state in states:\n    if isinstance(state, dict | State):\n      flat_state.extend(traversals.flatten_to_sequence(state))\n    elif isinstance(state, FlatState):\n      flat_state.extend(state)\n    else:\n      flat_state.append(((), state))\n\n  flat_state.sort()\n  return [value for _, value in flat_state]\n\n\ndef merge(  # type: ignore[invalid-annotation]\n  graphdef: GraphDef[A],\n  state: tp.Any,\n  /,\n  *states: tp.Any,\n  copy: bool = False,\n) -> A:\n  \"\"\"The inverse of :func:`flax.nnx.split`.\n\n  ``nnx.merge`` takes a :class:`flax.nnx.GraphDef` and one or more :class:`flax.nnx.State`'s\n  and creates a new node with the same structure as the original node.\n\n  Recall: :func:`flax.nnx.split` is used to represent a :class:`flax.nnx.Module`\n  by: 1) a static ``nnx.GraphDef`` that captures its Pythonic static information;\n  and 2) one or more :class:`flax.nnx.Variable` ``nnx.State``'(s) that capture\n  its ``jax.Array``'s in the form of JAX pytrees.\n\n  ``nnx.merge`` is used in conjunction with ``nnx.split`` to switch seamlessly\n  between stateful and stateless representations of the graph.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n    ...\n    >>> class Foo(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.batch_norm = nnx.BatchNorm(2, rngs=rngs)\n    ...     self.linear = nnx.Linear(2, 3, rngs=rngs)\n    ...\n    >>> node = Foo(nnx.Rngs(0))\n    >>> graphdef, params, batch_stats = nnx.split(node, nnx.Param, nnx.BatchStat)\n    ...\n    >>> new_node = nnx.merge(graphdef, params, batch_stats)\n    >>> assert isinstance(new_node, Foo)\n    >>> assert isinstance(new_node.batch_norm, nnx.BatchNorm)\n    >>> assert isinstance(new_node.linear, nnx.Linear)\n\n  ``nnx.split`` and ``nnx.merge`` are primarily used to interact directly with JAX\n  transformations (refer to\n  `Functional API <https://flax.readthedocs.io/en/latest/nnx_basics.html#the-flax-functional-api>`__\n  for more information.\n\n  Args:\n    graphdef: A :class:`flax.nnx.GraphDef` object.\n    state: A :class:`flax.nnx.State` object.\n    *states: Additional :class:`flax.nnx.State` objects.\n    copy: Whether to create new copies of the Variables in the states, defaults to ``False``.\n  Returns:\n    The merged :class:`flax.nnx.Module`.\n  \"\"\"\n  if isinstance(state, list):\n    if len(states) != 0:\n      raise ValueError(f'Only one state can be passed as a list.')\n    _state = state\n  else:\n    _state = _merge_to_flat_state((state, *states))\n  node = unflatten(graphdef, _state, copy_variables=copy)\n  return node\n\n\ndef update(node, state: tp.Any, /, *states: tp.Any) -> None:\n  \"\"\"Update the given graph node with a new state(s) in-place.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n\n    >>> x = jnp.ones((1, 2))\n    >>> y = jnp.ones((1, 3))\n    >>> model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    >>> def loss_fn(model, x, y):\n    ...   return jnp.mean((y - model(x))**2)\n    >>> prev_loss = loss_fn(model, x, y)\n\n    >>> grads = nnx.grad(loss_fn)(model, x, y)\n    >>> new_state = jax.tree.map(lambda p, g: p - 0.1*g, nnx.state(model), grads)\n    >>> nnx.update(model, new_state)\n    >>> assert loss_fn(model, x, y) < prev_loss\n\n  Args:\n    node: A graph node to update.\n    state: A :class:`State` object.\n    *states: Additional :class:`State` objects.\n  \"\"\"\n  if states:\n    if isinstance(node, Variable):\n      non_empty_states = [\n        _state\n        for _state in (state, *states)\n        if not isinstance(_state, tp.Mapping) or _state\n      ]\n      if len(non_empty_states) != 1:\n        all_states = (state, *states)\n        raise ValueError(\n          f'Expected exactly one non-empty state, got: {all_states!r}'\n        )\n      state = non_empty_states[0]\n    else:\n      state = statelib.merge_state(state, *states)\n  _graph_update_dynamic(node, state)\n\n\n@tp.overload\ndef state(node, /, *, graph: bool | None = None) -> GraphState: ...\n@tp.overload\ndef state(node, first: filterlib.Filter, /, *, graph: bool | None = None) -> GraphState: ...\n@tp.overload\ndef state(\n  node,\n  first: filterlib.Filter,\n  second: filterlib.Filter,\n  /,\n  *filters: filterlib.Filter,\n  graph: bool | None = None,\n) -> tuple[GraphState, ...]: ...\ndef state(\n  node,\n  *filters: filterlib.Filter,\n  graph: bool | None = None,\n) -> tp.Union[GraphState, tuple[GraphState, ...]]:\n  \"\"\"Similar to :func:`split` but only returns the :class:`State`'s indicated by the filters.\n\n  Example usage::\n\n    >>> from flax import nnx\n\n    >>> class Model(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.batch_norm = nnx.BatchNorm(2, rngs=rngs)\n    ...     self.linear = nnx.Linear(2, 3, rngs=rngs)\n    ...   def __call__(self, x):\n    ...     return self.linear(self.batch_norm(x))\n\n    >>> model = Model(rngs=nnx.Rngs(0))\n    >>> # get the learnable parameters from the batch norm and linear layer\n    >>> params = nnx.state(model, nnx.Param)\n    >>> # get the batch statistics from the batch norm layer\n    >>> batch_stats = nnx.state(model, nnx.BatchStat)\n    >>> # get them separately\n    >>> params, batch_stats = nnx.state(model, nnx.Param, nnx.BatchStat)\n    >>> # get them together\n    >>> state = nnx.state(model)\n\n  Args:\n    node: A graph node object.\n    *filters: One or more :class:`Variable` objects to filter by.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n  Returns:\n    One or more :class:`State` mappings.\n  \"\"\"\n  if graph is None:\n    graph = set_graph_mode.current_value()\n  _, flat_state = flatten(node, graph=graph)\n  state = flat_state.to_nested_state()\n\n  states: GraphState | tuple[GraphState, ...]\n  if len(filters) == 0:\n    states = state  # type: ignore[assignment]\n  elif len(filters) == 1:\n    states = statelib.filter_state(state, filters[0])\n  else:\n    states = statelib.filter_state(state, filters[0], filters[1], *filters[2:])\n\n  return states\n\n\nvariables = state\n\n\ndef map(\n  f: tp.Callable[[tuple, tp.Any], tp.Any],\n  node: A,\n  /,\n  *,\n  graph: bool | None = None,\n) -> A:\n  \"\"\"Map a function over the state of a graph node.\n\n  ``map`` extracts the state from ``node`` using :func:`split`, applies ``f``\n  to every ``(path, value)`` pair using :func:`map_state`, and returns a\n  new node with the mapped values merged back into the original structure.\n  Note that the leaves in the state are :class:`Variable` objects, so ``f``\n  should handle them accordingly.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n\n    >>> model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    >>> new_model = nnx.map(lambda path, v: v.replace(jnp.zeros_like(v)), model)\n    >>> assert jnp.all(new_model.kernel[...] == 0)\n    >>> assert jnp.all(new_model.bias[...] == 0)\n\n  Args:\n    f: A callable ``(path, value) -> new_value`` applied to each leaf in the\n      state. ``path`` is a tuple of path parts and ``value`` is the\n      corresponding leaf (typically a :class:`Variable`).\n    node: A graph node object.\n    graph: If ``True``, uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n  Returns:\n    A :class:`State` with the mapped values.\n  \"\"\"\n  graphdef, state = split(node, graph=graph)\n  state = statelib.map_state(f, state)\n  return merge(graphdef, state)\n\n\ndef graphdef(\n  node: tp.Any, /, *, graph: bool | None = None,\n) -> GraphDef[tp.Any]:\n  \"\"\"Get the :class:`GraphDef` of the given graph node.\n\n  Example usage::\n\n    >>> from flax import nnx\n\n    >>> model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    >>> graphdef, _ = nnx.split(model)\n    >>> assert graphdef == nnx.graphdef(model)\n\n  Args:\n    node: A graph node object.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n  Returns:\n    The :class:`GraphDef` of the :class:`Module` object.\n  \"\"\"\n  if graph is None:\n    graph = set_graph_mode.current_value()\n  graphdef, _ = flatten(node, graph=graph)\n  return graphdef\n\n\n@tp.overload\ndef pop(\n  node,\n  filter: filterlib.Filter,\n  /,\n) -> GraphState: ...\n\n\n@tp.overload\ndef pop(\n  node,\n  filter: filterlib.Filter,\n  filter2: filterlib.Filter,\n  /,\n  *filters: filterlib.Filter,\n) -> tuple[GraphState, ...]: ...\n\n\ndef pop(\n  node, *filters: filterlib.Filter\n) -> tp.Union[GraphState, tuple[GraphState, ...]]:\n  \"\"\"Pop one or more :class:`Variable` types from the graph node.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n\n    >>> class Model(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.linear1 = nnx.Linear(2, 3, rngs=rngs)\n    ...     self.linear2 = nnx.Linear(3, 4, rngs=rngs)\n    ...   def __call__(self, x):\n    ...     x = self.linear1(x)\n    ...     self.i = nnx.Intermediate(x)\n    ...     x = self.linear2(x)\n    ...     return x\n\n    >>> x = jnp.ones((1, 2))\n    >>> model = Model(rngs=nnx.Rngs(0))\n    >>> assert not hasattr(model, 'i')\n    >>> y = model(x)\n    >>> assert hasattr(model, 'i')\n\n    >>> intermediates = nnx.pop(model, nnx.Intermediate)\n    >>> assert intermediates['i'].shape == (1, 3)\n    >>> assert not hasattr(model, 'i')\n\n  Args:\n    node: A graph node object.\n    *filters: One or more :class:`Variable` objects to filter by.\n  Returns:\n    The popped :class:`State` containing the :class:`Variable`\n    objects that were filtered for.\n  \"\"\"\n  if len(filters) == 0:\n    raise ValueError('Expected at least one filter')\n\n  id_to_index: dict[int, Index] = {}\n  path_parts: PathParts = ()\n  predicates = tuple(filterlib.to_predicate(filter) for filter in filters)\n  flat_states: tuple[dict[PathParts, LeafType], ...] = tuple(\n    {} for _ in predicates\n  )\n  _graph_pop(\n    node=node,\n    id_to_index=id_to_index,\n    path_parts=path_parts,\n    flat_states=flat_states,\n    predicates=predicates,\n  )\n  states = tuple(\n    statelib.from_flat_state(flat_state) for flat_state in flat_states\n  )\n\n  if len(states) == 1:\n    return states[0]\n  else:\n    return states\n\n\ndef clone(node: Node, variables: bool = True, *, graph: bool | None = None) -> Node:\n  \"\"\"Create a deep copy of the given graph node.\n\n  Example usage::\n\n    >>> from flax import nnx\n\n    >>> model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    >>> cloned_model = nnx.clone(model)\n    >>> model.bias[...] += 1\n    >>> assert (model.bias[...] != cloned_model.bias[...]).all()\n\n  Args:\n    node: A graph node object.\n    variables: If ``True`` (default) copies of the :class:`Variable` objects are created,\n      otherwise the Variables are shared between the original and cloned node.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n  Returns:\n    A deep copy of the :class:`Module` object.\n  \"\"\"\n  graphdef, state = split(node, graph=graph)\n  return merge(graphdef, state, copy=variables)\n\n\ndef vars_as(\n  node: A,\n  /,\n  *,\n  hijax: bool | None = None,\n  ref: bool | None = None,\n  mutable: bool | None = None,\n  only: filterlib.Filter = ...,\n  allow_duplicates: bool = False,\n) -> A:\n  \"\"\" \"\"\"\n  new_attrs: dict[str, bool] = {}\n  if hijax is not None:\n    new_attrs['hijax'] = hijax\n  if ref is not None:\n    new_attrs['ref'] = ref\n  if mutable is not None:\n    new_attrs['mutable'] = mutable\n\n  def _different_vars(path, x):\n    return isinstance(x, Variable) and any(\n      getattr(x, attr) != value for attr, value in new_attrs.items()\n    )\n\n  only = filterlib.All(_different_vars, only)\n  predicate = filterlib.to_predicate(only)\n\n  if not allow_duplicates and (\n    all_duplicates := find_duplicates(node, only=only)\n  ):\n    duplicates_strs = '\\n  ---'\n    for node_duplicates in all_duplicates:\n      for path in node_duplicates:\n        path_str = '/'.join(builtins.map(str, path))\n        duplicates_strs += f'\\n  {path_str}'\n      duplicates_strs += '\\n  ---'\n    raise ValueError(f'Found duplicate at paths:{duplicates_strs}')\n\n  def _to_refs(jax_path, x):\n    if predicate(jax_to_nnx_path(jax_path), x):\n      assert isinstance(x, Variable)\n      variable = x.copy(**new_attrs)\n      return variable\n    return x\n\n  node = jax.tree.map_with_path(\n    _to_refs, node, is_leaf=lambda x: isinstance(x, Variable)\n  )\n  return node\n\n\ndef pure(tree: A) -> A:\n  \"\"\"Returns a new tree with all ``Variable`` objects replaced with inner values.\n\n  This can be used to remove Variable metadata when its is not needed for tasks like\n  serialization or exporting.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> import jax\n    >>> import jax.numpy as jnp\n    ...\n    >>> model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    >>> graphdef, state = nnx.split(model)\n    >>> jax.tree.map(jnp.shape, state)\n    State({\n      'bias': Param(\n        value=(3,)\n      ),\n      'kernel': Param(\n        value=(2, 3)\n      )\n    })\n    >>> pure_state = nnx.pure(state)\n    >>> jax.tree.map(jnp.shape, pure_state)\n    State({\n      'bias': (3,),\n      'kernel': (2, 3)\n    })\n\n  Args:\n    tree: A pytree potentially containing ``Variable`` objects.\n  Returns:\n    A new pytree with all ``Variable`` objects replaced with their\n    inner values.\n  \"\"\"\n\n  def _pure_fn(x):\n    if isinstance(x, Variable):\n      return pure(x.get_raw_value())\n    elif variablelib.is_array_ref(x):\n      return x[...]\n    return x\n\n  return jax.tree.map(\n    _pure_fn,\n    tree,\n    is_leaf=lambda x: isinstance(x, Variable),\n  )\n\n\ndef call(\n  graphdef_state: tuple[GraphDef[A], GraphState], /\n) -> ApplyCaller[tuple[GraphDef[A], GraphState]]:\n  \"\"\"Calls a method underlying graph node defined by a (GraphDef, State) pair.\n\n  ``call`` takes a ``(GraphDef, State)`` pair and creates a proxy object that can be\n  used to call methods on the underlying graph node. When a method is called, the\n  output is returned along with a new (GraphDef, State) pair that represents the\n  updated state of the graph node. ``call`` is equivalent to :func:`merge` > ``method``\n  > :func:`split` but is more convenient to use in pure JAX functions.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> import jax\n    >>> import jax.numpy as jnp\n    ...\n    >>> class StatefulLinear(nnx.Module):\n    ...   def __init__(self, din, dout, rngs):\n    ...     self.w = nnx.Param(jax.random.uniform(rngs(), (din, dout)))\n    ...     self.b = nnx.Param(jnp.zeros((dout,)))\n    ...     self.count = Variable(jnp.array(0, dtype=jnp.uint32))\n    ...\n    ...   def increment(self):\n    ...     self.count[...] += 1\n    ...\n    ...   def __call__(self, x):\n    ...     self.increment()\n    ...     return x @ self.w + self.b\n    ...\n    >>> linear = StatefulLinear(3, 2, nnx.Rngs(0))\n    >>> linear_state = nnx.split(linear)\n    ...\n    >>> @jax.jit\n    ... def forward(x, linear_state):\n    ...   y, linear_state = nnx.call(linear_state)(x)\n    ...   return y, linear_state\n    ...\n    >>> x = jnp.ones((1, 3))\n    >>> y, linear_state = forward(x, linear_state)\n    >>> y, linear_state = forward(x, linear_state)\n    ...\n    >>> linear = nnx.merge(*linear_state)\n    >>> linear.count[...]\n    Array(2, dtype=uint32)\n\n  The proxy object returned by ``call`` supports indexing and attribute access\n  to access nested methods. In the example below, the ``increment`` method indexing\n  is used to call the ``increment`` method of the ``StatefulLinear`` module\n  at the ``b`` key of a ``nodes`` dictionary.\n\n    >>> class StatefulLinear(nnx.Module):\n    ...   def __init__(self, din, dout, rngs):\n    ...     self.w = nnx.Param(jax.random.uniform(rngs(), (din, dout)))\n    ...     self.b = nnx.Param(jnp.zeros((dout,)))\n    ...     self.count = nnx.Variable(jnp.array(0, dtype=jnp.uint32))\n    ...\n    ...   def increment(self):\n    ...     self.count[...] += 1\n    ...\n    ...   def __call__(self, x):\n    ...     self.increment()\n    ...     return x @ self.w + self.b\n    ...\n    >>> rngs = nnx.Rngs(0)\n    >>> nodes = dict(\n    ...   a=StatefulLinear(3, 2, rngs),\n    ...   b=StatefulLinear(2, 1, rngs),\n    ... )\n    ...\n    >>> node_state = nnx.split(nodes)\n    >>> # use attribute access\n    >>> _, node_state = nnx.call(node_state)['b'].increment()\n    ...\n    >>> nodes = nnx.merge(*node_state)\n    >>> nodes['a'].count[...]\n    Array(0, dtype=uint32)\n    >>> nodes['b'].count[...]\n    Array(1, dtype=uint32)\n  \"\"\"\n\n  def pure_caller(accessor: DelayedAccessor, *args, **kwargs):\n    node = merge(*graphdef_state)\n    method = accessor(node)\n    out = method(*args, **kwargs)\n    return out, split(node)\n\n  return CallableProxy(pure_caller)  # type: ignore\n\n\ndef set_metadata(\n  node: tp.Any, /, *, only: filterlib.Filter = Variable, **metadata: tp.Any\n) -> None:\n  \"\"\"Sets the metadata of all :class:`Variable` objects in the given graph node in-place.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n    ...\n    >>> class Foo(nnx.Module):\n    ...   def __init__(self):\n    ...     self.param = nnx.Param(0.0)\n    ...     self.variable = nnx.Variable(0.0)\n    ...\n    >>> node = Foo()\n    ...\n    >>> # set differentiable to False for all nnx.Param objects\n    >>> nnx.set_metadata(node, differentiable=False, only=nnx.Param)\n    ...\n    >>> # check that only the nnx.Param was updated\n    >>> assert node.param.get_metadata('differentiable') is False\n\n  Args:\n    node: A graph node object.\n    only: A Filter to specify which :class:`Variable` objects to set metadata for.\n    metadata: Key-value pairs to set as metadata on the :class:`Variable` objects.\n  \"\"\"\n  def _set_metadata(path: PathParts, variable: V) -> None:\n    del path  # unused\n    if isinstance(variable, Variable):\n      variable.set_metadata(**metadata)\n\n  # inplace update of variable_state metadata\n  map_state(_set_metadata, state(node, only))\n\n\ndef iter_graph(\n  node: tp.Any, /, *, graph: bool | None = None,\n) -> tp.Iterator[tuple[PathParts, tp.Any]]:\n  \"\"\"Iterates over all nested nodes and leaves of the given graph node, including the current node.\n\n  ``iter_graph`` creates a generator that yields path and value pairs, where the\n  path is a tuple of strings or integers representing the path to the value from\n  the root. Repeated nodes are visited only once. Leaves include static values.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n    ...\n    >>> class Linear(nnx.Module):\n    ...   def __init__(self, din, dout, *, rngs: nnx.Rngs):\n    ...     self.din, self.dout = din, dout\n    ...     self.w = nnx.Param(jax.random.uniform(rngs.next(), (din, dout)))\n    ...     self.b = nnx.Param(jnp.zeros((dout,)))\n    ...\n    >>> module = Linear(3, 4, rngs=nnx.Rngs(0))\n    >>> graph = [module, module]\n    ...\n    >>> for path, value in nnx.iter_graph(graph):\n    ...   print(path, type(value).__name__)\n    ...\n    (0, '_pytree__nodes') HashableMapping\n    (0, '_pytree__state') PytreeState\n    (0, 'b') Param\n    (0, 'din') int\n    (0, 'dout') int\n    (0, 'w') Param\n    (0,) Linear\n    () list\n\n  Args:\n    node: A graph node object.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n  \"\"\"\n  if graph is None:\n    graph = set_graph_mode.current_value()\n  if graph:\n    return _iter_graph(node)\n  else:\n    return _iter_tree(node)\n\n\ndef _iter_graph(node: tp.Any, /) -> tp.Iterator[tuple[PathParts, tp.Any]]:\n  visited: set[int] = set()\n  stack: list[tuple[PathParts, tp.Any, bool]] = [((), node, False)]\n  while stack:\n    path_parts, node, traversed = stack.pop(-1)\n    if traversed or not (is_node(node) or isinstance(node, Variable)):\n      yield path_parts, node\n      continue\n\n    if id(node) in visited:\n      continue\n    visited.add(id(node))\n\n    if (node_impl := get_node_impl(node)) is None:\n      yield path_parts, node\n      continue\n\n    stack.append((path_parts, node, True))\n    for key, child in reversed(node_impl.node_dict(node).items()):\n      stack.append(((*path_parts, key), child, False))\n\n\ndef _iter_tree(node: tp.Any, /) -> tp.Iterator[tuple[PathParts, tp.Any]]:\n  in_progress: dict[int, str] = {}\n  seen_refs: dict[int, str] = {}\n  stack: list[tuple[PathParts, tp.Any, bool]] = [((), node, False)]\n  while stack:\n    path, current, traversed = stack.pop()\n\n    if traversed:\n      in_progress.pop(id(current), None)\n      yield path, current\n      continue\n\n    if not is_pytree_node(current, check_graph_registry=False):\n      _check_valid_pytree(current, 'iter_graph', '/'.join(builtins.map(str, path)))\n      if isinstance(current, Variable) or variablelib.is_array_ref(current):\n        obj_id = id(current)\n        str_path = '/'.join(builtins.map(str, path))\n        if obj_id in seen_refs:\n          raise ValueError(\n            f'Duplicate {current}\\nfound at paths:\\n\\n'\n            f'  - {seen_refs[obj_id]}\\n'\n            f'  - {str_path}\\n\\n'\n            'Tree mode (graph=False) does not support shared references. '\n            + _tree_mode_suggestion_api('iter_graph')\n          )\n        seen_refs[obj_id] = str_path\n      yield path, current\n      continue\n\n    obj_id = id(current)\n    str_path = '/'.join(builtins.map(str, path))\n    if obj_id in in_progress:\n      raise ValueError(\n        f'Cycle detected for {type(current).__name__}\\nfound at paths:\\n\\n'\n        f'  - {in_progress[obj_id]}\\n'\n        f'  - {str_path}\\n\\n'\n        'Cycles are not supported with graph=False. '\n        + _tree_mode_suggestion_api('iter_graph')\n      )\n    in_progress[obj_id] = str_path\n\n    stack.append((path, current, True))\n    children, _ = jax.tree_util.tree_flatten_with_path(\n      current, is_leaf=lambda x: x is not current\n    )\n    for jax_key_path, child in reversed(children):\n      key = _key_path_to_key(jax_key_path[0])\n      stack.append(((*path, key), child, False))\n\n\ndef iter_children(\n  node: tp.Any, /, *, graph: bool | None = None,\n) -> tp.Iterator[tuple[Key, tp.Any]]:\n  \"\"\"Iterates over all immediate child nodes of a given node. This\n  function is similar to :func:`iter_graph`, except it only iterates over the\n  immediate children, and does not recurse further down.\n\n  Specifically, this function creates a generator that yields the key and the\n  child node instance, where the key is a string representing the attribute\n  name to access the corresponding child.\n\n  Example::\n\n    >>> from flax import nnx\n    ...\n    >>> class SubModule(nnx.Module):\n    ...   def __init__(self, din, dout, rngs):\n    ...     self.linear1 = nnx.Linear(din, dout, rngs=rngs)\n    ...     self.linear2 = nnx.Linear(din, dout, rngs=rngs)\n    ...\n    >>> class Block(nnx.Module):\n    ...   def __init__(self, din, dout, *, rngs: nnx.Rngs):\n    ...     self.linear = nnx.Linear(din, dout, rngs=rngs)\n    ...     self.submodule = SubModule(din, dout, rngs=rngs)\n    ...     self.dropout = nnx.Dropout(0.5)\n    ...     self.batch_norm = nnx.BatchNorm(10, rngs=rngs)\n    ...\n    >>> model = Block(2, 5, rngs=nnx.Rngs(0))\n    >>> for path, module in nnx.iter_children(model):\n    ...  print(path, type(module).__name__)\n    ...\n    batch_norm BatchNorm\n    dropout Dropout\n    linear Linear\n    submodule SubModule\n\n  Args:\n    node: A graph node object.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n  \"\"\"\n  if graph is None:\n    graph = set_graph_mode.current_value()\n  if graph:\n    node_impl = get_node_impl(node)\n    if node_impl is None:\n      raise ValueError(\n        f'Expected a graph node, got {type(node).__name__}. '\n        'If this is a regular pytree, use graph=False.'\n      )\n    node_dict = node_impl.node_dict(node)\n    for key, value in node_dict.items():\n      if is_graph_node(value):\n        yield key, value\n  else:\n    _check_valid_pytree(node, 'iter_children')\n    if not is_pytree_node(node, check_graph_registry=False):\n      raise ValueError(\n        f'Expected a pytree node, got {type(node).__name__}. '\n        'If this is a graph node, use graph=True.'\n      )\n    children, _ = jax.tree_util.tree_flatten_with_path(\n      node, is_leaf=lambda x: x is not node\n    )\n    for jax_key_path, child in children:\n      if is_graph_node(child):\n        key = _key_path_to_key(jax_key_path[0])\n        yield key, child\n\n\ndef recursive_map(\n  f: tp.Callable[[PathParts, tp.Any], tp.Any],\n  node: tp.Any,\n  /,\n  *,\n  graph: bool | None = None,\n):\n  \"\"\"Recursively applies a function to all nodes and leaves of the given graph node.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> class MyModule(nnx.Module):\n    ...   def __init__(self, *, rngs: nnx.Rngs):\n    ...     self.lin = nnx.Linear(16, 16, rngs=rngs)\n    ...     self.conv = nnx.Conv(16, 3, 1, 1, rngs=rngs)\n    ...\n    >>> def print_modules(path, node):\n    ...   if isinstance(node, nnx.Module):\n    ...     s = \".\" + \".\".join(path)\n    ...     print(f\"Path = {s:<10}{node.__class__.__name__}\")\n    ...   return node\n    ...\n    >>> model = MyModule(rngs=nnx.Rngs(0))\n    >>> new_model = nnx.recursive_map(print_modules, model)\n    ...\n    Path = .conv     Conv\n    Path = .lin      Linear\n    Path = .         MyModule\n\n  Args:\n    f: A function that takes a path and a node and returns a new node.\n    node: A graph node object.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n  \"\"\"\n  if graph is None:\n    graph = set_graph_mode.current_value()\n  if graph:\n    node = clone(node, variables=False, graph=True)\n    path_parts: PathParts = ()\n    visited: set[int] = set()\n    results: dict[int, tp.Any] = {}\n    return _recursive_map_graph(f, node, path_parts, visited, results)\n  else:\n    return _recursive_map_tree(f, node)\n\n\ndef _recursive_map_graph(\n    f: tp.Callable[[PathParts, tp.Any], tp.Any],\n    node: tp.Any,\n    path: PathParts,\n    visited: set[int],\n    results: dict[int, tp.Any],\n) -> tp.Any:\n  node_id = id(node)\n  if node_id in visited:\n    if node_id in results:\n      return results[node_id]\n    path_str = '/'.join(builtins.map(str, path))\n    raise ValueError(\n        f\"Found cycle in the graph at path '{path_str}'. Node of type\"\n        f' {type(node)} has already been visited but has not been returned yet.'\n    )\n  node_impl = get_node_impl(node)\n  if (\n      type(node_impl) is GraphNodeImpl\n      or isinstance(node, Variable)\n      or is_array_ref(node)\n  ):\n    visited.add(node_id)\n  if node_impl is not None:\n    for key, value in node_impl.node_dict(node).items():\n      new_value = _recursive_map_graph(f, value, (*path, key), visited, results)\n      if new_value is not value:\n        if node_impl.set_key is not None and value is not new_value:\n          node_impl.set_key(node, key, new_value)\n        else:\n          raise ValueError(\n              f\"Cannot update key '{key}' for node of type '{type(node)}'\"\n              ' because the node does not support mutation.'\n          )\n\n  new_node = f(path, node)\n  results[node_id] = new_node\n  return new_node\n\n\ndef _recursive_map_tree(\n    f: tp.Callable[[PathParts, tp.Any], tp.Any],\n    node: tp.Any,\n) -> tp.Any:\n  in_progress: dict[int, str] = {}\n  seen_refs: dict[int, str] = {}\n\n  def _recurse(path: PathParts, current: tp.Any) -> tp.Any:\n    if not is_pytree_node(current, check_graph_registry=False):\n      _check_valid_pytree(current, 'recursive_map', '/'.join(builtins.map(str, path)))\n      if isinstance(current, Variable) or is_array_ref(current):\n        obj_id = id(current)\n        str_path = '/'.join(builtins.map(str, path))\n        if obj_id in seen_refs:\n          raise ValueError(\n            f'Duplicate {current}\\nfound at paths:\\n\\n'\n            f'  - {seen_refs[obj_id]}\\n'\n            f'  - {str_path}\\n\\n'\n            'Tree mode (graph=False) does not support shared references. '\n            + _tree_mode_suggestion_api('recursive_map')\n          )\n        seen_refs[obj_id] = str_path\n      return f(path, current)\n\n    obj_id = id(current)\n    str_path = '/'.join(builtins.map(str, path))\n    if obj_id in in_progress:\n      raise ValueError(\n        f'Cycle detected for {type(current).__name__}\\nfound at paths:\\n\\n'\n        f'  - {in_progress[obj_id]}\\n'\n        f'  - {str_path}\\n\\n'\n        'Cycles are not supported with graph=False. '\n        + _tree_mode_suggestion_api('recursive_map')\n      )\n    in_progress[obj_id] = str_path\n\n    children_with_path, treedef = jax.tree_util.tree_flatten_with_path(\n      current, is_leaf=lambda x: x is not current\n    )\n    new_children = []\n    for jax_key_path, child in children_with_path:\n      key = _key_path_to_key(jax_key_path[0])\n      new_child = _recurse((*path, key), child)\n      new_children.append(new_child)\n\n    new_node = treedef.unflatten(new_children)\n    result = f(path, new_node)\n\n    in_progress.pop(obj_id, None)\n    return result\n\n  return _recurse((), node)\n\n\ndef find_duplicates(node: tp.Any, /, *, only: filterlib.Filter = ...) -> list[list[PathParts]]:\n  \"\"\"Finds duplicate nodes or node leaves in the given node.\n\n  This function traverses the graph node and collects paths to nodes and leaves\n  that have the same identity. It returns a list of lists, where each inner list\n  contains paths to nodes or leaves that are duplicates.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n    ...\n    >>> class SharedVariables(nnx.Module):\n    ...   def __init__(self):\n    ...     self.a = nnx.Param(jnp.array(1.0))\n    ...     self.b = nnx.Param(jnp.array(2.0))\n    ...     self.c = self.b  # shared Variable\n    ...\n    >>> model = SharedVariables()\n    >>> duplicates = nnx.find_duplicates(model)\n    >>> len(duplicates)\n    1\n    >>> for path in duplicates[0]:\n    ...   print(path)\n    ('b',)\n    ('c',)\n\n  ``find_duplicates`` will also find duplicates nodes such as Modules that are\n  referenced multiple times in the graph::\n\n    >>> class SharedModules(nnx.Module):\n    ...   def __init__(self, rngs: nnx.Rngs):\n    ...     self.a = nnx.Linear(1, 1, rngs=rngs)\n    ...     self.b = nnx.Linear(1, 1, rngs=rngs)\n    ...     self.c = self.a  # shared Module\n    ...\n    >>> model = SharedModules(nnx.Rngs(0))\n    >>> for duplicate_paths in nnx.find_duplicates(model):\n    ...   print(duplicate_paths)\n    [('a',), ('c',)]\n\n  Args:\n    node: A graph node object.\n    only: A Filter to specify which nodes or leaves to consider for duplicates.\n  Returns:\n    A list of lists, where each inner list contains the different paths for a\n    for a duplicate node or leaf.\n  \"\"\"\n  node_paths: dict[int, list[PathParts]] = {}\n  duplicate_candidate = filterlib.to_predicate(only)\n  _node_paths(node, node_paths, (), duplicate_candidate)\n  _duplicates = [paths for paths in node_paths.values() if len(paths) > 1]\n  return _duplicates\n\n\ndef _node_paths(\n  node: tp.Any,\n  node_paths: dict[int, list[PathParts]],\n  path: PathParts,\n  duplicate_candidate: filterlib.Predicate,\n  /,\n):\n  _is_graph_node = is_graph_node(node)\n  _is_pytree_node = is_pytree_node(node)\n  _is_node_leaf = is_node_leaf(node)\n\n  if _is_graph_node or _is_pytree_node or _is_node_leaf:\n    node_id = id(node)\n    if node_id in node_paths:\n      if (_is_graph_node or _is_node_leaf) and duplicate_candidate(path, node):\n        node_paths[node_id].append(path)\n      return\n    if _is_graph_node or _is_node_leaf:\n      node_paths[node_id] = [path]\n    node_impl = get_node_impl(node)\n    if node_impl is None:\n      return\n    node_dict = node_impl.node_dict(node)\n    for key, value in node_dict.items():\n      _node_paths(value, node_paths, (*path, key), duplicate_candidate)\n\n\n@jax.tree_util.register_static\n@dataclasses.dataclass(frozen=True, slots=True)\nclass Static(tp.Generic[A]):\n  \"\"\"An empty pytree node that treats its inner value as static.\n  ``value`` must define ``__eq__`` and ``__hash__``.\n  \"\"\"\n\n  value: A\n\n\n# ---------------------------------------------------------\n# Pytree\n# ---------------------------------------------------------\nclass GenericPytree: ...\n\n\nfrom jax._src.tree_util import _registry as JAX_PYTREE_REGISTRY\n\n\ndef is_pytree_node(\n  x: tp.Any, *, check_graph_registry: bool = True,\n) -> bool:\n  if check_graph_registry and type(x) in GRAPH_REGISTRY:\n    return False\n  elif isinstance(x, Variable):\n    return False\n  elif type(x) in JAX_PYTREE_REGISTRY:\n    return True\n  elif isinstance(x, tuple):\n    return True\n  else:\n    return False\n\n\ndef _key_path_to_key(key: tp.Any) -> Key:\n  if isinstance(key, jax.tree_util.SequenceKey):\n    return key.idx\n  elif isinstance(\n    key, (jax.tree_util.DictKey, jax.tree_util.FlattenedIndexKey)\n  ):\n    if not is_key_like(key.key):  # type: ignore[not-supported-yet]\n      raise ValueError(\n        f'Invalid key: {key.key}. May be due to its type not being hashable or comparable.'\n      )\n    return key.key\n  elif isinstance(key, jax.tree_util.GetAttrKey):\n    return key.name\n  else:\n    return str(key)\n\n\ndef jax_to_nnx_path(jax_path: tuple, /):\n  return tuple(_key_path_to_key(part) for part in jax_path)\n\n\nclass IndexesPytreeDef(tp.NamedTuple):\n  key_index: HashableMapping[Key, int]\n  treedef: jax.tree_util.PyTreeDef\n\n\ndef _flatten_pytree(pytree: tp.Any):\n  leaves, treedef = jax.tree_util.tree_flatten_with_path(\n    pytree, is_leaf=lambda x: x is not pytree\n  )\n  nodes = [(_key_path_to_key(path[0]), value) for path, value in leaves]\n  key_index = HashableMapping(\n    {key: i for i, (key, _) in enumerate(nodes)}, copy=False\n  )\n  # Sort by key to match the path-sorted order used by _merge_to_flat_state.\n  # key_index records the original jax tree_flatten order so _unflatten_pytree\n  # can restore it before calling treedef.unflatten.\n  nodes.sort()\n  return nodes, IndexesPytreeDef(key_index, treedef)\n\n\ndef _unflatten_pytree(\n  nodes: tuple[tuple[Key, tp.Any], ...], metadata: IndexesPytreeDef\n):\n  # sort to original order\n  sorted_nodes = sorted(nodes, key=lambda x: metadata.key_index[x[0]])\n  pytree = metadata.treedef.unflatten(value for _, value in sorted_nodes)\n  return pytree\n\n\nPYTREE_NODE_IMPL = PytreeNodeImpl(\n  type=GenericPytree,\n  flatten=_flatten_pytree,\n  unflatten=_unflatten_pytree,  # type: ignore\n  set_key=None,\n  pop_key=None,\n)\ndef _list_set_key(x: list[tp.Any], key: int, value: tp.Any):\n  x[key] = value\n\n# common pytrees\n# list\nregister_pytree_node_type(\n  list,\n  flatten=lambda x: (list(enumerate(x)), None),\n  unflatten=lambda nodes, _: [value for _, value in nodes],  # type: ignore\n  set_key=_list_set_key,  # type: ignore\n)\n# tuple\nregister_pytree_node_type(\n  tuple,\n  flatten=lambda x: (list(enumerate(x)), None),\n  unflatten=lambda nodes, _: tuple(value for _, value in nodes),  # type: ignore\n)\n\n\ndef _mutable_mapping_set_key(\n  x: tp.MutableMapping[Key, tp.Any], key: Key, value: tp.Any\n):\n  x[key] = value\n\n\ndef _mutable_mapping_pop_key(x: tp.MutableMapping[Key, tp.Any], key: Key):\n  x.pop(key)\n\n\n# dict\nregister_pytree_node_type(\n  dict,\n  flatten=lambda x: (sorted(x.items()), None),\n  unflatten=lambda nodes, _: dict(nodes),  # type: ignore\n  set_key=_mutable_mapping_set_key,\n  pop_key=_mutable_mapping_pop_key,\n)\n# State\nregister_pytree_node_type(\n  State,\n  flatten=lambda x: (sorted(x.raw_mapping.items()), None),\n  unflatten=lambda nodes, _: State(nodes),  # type: ignore\n  set_key=_mutable_mapping_set_key,\n  pop_key=_mutable_mapping_pop_key,\n)\n# None\nregister_pytree_node_type(\n  type(None),\n  flatten=lambda x: ([], None),\n  unflatten=lambda _, __: None,  # type: ignore\n)\n"
  },
  {
    "path": "flax/nnx/helpers.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom __future__ import annotations\n\nimport inspect\nimport typing as tp\n\nimport jax\nimport jax.numpy as jnp\nimport optax\n\nfrom flax.nnx import graphlib, reprlib\nfrom flax.nnx.graphlib import GraphDef\nfrom flax.nnx.module import Module\nfrom flax.nnx.proxy_caller import ApplyCaller\nfrom flax.nnx.rnglib import Rngs\nfrom flax.nnx.statelib import State\nfrom flax.training.train_state import struct\nfrom flax.nnx.variablelib import Variable\n\nA = tp.TypeVar('A')\nM = tp.TypeVar('M', bound=Module)\nTS = tp.TypeVar('TS', bound='TrainState')\n\nclass Dict(reprlib.MappingReprMixin, Module, tp.MutableMapping[str, A]):\n  \"\"\"A Module that implements a mutable mapping.\n\n  This class provides a way to store and manipulate a mapping of keys to values\n  contained a mixed set of data (e.g. Array, Variables, Modules) and static\n  (e.g. functions, strings) types.\n\n  Example:\n    >>> from flax import nnx\n    ...\n    >>> rngs = nnx.Rngs(0)\n    >>> layers = nnx.Dict({\n    ...     'kernel1': nnx.Linear(1, 32, rngs=rngs),   # data\n    ...     'activation1': nnx.relu,                   # static\n    ...     'kernel2': nnx.Linear(32, 1, rngs=rngs),   # data\n    ... })\n  \"\"\"\n  @tp.overload\n  def __init__(self, iterable: tp.Iterable[tp.Tuple[str, A]], /): ...\n\n  @tp.overload\n  def __init__(\n    self, mapping: tp.Optional[tp.Mapping[str, A]] = None, /, **kwargs: A\n  ): ...\n\n  def __init__(self, *args, **kwargs):\n    for name, value in dict(*args, **kwargs).items():\n      setattr(self, name, value)\n\n  def __getitem__(self, key) -> A:\n    try:\n      return getattr(self, key)\n    except AttributeError as e:\n      raise KeyError(key) from e\n\n  def __setitem__(self, key, value):\n    setattr(self, key, value)\n\n  def __iter__(self) -> tp.Iterator[str]:\n    return (k for k in vars(self) if not k.startswith('_pytree__'))\n\n  def __len__(self) -> int:\n    length = 0\n    for _ in self:\n      length += 1\n    return length\n\n  def __hash__(self) -> int:\n    return id(self)\n\n  def __delitem__(self, key: str) -> None:\n    try:\n      delattr(self, key)\n    except AttributeError as e:\n      raise KeyError(key) from e\n\n  if tp.TYPE_CHECKING:\n    def __getattr__(self, key: str) -> A:\n      ...\n    def __setattr__(self, key: str, value: A) -> None:\n      ...\n\n\nclass List(reprlib.SequenceReprMixin, Module, tp.MutableSequence[A]):\n  \"\"\"A Module that implements a mutable sequence.\n\n  This class provides a way to store and manipulate a sequence of values\n  contained a mixed set of data (e.g. Array, Variables, Modules) and static\n  (e.g. functions, strings) types.\n\n  Example:\n    >>> from flax import nnx\n    ...\n    >>> rngs = nnx.Rngs(0)\n    >>> layers = nnx.List([\n    ...     nnx.Linear(1, 32, rngs=rngs),  # data\n    ...     nnx.relu,                      # static\n    ...     nnx.Linear(32, 1, rngs=rngs),  # data\n    ... ])\n  \"\"\"\n  def __init__(self, it: tp.Iterable[A] | None = None, /):\n    \"\"\"\n    Args:\n      it: An iterable of values to initialize the list.\n    \"\"\"\n    self._length: int = 0\n    if it is not None:\n      for value in it:\n        self.append(value)\n\n  def _get_elem(self, key: int) -> A:\n    return getattr(self, str(key))\n\n  def _set_elem(self, key: int, value: A) -> None:\n    setattr(self, str(key), value)\n\n  def _del_elem(self, key: int) -> None:\n    delattr(self, str(key))\n\n  def __len__(self) -> int:\n    return self._length\n\n  def append(self, value: A) -> None:\n    self._set_elem(self._length, value)\n    self._length += 1\n\n  def insert(self, index: int, value: A) -> None:\n    if index < 0:\n      index += self._length\n    if index < 0:\n      index = 0\n    if index > self._length:\n      index = self._length\n\n    # Shift elements to the right\n    for i in range(self._length, index, -1):\n      self._set_elem(i, self._get_elem(i - 1))\n\n    # Insert the new value\n    self._set_elem(index, value)\n    self._length += 1\n\n  def __iter__(self) -> tp.Iterator[A]:\n    for i in range(self._length):\n      yield self._get_elem(i)\n\n  @tp.overload\n  def __getitem__(self, index: int) -> A: ...\n  @tp.overload\n  def __getitem__(self, index: slice) -> tp.List[A]: ...\n  def __getitem__(self, index: int | slice) -> A | tp.List[A]:\n    if isinstance(index, int):\n      if index < 0:\n        index += self._length\n      if index < 0 or index >= self._length:\n        raise IndexError('Index out of bounds')\n      return self._get_elem(index)\n    elif isinstance(index, slice):\n      idxs = list(range(self._length))[index]\n      return [self._get_elem(i) for i in idxs]\n    else:\n      raise TypeError('Invalid index type')\n\n  def __setitem__(self, index: int | slice, value: A | tp.Iterable[A]) -> None:\n    if isinstance(index, int):\n      if index < 0:\n        index += self._length\n      if index < 0 or index >= self._length:\n        raise IndexError('Index out of bounds')\n      self._set_elem(index, value)  # type: ignore[arg-type]\n    elif isinstance(index, slice):\n      if not isinstance(value, tp.Iterable):\n        raise TypeError('Expected an iterable')\n      values = list(value)\n      idxs = list(range(self._length))[index]\n      if len(idxs) != len(values):\n        raise ValueError('Length mismatch')\n      for i, v in zip(idxs, values):\n        self._set_elem(i, v)\n    else:\n      raise TypeError('Invalid index type')\n\n  def _graph_node_set_key(self, key: str, value: tp.Any):\n    if not isinstance(key, int):\n      raise KeyError(f'Invalid key: {key}')\n    elif key < len(self):\n      if isinstance(variable := self[key], Variable) and isinstance(value, Variable):\n        variable.update_from_state(value)\n      else:\n        self[key] = value\n    else:\n      self.insert(key, value)\n\n  def __delitem__(self, index: int | slice) -> None:\n    if isinstance(index, int):\n      if index < 0:\n        index += self._length\n      if index < 0 or index >= self._length:\n        raise IndexError('Index out of bounds')\n      self._del_elem(index)\n      for i in range(index + 1, self._length):\n        self._set_elem(i - 1, self._get_elem(i))\n      self._length -= 1\n    elif isinstance(index, slice):\n      idxs = list(range(self._length))[index]\n      for i in reversed(idxs):\n        # implement recursively\n        del self[i]\n    else:\n      raise TypeError('Invalid index type')\n\n  _pytree__has_int_keys = True\n\n\nclass Sequential(Module):\n  \"\"\"A Module that applies a sequence of callables.\n\n  This class provides a way to store and manipulate a sequence of callables\n  (e.g. layers, activation functions) and apply them in order.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n    ...\n    >>> rngs = nnx.Rngs(0)\n    >>> model = nnx.Sequential(\n    ...   nnx.Linear(1, 4, rngs=rngs),  # data\n    ...   nnx.relu,                     # static\n    ...   nnx.Linear(4, 2, rngs=rngs),  # data\n    ... )\n    >>> x = jnp.ones((1, 1))\n    >>> y = model(x)\n    >>> y.shape\n    (1, 2)\n  \"\"\"\n\n  def __init__(self, *fns: tp.Callable[..., tp.Any]):\n    \"\"\"\n    Args:\n      *fns: A sequence of callables to apply.\n    \"\"\"\n    self.layers = List(fns)\n\n  def __call__(self, *args, rngs: tp.Optional[Rngs] = None, **kwargs) -> tp.Any:\n    if len(self.layers) == 0:\n      if len(args) == 1:\n        return args[0]\n      elif len(args) > 0:\n        return args\n      elif len(kwargs) > 0:\n        return kwargs\n      else:\n        return None\n\n    output: tp.Any = None\n\n    for i, f in enumerate(self.layers):\n      if not callable(f):\n        raise TypeError(f'Sequence[{i}] is not callable: {f}')\n      if i > 0:\n        if isinstance(output, tuple):\n          args = output\n          kwargs = {}\n        elif isinstance(output, dict):\n          args = ()\n          kwargs = output\n        else:\n          args = (output,)\n          kwargs = {}\n      if rngs is not None and has_keyword_arg(f, 'rngs'):\n        kwargs['rngs'] = rngs\n\n      output = f(*args, **kwargs)\n\n    return output\n\n\nclass ModuleDefApply(tp.Protocol, tp.Generic[M]):\n  def __call__(\n    self, state: State, *states: State\n  ) -> ApplyCaller[tuple[State, GraphDef[M]]]: ...\n\n\nclass TrainState(tp.Generic[M], struct.PyTreeNode):\n  graphdef: graphlib.GraphDef[M]\n  params: State\n  opt_state: optax.OptState\n  step: jax.Array\n  tx: optax.GradientTransformation = struct.field(pytree_node=False)\n\n  @classmethod\n  def create(\n    cls,\n    graphdef: graphlib.GraphDef[M],\n    *,\n    params: State,\n    tx: optax.GradientTransformation,\n    step: int = 0,\n    **kwargs,\n  ):\n    return cls(\n      graphdef=graphdef,\n      params=params,\n      opt_state=tx.init(params),\n      step=jnp.asarray(step),\n      tx=tx,\n      **kwargs,\n    )\n\n  if tp.TYPE_CHECKING:\n\n    def __getattr__(self, key: str) -> tp.Any: ...\n\n  def apply(\n    self, state: tp.Union[State, str], *states: tp.Union[State, str]\n  ) -> ApplyCaller[tuple[GraphDef[M], State]]:\n    states = (state, *states)\n\n    _states: list[State] = []\n\n    for _state in states:\n      if isinstance(_state, str):\n        _state_key = _state\n        _state = getattr(self, _state_key)\n        if not isinstance(_state, State):\n          raise TypeError(\n            f'Expected {self.__class__.__name__}.{_state_key} to be a State, got {type(_state)}'\n          )\n      _states.append(_state)\n\n    return self.graphdef.apply(*_states)\n\n  def apply_gradients(self: TS, grads: State, **kwargs) -> TS:\n    updates, opt_state = self.tx.update(grads, self.opt_state, self.params)\n    params = optax.apply_updates(self.params, updates)  # type: ignore\n    step = self.step + 1\n    return self.replace(\n      params=params,\n      opt_state=opt_state,\n      step=step,\n      **kwargs,\n    )\n\n\ndef has_keyword_arg(func: tp.Callable[..., tp.Any], name: str) -> bool:\n  \"\"\"Return True if func has keyword-only arguments with the given name.\"\"\"\n  return any(\n    param.name == name\n    and param.kind in (param.KEYWORD_ONLY, param.POSITIONAL_OR_KEYWORD)\n    for param in inspect.signature(func).parameters.values()\n  )\n"
  },
  {
    "path": "flax/nnx/ids.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"UUIDs for Flax internals.\"\"\"\n\nimport threading\n\n\nclass UUIDManager:\n  \"\"\"Globally unique counter-based id manager.\n\n  We need globally unique key ids for Module and Variable object instances\n  to preserve and recreate sharing-by-reference relationship when lifting\n  transforms and adopting outside Modules.\n  - Use of id() is unacceptable because these identifiers are literally\n    pointers which can be recycled, so we rely on a globally unique counter id\n    instead.\n  - We need to handle copy/deepcopy uniqueness via a wrapped type.\n  \"\"\"\n\n  def __init__(self):\n    self._lock = threading.Lock()\n    self._id = 0\n\n  def __call__(self):\n    with self._lock:\n      self._id += 1\n      return UUID(self._id)\n\n\nuuid = UUIDManager()\n\n\nclass UUID:\n  \"\"\"Hashable wrapper for ids that handles uniqueness of copies.\"\"\"\n\n  def __init__(self, rawid):\n    self.id = rawid\n\n  def __eq__(self, other):\n    return isinstance(other, UUID) and other.id == self.id\n\n  def __hash__(self):\n    return hash(self.id)\n\n  def __repr__(self):\n    return f'UUID({self.id})'\n\n  def __deepcopy__(self, memo):\n    del memo\n    return uuid()\n\n  def __copy__(self):\n    return uuid()\n"
  },
  {
    "path": "flax/nnx/module.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom __future__ import annotations\n\nimport inspect\nimport typing as tp\n\nimport jax\nimport jax.numpy as jnp\n\nfrom flax.nnx import (\n  filterlib,\n  graphlib,\n  pytreelib,\n)\nfrom flax.nnx import variablelib as variableslib\nfrom flax.nnx.pytreelib import Pytree, PytreeMeta\nfrom flax.nnx.graphlib import GraphState\nfrom flax.nnx.statelib import split_state, State\nimport functools as ft\nfrom flax.typing import Key, Path, PathParts\nfrom collections.abc import MutableMapping\nimport warnings\n\nA = tp.TypeVar('A')\nB = tp.TypeVar('B')\nM = tp.TypeVar('M', bound='Module')\nS = tp.TypeVar('S', bound=tp.Union[GraphState, tuple[GraphState, ...]])\nV = tp.TypeVar('V', bound=variableslib.Variable[tp.Any])\nF = tp.TypeVar('F', bound=tp.Callable[..., tp.Any])\n\nStateMapping = tp.Mapping[Path, tp.Any]\ntuple_reduce = lambda xs, x: xs + (x,)\ntuple_init = lambda: ()\n\n\nclass ModuleMeta(PytreeMeta):\n  # we keep a trivial derived class just in case we need to\n  # add more functionality in the future\n  pass\n\n\nclass Module(Pytree, metaclass=ModuleMeta):\n  \"\"\"Base class for all neural network modules.\n\n  Layers and models should subclass this class.\n\n  ``Module``'s can contain submodules, and in this way can be nested in a tree\n  structure. Submodules can be assigned as regular attributes inside the\n  ``__init__`` method.\n\n  You can define arbitrary \"forward pass\" methods on your ``Module`` subclass.\n  While no methods are special-cased, ``__call__`` is a popular choice since\n  you can call the ``Module`` directly::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n\n    >>> class Model(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.linear1 = nnx.Linear(2, 3, rngs=rngs)\n    ...     self.linear2 = nnx.Linear(3, 4, rngs=rngs)\n    ...   def __call__(self, x):\n    ...     x = self.linear1(x)\n    ...     x = nnx.relu(x)\n    ...     x = self.linear2(x)\n    ...     return x\n\n    >>> x = jnp.ones((1, 2))\n    >>> model = Model(rngs=nnx.Rngs(0))\n    >>> y = model(x)\n  \"\"\"\n\n  def sow(\n      self,\n      variable_type: type[variableslib.Variable[B]] | str,\n      name: str,\n      value: A,\n      reduce_fn: tp.Callable[[B, A], B] = tuple_reduce,\n      init_fn: tp.Callable[[], B] = tuple_init,  # type: ignore\n  ) -> bool:\n    \"\"\"Store intermediate values during module execution for later extraction.\n\n    Used with :func:`nnx.capture` decorator to collect intermediate values without\n    explicitly passing containers through module calls. Values are stored under\n    the specified ``name`` in a collection associated with ``variable_type``.\n\n    By default, values are appended to a tuple, allowing multiple values to be\n    tracked when the same module is called multiple times.\n\n    Example usage::\n\n      >>> from flax import nnx\n      >>> import jax.numpy as jnp\n\n      >>> class Model(nnx.Module):\n      ...   def __init__(self, rngs):\n      ...     self.linear1 = nnx.Linear(2, 3, rngs=rngs)\n      ...     self.linear2 = nnx.Linear(3, 4, rngs=rngs)\n      ...   def __call__(self, x):\n      ...     x = self.linear1(x)\n      ...     self.sow(nnx.Intermediate, 'features', x)\n      ...     x = self.linear2(x)\n      ...     return x\n\n      >>> # With the capture decorator, sow returns intermediates\n      >>> model = Model(rngs=nnx.Rngs(0))\n      >>> @nnx.capture(nnx.Intermediate)\n      ... def forward(model, x):\n      ...   return model(x)\n      >>> result, intermediates = forward(model, jnp.ones(2))\n      >>> assert 'features' in intermediates\n\n    Custom init/reduce functions can be passed to control accumulation::\n\n      >>> class Model(nnx.Module):\n      ...   def __init__(self, rngs):\n      ...     self.linear = nnx.Linear(2, 3, rngs=rngs)\n      ...   def __call__(self, x):\n      ...     x = self.linear(x)\n      ...     self.sow(nnx.Intermediate, 'sum', x,\n      ...              init_fn=lambda: 0,\n      ...              reduce_fn=lambda prev, curr: prev+curr)\n      ...     return x\n\n    Args:\n      variable_type: The :class:`Variable` type for the stored value.\n        Typically :class:`Intermediate` or a subclass is used.\n      name: A string key for storing the value in the collection.\n      value: The value to be stored.\n      reduce_fn: Function to combine existing and new values. Default appends\n        to a tuple.\n      init_fn: Function providing initial value for first ``reduce_fn`` call.\n        Default is an empty tuple.\n    \"\"\"\n    if isinstance(variable_type, str):\n      variable_type = variableslib.variable_type_from_name(\n          variable_type, allow_register=True\n      )\n\n    if hasattr(self, '__captures__'):\n      for var in self.__captures__:\n        if type(var) == variable_type:\n          if name in var:\n            var[name] = reduce_fn(var[name], value)\n          else:\n            var[name] = reduce_fn(init_fn(), value)\n          return True\n      else:\n        return False\n    elif hasattr(self, name):\n        variable = getattr(self, name)\n        if not isinstance(variable, variableslib.Variable):\n          raise ValueError(\n            f\"Expected '{name}' to be a Variable, got {type(variable).__name__}\"\n          )\n        elif type(variable) != variable_type:\n          raise ValueError(\n            f\"Expected '{name}' to be of type '{variable_type.__name__}', \"\n            f\"got '{type(variable).__name__}'\"\n          )\n        variable.set_value(reduce_fn(variable.get_value(), value))\n    else:\n      reduced_value = reduce_fn(init_fn(), value)\n      setattr(self, name, variable_type(reduced_value))\n    warnings.warn(\n        \"\"\"Using 'Module.sow()' outside of 'nnx.capture()' is deprecated; see\n        https://flax.readthedocs.io/en/stable/capturing_intermediates.html for more information.\n        \"\"\",\n        DeprecationWarning,\n        stacklevel=2,\n      )\n    return True\n\n  def perturb(\n      self,\n      name: str,\n      value: tp.Any,\n      variable_type: (\n          str | type[variableslib.Variable[tp.Any]]\n      ) = variableslib.Perturbation,\n  ):\n    \"\"\"Extract gradients of intermediate values during training.\n\n    Used with :func:`nnx.capture` to record intermediate values in the forward pass\n    and their gradients in the backward pass. Returns the value plus whatever perturbation\n    is stored under ``name`` in the current capture context, allowing gradient computation via ``nnx.grad``.\n\n    The workflow has four steps:\n    1. Initialize perturbations with ``nnx.capture(model, nnx.Perturbation)``\n    2. Run model with ``nnx.capture(model, nnx.Intermediate, init=perturbations)``\n    3. Take gradients with respect to perturbations using ``nnx.grad``\n    4. Combine results with ``nnx.merge_state(perturb_grads, intermediates)``\n\n    .. note::\n      This creates extra variables of the same size as ``value``, thus\n      occupies more memory. Use it only to debug gradients in training.\n\n    Example usage::\n\n      >>> from flax import nnx\n      >>> import jax.numpy as jnp\n\n      >>> class Model(nnx.Module):\n      ...   def __call__(self, x):\n      ...     x2 = self.perturb('grad_of_x', x)\n      ...     return 3 * x2\n\n      >>> model = Model()\n      >>> x = 1.0\n\n      >>> # Step 1: Initialize perturbations\n      >>> forward = nnx.capture(model, nnx.Perturbation)\n      >>> _, perturbations = forward(x)\n\n      >>> # Steps 2-4: Capture gradients\n      >>> def train_step(model, perturbations, x):\n      ...   def loss(model, perturbations, x):\n      ...     return nnx.capture(model, nnx.Intermediate, init=perturbations)(x)\n      ...   (grads, perturb_grads), sowed = nnx.grad(loss, argnums=(0, 1), has_aux=True)(model, perturbations, x)\n      ...   return nnx.merge_state(perturb_grads, sowed)\n\n      >>> metrics = train_step(model, perturbations, x)\n      >>> # metrics contains gradients of intermediate values\n\n    Args:\n      name: A string key for storing the perturbation value.\n      value: The intermediate value to capture gradients for. You must use\n        the returned value (not the original) for gradient capturing to work.\n      variable_type: The :class:`Variable` type for the stored perturbation.\n        Default is :class:`nnx.Perturbation`.\n    \"\"\"\n    if isinstance(variable_type, str):\n      variable_type = variableslib.variable_type_from_name(\n          variable_type, allow_register=True\n      )\n\n    if hasattr(self, '__captures__'):\n      for var in self.__captures__:\n        if type(var) == variable_type:\n          if name not in var:\n            zeros = jax.tree.map(jnp.zeros_like, value)\n            var[name] = zeros\n          old_value = var[name]\n          return old_value + value\n      else:\n        return value\n    elif hasattr(self, name):\n      var = getattr(self, name)\n      if not isinstance(var, variable_type):\n        raise ValueError(\n          f\"Expected '{name}' to be of type '{variable_type.__name__}', \"\n          f\"got '{type(var).__name__}'\"\n        )\n      old_value = var.get_value()\n    else:\n      old_value = jax.tree.map(jnp.zeros_like, value)\n      setattr(self, name, variable_type(old_value))\n    warnings.warn(\"\"\"\n      Using 'Module.perturb()' outside of 'nnx.capture()' is deprecated; see\n      https://flax.readthedocs.io/en/stable/capturing_intermediates.html for more information.\n      \"\"\",\n      DeprecationWarning,\n      stacklevel=2)\n    return old_value + value\n\n  def iter_modules(self) -> tp.Iterator[tuple[PathParts, Module]]:\n    \"\"\"\n    Warning: this method is method is deprecated; use :func:`iter_modules` instead.\n\n    Recursively iterates over all nested :class:`Module`'s of the current Module, including\n    the current Module. Alias of :func:`iter_modules`.\n    \"\"\"\n    warnings.warn(\n      \"The 'm.iter_modules()' method is deprecated; use the 'nnx.iter_modules(m)' function instead.\",\n      DeprecationWarning,\n      stacklevel=2,\n    )\n    yield from iter_modules(self)\n\n  def iter_children(self) -> tp.Iterator[tuple[Key, Module]]:\n    \"\"\"\n    Warning: this method is method is deprecated; use :func:`iter_children` instead.\n\n    Iterates over all children :class:`Module`'s of the current Module. This\n    method is similar to :func:`iter_modules`, except it only iterates over the\n    immediate children, and does not recurse further down. Alias of :func:`iter_children`.\n    \"\"\"\n    warnings.warn(\n      \"The 'm.iter_children()' method is deprecated; use the 'nnx.iter_children(m)' function instead.\",\n      DeprecationWarning,\n      stacklevel=2,\n    )\n    yield from iter_children(self)\n\n  def set_attributes(\n    self,\n    *filters: filterlib.Filter,\n    raise_if_not_found: bool = True,\n    graph: bool | None = None,\n    **attributes: tp.Any,\n  ) -> None:\n    \"\"\"Sets the attributes of nested Modules including the current Module.\n    If the attribute is not found in the Module, it is ignored.\n\n    Example::\n\n      >>> from flax import nnx\n      ...\n      >>> class Block(nnx.Module):\n      ...   def __init__(self, din, dout, *, rngs: nnx.Rngs):\n      ...     self.linear = nnx.Linear(din, dout, rngs=rngs)\n      ...     self.dropout = nnx.Dropout(0.5, deterministic=False)\n      ...     self.batch_norm = nnx.BatchNorm(10, use_running_average=False, rngs=rngs)\n      ...\n      >>> block = Block(2, 5, rngs=nnx.Rngs(0))\n      >>> block.dropout.deterministic, block.batch_norm.use_running_average\n      (False, False)\n      >>> block.set_attributes(deterministic=True, use_running_average=True)\n      >>> block.dropout.deterministic, block.batch_norm.use_running_average\n      (True, True)\n\n    ``Filter``'s can be used to set the attributes of specific Modules::\n\n      >>> block = Block(2, 5, rngs=nnx.Rngs(0))\n      >>> block.set_attributes(nnx.Dropout, deterministic=True)\n      >>> # Only the dropout will be modified\n      >>> block.dropout.deterministic, block.batch_norm.use_running_average\n      (True, False)\n\n    Args:\n      *filters: Filters to select the Modules to set the attributes of.\n      raise_if_not_found: If True (default), raises a ValueError if at least one attribute\n        instance is not found in one of the selected Modules.\n      **attributes: The attributes to set.\n    \"\"\"\n    remaining_attributes = set(attributes.keys())\n    if not filters:\n      filters = (True,)\n    predicates = tuple(map(filterlib.to_predicate, filters))\n    for path, module in iter_modules(self, graph=graph):\n      for predicate in predicates:\n        if predicate(path, module):\n          for name, value in attributes.items():\n            if hasattr(module, name):\n              if name in remaining_attributes:\n                remaining_attributes.remove(name)\n              setattr(module, name, value)\n          break\n\n    if remaining_attributes and raise_if_not_found:\n      raise ValueError(\n        'Could not find at least one instance of the following'\n        f' attributes: {sorted(remaining_attributes)}'\n      )\n\n  def train(self, **attributes):\n    \"\"\"Sets the Module to training mode.\n\n    ``train`` uses ``set_attributes`` to recursively set attributes ``deterministic=False``\n    and ``use_running_average=False`` of all nested Modules that have these attributes.\n    Its primarily used to control the runtime behavior of the ``Dropout`` and ``BatchNorm``\n    Modules.\n\n    Example::\n\n      >>> from flax import nnx\n      ...\n      >>> class Block(nnx.Module):\n      ...   def __init__(self, din, dout, *, rngs: nnx.Rngs):\n      ...     self.linear = nnx.Linear(din, dout, rngs=rngs)\n      ...     # initialize Dropout and BatchNorm in eval mode\n      ...     self.dropout = nnx.Dropout(0.5, deterministic=True)\n      ...     self.batch_norm = nnx.BatchNorm(10, use_running_average=True, rngs=rngs)\n      ...\n      >>> block = Block(2, 5, rngs=nnx.Rngs(0))\n      >>> block.dropout.deterministic, block.batch_norm.use_running_average\n      (True, True)\n      >>> block.train()\n      >>> block.dropout.deterministic, block.batch_norm.use_running_average\n      (False, False)\n\n    Args:\n      **attributes: additional attributes passed to ``set_attributes``.\n    \"\"\"\n    return self.set_attributes(\n      deterministic=False,\n      use_running_average=False,\n      **attributes,\n      raise_if_not_found=False,\n    )\n\n  def eval(self, **attributes):\n    \"\"\"Sets the Module to evaluation mode.\n\n    ``eval`` uses ``set_attributes`` to recursively set attributes ``deterministic=True``\n    and ``use_running_average=True`` of all nested Modules that have these attributes.\n    Its primarily used to control the runtime behavior of the ``Dropout`` and ``BatchNorm``\n    Modules.\n\n    Example::\n\n      >>> from flax import nnx\n      ...\n      >>> class Block(nnx.Module):\n      ...   def __init__(self, din, dout, *, rngs: nnx.Rngs):\n      ...     self.linear = nnx.Linear(din, dout, rngs=rngs)\n      ...     self.dropout = nnx.Dropout(0.5)\n      ...     self.batch_norm = nnx.BatchNorm(10, rngs=rngs)\n      ...\n      >>> block = Block(2, 5, rngs=nnx.Rngs(0))\n      >>> block.dropout.deterministic, block.batch_norm.use_running_average\n      (False, False)\n      >>> block.eval()\n      >>> block.dropout.deterministic, block.batch_norm.use_running_average\n      (True, True)\n\n    Args:\n      **attributes: additional attributes passed to ``set_attributes``.\n    \"\"\"\n    return self.set_attributes(\n      deterministic=True,\n      use_running_average=True,\n      **attributes,\n      raise_if_not_found=False,\n    )\n\ndef view(node: A, /, *, only: filterlib.Filter = ..., raise_if_not_found: bool = True, graph: bool | None = None, **kwargs) -> A:\n  \"\"\"Creates a new node with static attributes updated according to ``**kwargs``.\n\n  The new node contains references to jax arrays in the original node. If a\n  kwarg is not found in any module, this method raises a ValueError. Uses the\n  ``set_view`` class method in nnx.Modules. ``set_view`` class methods should\n  return any unused kwargs.\n\n  Example::\n    >>> from flax import nnx\n    ...\n    >>> class Block(nnx.Module):\n    ...   def __init__(self, din, dout, *, rngs: nnx.Rngs):\n    ...     self.linear = nnx.Linear(din, dout, rngs=rngs)\n    ...     self.dropout = nnx.Dropout(0.5, deterministic=False)\n    ...     self.batch_norm = nnx.BatchNorm(10, use_running_average=False, rngs=rngs)\n    ...\n    >>> block = Block(2, 5, rngs=nnx.Rngs(0))\n    >>> block.dropout.deterministic, block.batch_norm.use_running_average\n    (False, False)\n    >>> new_block = nnx.view(block, deterministic=True, use_running_average=True)\n    >>> new_block.dropout.deterministic, new_block.batch_norm.use_running_average\n    (True, True)\n\n  ``Filter``'s can be used to set the attributes of specific Modules::\n    >>> block = Block(2, 5, rngs=nnx.Rngs(0))\n    >>> new_block = nnx.view(block, only=nnx.Dropout, deterministic=True)\n    >>> # Only the dropout will be modified\n    >>> new_block.dropout.deterministic, new_block.batch_norm.use_running_average\n    (True, False)\n\n  Args:\n    node: the object to create a copy of.\n    only: Filters to select the Modules to set the attributes of.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n    **kwargs: The attributes to set.\n  \"\"\"\n  predicate = filterlib.to_predicate(only)\n\n  remaining = set(kwargs)\n\n  def _set_mode_fn(path, node):\n    if hasattr(node, 'set_view') and predicate(path, node):\n      sig = inspect.signature(node.set_view)\n      has_var_keyword = any(\n        p.kind == inspect.Parameter.VAR_KEYWORD\n        for p in sig.parameters.values()\n      )\n      if has_var_keyword:\n        node.set_view(**kwargs)\n        remaining.clear()\n      else:\n        named_params = {\n          name\n          for name, p in sig.parameters.items()\n          if p.kind\n          in (\n            inspect.Parameter.POSITIONAL_OR_KEYWORD,\n            inspect.Parameter.KEYWORD_ONLY,\n          )\n        }\n        filtered_kwargs = {\n          k: v for k, v in kwargs.items() if k in named_params\n        }\n        node.set_view(**filtered_kwargs)\n        remaining.difference_update(named_params)\n    return node\n\n  out = graphlib.recursive_map(_set_mode_fn, node, graph=graph)\n\n  if raise_if_not_found and remaining:\n    raise ValueError(f\"Unused keys found in nnx.view: {sorted(remaining)}\")\n\n  return out\n\ndef with_attributes(\n  node: A,\n  /,\n  *,\n  only: filterlib.Filter = ...,\n  raise_if_not_found: bool = True,\n  graph: bool | None = None,\n  **attributes: tp.Any,\n) -> A:\n  \"\"\"Creates a new node with attributes updated according to ``**attributes``.\n\n  The new node contains references to jax arrays in the original node. Unlike\n  ``set_attributes``, this function does not modify the original node.\n\n  Example::\n    >>> from flax import nnx\n    ...\n    >>> class Block(nnx.Module):\n    ...   def __init__(self, din, dout, *, rngs: nnx.Rngs):\n    ...     self.linear = nnx.Linear(din, dout, rngs=rngs)\n    ...     self.dropout = nnx.Dropout(0.5, deterministic=False)\n    ...     self.batch_norm = nnx.BatchNorm(10, use_running_average=False, rngs=rngs)\n    ...\n    >>> block = Block(2, 5, rngs=nnx.Rngs(0))\n    >>> block.dropout.deterministic, block.batch_norm.use_running_average\n    (False, False)\n    >>> new_block = nnx.with_attributes(block, deterministic=True, use_running_average=True)\n    >>> new_block.dropout.deterministic, new_block.batch_norm.use_running_average\n    (True, True)\n    >>> block.dropout.deterministic, block.batch_norm.use_running_average\n    (False, False)\n\n  ``Filter``'s can be used to set the attributes of specific Modules::\n    >>> block = Block(2, 5, rngs=nnx.Rngs(0))\n    >>> new_block = nnx.with_attributes(block, only=nnx.Dropout, deterministic=True)\n    >>> # Only the dropout will be modified\n    >>> new_block.dropout.deterministic, new_block.batch_norm.use_running_average\n    (True, False)\n\n  Args:\n    node: the object to create a copy of.\n    only: Filters to select the Modules to set the attributes of.\n    raise_if_not_found: If True (default), raises a ValueError if at least one\n      attribute instance is not found in one of the selected Modules.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n    **attributes: The attributes to set.\n  \"\"\"\n  predicate = filterlib.to_predicate(only)\n  remaining_attributes = set(attributes.keys())\n\n  def _set_attributes_fn(path, node):\n    if isinstance(node, Module) and predicate(path, node):\n      for name, value in attributes.items():\n        if hasattr(node, name):\n          setattr(node, name, value)\n          remaining_attributes.discard(name)\n    return node\n\n  out = graphlib.recursive_map(_set_attributes_fn, node, graph=graph)\n\n  if remaining_attributes and raise_if_not_found:\n    raise ValueError(\n      'Could not find at least one instance of the '\n      f'following attributes: {sorted(remaining_attributes)}'\n    )\n\n  return out\n\ndef _parse_docstring_args(doc_str: str) -> dict[str, str]:\n  \"\"\"Parses parameters from `Args:` section of a function docstring.\n  Assumes Google style docstrings. Returns a dictionary with\n  keys representing argument names and values representing descriptions.\n  Each description has lines starting with 4 spaces.\n  \"\"\"\n  lines = doc_str.split(\"\\n\")\n\n  # Get lines with the parameter names\n  inds = [i for i, l in enumerate(lines) if l.startswith(\"  \") and not l.startswith(\"    \")]\n  inds.append(len(lines))\n  out = dict()\n\n  # Parse each argument\n  for i in range(len(inds)-1):\n    start, end = inds[i], inds[i+1]\n\n    # Process first line for the description\n    first_colon = lines[start].find(\":\")\n    name = lines[start][:first_colon].strip()\n    desc = [\" \"*4 + lines[start][first_colon+1:].strip()]\n\n    # Append remaining description lines\n    for j in range(start+1, end):\n      desc.append(lines[j])\n\n    out[name] = \"\\n\".join(desc)\n  return out\n\n\n\ndef view_info(node: Module, /, *, only: filterlib.Filter = ..., graph: bool | None = None) -> str:\n  \"\"\"Provides information about the ``view`` arguments for a module and all\n  submodules. If no docstring is provided for a module's `set_view`, this function\n  puts the `set_view` signature below the function.\n\n  Example::\n    >>> from flax import nnx\n    ...\n    >>> class CustomModel(nnx.Module):\n    ...   def __init__(self, *, rngs):\n    ...       self.mha = nnx.MultiHeadAttention(4, 8, 32, rngs=rngs)\n    ...       self.drop = nnx.Dropout(0.5, rngs=rngs)\n    ...       self.bn = nnx.BatchNorm(32, rngs=rngs)\n    ...\n    >>> model = CustomModel(rngs=nnx.Rngs(0))\n    >>> print(nnx.view_info(model))\n    BatchNorm:\n      use_running_average: bool | None = None\n        if True, the stored batch statistics will be\n        used instead of computing the batch statistics on the input.\n    Dropout:\n      deterministic: bool | None = None\n        if True, disables dropout masking.\n    MultiHeadAttention:\n      deterministic: bool | None = None\n        if True, the module is set to deterministic mode.\n      decode: bool | None = None\n        if True, the module is set to decode mode.\n      batch_size: int | Shape | None = None\n        the batch size to use for the cache.\n      max_length: int | None = None\n        the max length to use for the cache.\n\n  Args:\n    node: the object to display ``view`` information for.\n    only: Filters to select the Modules to display information for.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n  \"\"\"\n  predicate = filterlib.to_predicate(only)\n  classes: set[Module] = set()\n\n  def _set_mode_info_fn(path, node):\n    if hasattr(node, 'set_view') and predicate(path, node):\n      classes.add(node.__class__)\n    return node\n\n  graphlib.recursive_map(_set_mode_info_fn, node, graph=graph)\n\n  class_list = sorted(list(classes), key=lambda x: x.__qualname__)\n  out_str = []\n  for c in class_list:\n    out_str.append(f\"{c.__qualname__}:\")\n    sig = inspect.signature(c.set_view)\n    doc = inspect.getdoc(c.set_view)\n\n    # Parse docstring\n    if isinstance(doc, str):\n      start, end = doc.find(\"Args:\\n\"), doc.find(\"Returns:\\n\")\n      if end == -1:\n        end = len(doc)\n      doc = doc[start+6:end]\n      parsed_docstring = _parse_docstring_args(doc)\n\n      # Generate output from signature and docstring\n      skip_names = {\"self\", \"args\", \"kwargs\"}\n      for name, param in sig.parameters.items():\n        if name in skip_names:\n          continue\n\n        if param.default is inspect.Parameter.empty:\n          out_str.append(f\"  {name}: {param.annotation}\")\n        else:\n          out_str.append(f\"  {name}: {param.annotation} = {param.default}\")\n        out_str.append(parsed_docstring[name])\n    else:\n      out_str.append(f\"  set_view{sig}\")\n\n\n  return \"\\n\".join(out_str)\n\ndef first_from(*args: tp.Optional[A], error_msg: str) -> A:\n  \"\"\"Return the first non-None argument.\n\n  If all arguments are None, raise a ValueError with the given error message.\n\n  Args:\n    *args: the arguments to check\n    error_msg: the error message to raise if all arguments are None\n  Returns:\n    The first non-None argument.\n  \"\"\"\n  for arg in args:\n    if arg is not None:\n      return arg\n  raise ValueError(error_msg)\n\ndef iter_modules(\n  module: Module, /, *, graph: bool | None = None,\n) -> tp.Iterator[tuple[PathParts, Module]]:\n  \"\"\"Recursively iterates over all nested :class:`Module`'s of the given Module, including\n  the argument.\n\n  Specifically, this function creates a generator that yields the path and the Module instance, where\n  the path is a tuple of strings or integers representing the path to the Module from the\n  root Module.\n\n  Example::\n\n    >>> from flax import nnx\n    ...\n    >>> class SubModule(nnx.Module):\n    ...   def __init__(self, din, dout, rngs):\n    ...     self.linear1 = nnx.Linear(din, dout, rngs=rngs)\n    ...     self.linear2 = nnx.Linear(din, dout, rngs=rngs)\n    ...\n    >>> class Block(nnx.Module):\n    ...   def __init__(self, din, dout, *, rngs: nnx.Rngs):\n    ...     self.linear = nnx.Linear(din, dout, rngs=rngs)\n    ...     self.submodule = SubModule(din, dout, rngs=rngs)\n    ...     self.dropout = nnx.Dropout(0.5)\n    ...     self.batch_norm = nnx.BatchNorm(10, rngs=rngs)\n    ...\n    >>> model = Block(2, 5, rngs=nnx.Rngs(0))\n    >>> for path, module in nnx.iter_modules(model):\n    ...   print(path, type(module).__name__)\n    ...\n    ('batch_norm',) BatchNorm\n    ('dropout',) Dropout\n    ('linear',) Linear\n    ('submodule', 'linear1') Linear\n    ('submodule', 'linear2') Linear\n    ('submodule',) SubModule\n    () Block\n\n  Args:\n    module: A :class:`Module` object.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n  \"\"\"\n  for path, value in graphlib.iter_graph(module, graph=graph):\n    if isinstance(value, Module):\n      yield path, value\n\niter_children = graphlib.iter_children\n\nP = tp.ParamSpec(\"P\")\nR = tp.TypeVar(\"R\")\n\n@tp.overload\ndef capture(\n  fn: tp.Callable[P, R],\n  *var_types: type[variableslib.Variable],\n  init: tp.Optional[State] = None,\n  method_outputs: tp.Optional[type[variableslib.Variable]] = None\n) -> tp.Callable[P, tuple[R, State]]: ...\n\n@tp.overload\ndef capture(\n  fn: type[variableslib.Variable],\n  *var_types: type[variableslib.Variable],\n  init: tp.Optional[State] = None,\n  method_outputs: tp.Optional[type[variableslib.Variable]] = None\n) -> tp.Callable[[tp.Callable[P, R]], tp.Callable[P, tuple[R, State]]]: ...\n\ndef capture(fn: tp.Callable[P, R] | type[variableslib.Variable], *var_types: type[variableslib.Variable],\n  init : tp.Optional[State] = None,\n  method_outputs : tp.Optional[type[variableslib.Variable]] = None\n) -> tp.Callable[P, tuple[R, State]] | tp.Callable[[tp.Callable[P, R]], tp.Callable[P, tuple[R, State]]]:\n    \"\"\"Wraps a function to capture intermediate values from a module during execution.\n\n    This function wraps a `Callable`, executing it while collecting intermediate values that were stored using\n    ``Module.sow()`` or ``Module.perturb()``.\n\n    The `fn` argument can be either a function, a Module instance, or a bound method.\n    If `fn` is a function, its first argument should be the module in which intermediate values are to be recorded.\n    If `fn` is a bound method, the module used for storage is inferred from the instance.\n    If `fn` is a Module, its `__call__` method will be wrapped.\n\n    Args:\n      fn: The `Callable` to wrap.\n      var_types: Variable types to capture. If None, defaults to [].\n      init: MutableMapping used to initialize perturbation values. This is useful for gradient extraction.\n      method_outputs: If provided, automatically sows the output of each method\n        in the module and its submodules using this variable type.\n\n    Returns:\n      A wrapped function that returns\n      a tuple of (result, *intermediates) where result is the output of the function\n      and each intermediate is a State containing the captured values with the corresponding type in `var_types`.\n\n    Example with manual sowing::\n\n      class Foo(nnx.Module):\n        def __call__(self, x):\n          self.sow(nnx.Intermediate, 'features', x)\n          return x\n\n      model = Foo(rngs=nnx.Rngs(0))\n      forward = nnx.capture(model, nnx.Intermediate)\n      result, intermediates = forward(x)\n      # intermediates['features'] contains the sowed value\n\n    Example with method outputs::\n\n      class Foo(nnx.Module):\n        def features(self, x):\n          return x\n        def classifier(self, x):\n          return x\n        def __call__(self, x):\n          return self.classifier(self.features(x))\n\n      model = Foo(rngs=nnx.Rngs(0))\n      result, intermediates = nnx.capture(\n        model, method_output_type=nnx.Intermediate)(x)\n      # intermediates contains outputs of features(), classifier(), and __call__()\n\n    Example with gradient extraction::\n\n      class Model(nnx.Module):\n        def __call__(self, x):\n          x2 = self.perturb('grad_of_x', x)\n          return 3 * x2\n\n      model = Model()\n      forward = nnx.capture(lambda model, x: model(x), nnx.Perturbation) # Initialize perturbations\n      _, perturbations = forward_capture(model, x)\n\n      # Compute gradients with respect to perturbations\n      loss = nnx.capture(forward, init=perturbations)\n      grads, sowed = nnx.grad(loss, has_aux=True)(model, perturbations, x)\n    \"\"\"\n\n    # Handle partial evaluation when first arg is a Variable type\n    if isinstance(fn, type) and issubclass(fn, variableslib.Variable):\n      # Partial application: return a function that waits for the actual fn\n      all_var_types = (fn,) + var_types\n      def partial_capture(actual_fn: tp.Callable[P, R] | Module) -> tp.Callable[P, tuple[R, State]]:\n        return capture(actual_fn, *all_var_types, init=init, method_outputs=method_outputs)\n      return partial_capture\n\n    # Handle bound methods and callable Modules\n    module_instance = None\n    if inspect.ismethod(fn) and isinstance(fn.__self__, Module):\n      module_instance = fn.__self__\n    elif isinstance(fn, Module):\n      module_instance = fn\n\n    ft.wraps(fn)\n    def wrapper(*fn_args, **kwargs):\n      if module_instance is None:\n        module = fn_args[0]\n      else:\n        module = module_instance\n\n      # Extract initial values from state\n      state_by_path = _collect_state_by_path(init) if init else {}\n\n      # Initialize __captures__ as a tuple of Variables (one per type)\n      for path, m in iter_modules(module):\n        # Create initial dicts for each variable type\n        initial_dicts = {}\n        for var_type in var_types:\n          initial_dicts[var_type] = {}\n\n        # Populate from state if available\n        if path in state_by_path:\n          for name, var in state_by_path[path].items():\n            var_type = type(var)\n            if var_type not in initial_dicts:\n              initial_dicts[var_type] = {}\n            initial_dicts[var_type][name] = var.get_value()\n\n        # Create the captures tuple\n        captures_tuple = tuple(k(v) for (k,v) in initial_dicts.items())\n        m.__captures__ = pytreelib.data(captures_tuple)\n\n      # Wrap methods with capturing if required\n      if method_outputs:\n        for _, m in iter_modules(module):\n          _add_capturing(type(m), method_outputs)\n\n      try:\n        result = fn(*fn_args, **kwargs)\n      finally:\n\n        # Undo method sowing modification\n        for _, m in iter_modules(module):\n          _remove_capturing(type(m))\n\n      # Extract intermediates manually from __captures__\n      interms = State({})\n      _extract_captures(module, interms, set(var_types))\n      if len(var_types) == 0:\n          return result\n      split_states = split_state(interms, *var_types)\n      if len(var_types) == 1:\n        return result, split_states\n      else:\n        return (result, *split_states)\n\n    return wrapper\n\ndef _collect_state_by_path(state):\n  \"\"\"Build a mapping from module path to state Variables.\"\"\"\n  state_by_path = {}\n\n  def collect(s, path_parts):\n    if isinstance(s, MutableMapping):\n      for key, value in s.items():\n        if isinstance(value, variableslib.Variable):\n          path_tuple = tuple(path_parts)\n          if path_tuple not in state_by_path:\n            state_by_path[path_tuple] = {}\n          state_by_path[path_tuple][key] = value\n        elif isinstance(value, MutableMapping):\n          collect(value, path_parts + [key])\n\n  collect(state, [])\n  return state_by_path\n\ndef _navigate_to_path(state, path):\n  \"\"\"Navigate to a nested path in state, creating dicts as needed.\"\"\"\n  current = state\n  for part in path:\n    if part not in current:\n      current[part] = State({})\n    current = current[part]\n  return current\n\ndef _extract_captures(module, state, var_types):\n  \"\"\"Extract intermediates from __captures__ tuple into state dict.\"\"\"\n  for path, mod in iter_modules(module):\n    if hasattr(mod, '__captures__'):\n      captures_tuple = mod.__captures__\n      for var in captures_tuple:\n        if not type(var) in var_types:\n          continue\n        current = _navigate_to_path(state, path)\n        for key, value in var.items():\n          current[key] = type(var)(value)\n      delattr(mod, '__captures__')\n\n\ndef _add_capturing(cls, variable_type):\n  \"\"\"Adds capturing to methods of a Module.\n  Does not instrument superclass methods.\"\"\"\n  for name, method in cls.__dict__.items():\n    if callable(method) and (not name.startswith('_') or name == '__call__'):\n      if not hasattr(method, '_does_capturing'):\n        def closure(name, method): # Necessary to make 'name' immutable during iteration\n          @ft.wraps(method)\n          def wrapper(self, *args, **kwargs):\n            result = method(self, *args, **kwargs)\n            self.sow(variable_type, name, result)\n            return result\n          wrapper._does_capturing = True\n          setattr(cls, name, wrapper)\n        closure(name, method)\n  return cls\n\ndef _remove_capturing(cls):\n  \"\"\"Remove capturing methods from a Module.\"\"\"\n  for name, method in cls.__dict__.items():\n    if hasattr(method, '_does_capturing'):\n      setattr(cls, name, method.__wrapped__)\n  return cls\n"
  },
  {
    "path": "flax/nnx/nn/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "flax/nnx/nn/activations.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing as tp\nfrom types import MappingProxyType\nfrom jax.nn import (\n  celu,\n  elu,\n  gelu,\n  glu,\n  hard_sigmoid,\n  hard_silu,\n  hard_swish,\n  hard_tanh,\n  leaky_relu,\n  log_sigmoid,\n  log_softmax,\n  logsumexp,\n  one_hot,\n  relu,\n  identity,\n  relu6,\n  selu,\n  sigmoid,\n  silu,\n  soft_sign,\n  softmax,\n  softplus,\n  standardize,\n  swish,\n)\nimport jax.numpy as jnp\nfrom jax.numpy import tanh\n\nfrom flax import nnx\nfrom flax.nnx.nn import dtypes\nfrom flax.typing import Array, Dtype, PromoteDtypeFn\n\n\n__all__ = [\n  'celu',\n  'elu',\n  'gelu',\n  'glu',\n  'hard_sigmoid',\n  'hard_silu',\n  'hard_swish',\n  'hard_tanh',\n  'leaky_relu',\n  'log_sigmoid',\n  'log_softmax',\n  'logsumexp',\n  'one_hot',\n  'relu',\n  'identity',\n  'relu6',\n  'selu',\n  'sigmoid',\n  'silu',\n  'soft_sign',\n  'softmax',\n  'softplus',\n  'standardize',\n  'swish',\n  'tanh',\n  'PReLU',\n]\n\n\nclass PReLU(nnx.Module):\n  \"\"\"Parametric Rectified Linear Unit (PReLU) activation function.\n\n  Note that PReLU is a Flax layer and not a simple activation function, so\n  it needs to be initialized before being called.\n\n  Example::\n\n    >>> import flax.nnx as nnx\n\n    >>> class MLP(nnx.Module):\n    ...   def __init__(self):\n    ...     self.linear = nnx.Linear(3, 2)\n    ...     self.act = nnx.PReLU(negative_slope_init=0.1)\n    ...\n    ...   def __call__(self, x):\n    ...     x = self.linear(x)\n    ...     x = self.act(x)\n    ...     return x\n\n  Args:\n    negative_slope_init: the value to initialize the negative slope (default 0.01).\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    promote_dtype: function to promote the dtype of all input array arguments\n      (including Variables accessed through ``self``) to the desired dtype. The\n      function should accept a tuple of ``(inputs, negative_slope)`` and a ``dtype``\n      keyword argument, and return a tuple of arrays with the promoted dtype.\n    negative_slope_metadata: Optional metadata dictionary to set when initializing\n      the negative slope.\n  \"\"\"\n  def __init__(\n    self,\n    negative_slope_init: float = 0.01,\n    *,\n    dtype: Dtype | None = None,\n    param_dtype: Dtype = jnp.float32,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    negative_slope_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    self.negative_slope = nnx.Param(\n      jnp.asarray(negative_slope_init, dtype=param_dtype), **negative_slope_metadata\n    )\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.promote_dtype = promote_dtype\n\n  def __call__(self, inputs: Array) -> Array:\n    negative_slope = self.negative_slope[...]\n    if self.dtype is not None:\n      inputs, negative_slope = self.promote_dtype(\n        (inputs, negative_slope), dtype=self.dtype\n      )\n    else:\n      # Match Linen behavior: cast parameter to input dtype\n      negative_slope = jnp.asarray(negative_slope, inputs.dtype)\n\n    return jnp.where(\n      inputs >= 0,\n      inputs,\n      negative_slope * inputs,\n    )\n"
  },
  {
    "path": "flax/nnx/nn/attention.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Attention core modules for Flax.\"\"\"\n\nfrom __future__ import annotations\n\nimport functools\nfrom typing import Any\nfrom collections.abc import Mapping\nfrom types import MappingProxyType\nfrom collections.abc import Callable\nimport math\n\nimport jax\nimport jax.numpy as jnp\nfrom jax import lax, random\n\nfrom flax import nnx\nfrom flax.nnx import rnglib\nfrom flax.nnx.module import Module, first_from\nfrom flax.nnx.nn import initializers\nfrom flax.nnx.nn import dtypes\nfrom flax.nnx.nn.linear import (\n  LinearGeneral,\n  default_kernel_init,\n)\nfrom flax.nnx.nn.normalization import LayerNorm\nfrom flax.typing import (\n  Dtype,\n  PromoteDtypeFn,\n  Shape,\n  Initializer,\n  PrecisionLike,\n  DotGeneralT,\n)\n\nArray = jax.Array\n\n\ndef dot_product_attention_weights(\n  query: Array,\n  key: Array,\n  bias: Array | None = None,\n  mask: Array | None = None,\n  broadcast_dropout: bool = True,\n  dropout_rng: Array | None = None,\n  dropout_rate: float = 0.0,\n  deterministic: bool = False,\n  dtype: Dtype | None = None,\n  precision: PrecisionLike = None,\n  module: Module | None = None,\n  promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n  is_causal: bool = False,\n):\n  \"\"\"Computes dot-product attention weights given query and key.\n\n  Used by :func:`dot_product_attention`, which is what you'll most likely use.\n  But if you want access to the attention weights for introspection, then\n  you can directly call this function and call einsum yourself.\n\n  Args:\n    query: queries for calculating attention with shape of `[batch..., q_length,\n      num_heads, qk_depth_per_head]`.\n    key: keys for calculating attention with shape of `[batch..., kv_length,\n      num_heads, qk_depth_per_head]`.\n    bias: bias for the attention weights. This should be broadcastable to the\n      shape `[batch..., num_heads, q_length, kv_length]`. This can be used for\n      incorporating causal masks, padding masks, proximity bias, etc.\n    mask: mask for the attention weights. This should be broadcastable to the\n      shape `[batch..., num_heads, q_length, kv_length]`. This can be used for\n      incorporating causal masks. Attention weights are masked out if their\n      corresponding mask value is `False`.\n    broadcast_dropout: bool: use a broadcasted dropout along batch dims.\n    dropout_rng: JAX PRNGKey: to be used for dropout\n    dropout_rate: dropout rate\n    deterministic: bool, deterministic or not (to apply dropout)\n    dtype: the dtype of the computation (default: infer from inputs and params)\n    precision: numerical precision of the computation see `jax.lax.Precision`\n      for details.\n    module: the Module that will sow the attention weights into the\n      ``nnx.Intermediate`` collection. If ``module`` is None, the attention\n      weights will not be sowed.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(query, key)`` and a ``dtype``\n      keyword argument, and return a tuple of arrays with the promoted dtype.\n    is_causal: If true, causal attention will be applied. Note, some\n      implementations like xla will generate a mask tensor and apply it to\n      the logits to mask out the non-causal parts of the attention matrix,\n      but other implementations like cudnn will avoid computing the\n      non-causal regions, providing speedups.\n\n  Returns:\n    Output of shape `[batch..., num_heads, q_length, kv_length]`.\n  \"\"\"\n  query, key = promote_dtype((query, key), dtype=dtype)  # type: ignore[bad-unpacking]\n  dtype = query.dtype\n\n  assert query.ndim == key.ndim, 'q, k must have same rank.'\n  assert query.shape[:-3] == key.shape[:-3], 'q, k batch dims must match.'\n  assert query.shape[-1] == key.shape[-1], 'q, k depths must match.'\n\n  # check if we need to broadcast Key heads to match Query heads\n  is_gqa = False\n  if query.shape[-2] != key.shape[-2]:\n    q_heads = query.shape[-2]\n    k_heads = key.shape[-2]\n\n    if q_heads % k_heads != 0:\n      raise ValueError(\n        f\"Query heads ({q_heads}) must be multiple of \"\n        f\"Key heads ({k_heads}) for Grouped Query Attention.\"\n      )\n\n    n_rep = q_heads // k_heads\n    is_gqa = True\n    # Reshape Query: [..., Q, H_k * n_rep, D] -> [..., Q, H_k, n_rep, D]\n    query = query.reshape(query.shape[:-2] + (k_heads, n_rep, query.shape[-1]))\n    # Expand Key: [..., K, H_k, D] -> [..., K, H_k, 1, D]\n    key = jnp.expand_dims(key, axis=-2)\n\n    # Contract: q(h)gd, k(h)1d -> hgqk (h=H_k, g=n_rep)\n    einsum_str = '...qhgd,...kh1d->...hgqk'\n  else:\n    q_heads = query.shape[-2]\n    einsum_str = '...qhd,...khd->...hqk'\n    assert query.shape[-2] == key.shape[-2], 'q, k num_heads must match.'\n\n  # calculate attention matrix\n  depth = query.shape[-1]\n  query = query / jnp.sqrt(depth).astype(dtype)\n\n  # attn weight shape is (batch..., num_heads, q_length, kv_length)\n  attn_weights = jnp.einsum(einsum_str, query, key, precision=precision)\n\n  if is_gqa:\n      attn_weights = attn_weights.reshape(attn_weights.shape[:-4] + (q_heads, attn_weights.shape[-2], attn_weights.shape[-1]))\n\n  # apply attention bias: masking, dropout, proximity bias, etc.\n  if bias is not None:\n    attn_weights = attn_weights + bias\n  # apply attention mask\n  if mask is not None or is_causal:\n    big_neg = jnp.finfo(dtype).min\n    masks = [m for m in [mask] if m is not None]\n    if is_causal:\n      T, S = attn_weights.shape[-2:]\n      causal_mask = jnp.tril(jnp.ones((T, S), dtype=dtype))\n      target_shape = mask.shape if mask is not None else attn_weights.shape\n      masks.append(jnp.broadcast_to(causal_mask, target_shape))\n    combined_mask = combine_masks(*masks, dtype=dtype)\n    assert combined_mask is not None\n    attn_weights = jnp.where(combined_mask, attn_weights, big_neg)\n\n  # normalize the attention weights\n  attn_weights = jax.nn.softmax(attn_weights).astype(dtype)\n\n  if module:\n    module.sow(nnx.Intermediate, 'attention_weights', attn_weights)\n\n  # apply attention dropout\n  if not deterministic and dropout_rate > 0.0:\n    keep_prob = 1.0 - dropout_rate\n    # use original key.ndim because we might have expanded key dim\n    ndim_base = key.ndim - 1 if is_gqa else key.ndim\n\n    if broadcast_dropout:\n      # dropout is broadcast across the batch + head dimensions\n      dropout_shape = tuple([1] * (ndim_base - 2)) + attn_weights.shape[-2:]\n      keep = random.bernoulli(dropout_rng, keep_prob, dropout_shape)  # type: ignore\n    else:\n      keep = random.bernoulli(dropout_rng, keep_prob, attn_weights.shape)  # type: ignore\n    multiplier = keep.astype(dtype) / jnp.asarray(keep_prob, dtype=dtype)\n    attn_weights = attn_weights * multiplier\n\n  return attn_weights\n\n\ndef dot_product_attention(\n  query: Array,\n  key: Array,\n  value: Array,\n  bias: Array | None = None,\n  mask: Array | None = None,\n  broadcast_dropout: bool = True,\n  dropout_rng: Array | None = None,\n  dropout_rate: float = 0.0,\n  deterministic: bool = False,\n  dtype: Dtype | None = None,\n  precision: PrecisionLike = None,\n  module: Module | None = None,\n  promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n  is_causal: bool = False,\n):\n  \"\"\"Computes dot-product attention given query, key, and value.\n\n  This is the core function for applying attention based on\n  https://arxiv.org/abs/1706.03762. It calculates the attention weights given\n  query and key and combines the values using the attention weights.\n\n  Will use the more optimized `jax.nn.dot_product_attention` if dropout is\n  not activated and `module=None`.\n\n  .. note::\n    ``query``, ``key``, ``value`` needn't have any batch dimensions.\n\n  Args:\n    query: queries for calculating attention with shape of ``[batch..., q_length,\n      num_heads, qk_depth_per_head]``.\n    key: keys for calculating attention with shape of ``[batch..., kv_length,\n      num_heads, qk_depth_per_head]``.\n    value: values to be used in attention with shape of ``[batch..., kv_length,\n      num_heads, v_depth_per_head]``.\n    bias: bias for the attention weights. This should be broadcastable to the\n      shape `[batch..., num_heads, q_length, kv_length]`. This can be used for\n      incorporating causal masks, padding masks, proximity bias, etc.\n    mask: mask for the attention weights. This should be broadcastable to the\n      shape `[batch..., num_heads, q_length, kv_length]`. This can be used for\n      incorporating causal masks. Attention weights are masked out if their\n      corresponding mask value is `False`.\n    broadcast_dropout: bool: use a broadcasted dropout along batch dims.\n    dropout_rng: JAX PRNGKey: to be used for dropout\n    dropout_rate: dropout rate\n    deterministic: bool, deterministic or not (to apply dropout)\n    dtype: the dtype of the computation (default: infer from inputs)\n    precision: numerical precision of the computation see `jax.lax.Precision`\n      for details.\n    module: the Module that will sow the attention weights into the\n      ``nnx.Intermediate`` collection. If ``module`` is None, the attention\n      weights will not be sowed.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(query, key, value)`` and a\n      ``dtype`` keyword argument, and return a tuple of arrays with the promoted\n      dtype.\n    is_causal: If true, causal attention will be applied. Note, some\n      implementations like xla will generate a mask tensor and apply it to\n      the logits to mask out the non-causal parts of the attention matrix,\n      but other implementations like cudnn will avoid computing the\n      non-causal regions, providing speedups.\n\n  Returns:\n    Output of shape `[batch..., q_length, num_heads, v_depth_per_head]`.\n  \"\"\"\n  query, key, value = promote_dtype((query, key, value), dtype=dtype)  # type: ignore[bad-unpacking]\n  dtype = query.dtype\n\n  assert key.ndim == query.ndim == value.ndim, 'q, k, v must have same rank.'\n  assert (\n    query.shape[:-3] == key.shape[:-3] == value.shape[:-3]\n  ), 'q, k, v batch dims must match.'\n  assert key.shape[-3] == value.shape[-3], 'k, v lengths must match.'\n\n  # Criteria that invoke the more optimized dot product attention\n  if dropout_rate == 0.0 and module is None:\n    # make sure qkv batch are compressed to one dim\n    query_shape = query.shape\n    if len(query_shape) > 4:\n      def reshape_4d(x):\n        return jnp.reshape(x, (math.prod(x.shape[:-3]), *x.shape[-3:]))\n      query, key, value, bias, mask = jax.tree.map(\n        reshape_4d, (query, key, value, bias, mask))\n    if mask is not None:\n      mask = mask.astype(jnp.bool)\n    out = jax.nn.dot_product_attention(query, key, value, bias, mask, is_causal=is_causal)\n    if len(query_shape) > 4:\n      out = jnp.reshape(out, query_shape)\n    return out\n\n  # compute attention weights\n  attn_weights = dot_product_attention_weights(\n    query,\n    key,\n    bias,\n    mask,\n    broadcast_dropout,\n    dropout_rng,\n    dropout_rate,\n    deterministic,\n    dtype,\n    precision,\n    module,\n    promote_dtype,\n    is_causal,\n  )\n\n  # return weighted sum over values for each query position\n  # check if need to broadcast Value heads to match Query heads (GQA)\n  if attn_weights.shape[-3] != value.shape[-2]:\n      q_heads = attn_weights.shape[-3]\n      v_heads = value.shape[-2]\n      if q_heads % v_heads != 0:\n         raise ValueError(f\"Query heads ({q_heads}) must be multiple of Value heads ({v_heads})\")\n\n      n_rep = q_heads // v_heads\n      # Reshape weights: [..., H_v, n_rep, Q, K]\n      attn_weights = attn_weights.reshape(attn_weights.shape[:-3] + (v_heads, n_rep) + attn_weights.shape[-2:])\n      # Expand Value: [..., K, H_v, 1, D]\n      value = jnp.expand_dims(value, axis=-2)\n      # Contract: hgqk, kh1d -> qhgd (h=H_v, g=n_rep)\n      out = jnp.einsum('...hgqk,...kh1d->...qhgd', attn_weights, value, precision=precision)\n      # Flatten: [..., Q, H_q, D]\n      out = out.reshape(out.shape[:-3] + (q_heads, out.shape[-1]))\n  else:\n      out = jnp.einsum(\n        '...hqk,...khd->...qhd', attn_weights, value, precision=precision\n      )\n\n  return out\n\n\nclass MultiHeadAttention(Module):\n  \"\"\"Multi-head attention.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax\n\n    >>> layer = nnx.MultiHeadAttention(num_heads=8, in_features=5, qkv_features=16,\n    ...                                decode=False, rngs=nnx.Rngs(0))\n    >>> key1, key2, key3 = jax.random.split(jax.random.key(0), 3)\n    >>> shape = (4, 3, 2, 5)\n    >>> q, k, v = (\n    ...   jax.random.uniform(key1, shape),\n    ...   jax.random.uniform(key2, shape),\n    ...   jax.random.uniform(key3, shape),\n    ... )\n\n    >>> # different inputs for inputs_q, inputs_k and inputs_v\n    >>> out = layer(q, k, v)\n    >>> # equivalent output when inferring v\n    >>> assert (layer(q, k) == layer(q, k, k)).all()\n    >>> # equivalent output when inferring k and v\n    >>> assert (layer(q) == layer(q, q)).all()\n    >>> assert (layer(q) == layer(q, q, q)).all()\n\n  Args:\n    num_heads: number of attention heads. Features (i.e. inputs_q.shape[-1])\n      should be divisible by the number of heads.\n    in_features: int or tuple with number of input features.\n    qkv_features: dimension of the key, query, and value.\n    out_features: dimension of the last projection.\n    in_kv_features: number of input features for computing key and value.\n    num_kv_heads: number of key and value heads. If None, it defaults to\n      ``num_heads``. If set to a value smaller than ``num_heads``, Grouped Query\n      Attention (GQA) is used. ``num_heads`` must be divisible by\n      ``num_kv_heads``.\n    dtype: the dtype of the computation (default: infer from inputs and params)\n    param_dtype: the dtype passed to parameter initializers (default: float32)\n    broadcast_dropout: bool: use a broadcasted dropout along batch dims.\n    dropout_rate: dropout rate\n    deterministic: if false, the attention weight is masked randomly using\n      dropout, whereas if true, the attention weights are deterministic.\n    precision: numerical precision of the computation see `jax.lax.Precision`\n      for details.\n    kernel_init: initializer for the kernel of the Dense layers.\n    out_kernel_init: optional initializer for the kernel of the output Dense layer,\n      if None, the kernel_init is used.\n    bias_init: initializer for the bias of the Dense layers.\n    out_bias_init: optional initializer for the bias of the output Dense layer,\n      if None, the bias_init is used.\n    use_bias: bool: whether pointwise QKVO dense transforms use bias.\n    attention_fn: dot_product_attention or compatible function. Accepts query,\n      key, value, and returns output of shape `[bs, dim1, dim2, ..., dimN,,\n      num_heads, value_channels]``\n    decode: whether to prepare and use an autoregressive cache.\n    normalize_qk: should QK normalization be applied (arxiv.org/abs/2302.05442).\n    qkv_promote_dtype: function to promote the dtype of all input array arguments\n      (including Variables accessed through ``self``) to the desired dtype for the\n      query, key, and value LinearGeneral submodules.\n    out_promote_dtype: function to promote the dtype of all input array arguments\n      (including Variables accessed through ``self``) to the desired dtype for the\n      output LinearGeneral submodule.\n    ln_promote_dtype: function to promote the dtype of all input array arguments\n      (including Variables accessed through ``self``) to the desired dtype for the\n      LayerNorm submodules (query_ln and key_ln) when normalize_qk=True.\n    rngs: rng key.\n    keep_rngs: whether to store the input rngs as attribute (i.e. `self.rngs = rngs`)\n      (default: True). If rngs is stored, we should split the module as\n      `graphdef, params, nondiff = nnx.split(module, nnx.Param, ...)` where `nondiff`\n      contains RNG object associated with stored `self.rngs`.\n    kernel_metadata: Optional metadata dictionary to set when initializing\n      the Dense layers.\n    out_kernel_metadata: Optional metadata dictionary to set when initializing\n      the output Dense layers. If None, the kernel_metadata is used.\n    bias_metadata: Optional metadata dictionary to set when initializing\n      the bias of the Dense layers.\n    out_bias_metadata: Optional metadata dictionary to set when initializing\n      the bias of the output Dense layers. If None, the bias_metadata is used.\n    query_ln_scale_metadata: Optional metadata dictionary to set when initializing\n      the scale of the query layer norm layer.\n    key_ln_scale_metadata: Optional metadata dictionary to set when initializing\n      the scale of the key layer norm layer.\n  \"\"\"\n\n  def __init__(\n    self,\n    num_heads: int,\n    in_features: int,\n    qkv_features: int | None = None,\n    out_features: int | None = None,\n    num_kv_heads: int | None = None,\n    in_kv_features: int | None = None,\n    *,\n    dtype: Dtype | None = None,\n    param_dtype: Dtype = jnp.float32,\n    broadcast_dropout: bool = True,\n    dropout_rate: float = 0.0,\n    deterministic: bool | None = None,\n    precision: PrecisionLike = None,\n    kernel_init: Initializer = default_kernel_init,\n    out_kernel_init: Initializer | None = None,\n    bias_init: Initializer = initializers.zeros_init(),\n    out_bias_init: Initializer | None = None,\n    use_bias: bool = True,\n    attention_fn: Callable[..., Array] = dot_product_attention,\n    decode: bool | None = None,\n    normalize_qk: bool = False,\n    qkv_promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    out_promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    ln_promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    # Deprecated, will be removed.\n    qkv_dot_general: DotGeneralT | None = None,\n    out_dot_general: DotGeneralT | None = None,\n    qkv_dot_general_cls: Any = None,\n    out_dot_general_cls: Any = None,\n    rngs: rnglib.Rngs,\n    keep_rngs: bool = True,\n    kernel_metadata: Mapping[str, Any] = MappingProxyType({}),\n    out_kernel_metadata: Mapping[str, Any] = MappingProxyType({}),\n    bias_metadata: Mapping[str, Any] = MappingProxyType({}),\n    out_bias_metadata: Mapping[str, Any] = MappingProxyType({}),\n    query_ln_scale_metadata: Mapping[str, Any] = MappingProxyType({}),\n    key_ln_scale_metadata: Mapping[str, Any] = MappingProxyType({}),\n  ):\n    self.num_heads = num_heads\n    self.in_features = in_features\n    self.qkv_features = (\n      qkv_features if qkv_features is not None else in_features\n    )\n    self.out_features = (\n      out_features if out_features is not None else in_features\n    )\n    self.in_kv_features = (\n      in_kv_features if in_kv_features is not None else in_features\n    )\n    self.num_kv_heads = (\n        num_kv_heads if num_kv_heads is not None else num_heads\n    )\n\n    if self.num_heads % self.num_kv_heads != 0:\n        raise ValueError(\n            f\"num_heads ({self.num_heads}) must be divisible by \"\n            f\"num_kv_heads ({self.num_kv_heads}).\"\n        )\n\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.broadcast_dropout = broadcast_dropout\n    self.dropout_rate = dropout_rate\n    self.deterministic = deterministic\n    self.precision = precision\n    self.use_bias = use_bias\n    self.attention_fn = attention_fn\n    self.decode = decode\n    self.normalize_qk = normalize_qk\n    self.qkv_promote_dtype = qkv_promote_dtype\n    self.out_promote_dtype = out_promote_dtype\n    self.ln_promote_dtype = ln_promote_dtype\n    self.qkv_dot_general = qkv_dot_general\n    self.out_dot_general = out_dot_general\n    self.qkv_dot_general_cls = qkv_dot_general_cls\n    self.out_dot_general_cls = out_dot_general_cls\n\n    if self.qkv_features % self.num_heads != 0:\n      raise ValueError(\n        f'Memory dimension ({self.qkv_features}) must be divisible by '\n        f\"'num_heads' heads ({self.num_heads}).\"\n      )\n\n    self.head_dim = self.qkv_features // self.num_heads\n\n    linear_general = functools.partial(\n      LinearGeneral,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      kernel_init=kernel_init,\n      bias_init=bias_init,\n      use_bias=self.use_bias,\n      precision=self.precision,\n      promote_dtype=self.qkv_promote_dtype,\n      dot_general=self.qkv_dot_general,\n      dot_general_cls=self.qkv_dot_general_cls,\n      kernel_metadata=kernel_metadata,\n      bias_metadata=bias_metadata,\n    )\n\n    # project inputs_q to multi-headed q/k/v\n    # dimensions are then [batch..., length, n_heads, n_features_per_head]\n    self.query = linear_general(\n        self.in_features,\n        out_features=(self.num_heads, self.head_dim),\n        rngs=rngs\n    )\n    self.key = linear_general(\n        self.in_kv_features,\n        out_features=(self.num_kv_heads, self.head_dim),\n        rngs=rngs\n    )\n    self.value = linear_general(\n        self.in_kv_features,\n        out_features=(self.num_kv_heads, self.head_dim),\n        rngs=rngs\n    )\n\n    self.query_ln: LayerNorm | None\n    self.key_ln: LayerNorm | None\n    if self.normalize_qk:\n      # Normalizing query and key projections stabilizes training with higher\n      # LR. See ViT-22B paper http://arxiv.org/abs/2302.05442 for analysis.\n      self.query_ln = LayerNorm(\n        self.head_dim,\n        use_bias=False,\n        dtype=self.dtype,\n        param_dtype=self.param_dtype,\n        promote_dtype=self.ln_promote_dtype,\n        rngs=rngs,\n        scale_metadata=query_ln_scale_metadata,\n      )\n      self.key_ln = LayerNorm(\n        self.head_dim,\n        use_bias=False,\n        dtype=self.dtype,\n        param_dtype=self.param_dtype,\n        promote_dtype=self.ln_promote_dtype,\n        rngs=rngs,\n        scale_metadata=key_ln_scale_metadata,\n      )\n    else:\n      self.query_ln = nnx.data(None)\n      self.key_ln = nnx.data(None)\n\n    self.out = LinearGeneral(\n      in_features=(self.num_heads, self.head_dim),\n      out_features=self.out_features,\n      axis=(-2, -1),\n      kernel_init=out_kernel_init or kernel_init,\n      bias_init=out_bias_init or bias_init,\n      use_bias=self.use_bias,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      precision=self.precision,\n      promote_dtype=self.out_promote_dtype,\n      dot_general=self.out_dot_general,\n      dot_general_cls=self.out_dot_general_cls,\n      rngs=rngs,\n      kernel_metadata=out_kernel_metadata or kernel_metadata,\n      bias_metadata=out_bias_metadata or bias_metadata,\n    )\n    self.rngs = rngs.dropout.fork() if keep_rngs and dropout_rate > 0 else None\n\n    self.cached_key: nnx.Cache[Array] | None = nnx.data(None)\n    self.cached_value: nnx.Cache[Array] | None = nnx.data(None)\n    self.cache_index: nnx.Cache[Array] | None = nnx.data(None)\n\n  def __call__(\n    self,\n    inputs_q: Array,\n    inputs_k: Array | None = None,\n    inputs_v: Array | None = None,\n    *,\n    mask: Array | None = None,\n    deterministic: bool | None = None,\n    rngs: rnglib.Rngs | rnglib.RngStream | None = None,\n    sow_weights: bool = False,\n    decode: bool | None = None,\n  ):\n    \"\"\"Applies multi-head dot product attention on the input data.\n\n    Projects the inputs into multi-headed query, key, and value vectors,\n    applies dot-product attention and project the results to an output vector.\n\n    If both inputs_k and inputs_v are None, they will both copy the value of\n    inputs_q (self attention).\n    If only inputs_v is None, it will copy the value of inputs_k.\n\n    Args:\n      inputs_q: input queries of shape `[batch_sizes..., length, features]`.\n      inputs_k: key of shape `[batch_sizes..., length, features]`. If None,\n        inputs_k will copy the value of inputs_q.\n      inputs_v: values of shape `[batch_sizes..., length, features]`. If None,\n        inputs_v will copy the value of inputs_k.\n      mask: attention mask of shape `[batch_sizes..., num_heads, query_length,\n        key/value_length]`. Attention weights are masked out if their\n        corresponding mask value is `False`.\n      deterministic: if false, the attention weight is masked randomly using\n        dropout, whereas if true, the attention weights are deterministic. The\n        ``deterministic`` flag passed into the call method will take precedence\n        over the ``deterministic`` flag passed into the constructor.\n      rngs: rng key. The rng key passed into the call method will take\n        precedence over the rng key passed into the constructor.\n      sow_weights: if ``True``, the attention weights are sowed into the\n        'intermediates' collection.\n      decode: whether to prepare and use an autoregressive cache. The ``decode``\n        flag passed into the call method will take precedence over the ``decode``\n        flag passed into the constructor.\n\n    Returns:\n      output of shape `[batch_sizes..., length, features]`.\n    \"\"\"\n    if rngs is None:\n      rngs = self.rngs\n    elif isinstance(rngs, rnglib.Rngs):\n      rngs = rngs.dropout\n\n    if inputs_k is None:\n      if inputs_v is not None:\n        raise ValueError(\n          '`inputs_k` cannot be None if `inputs_v` is not None. '\n          'To have both `inputs_k` and `inputs_v` be the same value, pass in the '\n          'value to `inputs_k` and leave `inputs_v` as None.'\n        )\n      inputs_k = inputs_q\n    if inputs_v is None:\n      inputs_v = inputs_k\n\n    if inputs_q.shape[-1] != self.in_features:\n      raise ValueError(\n        f'Incompatible input dimension, got {inputs_q.shape[-1]} '\n        f'but module expects {self.in_features}.'\n      )\n\n    query = self.query(inputs_q)\n    key = self.key(inputs_k)\n    value = self.value(inputs_v)\n\n    if self.normalize_qk:\n      assert self.query_ln is not None and self.key_ln is not None\n      # Normalizing query and key projections stabilizes training with higher\n      # LR. See ViT-22B paper http://arxiv.org/abs/2302.05442 for analysis.\n      query = self.query_ln(query)\n      key = self.key_ln(key)\n\n    # During fast autoregressive decoding, we feed one position at a time,\n    # and cache the keys and values step by step.\n    decode = first_from(\n      decode,\n      self.decode,\n      error_msg=\"\"\"No `decode` argument was provided to MultiHeadAttention\n        as either a __call__ argument, class attribute, or nnx.flag.\"\"\",\n    )\n\n    if decode:\n      if (\n        self.cached_key is None\n        or self.cached_value is None\n        or self.cache_index is None\n      ):\n        raise ValueError(\n          'Autoregressive cache not initialized, call ``init_cache`` first.'\n        )\n      (\n        *batch_dims,\n        max_length,\n        num_kv_heads,\n        depth_per_head,\n      ) = self.cached_key.shape\n      # shape check of cached keys against key input\n      expected_shape = tuple(batch_dims) + (1, num_kv_heads, depth_per_head)\n      if expected_shape != key.shape:\n        raise ValueError(\n          'Autoregressive cache shape error, '\n          f'expected key shape {expected_shape} instead got {key.shape}.'\n        )\n      # update key, value caches with our new 1d spatial slices\n      cur_index = self.cache_index[...]\n      zero = jnp.array(0, dtype=lax.dtype(cur_index.dtype))\n      indices = (zero,) * len(batch_dims) + (cur_index, zero, zero)\n      key = lax.dynamic_update_slice(self.cached_key[...], key, indices)\n      value = lax.dynamic_update_slice(self.cached_value[...], value, indices)\n      self.cached_key[...] = key\n      self.cached_value[...] = value\n      self.cache_index[...] += 1\n      # causal mask for cached decoder self-attention:\n      # our single query position should only attend to those key\n      # positions that have already been generated and cached,\n      # not the remaining zero elements.\n      mask = combine_masks(\n        mask,\n        jnp.broadcast_to(\n          jnp.arange(max_length) <= cur_index,\n          tuple(batch_dims) + (1, 1, max_length),\n        ),\n      )\n\n    if (\n      self.dropout_rate > 0.0\n    ):  # Require `deterministic` only if using dropout.\n      deterministic = first_from(\n        deterministic,\n        self.deterministic,\n        error_msg=\"\"\"No `deterministic` argument was provided to MultiHeadAttention\n          as either a __call__ argument, class attribute, or nnx.flag.\"\"\",\n      )\n      if not deterministic:\n        if rngs is None:\n          raise ValueError(\n            \"'rngs' must be provided to __call__ method if \"\n            \"MultiHeadAttention instance is defined with keep_rngs=False.\"\n          )\n        dropout_rng = rngs()\n      else:\n        dropout_rng = None\n    else:\n      deterministic = True\n      dropout_rng = None\n\n    # apply attention\n    x = self.attention_fn(\n      query,\n      key,\n      value,\n      mask=mask,\n      dropout_rng=dropout_rng,\n      dropout_rate=self.dropout_rate,\n      broadcast_dropout=self.broadcast_dropout,\n      deterministic=deterministic,\n      dtype=self.dtype,\n      precision=self.precision,\n      module=self if sow_weights else None,\n    )\n    # back to the original inputs dimensions\n    out = self.out(x)\n    return out\n\n  def init_cache(self, input_shape: Shape, dtype: Dtype = jnp.float32):\n    \"\"\"Initializes cache for fast autoregressive decoding. When\n    ``decode=True``, this method must be called first before performing\n    forward inference. When in decode mode, only one token must be passed\n    at a time.\n\n    Example usage::\n\n      >>> from flax import nnx\n      >>> import jax.numpy as jnp\n      ...\n      >>> batch_size = 5\n      >>> embed_dim = 3\n      >>> x = jnp.ones((batch_size, 1, embed_dim)) # single token\n      ...\n      >>> model_nnx = nnx.MultiHeadAttention(\n      ...   num_heads=2,\n      ...   in_features=3,\n      ...   qkv_features=6,\n      ...   out_features=6,\n      ...   decode=True,\n      ...   rngs=nnx.Rngs(42),\n      ... )\n      ...\n      >>> # out_nnx = model_nnx(x)  <-- throws an error because cache isn't initialized\n      ...\n      >>> model_nnx.init_cache(x.shape)\n      >>> out_nnx = model_nnx(x)\n    \"\"\"\n    cache_shape = (*input_shape[:-1], self.num_kv_heads, self.head_dim)\n    self.cached_key = nnx.Cache(jnp.zeros(cache_shape, dtype))\n    self.cached_value = nnx.Cache(jnp.zeros(cache_shape, dtype))\n    self.cache_index = nnx.Cache(jnp.array(0, dtype=jnp.int32))\n\n  def set_view(\n      self,\n      deterministic: bool | None = None,\n      decode: bool | None = None,\n      batch_size: int | Shape | None = None,\n      max_length: int | None = None,\n  ):\n    \"\"\"Class method used by ``nnx.view``.\n\n    Args:\n      train: if True, the module is set to training mode.\n      deterministic: if True, the module is set to deterministic mode.\n      decode: if True, the module is set to decode mode.\n      batch_size: the batch size to use for the cache.\n      max_length: the max length to use for the cache.\n    \"\"\"\n    if deterministic is not None:\n      self.deterministic = deterministic\n\n    if decode is not None:\n      self.decode = decode\n      if (\n          not hasattr(self, 'cached_key')\n          or not hasattr(self, 'cached_value')\n          or not hasattr(self, 'cache_index')\n      ):\n        if batch_size is None:\n          raise TypeError(\n              \"'batch_size' must be provided when initializing cache.\"\n          )\n        if max_length is None:\n          raise TypeError(\n              \"'max_length' must be provided when initializing cache.\"\n          )\n        if isinstance(batch_size, int):\n          batch_size = (batch_size,)\n\n        # initialize cache\n        cache_shape = (*batch_size, max_length, self.num_kv_heads, self.head_dim)\n        self.cached_key = nnx.Cache(jnp.zeros(cache_shape, self.dtype))\n        self.cached_value = nnx.Cache(jnp.zeros(cache_shape, self.dtype))\n        self.cache_index = nnx.Cache(jnp.array(0, dtype=jnp.int32))\n\n\n# mask-making utility functions\n\n\ndef make_attention_mask(\n  query_input: Array,\n  key_input: Array,\n  pairwise_fn: Callable[..., Any] = jnp.multiply,\n  extra_batch_dims: int = 0,\n  dtype: Dtype = jnp.float32,\n):\n  \"\"\"Mask-making helper for attention weights.\n\n  In case of 1d inputs (i.e., `[batch..., len_q]`, `[batch..., len_kv]`, the\n  attention weights will be `[batch..., heads, len_q, len_kv]` and this\n  function will produce `[batch..., 1, len_q, len_kv]`.\n\n  Args:\n    query_input: a batched, flat input of query_length size\n    key_input: a batched, flat input of key_length size\n    pairwise_fn: broadcasting elementwise comparison function\n    extra_batch_dims: number of extra batch dims to add singleton axes for, none\n      by default\n    dtype: mask return dtype\n\n  Returns:\n    A `[batch..., 1, len_q, len_kv]` shaped mask for 1d attention.\n  \"\"\"\n  mask = pairwise_fn(\n    jnp.expand_dims(query_input, axis=-1), jnp.expand_dims(key_input, axis=-2)\n  )\n  mask = jnp.expand_dims(mask, axis=-3)\n  mask = jnp.expand_dims(mask, axis=tuple(range(extra_batch_dims)))\n  return mask.astype(dtype)\n\n\ndef make_causal_mask(\n  x: Array, extra_batch_dims: int = 0, dtype: Dtype = jnp.float32\n) -> Array:\n  \"\"\"Make a causal mask for self-attention.\n\n  In case of 1d inputs (i.e., `[batch..., len]`, the self-attention weights\n  will be `[batch..., heads, len, len]` and this function will produce a\n  causal mask of shape `[batch..., 1, len, len]`.\n\n  Args:\n    x: input array of shape `[batch..., len]`\n    extra_batch_dims: number of batch dims to add singleton axes for, none by\n      default\n    dtype: mask return dtype\n\n  Returns:\n    A `[batch..., 1, len, len]` shaped causal mask for 1d attention.\n  \"\"\"\n  idxs = jnp.broadcast_to(jnp.arange(x.shape[-1], dtype=jnp.int32), x.shape)\n  return make_attention_mask(\n    idxs,\n    idxs,\n    jnp.greater_equal,\n    extra_batch_dims=extra_batch_dims,\n    dtype=dtype,\n  )\n\n\ndef combine_masks(\n  *masks: Array | None, dtype: Dtype = jnp.float32\n) -> Array | None:\n  \"\"\"Combine attention masks.\n\n  Args:\n    *masks: set of attention mask arguments to combine, some can be None.\n    dtype: dtype for the returned mask.\n\n  Returns:\n    Combined mask, reduced by logical and, returns None if no masks given.\n  \"\"\"\n  masks_list = [m for m in masks if m is not None]\n  if not masks_list:\n    return None\n  assert all(\n    map(lambda x: x.ndim == masks_list[0].ndim, masks_list)\n  ), f'masks must have same rank: {tuple(map(lambda x: x.ndim, masks_list))}'\n  mask, *other_masks = masks_list\n  for other_mask in other_masks:\n    mask = jnp.logical_and(mask, other_mask)\n  return mask.astype(dtype)\n"
  },
  {
    "path": "flax/nnx/nn/dtypes.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing as tp\nfrom flax.typing import Dtype\nfrom jax import numpy as jnp\n\nT = tp.TypeVar('T', bound=tuple)\n\n\ndef canonicalize_dtype(\n  *args, dtype: Dtype | None = None, inexact: bool = True\n) -> Dtype:\n  \"\"\"Canonicalize an optional dtype to the definitive dtype.\n\n  If the ``dtype`` is None this function will infer the dtype. If it is not\n  None it will be returned unmodified or an exceptions is raised if the dtype\n  is invalid.\n  from the input arguments using ``jnp.result_type``.\n\n  Args:\n    *args: JAX array compatible values. None values\n      are ignored.\n    dtype: Optional dtype override. If specified the arguments are cast to\n      the specified dtype instead and dtype inference is disabled.\n    inexact: When True, the output dtype must be a subdtype\n    of `jnp.inexact`. Inexact dtypes are real or complex floating points. This\n    is useful when you want to apply operations that don't work directly on\n    integers like taking a mean for example.\n  Returns:\n    The dtype that *args should be cast to.\n  \"\"\"\n  if dtype is None:\n    args_filtered = [jnp.asarray(x) for x in args if x is not None]\n    dtype = jnp.result_type(*args_filtered)\n    if inexact and not jnp.issubdtype(dtype, jnp.inexact):\n      dtype = jnp.promote_types(jnp.float32, dtype)\n  if inexact and not jnp.issubdtype(dtype, jnp.inexact):\n    raise ValueError(f'Dtype must be inexact: {dtype}')\n  return dtype\n\n\ndef promote_dtype(args: T, /, *, dtype=None, inexact=True) -> T:\n  \"\"\" \"Promotes input arguments to a specified or inferred dtype.\n\n  All args are cast to the same dtype. See ``canonicalize_dtype`` for how\n  this dtype is determined.\n\n  The behavior of promote_dtype is mostly a convinience wrapper around\n  ``jax.numpy.promote_types``. The differences being that it automatically casts\n  all input to the inferred dtypes, allows inference to be overridden by a\n  forced dtype, and has an optional check to garantuee the resulting dtype is\n  inexact.\n\n  Args:\n    *args: JAX array compatible values. None values\n      are returned as is.\n    dtype: Optional dtype override. If specified the arguments are cast to\n      the specified dtype instead and dtype inference is disabled.\n    inexact: When True, the output dtype must be a subdtype\n    of `jnp.inexact`. Inexact dtypes are real or complex floating points. This\n    is useful when you want to apply operations that don't work directly on\n    integers like taking a mean for example.\n  Returns:\n    The arguments cast to arrays of the same dtype.\n  \"\"\"\n  dtype = canonicalize_dtype(*args, dtype=dtype, inexact=inexact)\n  arrays = tuple(jnp.asarray(x, dtype) if x is not None else None for x in args)\n  return arrays  # type: ignore[return-value]\n"
  },
  {
    "path": "flax/nnx/nn/initializers.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing as tp\n\nfrom jax.nn.initializers import constant as constant\nfrom jax.nn.initializers import delta_orthogonal as delta_orthogonal\nfrom jax.nn.initializers import glorot_normal as glorot_normal\nfrom jax.nn.initializers import glorot_uniform as glorot_uniform\nfrom jax.nn.initializers import he_normal as he_normal\nfrom jax.nn.initializers import he_uniform as he_uniform\nfrom jax.nn.initializers import kaiming_normal as kaiming_normal\nfrom jax.nn.initializers import kaiming_uniform as kaiming_uniform\nfrom jax.nn.initializers import lecun_normal as lecun_normal\nfrom jax.nn.initializers import lecun_uniform as lecun_uniform\nfrom jax.nn.initializers import normal as normal\nfrom jax.nn.initializers import ones as ones\nfrom jax.nn.initializers import orthogonal as orthogonal\nfrom jax.nn.initializers import truncated_normal as truncated_normal\nfrom jax.nn.initializers import uniform as uniform\nfrom jax.nn.initializers import variance_scaling as variance_scaling\nfrom jax.nn.initializers import xavier_normal as xavier_normal\nfrom jax.nn.initializers import xavier_uniform as xavier_uniform\nfrom jax.nn.initializers import zeros as zeros\nfrom flax.typing import Initializer\n\nDtypeLikeInexact = tp.Any\n\n\ndef zeros_init() -> Initializer:\n  \"\"\"Builds an initializer that returns a constant array full of zeros.\n\n  >>> import jax, jax.numpy as jnp\n  >>> from flax.nnx import initializers\n  >>> zeros_initializer = initializers.zeros_init()\n  >>> zeros_initializer(jax.random.key(42), (2, 3), jnp.float32)\n  Array([[0., 0., 0.],\n         [0., 0., 0.]], dtype=float32)\n  \"\"\"\n  return zeros\n\n\ndef ones_init() -> Initializer:\n  \"\"\"Builds an initializer that returns a constant array full of ones.\n\n  >>> import jax, jax.numpy as jnp\n  >>> from flax.nnx import initializers\n  >>> ones_initializer = initializers.ones_init()\n  >>> ones_initializer(jax.random.key(42), (3, 2), jnp.float32)\n  Array([[1., 1.],\n         [1., 1.],\n         [1., 1.]], dtype=float32)\n  \"\"\"\n  return ones\n"
  },
  {
    "path": "flax/nnx/nn/linear.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom __future__ import annotations\n\nimport typing as tp\nfrom types import MappingProxyType\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom jax import lax\nimport opt_einsum\n\nfrom flax.core.frozen_dict import FrozenDict\nfrom flax import nnx\nfrom flax.nnx import rnglib, variablelib\nfrom flax.nnx.module import Module, first_from\nfrom flax.nnx.nn import dtypes, initializers\nfrom flax.typing import (\n  Dtype,\n  Shape,\n  Initializer,\n  PrecisionLike,\n  DotGeneralT,\n  ConvGeneralDilatedT,\n  PaddingLike,\n  LaxPadding,\n  PromoteDtypeFn,\n  EinsumT,\n)\n\nArray = jax.Array\nAxis = int\nSize = int\n\n\ndefault_kernel_init = initializers.lecun_normal()\ndefault_bias_init = initializers.zeros_init()\n\n\ndef canonicalize_padding(padding: PaddingLike, rank: int) -> LaxPadding:\n  \"\"\" \"Canonicalizes conv padding to a jax.lax supported format.\"\"\"\n  if isinstance(padding, str):\n    return padding\n  if isinstance(padding, int):\n    return [(padding, padding)] * rank\n  if isinstance(padding, tp.Sequence) and len(padding) == rank:\n    new_pad = []\n    for p in padding:\n      if isinstance(p, int):\n        new_pad.append((p, p))\n      elif isinstance(p, tuple) and len(p) == 2:\n        new_pad.append(p)\n      else:\n        break\n    if len(new_pad) == rank:\n      return new_pad\n  raise ValueError(\n    f'Invalid padding format: {padding}, should be str, int,'\n    f' or a sequence of len {rank} where each element is an'\n    ' int or pair of ints.'\n  )\n\n\ndef _conv_dimension_numbers(input_shape):\n  \"\"\"Computes the dimension numbers based on the input shape.\"\"\"\n  ndim = len(input_shape)\n  lhs_spec = (0, ndim - 1) + tuple(range(1, ndim - 1))\n  rhs_spec = (ndim - 1, ndim - 2) + tuple(range(0, ndim - 2))\n  out_spec = lhs_spec\n  return lax.ConvDimensionNumbers(lhs_spec, rhs_spec, out_spec)\n\n\ndef _normalize_axes(axes: tuple[int, ...], ndim: int) -> tuple[int, ...]:\n  # A tuple by convention. len(axes_tuple) then also gives the rank efficiently.\n  return tuple(sorted(ax if ax >= 0 else ndim + ax for ax in axes))\n\n\ndef _canonicalize_tuple(x: tp.Sequence[int] | int) -> tuple[int, ...]:\n  if isinstance(x, tp.Iterable):\n    return tuple(x)\n  else:\n    return (x,)\n\n\nclass LinearGeneral(Module):\n  \"\"\"A linear transformation with flexible axes.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n    ...\n    >>> # equivalent to `nnx.Linear(2, 4)`\n    >>> layer = nnx.LinearGeneral(2, 4, rngs=nnx.Rngs(0))\n    >>> layer.kernel.shape\n    (2, 4)\n    >>> # output features (4, 5)\n    >>> layer = nnx.LinearGeneral(2, (4, 5), rngs=nnx.Rngs(0))\n    >>> layer.kernel.shape\n    (2, 4, 5)\n    >>> layer.bias.shape\n    (4, 5)\n    >>> # apply transformation on the the second and last axes\n    >>> layer = nnx.LinearGeneral((2, 3), (4, 5), axis=(1, -1), rngs=nnx.Rngs(0))\n    >>> layer.kernel.shape\n    (2, 3, 4, 5)\n    >>> layer.bias.shape\n    (4, 5)\n    >>> y = layer(jnp.ones((16, 2, 3)))\n    >>> y.shape\n    (16, 4, 5)\n\n  Args:\n    in_features: int or tuple with number of input features.\n    out_features: int or tuple with number of output features.\n    axis: int or tuple with axes to apply the transformation on. For instance,\n      (-2, -1) will apply the transformation to the last two axes.\n    batch_axis: mapping of batch axis indices to axis size.\n    use_bias: whether to add a bias to the output (default: True).\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    kernel_init: initializer function for the weight matrix.\n    bias_init: initializer function for the bias.\n    dot_general: dot product function (default: None). If neither this nor\n        ``dot_general_cls`` are provided, ``jax.lax.dot_general`` is used.\n    dot_general_cls: dot product function class to instantiate a dot product function\n    as ``dot_general = dot_general_cls()`` (default: None).\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(inputs, kernel, bias)``\n      and a ``dtype`` keyword argument, and return a tuple of arrays with the\n      promoted dtype.\n    preferred_element_type: Optional parameter controls the data type output by\n      the dot product. This argument is passed to ``dot_general`` function.\n      See ``jax.lax.dot`` for details.\n    rngs: rng key.\n    kernel_metadata: Optional metadata dictionary to set when initializing\n      the weight matrix.\n    bias_metadata: Optional metadata dictionary to set when initializing\n      the bias.\n  \"\"\"\n\n  def __init__(\n    self,\n    in_features: Size | tp.Sequence[Size],\n    out_features: Size | tp.Sequence[Size],\n    *,\n    axis: Axis | tp.Sequence[Axis] = -1,\n    batch_axis: tp.Mapping[Axis, Size] = FrozenDict({}),\n    use_bias: bool = True,\n    dtype: Dtype | None = None,\n    param_dtype: Dtype = jnp.float32,\n    kernel_init: Initializer = default_kernel_init,\n    bias_init: Initializer = default_bias_init,\n    precision: PrecisionLike = None,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    dot_general: DotGeneralT | None = None,\n    dot_general_cls: tp.Any = None,\n    preferred_element_type: Dtype | None = None,\n    rngs: rnglib.Rngs,\n    kernel_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n    bias_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    self.in_features = _canonicalize_tuple(in_features)\n    self.out_features = _canonicalize_tuple(out_features)\n    self.axis = _canonicalize_tuple(axis)\n    self.batch_axis = FrozenDict[Axis, Size](batch_axis)\n    self.use_bias = use_bias\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.precision = precision\n    self.dot_general = dot_general\n    self.dot_general_cls = dot_general_cls\n    self.promote_dtype = promote_dtype\n    self.preferred_element_type = preferred_element_type\n\n    if len(self.in_features) != len(self.axis):\n      raise ValueError(\n        'in_features and axis must have the same length. '\n        f'Got {self.in_features} and {self.axis}.'\n      )\n\n    if batch_axis:\n      batch_dims = tuple(batch_axis.keys())\n      max_dim = np.max(batch_dims)\n      if set(batch_dims) != set(range(max_dim + 1)):\n        raise ValueError(\n          'batch_dims %s must be consecutive leading '\n          'dimensions starting from 0.' % str(batch_dims)\n        )\n\n    n_batch_axis = len(self.batch_axis)\n    n_in_features = len(self.in_features)\n    n_out_features = len(self.out_features)\n\n    def kernel_init_wrap(rng, shape, dtype):\n      flat_shape = (\n        np.prod(shape[:n_batch_axis])\n        * np.prod(shape[n_batch_axis : n_in_features + n_batch_axis]),\n        np.prod(shape[-n_out_features:]),\n      )\n      flat_shape = jax.tree.map(int, flat_shape)\n      kernel = kernel_init(rng, flat_shape, dtype)\n      if isinstance(kernel, variablelib.VariableMetadata):\n        kernel.raw_value = jnp.reshape(kernel.raw_value, shape)\n      else:\n        kernel = jnp.reshape(kernel, shape)\n\n      return kernel\n\n    batch_shape = tuple(self.batch_axis.values())\n    kernel_shape = (\n      *batch_shape,\n      *self.in_features,\n      *self.out_features,\n    )\n    self.kernel = nnx.Param(\n      kernel_init_wrap(rngs.params(), kernel_shape, self.param_dtype), **kernel_metadata\n    )\n\n    self.bias: nnx.Param[jax.Array] | None\n    if self.use_bias:\n      def bias_init_wrap(rng, shape, dtype):\n        flat_shape = (int(np.prod(shape)),)\n        bias = bias_init(rng, flat_shape, dtype)\n        if isinstance(bias, variablelib.VariableMetadata):\n          bias.raw_value = jnp.reshape(bias.raw_value, shape)\n        else:\n          bias = jnp.reshape(bias, shape)\n        return bias\n\n      bias_shape = (*batch_shape, *self.out_features)\n      self.bias = nnx.Param(\n        bias_init_wrap(rngs.params(), bias_shape, self.param_dtype),\n        **bias_metadata,\n      )\n    else:\n      self.bias = nnx.data(None)\n\n  def __call__(self, inputs: Array, out_sharding = None) -> Array:\n    \"\"\"Applies a linear transformation to the inputs along multiple dimensions.\n\n    Args:\n      inputs: The nd-array to be transformed.\n\n    Returns:\n      The transformed input.\n    \"\"\"\n\n    ndim = inputs.ndim\n    n_batch_dims = len(self.batch_axis)\n    axis = _normalize_axes(self.axis, ndim)\n    batch_axis = _normalize_axes(tuple(self.batch_axis.keys()), ndim)\n    n_axis = len(axis)\n\n    # batch and non-contracting dims of input with 1s for batch dims.\n    expanded_batch_shape = tuple(\n      inputs.shape[ax] if ax in batch_axis else 1\n      for ax in range(inputs.ndim)\n      if ax not in axis\n    )\n    kernel = self.kernel[...]\n    bias = self.bias[...] if self.bias is not None else None\n\n    batch_ind = tuple(range(n_batch_dims))\n    contract_ind = tuple(range(n_batch_dims, n_axis + n_batch_dims))\n\n    inputs, kernel, bias = self.promote_dtype(\n      (inputs, kernel, bias), dtype=self.dtype\n    )\n\n    if self.dot_general_cls is not None:\n      dot_general = self.dot_general_cls()\n    elif self.dot_general is not None:\n      dot_general = self.dot_general\n    else:\n      dot_general = lax.dot_general\n    # We use dot_general_kwargs for BC compatibility with\n    # user custom dot_general/dot_general_cls which may not have\n    # preferred_element_type argument to avoid breaking\n    # existing code\n    dot_general_kwargs = {'out_sharding': out_sharding}\n    if self.preferred_element_type is not None:\n      dot_general_kwargs[\"preferred_element_type\"] = self.preferred_element_type\n    out = dot_general(\n      inputs,\n      kernel,\n      ((axis, contract_ind), (batch_axis, batch_ind)),\n      precision=self.precision,\n      **dot_general_kwargs,\n    )\n    # dot_general output has shape [batch_dims/group_dims] + [feature_dims]\n    if bias is not None:\n      # expand bias shape to broadcast bias over batch dims.\n      bias = jnp.reshape(bias, (*expanded_batch_shape, *self.out_features))\n      out += bias\n    return out\n\n\nclass Linear(Module):\n  \"\"\"A linear transformation applied over the last dimension of the input.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n\n    >>> layer = nnx.Linear(in_features=3, out_features=4, rngs=nnx.Rngs(0))\n    >>> jax.tree.map(jnp.shape, nnx.state(layer))\n    State({\n      'bias': Param(\n        value=(4,)\n      ),\n      'kernel': Param(\n        value=(3, 4)\n      )\n    })\n\n  Args:\n    in_features: the number of input features.\n    out_features: the number of output features.\n    use_bias: whether to add a bias to the output (default: True).\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    kernel_init: initializer function for the weight matrix.\n    bias_init: initializer function for the bias.\n    dot_general: dot product function.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(inputs, kernel, bias)``\n      and a ``dtype`` keyword argument, and return a tuple of arrays with the\n      promoted dtype.\n    preferred_element_type: Optional parameter controls the data type output by\n      the dot product. This argument is passed to ``dot_general`` function.\n      See ``jax.lax.dot`` for details.\n    rngs: rng key.\n    kernel_metadata: Optional metadata dictionary to set when initializing\n      the weight matrix.\n    bias_metadata: Optional metadata dictionary to set when initializing\n      the bias.\n  \"\"\"\n\n  def __init__(\n    self,\n    in_features: int,\n    out_features: int,\n    *,\n    use_bias: bool = True,\n    dtype: tp.Optional[Dtype] = None,\n    param_dtype: Dtype = jnp.float32,\n    precision: PrecisionLike = None,\n    kernel_init: Initializer = default_kernel_init,\n    bias_init: Initializer = default_bias_init,\n    dot_general: DotGeneralT = lax.dot_general,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    preferred_element_type: Dtype | None = None,\n    rngs: rnglib.Rngs,\n    kernel_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n    bias_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    kernel_key = rngs.params()\n    self.kernel = nnx.Param(\n      kernel_init(kernel_key, (in_features, out_features), param_dtype),\n      **kernel_metadata,\n    )\n    self.bias: nnx.Param[jax.Array] | None\n    if use_bias:\n      bias_key = rngs.params()\n      self.bias = nnx.Param(\n        bias_init(bias_key, (out_features,), param_dtype),\n        **bias_metadata,\n      )\n    else:\n      self.bias = nnx.data(None)\n\n    self.in_features = in_features\n    self.out_features = out_features\n    self.use_bias = use_bias\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.precision = precision\n    self.dot_general = dot_general\n    self.promote_dtype = promote_dtype\n    self.preferred_element_type = preferred_element_type\n\n  def __call__(self, inputs: Array, out_sharding = None) -> Array:\n    \"\"\"Applies a linear transformation to the inputs along the last dimension.\n\n    Args:\n      inputs: The nd-array to be transformed.\n\n    Returns:\n      The transformed input.\n    \"\"\"\n    kernel = self.kernel[...]\n    bias = self.bias[...] if self.bias is not None else None\n\n    inputs, kernel, bias = self.promote_dtype(\n      (inputs, kernel, bias), dtype=self.dtype\n    )\n    # We use dot_general_kwargs for BC compatibility with\n    # user custom self.dot_general method which may not have\n    # preferred_element_type argument to avoid breaking\n    # existing code\n    dot_general_kwargs = {'out_sharding': out_sharding}\n    if self.preferred_element_type is not None:\n      dot_general_kwargs[\"preferred_element_type\"] = self.preferred_element_type\n    y = self.dot_general(\n      inputs,\n      kernel,\n      (((inputs.ndim - 1,), (0,)), ((), ())),\n      precision=self.precision,\n      **dot_general_kwargs,\n    )\n    assert self.use_bias == (bias is not None)\n    if bias is not None:\n      y += jnp.reshape(bias, (1,) * (y.ndim - 1) + (-1,))\n    return y\n\n\nclass Einsum(Module):\n  \"\"\"An einsum transformation with learnable kernel and bias.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n    ...\n    >>> layer = nnx.Einsum('nta,hab->nthb', (8, 2, 4), (8, 4), rngs=nnx.Rngs(0))\n    >>> layer.kernel.shape\n    (8, 2, 4)\n    >>> layer.bias.shape\n    (8, 4)\n    >>> y = layer(jnp.ones((16, 11, 2)))\n    >>> y.shape\n    (16, 11, 8, 4)\n\n  Args:\n    einsum_str: a string to denote the einsum equation. The equation must\n      have exactly two operands, the lhs being the input passed in, and\n      the rhs being the learnable kernel. Exactly one of ``einsum_str``\n      in the constructor argument and call argument must be not None,\n      while the other must be None.\n    kernel_shape: the shape of the kernel.\n    bias_shape: the shape of the bias. If this is None, a bias won't be used.\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    kernel_init: initializer function for the weight matrix.\n    bias_init: initializer function for the bias.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(inputs, kernel, bias)``\n      and a ``dtype`` keyword argument, and return a tuple of arrays with the\n      promoted dtype.\n    einsum_op: An injectable alternative of `jnp.einsum` to do the computation.\n      Should support same signature as `jnp.einsum`.\n    preferred_element_type: Optional parameter controls the data type output by\n      the dot product. This argument is passed to ``dot_general`` function.\n      See ``jax.lax.dot`` for details.\n    rngs: rng key.\n    kernel_metadata: Optional metadata dictionary to set when initializing\n      the weight matrix.\n    bias_metadata: Optional metadata dictionary to set when initializing\n      the bias.\n  \"\"\"\n\n  def __init__(\n    self,\n    einsum_str: str,\n    kernel_shape: Shape,\n    bias_shape: tp.Optional[Shape] = None,\n    *,\n    dtype: tp.Optional[Dtype] = None,\n    param_dtype: Dtype = jnp.float32,\n    precision: PrecisionLike = None,\n    kernel_init: Initializer = default_kernel_init,\n    bias_init: Initializer = default_bias_init,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    einsum_op: EinsumT = jnp.einsum,\n    preferred_element_type: Dtype | None = None,\n    rngs: rnglib.Rngs,\n    kernel_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n    bias_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    einsum_str = einsum_str.replace(' ', '')\n    self._einsum_str_check(einsum_str)\n\n    kernel_key = rngs.params()\n    self.kernel = nnx.Param(\n      kernel_init(kernel_key, kernel_shape, param_dtype), **kernel_metadata\n    )\n\n    self.bias: nnx.Param | None\n    if bias_shape is not None:\n      bias_key = rngs.params()\n      self.bias = nnx.Param(\n        bias_init(bias_key, bias_shape, param_dtype), **bias_metadata\n      )\n    else:\n      self.bias = nnx.data(None)\n\n    self.einsum_str = einsum_str\n    self.kernel_shape = kernel_shape\n    self.bias_shape = bias_shape\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.precision = precision\n    self.promote_dtype = promote_dtype\n    self.einsum_op = einsum_op\n    self.preferred_element_type = preferred_element_type\n\n  def __call__(\n    self, inputs: Array, einsum_str: tp.Optional[str] = None, out_sharding = None\n  ) -> Array:\n    \"\"\"Applies a linear transformation to the inputs along the last dimension.\n\n    Args:\n      inputs: The nd-array to be transformed.\n      einsum_str: a string to denote the einsum equation. The equation must\n        have exactly two operands, the lhs being the input passed in, and\n        the rhs being the learnable kernel. Exactly one of ``einsum_str``\n        in the constructor argument and call argument must be not None,\n        while the other must be None.\n\n    Returns:\n      The transformed input.\n    \"\"\"\n    einsum_str = first_from(\n      einsum_str,\n      self.einsum_str,\n      error_msg=\"\"\"No `einsum_str` argument was provided to Einsum\n        as either a __call__ argument, or class attribute.\"\"\",\n    )\n    einsum_str = einsum_str.replace(' ', '')\n    self._einsum_str_check(einsum_str)\n\n    inputs, kernel, bias = self.promote_dtype(\n      (\n        inputs,\n        self.kernel[...],\n        self.bias[...] if self.bias is not None else self.bias,\n      ),\n      dtype=self.dtype,\n    )\n    # We use einsum_op_kwargs for BC compatibility with\n    # user custom self.einsum_op method which may not have\n    # preferred_element_type argument to avoid breaking\n    # existing code\n    einsum_op_kwargs = {'out_sharding': out_sharding}\n    if self.preferred_element_type is not None:\n      einsum_op_kwargs[\"preferred_element_type\"] = self.preferred_element_type\n\n    y = self.einsum_op(\n      einsum_str, inputs, kernel, precision=self.precision, **einsum_op_kwargs\n    )\n\n    if bias is not None:\n      broadcasted_bias_shape = self._infer_broadcasted_bias_shape(\n        einsum_str, inputs, kernel\n      )\n      y += jnp.reshape(bias, broadcasted_bias_shape)\n    return y\n\n  def _infer_broadcasted_bias_shape(\n    self, einsum_str: str, lhs: Array, rhs: Array\n  ):\n    \"\"\"Infer the broadcasted bias shape given the ``einsum_str``, ``lhs``\n    and ``rhs`` arrays. This is needed reshaping the bias and it to the\n    output during forward inference.\n\n    This function first replaces all ellipses with actual letter characters,\n    then computes the broadcasted bias shape by checking to see which axes in\n    the rhs array remain in the resulting array after einsumming. These axes\n    are the embedding/feature dimensions, and all other axes in rhs are\n    reduction axes.\n    \"\"\"\n    # More details on the parsing function: https://github.com/dgasmith/opt_einsum/blob/c826bb7df16f470a69f7bf90598fc27586209d11/opt_einsum/parser.py#L246\n    # returns the einsum string representation of the operands and result, with\n    # ellipsis replaced by actual letter characters\n    operands_str, result_str, _ = opt_einsum.parser.parse_einsum_input(\n      (einsum_str, lhs, rhs)\n    )\n\n    # rhs_dict is a dict{character:index} mapping that maps every character in\n    # the rhs einsum string representation to its corresponding index position in the string\n    rhs_dict = {c: i for i, c in enumerate(operands_str.split(',')[1])}\n    assert len(rhs_dict) == len(self.kernel_shape)\n\n    broadcasted_bias_shape = [1] * len(result_str)\n    for i, c in enumerate(result_str):\n      if c in rhs_dict:\n        broadcasted_bias_shape[i] = self.kernel_shape[rhs_dict[c]]\n\n    return broadcasted_bias_shape\n\n  def _einsum_str_check(self, einsum_str):\n    if '->' not in einsum_str:\n      raise ValueError(\n        '`einsum_str` equation must be explicit and include \"->\".'\n      )\n    if einsum_str.count(',') != 1:\n      raise ValueError(\n        '`einsum_str` equation must have exactly two operands and '\n        'therefore, exactly one comma character, instead of '\n        f'{einsum_str.count(\",\")}'\n      )\n\n\nclass Conv(Module):\n  \"\"\"Convolution Module wrapping ``lax.conv_general_dilated``.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n\n    >>> rngs = nnx.Rngs(0)\n    >>> x = jnp.ones((1, 8, 3))\n\n    >>> # valid padding\n    >>> layer = nnx.Conv(in_features=3, out_features=4, kernel_size=(3,),\n    ...                  padding='VALID', rngs=rngs)\n    >>> layer.kernel.shape\n    (3, 3, 4)\n    >>> layer.bias.shape\n    (4,)\n    >>> out = layer(x)\n    >>> out.shape\n    (1, 6, 4)\n\n    >>> # circular padding with stride 2\n    >>> layer = nnx.Conv(in_features=3, out_features=4, kernel_size=(3, 3),\n    ...                  strides=2, padding='CIRCULAR', rngs=rngs)\n    >>> layer.kernel.shape\n    (3, 3, 3, 4)\n    >>> layer.bias.shape\n    (4,)\n    >>> out = layer(x)\n    >>> out.shape\n    (1, 4, 4)\n\n    >>> # apply lower triangle mask\n    >>> mask = jnp.tril(jnp.ones((3, 3, 4)))\n    >>> layer = nnx.Conv(in_features=3, out_features=4, kernel_size=(3,),\n    ...                  mask=mask, padding='VALID', rngs=rngs)\n    >>> out = layer(x)\n\n  Args:\n    in_features: int or tuple with number of input features.\n    out_features: int or tuple with number of output features.\n    kernel_size: shape of the convolutional kernel. For 1D convolution,\n      the kernel size can be passed as an integer, which will be interpreted\n      as a tuple of the single integer. For all other cases, it must be a\n      sequence of integers.\n    strides: an integer or a sequence of ``n`` integers, representing the\n      inter-window strides (default: 1).\n    padding: either the string ``'SAME'``, the string ``'VALID'``, the string\n      ``'CIRCULAR'`` (periodic boundary conditions), the string `'REFLECT'`\n      (reflection across the padding boundary), or a sequence of ``n``\n      ``(low, high)`` integer pairs that give the padding to apply before and after each\n      spatial dimension. A single int is interpreted as applying the same padding\n      in all dims and passing a single int in a sequence causes the same padding\n      to be used on both sides. ``'CAUSAL'`` padding for a 1D convolution will\n      left-pad the convolution axis, resulting in same-sized output.\n    input_dilation: an integer or a sequence of ``n`` integers, giving the\n      dilation factor to apply in each spatial dimension of ``inputs``\n      (default: 1). Convolution with input dilation ``d`` is equivalent to\n      transposed convolution with stride ``d``.\n    kernel_dilation: an integer or a sequence of ``n`` integers, giving the\n      dilation factor to apply in each spatial dimension of the convolution\n      kernel (default: 1). Convolution with kernel dilation\n      is also known as 'atrous convolution'.\n    feature_group_count: integer, default 1. If specified divides the input\n      features into groups.\n    use_bias: whether to add a bias to the output (default: True).\n    mask: Optional mask for the weights during masked convolution. The mask must\n          be the same shape as the convolution weight matrix.\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    conv_general_dilated: the convolution function to use (default:\n      ``jax.lax.conv_general_dilated``).\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    kernel_init: initializer for the convolutional kernel.\n    bias_init: initializer for the bias.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(inputs, kernel, bias)``\n      and a ``dtype`` keyword argument, and return a tuple of arrays with the\n      promoted dtype.\n    preferred_element_type: Optional parameter controls the data type output by\n      the convolution. This argument is passed to ``conv_general_dilated``\n      function. See ``jax.lax.conv_general_dilated`` for details.\n    rngs: rng key.\n    kernel_metadata: Optional metadata dictionary to set when initializing\n      the weight matrix.\n    bias_metadata: Optional metadata dictionary to set when initializing\n      the bias.\n  \"\"\"\n\n  def __init__(\n    self,\n    in_features: int,\n    out_features: int,\n    kernel_size: int | tp.Sequence[int],\n    strides: tp.Union[None, int, tp.Sequence[int]] = 1,\n    *,\n    padding: PaddingLike = 'SAME',\n    input_dilation: tp.Union[None, int, tp.Sequence[int]] = 1,\n    kernel_dilation: tp.Union[None, int, tp.Sequence[int]] = 1,\n    feature_group_count: int = 1,\n    use_bias: bool = True,\n    mask: tp.Optional[Array] = None,\n    dtype: tp.Optional[Dtype] = None,\n    param_dtype: Dtype = jnp.float32,\n    precision: PrecisionLike = None,\n    kernel_init: Initializer = default_kernel_init,\n    bias_init: Initializer = default_bias_init,\n    conv_general_dilated: ConvGeneralDilatedT = lax.conv_general_dilated,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    preferred_element_type: Dtype | None = None,\n    rngs: rnglib.Rngs,\n    kernel_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n    bias_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    if isinstance(kernel_size, int):\n      kernel_size = (kernel_size,)\n    else:\n      kernel_size = tuple(kernel_size)\n\n    kernel_shape = kernel_size + (\n      in_features // feature_group_count,\n      out_features,\n    )\n    kernel_key = rngs.params()\n    self.kernel_shape = kernel_shape\n    self.kernel = nnx.Param(\n      kernel_init(kernel_key, kernel_shape, param_dtype), **kernel_metadata\n    )\n\n    self.bias: nnx.Param[jax.Array] | None\n    if use_bias:\n      bias_shape = (out_features,)\n      bias_key = rngs.params()\n      self.bias = nnx.Param(\n        bias_init(bias_key, bias_shape, param_dtype), **bias_metadata\n      )\n    else:\n      self.bias = nnx.data(None)\n\n    self.in_features = in_features\n    self.out_features = out_features\n    self.kernel_size = kernel_size\n    self.strides = strides\n    self.padding = padding\n    self.input_dilation = input_dilation\n    self.kernel_dilation = kernel_dilation\n    self.feature_group_count = feature_group_count\n    self.use_bias = use_bias\n    self.mask = mask\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.precision = precision\n    self.conv_general_dilated = conv_general_dilated\n    self.promote_dtype = promote_dtype\n    self.preferred_element_type = preferred_element_type\n\n  def __call__(self, inputs: Array, out_sharding=None) -> Array:\n    \"\"\"Applies a (potentially unshared) convolution to the inputs.\n\n    Args:\n      inputs: input data with dimensions ``(*batch_dims, spatial_dims..., features)``.\n        This is the channels-last convention, i.e. NHWC for a 2d convolution and\n        NDHWC for a 3D convolution. Note: this is different from the input convention\n        used by ``lax.conv_general_dilated``, which puts the spatial dimensions last.\n        Note: If the input has more than 1 batch dimension, all batch dimensions\n        are flattened into a single dimension for the convolution and restored\n        before returning.  In some cases directly vmap'ing the layer may yield\n        better performance than this default flattening approach.  If the input\n        lacks a batch dimension it will be added for the convolution and removed\n        n return, an allowance made to enable writing single-example code.\n      out_sharding: Optional sharding specification (e.g.,\n        ``jax.sharding.PartitionSpec``) for the output array. When using JAX's\n        explicit sharding mode with a mesh context with ``AxisType.Explicit``.\n        If ``None`` (default), the compiler automatically determines output\n        sharding.\n\n    Returns:\n      The convolved data.\n    \"\"\"\n\n    assert isinstance(self.kernel_size, tuple)\n    kernel_size = self.kernel_size\n\n    def maybe_broadcast(\n      x: tp.Optional[tp.Union[int, tp.Sequence[int]]],\n    ) -> tuple[int, ...]:\n      if x is None:\n        # backward compatibility with using None as sentinel for\n        # broadcast 1\n        x = 1\n      if isinstance(x, int):\n        return (x,) * len(kernel_size)\n      return tuple(x)\n\n    # Combine all input batch dimensions into a single leading batch axis.\n    num_batch_dimensions = inputs.ndim - (len(kernel_size) + 1)\n    if num_batch_dimensions != 1:\n      input_batch_shape = inputs.shape[:num_batch_dimensions]\n      flat_input_shape = (-1,) + inputs.shape[\n        num_batch_dimensions:\n      ]\n      inputs = jnp.reshape(inputs, flat_input_shape)\n\n    # self.strides or (1,) * (inputs.ndim - 2)\n    strides = maybe_broadcast(self.strides)\n    input_dilation = maybe_broadcast(self.input_dilation)\n    kernel_dilation = maybe_broadcast(self.kernel_dilation)\n\n    padding_lax = canonicalize_padding(self.padding, len(kernel_size))\n    if padding_lax in ('CIRCULAR', 'REFLECT'):\n      assert isinstance(padding_lax, str)\n      kernel_size_dilated = [\n        (k - 1) * d + 1 for k, d in zip(kernel_size, kernel_dilation)\n      ]\n      zero_pad: list[tuple[int, int]] = [(0, 0)]\n      pads = (\n        zero_pad\n        + [((k - 1) // 2, k // 2) for k in kernel_size_dilated]\n        + [(0, 0)]\n      )\n      padding_mode = {'CIRCULAR': 'wrap', 'REFLECT': 'reflect'}[padding_lax]\n      inputs = jnp.pad(inputs, pads, mode=padding_mode)\n      padding_lax = 'VALID'\n    elif padding_lax == 'CAUSAL':\n      if len(kernel_size) != 1:\n        raise ValueError(\n          'Causal padding is only implemented for 1D convolutions.'\n        )\n      left_pad = kernel_dilation[0] * (kernel_size[0] - 1)\n      pads = [(0, 0), (left_pad, 0), (0, 0)]\n      inputs = jnp.pad(inputs, pads)\n      padding_lax = 'VALID'\n\n    dimension_numbers = _conv_dimension_numbers(inputs.shape)\n\n    # One shared convolutional kernel for all pixels in the output.\n    assert self.in_features % self.feature_group_count == 0\n\n    if self.mask is not None and self.mask.shape != self.kernel_shape:\n      raise ValueError(\n        'Mask needs to have the same shape as weights. '\n        f'Shapes are: {self.mask.shape}, {self.kernel_shape}'\n      )\n\n    kernel = self.kernel[...]\n\n    if self.mask is not None:\n      kernel *= self.mask\n\n    bias = self.bias[...] if self.bias is not None else None\n\n    inputs, kernel, bias = self.promote_dtype(\n      (inputs, kernel, bias), dtype=self.dtype\n    )\n\n    # We use conv_kwargs for BC compatibility with\n    # user custom self.conv_general_dilated method which may not have\n    # preferred_element_type argument to avoid breaking\n    # existing code\n    conv_kwargs = {'out_sharding': out_sharding}\n    if self.preferred_element_type is not None:\n      conv_kwargs[\"preferred_element_type\"] = self.preferred_element_type\n\n    y = self.conv_general_dilated(\n      inputs,\n      kernel,\n      strides,\n      padding_lax,\n      lhs_dilation=input_dilation,\n      rhs_dilation=kernel_dilation,\n      dimension_numbers=dimension_numbers,\n      feature_group_count=self.feature_group_count,\n      precision=self.precision,\n      **conv_kwargs,\n    )\n\n    if self.use_bias:\n      bias = bias.reshape((1,) * (y.ndim - bias.ndim) + bias.shape)  # type: ignore\n      y += bias\n\n    if num_batch_dimensions != 1:\n      output_shape = input_batch_shape + y.shape[1:]\n      y = jnp.reshape(y, output_shape)\n    return y\n\n\nclass ConvTranspose(Module):\n  \"\"\"Convolution Module wrapping ``lax.conv_transpose``.\n\n  **Note:** The `padding` argument behaves differently from PyTorch; see the argument description below.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n\n    >>> rngs = nnx.Rngs(0)\n    >>> x = jnp.ones((1, 8, 3))\n\n    >>> # valid padding\n    >>> layer = nnx.ConvTranspose(in_features=3, out_features=4, kernel_size=(3,),\n    ...                           padding='VALID', rngs=rngs)\n    >>> layer.kernel.shape\n    (3, 3, 4)\n    >>> layer.bias.shape\n    (4,)\n    >>> out = layer(x)\n    >>> out.shape\n    (1, 10, 4)\n\n    >>> # circular padding with stride 2\n    >>> layer = nnx.ConvTranspose(in_features=3, out_features=4, kernel_size=(6, 6),\n    ...                           strides=(2, 2), padding='CIRCULAR',\n    ...                           transpose_kernel=True, rngs=rngs)\n    >>> layer.kernel.shape\n    (6, 6, 4, 3)\n    >>> layer.bias.shape\n    (4,)\n    >>> out = layer(jnp.ones((1, 15, 15, 3)))\n    >>> out.shape\n    (1, 30, 30, 4)\n\n    >>> # apply lower triangle mask\n    >>> mask = jnp.tril(jnp.ones((3, 3, 4)))\n    >>> layer = nnx.Conv(in_features=3, out_features=4, kernel_size=(3,),\n    ...                  mask=mask, padding='VALID', rngs=rngs)\n    >>> out = layer(x)\n\n  Args:\n    in_features: int or tuple with number of input features.\n    out_features: int or tuple with number of output features.\n    kernel_size: shape of the convolutional kernel. For 1D convolution,\n      the kernel size can be passed as an integer, which will be interpreted\n      as a tuple of the single integer. For all other cases, it must be a\n      sequence of integers.\n    strides: an integer or a sequence of ``n`` integers, representing the\n      inter-window strides (default: 1).\n    padding: either a string indicating a specialized padding mode,\n      or a sequence of ``n`` ``(low, high)`` integer pairs that give the padding to apply before and after each\n      spatial dimension. A single int is interpreted as applying the same padding\n      in all dims and a single int in a sequence causes the same padding\n      to be used on both sides.\n\n        **Note that this behavior is different from\n        PyTorch**. In PyTorch, the padding argument effectively adds ``dilation * (kernel_size - 1) - padding``\n        amount of zero padding to the input instead. This is set so that when ``torch.Conv2d`` and ``torch.ConvTranspose2d``\n        are initialized with the same parameters, they are inverses of each other in regard to the input and output shapes.\n        ``nnx.Conv`` and ``nnx.ConvTranspose`` do *not* have this behavior; if you want a ``nnx.ConvTranspose`` layer\n        to invert the shape change produced by a ``nnx.Conv`` layer with a given padding and dilation, you should explicitly pass\n        ``dilation * (kernel_size - 1) - padding`` as the `padding` argument to the ``nnx.ConvTranspose`` layer.\n\n      Strings for specifying padding modes can be one of the following:\n\n      - ``VALID`` adds ``dilation * (kernel_size - 1)`` padding to all dimensions. This is set so that a\n        ``nnx.Conv`` layer with ``VALID`` padding would produce the inverse shape transformation.\n\n      - ``SAME`` pads the input so that the output shape is the same as the input shape.\n\n      - ``CIRCULAR`` pads the input with periodic boundary conditions.\n\n      - ``CAUSAL`` padding for a 1D convolution will left-pad the convolution axis, resulting in same-sized output.\n\n    kernel_dilation: an integer or a sequence of ``n`` integers, giving the\n      dilation factor to apply in each spatial dimension of the convolution\n      kernel (default: 1). Convolution with kernel dilation\n      is also known as 'atrous convolution'.\n    use_bias: whether to add a bias to the output (default: True).\n    mask: Optional mask for the weights during masked convolution. The mask must\n          be the same shape as the convolution weight matrix.\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    precision: numerical precision of the computation see ``jax.lax.Precision``\n      for details.\n    kernel_init: initializer for the convolutional kernel.\n    bias_init: initializer for the bias.\n    transpose_kernel: if ``True`` flips spatial axes and swaps the input/output\n      channel axes of the kernel.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(inputs, kernel, bias)``\n      and a ``dtype`` keyword argument, and return a tuple of arrays with the\n      promoted dtype.\n    preferred_element_type: Optional parameter controls the data type output by\n      the transposed convolution. This argument is passed to\n      ``jax.lax.conv_transpose`` function. See ``jax.lax.conv_transpose``\n      for details.\n    rngs: rng key.\n    kernel_metadata: Optional metadata dictionary to set when initializing\n      the weight matrix.\n    bias_metadata: Optional metadata dictionary to set when initializing\n      the bias.\n  \"\"\"\n\n  def __init__(\n    self,\n    in_features: int,\n    out_features: int,\n    kernel_size: int | tp.Sequence[int],\n    strides: int | tp.Sequence[int] | None = None,\n    *,\n    padding: PaddingLike = 'SAME',\n    kernel_dilation: int | tp.Sequence[int] | None = None,\n    use_bias: bool = True,\n    mask: Array | None = None,\n    dtype: Dtype | None = None,\n    param_dtype: Dtype = jnp.float32,\n    precision: PrecisionLike | None = None,\n    kernel_init: Initializer = default_kernel_init,\n    bias_init: Initializer = default_bias_init,\n    transpose_kernel: bool = False,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    preferred_element_type: Dtype | None = None,\n    rngs: rnglib.Rngs,\n    kernel_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n    bias_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    if isinstance(kernel_size, int):\n      kernel_size = (kernel_size,)\n    else:\n      kernel_size = tuple(kernel_size)\n\n    self.kernel_size = kernel_size\n    self.in_features = in_features\n    self.out_features = out_features\n    self.strides = strides\n    self.padding = padding\n    self.kernel_dilation = kernel_dilation\n    self.use_bias = use_bias\n    self.mask = mask\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.precision = precision\n    self.transpose_kernel = transpose_kernel\n    self.promote_dtype = promote_dtype\n    self.preferred_element_type = preferred_element_type\n\n    if self.transpose_kernel:\n      kernel_shape = kernel_size + (self.out_features, in_features)\n    else:\n      kernel_shape = kernel_size + (in_features, self.out_features)\n\n    self.kernel_shape = kernel_shape\n    self.kernel = nnx.Param(\n      kernel_init(rngs.params(), kernel_shape, self.param_dtype), **kernel_metadata\n    )\n\n    self.bias: nnx.Param | None\n    if self.use_bias:\n      self.bias = nnx.Param(\n        bias_init(rngs.params(), (self.out_features,), self.param_dtype), **bias_metadata\n      )\n    else:\n      self.bias = nnx.data(None)\n\n  def __call__(self, inputs: Array) -> Array:\n    \"\"\"Applies a transposed convolution to the inputs.\n\n    Behaviour mirrors of ``jax.lax.conv_transpose``.\n\n    Args:\n      inputs: input data with dimensions ``(*batch_dims, spatial_dims..., features).\n        This is the channels-last convention, i.e. NHWC for a 2d convolution and NDHWC\n        for a 3D convolution. Note: this is different from the input convention used by\n        ``lax.conv_general_dilated``, which puts the spatial dimensions last.\n        Note: If the input has more than 1 batch dimension, all batch dimensions\n        are flattened into a single dimension for the convolution and restored\n        before returning.  In some cases directly vmap'ing the layer may yield\n        better performance than this default flattening approach.  If the input\n        lacks a batch dimension it will be added for the convolution and removed\n        n return, an allowance made to enable writing single-example code.\n\n    Returns:\n      The convolved data.\n    \"\"\"\n    kernel_size = self.kernel_size\n\n    def maybe_broadcast(\n      x: tp.Optional[tp.Union[int, tp.Sequence[int]]],\n    ) -> tuple[int, ...]:\n      if x is None:\n        # backward compatibility with using None as sentinel for\n        # broadcast 1\n        x = 1\n      if isinstance(x, int):\n        return (x,) * len(kernel_size)\n      return tuple(x)\n\n    # Combine all input batch dimensions into a single leading batch axis.\n    num_batch_dimensions = inputs.ndim - (len(kernel_size) + 1)\n    if num_batch_dimensions != 1:\n      input_batch_shape = inputs.shape[:num_batch_dimensions]\n      flat_input_shape = (-1,) + inputs.shape[\n        num_batch_dimensions:\n      ]\n      inputs = jnp.reshape(inputs, flat_input_shape)\n\n    strides = maybe_broadcast(self.strides)\n    kernel_dilation = maybe_broadcast(self.kernel_dilation)\n\n    kernel_shape = self.kernel_shape\n\n    if self.mask is not None and self.mask.shape != kernel_shape:\n      raise ValueError(\n        'Mask needs to have the same shape as weights. '\n        f'Shapes are: {self.mask.shape}, {kernel_shape}'\n      )\n\n    kernel = self.kernel[...]\n\n    if self.mask is not None:\n      kernel *= self.mask\n\n    padding_lax = canonicalize_padding(self.padding, len(kernel_size))\n    if padding_lax == 'CIRCULAR':\n      padding_lax = 'VALID'\n\n    bias = self.bias[...] if self.bias is not None else None\n\n    inputs, kernel, bias = self.promote_dtype(\n      (inputs, kernel, bias), dtype=self.dtype\n    )\n\n    y = lax.conv_transpose(\n      inputs,\n      kernel,\n      strides,\n      padding_lax,\n      rhs_dilation=kernel_dilation,\n      transpose_kernel=self.transpose_kernel,\n      precision=self.precision,\n      preferred_element_type=self.preferred_element_type\n    )\n\n    if self.padding == 'CIRCULAR':\n      # For circular padding, we need to identify the size of the final output\n      # (\"period\") along each spatial dimension, pad each dimension to an\n      # integer number of periods, and wrap the array periodically around each\n      # dimension. Padding should be done in such a way that the start of the\n      # original input data inside the padded array is located at integer\n      # number of periods - otherwise the result would be circularly shifted.\n\n      # Compute period along each spatial dimension - it's input size scaled\n      # by the stride.\n      scaled_x_dims = [\n        x_dim * stride\n        for x_dim, stride in zip(jnp.shape(inputs)[1:-1], strides)\n      ]\n      # Compute difference between the current size of y and the final output\n      # size, and complement this difference to 2 * period - that gives how\n      # much we need to pad.\n      size_diffs = [\n        -(y_dim - x_dim) % (2 * x_dim)\n        for y_dim, x_dim in zip(y.shape[1:-1], scaled_x_dims)\n      ]\n      if self.transpose_kernel:\n        # If the kernel is transposed, the \"+1\" is put on the right to\n        # mirror the regular convolution. If the same kernel parameters are used\n        # as for Conv, this layer then computes the proper transpose convolution.\n        total_pad = [\n          (size_diff // 2, (size_diff + 1) // 2) for size_diff in size_diffs\n        ]\n      else:\n        # Divide the padding equally between left and right. The choice to put\n        # \"+1\" on the left (and not on the right) represents a convention for\n        # aligning even-sized kernels.\n        total_pad = [\n          ((size_diff + 1) // 2, size_diff // 2) for size_diff in size_diffs\n        ]\n      y = jnp.pad(y, [(0, 0)] + total_pad + [(0, 0)])\n      # Wrap the result periodically around each spatial dimension,\n      # one by one.\n      for i in range(1, y.ndim - 1):\n        y = y.reshape(\n          y.shape[:i] + (-1, scaled_x_dims[i - 1]) + y.shape[i + 1 :]\n        )\n        y = y.sum(axis=i)\n\n    if self.use_bias:\n      y += jnp.reshape(bias, (1,) * (y.ndim - 1) + (-1,))  # type: ignore\n\n    if num_batch_dimensions != 1:\n      output_shape = input_batch_shape + y.shape[1:]\n      y = jnp.reshape(y, output_shape)\n\n    return y\n\n\ndefault_embed_init = initializers.variance_scaling(\n  1.0, 'fan_in', 'normal', out_axis=0\n)\n\n\nclass Embed(Module):\n  \"\"\"Embedding Module.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n\n    >>> layer = nnx.Embed(num_embeddings=5, features=3, rngs=nnx.Rngs(0))\n    >>> nnx.state(layer)\n    State({\n      'embedding': Param( # 15 (60 B)\n        value=Array([[ 0.57966787, -0.523274  , -0.43195742],\n               [-0.676289  , -0.50300646,  0.33996582],\n               [ 0.41796115, -0.59212935,  0.95934135],\n               [-1.0917838 , -0.7441663 ,  0.07713798],\n               [-0.66570747,  0.13815777,  1.007365  ]], dtype=float32)\n      )\n    })\n    >>> # get the first three and last three embeddings\n    >>> indices_input = jnp.array([[0, 1, 2], [-1, -2, -3]])\n    >>> layer(indices_input)\n    Array([[[ 0.57966787, -0.523274  , -0.43195742],\n            [-0.676289  , -0.50300646,  0.33996582],\n            [ 0.41796115, -0.59212935,  0.95934135]],\n    <BLANKLINE>\n           [[-0.66570747,  0.13815777,  1.007365  ],\n            [-1.0917838 , -0.7441663 ,  0.07713798],\n            [ 0.41796115, -0.59212935,  0.95934135]]], dtype=float32)\n\n  A parameterized function from integers [0, ``num_embeddings``) to\n  ``features``-dimensional vectors. This ``Module`` will create an ``embedding``\n  matrix with shape ``(num_embeddings, features)``. When calling this layer,\n  the input values will be used to 0-index into the ``embedding`` matrix.\n  Indexing on a value greater than or equal to ``num_embeddings`` will result\n  in ``nan`` values. When ``num_embeddings`` equals to 1, it will\n  broadcast the ``embedding`` matrix to input shape with ``features``\n  dimension appended.\n\n  Args:\n    num_embeddings: number of embeddings / vocab size.\n    features: number of feature dimensions for each embedding.\n    dtype: the dtype of the embedding vectors (default: same as embedding).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    embedding_init: embedding initializer.\n    promote_dtype: function to promote the dtype of the arrays to the desired\n      dtype. The function should accept a tuple of ``(embedding,)`` during ``__call__``\n      or ``(query, embedding)`` during ``attend``, and a ``dtype`` keyword argument,\n      and return a tuple of arrays with the promoted dtype.\n    rngs: rng key.\n    embedding_metadata: Optional metadata dictionary to set when initializing\n      the embedding matrix.\n  \"\"\"\n\n  def __init__(\n    self,\n    num_embeddings: int,\n    features: int,\n    *,\n    dtype: tp.Optional[Dtype] = None,\n    param_dtype: Dtype = jnp.float32,\n    embedding_init: Initializer = default_embed_init,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    rngs: rnglib.Rngs,\n    embedding_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    self.embedding = nnx.Param(\n      embedding_init(rngs.params(), (num_embeddings, features), param_dtype),\n      **embedding_metadata,\n    )\n\n    self.num_embeddings = num_embeddings\n    self.features = features\n    self.dtype = dtype or self.embedding.dtype\n    self.param_dtype = param_dtype\n    self.promote_dtype = promote_dtype\n\n  def __call__(self, inputs: Array, out_sharding=None) -> Array:\n    \"\"\"Embeds the inputs along the last dimension.\n\n    Args:\n      inputs: input data, all dimensions are considered batch dimensions.\n        Values in the input array must be integers.\n\n    Returns:\n      Output which is embedded input data.  The output shape follows the input,\n      with an additional ``features`` dimension appended.\n    \"\"\"\n    if not jnp.issubdtype(inputs.dtype, jnp.integer):\n      raise ValueError('Input type must be an integer or unsigned integer.')\n    # Use take because fancy indexing numpy arrays with JAX indices does not\n    # work correctly.\n    (embedding,) = self.promote_dtype(\n      (self.embedding[...],), dtype=self.dtype, inexact=False\n    )\n    if self.num_embeddings == 1:\n      return jnp.broadcast_to(embedding, inputs.shape + (self.features,))\n    if out_sharding is not None:\n      # Use auto_axes to handle out_sharding as jnp.take does not support it.\n      take_fn = lambda embedding, inputs: jnp.take(embedding, inputs, axis=0)\n      sharded_take = jax.sharding.auto_axes(out_sharding=out_sharding)(take_fn)\n      return sharded_take(embedding, inputs)\n    return jnp.take(embedding, inputs, axis=0)\n\n  def attend(self, query: Array, out_sharding=None) -> Array:\n    \"\"\"Attend over the embedding using a query array.\n\n    Args:\n      query: array with last dimension equal the feature depth ``features`` of the\n        embedding.\n      out_sharding: Optional sharding specification (e.g.,\n        ``jax.sharding.PartitionSpec``) for the output array. When using JAX's\n        explicit sharding mode with a mesh context with ``AxisType.Explicit``.\n        If ``None`` (default), the compiler automatically determines output\n        sharding.\n\n    Returns:\n      An array with final dim ``num_embeddings`` corresponding to the batched\n      inner-product of the array of query vectors against each embedding.\n      Commonly used for weight-sharing between embeddings and logit transform\n      in NLP models.\n    \"\"\"\n    query, embedding = self.promote_dtype(\n      (query, self.embedding[...]), dtype=self.dtype\n    )\n    return jnp.dot(query, embedding.T, out_sharding=out_sharding)\n"
  },
  {
    "path": "flax/nnx/nn/lora.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom __future__ import annotations\n\nimport typing as tp\nfrom types import MappingProxyType\n\nfrom flax.nnx import rnglib, variablelib\nfrom flax.nnx.module import Module\nfrom flax.nnx.nn import initializers, dtypes\nfrom flax.nnx.nn.linear import Linear\nfrom flax.typing import Dtype, Initializer, PromoteDtypeFn\nimport jax\nimport jax.numpy as jnp\n\nArray = jax.Array\nAxis = int\nSize = int\nA = tp.TypeVar('A')\n\ndefault_a_initializer = initializers.he_uniform()\ndefault_b_initializer = initializers.zeros\n\n\nclass LoRAParam(variablelib.Param[A]):\n  pass\n\n\nclass LoRA(Module):\n  \"\"\"A standalone LoRA layer.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n    >>> layer = nnx.LoRA(3, 2, 4, rngs=nnx.Rngs(0))\n    >>> layer.lora_a.shape\n    (3, 2)\n    >>> layer.lora_b.shape\n    (2, 4)\n    >>> # Wrap around existing layer\n    >>> linear = nnx.Linear(3, 4, rngs=nnx.Rngs(0))\n    >>> wrapper = nnx.LoRA(3, 2, 4, base_module=linear, rngs=nnx.Rngs(1))\n    >>> assert wrapper.base_module == linear\n    >>> wrapper.lora_a.shape\n    (3, 2)\n    >>> layer.lora_b.shape\n    (2, 4)\n    >>> y = layer(jnp.ones((16, 3)))\n    >>> y.shape\n    (16, 4)\n\n  Args:\n    in_features: the number of input features.\n    lora_rank: the rank of the LoRA dimension.\n    out_features: the number of output features.\n    base_module: a base module to call and substitute, if possible.\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    precision: numerical precision of the computation see `jax.lax.Precision`\n      for details.\n    a_initializer: initializer function for the fan-in matrices. Default to\n      `he_uniform`.\n    b_initializer: initializer function for the fan-out matrices. Default to\n      `zero initializer`.\n    lora_param_type: the type of the LoRA params.\n    promote_dtype: function to promote the dtype of all input array arguments\n      (including Variables accessed through ``self``) to the desired dtype. The\n      function should accept a tuple of ``(inputs, lora_a, lora_b)`` and a ``dtype``\n      keyword argument, and return a tuple of arrays with the promoted dtype.\n    rngs: rng key.\n    a_metadata: Optional metadata dictionary to set when initializing\n      the fan-in matrices.\n    b_metadata: Optional metadata dictionary to set when initializing\n      the fan-out matrices.\n  \"\"\"\n\n  def __init__(\n    self,\n    in_features: int,\n    lora_rank: int,\n    out_features: int,\n    *,\n    base_module: tp.Optional[Module] = None,\n    dtype: tp.Optional[Dtype] = None,\n    param_dtype: Dtype = jnp.float32,\n    a_initializer: Initializer = default_a_initializer,\n    b_initializer: Initializer = default_b_initializer,\n    lora_param_type: tp.Type[variablelib.Variable] = LoRAParam,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    rngs: rnglib.Rngs,\n    a_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n    b_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    self.in_features = in_features\n    self.out_features = out_features\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.lora_param_type = lora_param_type\n    self.base_module = base_module\n    self.promote_dtype = promote_dtype\n\n    self.lora_a = lora_param_type(\n      a_initializer(rngs.params(), (in_features, lora_rank), param_dtype),\n      **a_metadata,\n    )\n    self.lora_b = lora_param_type(\n      b_initializer(rngs.params(), (lora_rank, out_features), param_dtype),\n      **b_metadata,\n    )\n\n  def __call__(self, x: jax.Array):\n    x, lora_a, lora_b = self.promote_dtype(\n      (x, self.lora_a[...], self.lora_b[...]), dtype=self.dtype\n    )\n    out = x @ lora_a @ lora_b\n    if self.base_module is not None:\n      if not callable(self.base_module):\n        raise ValueError('`self.base_module` must be callable.')\n      out += self.base_module(x)\n    return out\n\n\nclass LoRALinear(Linear):\n  \"\"\"An `nnx.Linear` layer in which the output will be LoRAified.\n\n  The model state structure will be compatible with that of Linear.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n    >>> linear = nnx.Linear(3, 4, rngs=nnx.Rngs(0))\n    >>> lora_linear = nnx.LoRALinear(3, 4, lora_rank=2, rngs=nnx.Rngs(0))\n    >>> linear.kernel.shape\n    (3, 4)\n    >>> lora_linear.kernel.shape\n    (3, 4)\n    >>> lora_linear.lora.lora_a.shape\n    (3, 2)\n    >>> jnp.allclose(linear.kernel[...], lora_linear.kernel[...])\n    Array(True, dtype=bool)\n    >>> y = lora_linear(jnp.ones((16, 3)))\n    >>> y.shape\n    (16, 4)\n\n  Args:\n    in_features: the number of input features.\n    out_features: the number of output features.\n    lora_rank: the rank of the LoRA dimension.\n    base_module: a base module to call and substitute, if possible.\n    dtype: the dtype of the computation (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    precision: numerical precision of the computation see `jax.lax.Precision`\n      for details.\n    a_initializer: initializer function for the fan-in matrices. Default to\n      `he_uniform`.\n    b_initializer: initializer function for the fan-out matrices. Default to\n      `zero initializer`.\n    lora_param_type: the type of the LoRA params.\n    lora_promote_dtype: function to promote the dtype for the LoRA submodule.\n    a_metadata: Optional metadata dictionary to set when initializing\n      the fan-in matrices.\n    b_metadata: Optional metadata dictionary to set when initializing\n      the fan-out matrices.\n  \"\"\"\n\n  def __init__(\n    self,\n    in_features: int,\n    out_features: int,\n    *,\n    lora_rank: int,\n    lora_dtype: tp.Optional[Dtype] = None,\n    lora_param_dtype: Dtype = jnp.float32,\n    a_initializer: Initializer = default_a_initializer,\n    b_initializer: Initializer = default_b_initializer,\n    lora_param_type: tp.Type[variablelib.Variable] = LoRAParam,\n    lora_promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    rngs: rnglib.Rngs,\n    a_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n    b_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n    **kwargs,\n  ):\n    super().__init__(in_features, out_features, rngs=rngs, **kwargs)\n    self.lora = LoRA(\n      in_features,\n      lora_rank,\n      out_features,\n      dtype=lora_dtype,\n      param_dtype=lora_param_dtype,\n      a_initializer=a_initializer,\n      b_initializer=b_initializer,\n      lora_param_type=lora_param_type,\n      promote_dtype=lora_promote_dtype,\n      rngs=rngs,\n      a_metadata=a_metadata,\n      b_metadata=b_metadata,\n    )\n\n  def __call__(self, x: jax.Array, out_sharding = None):\n    y = super().__call__(x, out_sharding=out_sharding)\n    y += self.lora(x)\n    return y\n"
  },
  {
    "path": "flax/nnx/nn/normalization.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing as tp\nfrom types import MappingProxyType\n\nimport jax\nimport jax.numpy as jnp\nfrom jax import lax\n\nfrom flax import nnx\nfrom flax.nnx import rnglib\nfrom flax.nnx.module import Module, first_from\nfrom flax.nnx.nn import dtypes, initializers\nfrom flax.typing import (\n  Array,\n  Dtype,\n  Initializer,\n  Axes,\n  PromoteDtypeFn,\n)\n\n\ndef _canonicalize_axes(rank: int, axes: Axes) -> tp.Tuple[int, ...]:\n  \"\"\"Returns a tuple of deduplicated, sorted, and positive axes.\"\"\"\n  if not isinstance(axes, tp.Iterable):\n    axes = (axes,)\n  return tuple({rank + axis if axis < 0 else axis for axis in axes})\n\n\ndef _abs_sq(x):\n  \"\"\"Computes the elementwise square of the absolute value |x|^2.\"\"\"\n  if jnp.iscomplexobj(x):\n    return lax.square(lax.real(x)) + lax.square(lax.imag(x))\n  else:\n    return lax.square(x)\n\n\ndef _compute_stats(\n  x: Array,\n  axes: Axes,\n  dtype: tp.Optional[Dtype],\n  axis_name: tp.Optional[str] = None,\n  axis_index_groups: tp.Any = None,\n  use_mean: bool = True,\n  use_fast_variance: bool = True,\n  mask: tp.Optional[Array] = None,\n) -> tuple[Array, Array]:\n  \"\"\"Computes mean and variance statistics.\n\n  This implementation takes care of a few important details:\n  - Computes in float32 precision for stability in half precision training.\n  - If ``use_fast_variance`` is ``True``, mean and variance are computed using\n    Var = E[|x|^2] - |E[x]|^2, instead of Var = E[|x - E[x]|^2]), in a single\n    XLA fusion.\n  - Clips negative variances to zero which can happen due to\n    roundoff errors. This avoids downstream NaNs.\n  - Supports averaging across a parallel axis and subgroups of a parallel axis\n    with a single ``lax.pmean`` call to avoid latency.\n\n  Arguments:\n    x: Input array.\n    axes: The axes in ``x`` to compute mean and variance statistics for.\n    dtype: Optional dtype specifying the minimal precision. Statistics are\n      always at least float32 for stability (default: dtype of x).\n    axis_name: Optional name for the pmapped axis to compute mean over. Note,\n      this is only used for pmap and shard map. For SPMD jit, you do not need to\n      manually synchronize. Just make sure that the axes are correctly annotated\n      and XLA:SPMD will insert the necessary collectives.\n    axis_index_groups: Optional groups of indices within that named axis.\n    use_mean: If true, calculate the mean from the input and use it when\n      computing the variance. If false, set the mean to zero and compute the\n      variance without subtracting the mean.\n    use_fast_variance: If true, use a faster, but less numerically stable,\n      calculation for the variance.\n    mask: Binary array of shape broadcastable to ``inputs`` tensor, indicating\n      the positions for which the mean and variance should be computed.\n\n  Returns:\n    A pair ``(mean, var)``.\n  \"\"\"\n  if dtype is None:\n    dtype = jnp.result_type(x)\n  # promote x to at least float32, this avoids half precision computation\n  # but preserves double or complex floating points\n  dtype = jnp.promote_types(dtype, jnp.float32)\n  x = jnp.asarray(x, dtype)\n  axes = _canonicalize_axes(x.ndim, axes)\n\n  def maybe_distributed_mean(*xs, mask=None):\n    mus = tuple(x.mean(axes, where=mask) for x in xs)\n    if axis_name is None:\n      return mus if len(xs) > 1 else mus[0]\n    else:\n      # In the distributed case we stack multiple arrays to speed comms.\n      if len(xs) > 1:\n        reduced_mus = lax.pmean(\n          jnp.stack(mus, axis=0),\n          axis_name,\n          axis_index_groups=axis_index_groups,\n        )\n        return tuple(reduced_mus[i] for i in range(len(xs)))\n      else:\n        return lax.pmean(mus[0], axis_name, axis_index_groups=axis_index_groups)\n\n  if use_mean:\n    if use_fast_variance:\n      mu, mu2 = maybe_distributed_mean(x, _abs_sq(x), mask=mask)\n      # mean2 - _abs_sq(mean) is not guaranteed to be non-negative due\n      # to floating point round-off errors.\n      var = jnp.maximum(0.0, mu2 - _abs_sq(mu))\n    else:\n      mu = maybe_distributed_mean(x, mask=mask)\n      var = maybe_distributed_mean(\n        _abs_sq(x - jnp.expand_dims(mu, axes)), mask=mask\n      )\n  else:\n    var = maybe_distributed_mean(_abs_sq(x), mask=mask)\n    mu = jnp.zeros_like(var)\n  return mu, var\n\n\ndef _normalize(\n  x: Array,\n  mean: Array,\n  var: Array,\n  scale: tp.Optional[Array],\n  bias: tp.Optional[Array],\n  reduction_axes: Axes,\n  feature_axes: Axes,\n  dtype: tp.Optional[Dtype],\n  epsilon: float,\n):\n  \"\"\" \"Normalizes the input of a normalization layer and optionally applies a learned scale and bias.\n\n  Arguments:\n    x: The input.\n    mean: Mean to use for normalization.\n    var: Variance to use for normalization.\n    reduction_axes: The axes in ``x`` to reduce.\n    feature_axes: Axes containing features. A separate bias and scale is learned\n      for each specified feature.\n    dtype: The dtype of the result (default: infer from input and params).\n    epsilon: Normalization epsilon.\n\n  Returns:\n    The normalized input.\n  \"\"\"\n  reduction_axes = _canonicalize_axes(x.ndim, reduction_axes)\n  feature_axes = _canonicalize_axes(x.ndim, feature_axes)\n  stats_shape = list(x.shape)\n  for axis in reduction_axes:\n    stats_shape[axis] = 1\n  mean = mean.reshape(stats_shape)\n  var = var.reshape(stats_shape)\n  feature_shape = [1] * x.ndim\n  for ax in feature_axes:\n    feature_shape[ax] = x.shape[ax]\n  y = x - mean\n  mul = lax.rsqrt(var + epsilon)\n  args = [x]\n  if scale is not None:\n    scale = scale.reshape(feature_shape)\n    mul *= scale\n    args.append(scale)\n  y *= mul\n  if bias is not None:\n    bias = bias.reshape(feature_shape)\n    y += bias\n    args.append(bias)\n  dtype = dtypes.canonicalize_dtype(*args, dtype=dtype)\n  return jnp.asarray(y, dtype)\n\n\ndef _l2_normalize(x, axis=None, eps=1e-12):\n  \"\"\"Normalizes along dimension `axis` using an L2 norm.\n  This specialized function exists for numerical stability reasons.\n  Args:\n    x: An input ndarray.\n    axis: Dimension along which to normalize, e.g. `1` to separately normalize\n      vectors in a batch. Passing `None` views `t` as a flattened vector when\n      calculating the norm (equivalent to Frobenius norm).\n    eps: Epsilon to avoid dividing by zero.\n  Returns:\n    An array of the same shape as 'x' L2-normalized along 'axis'.\n  \"\"\"\n  return x * jax.lax.rsqrt((x * x).sum(axis=axis, keepdims=True) + eps)\n\n\nclass BatchNorm(Module):\n  \"\"\"BatchNorm Module.\n\n  To calculate the batch norm on the input and update the batch statistics,\n  call the :func:`train` method (or pass in ``use_running_average=False`` in\n  the constructor or during call time).\n\n  To use the stored batch statistics' running average, call the :func:`eval`\n  method (or pass in ``use_running_average=True`` in the constructor or\n  during call time).\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n\n    >>> x = jax.random.normal(jax.random.key(0), (5, 6))\n    >>> layer = nnx.BatchNorm(num_features=6, momentum=0.9, epsilon=1e-5,\n    ...                       dtype=jnp.float32, rngs=nnx.Rngs(0))\n    >>> jax.tree.map(jnp.shape, nnx.state(layer))\n    State({\n      'bias': Param(\n        value=(6,)\n      ),\n      'mean': BatchStat(\n        value=(6,)\n      ),\n      'scale': Param(\n        value=(6,)\n      ),\n      'var': BatchStat(\n        value=(6,)\n      )\n    })\n\n    >>> # calculate batch norm on input and update batch statistics\n    >>> layer.train()\n    >>> y = layer(x)\n    >>> batch_stats1 = nnx.clone(nnx.state(layer, nnx.BatchStat)) # keep a copy\n    >>> y = layer(x)\n    >>> batch_stats2 = nnx.state(layer, nnx.BatchStat)\n    >>> assert (batch_stats1['mean'][...] != batch_stats2['mean'][...]).all()\n    >>> assert (batch_stats1['var'][...] != batch_stats2['var'][...]).all()\n\n    >>> # use stored batch statistics' running average\n    >>> layer.eval()\n    >>> y = layer(x)\n    >>> batch_stats3 = nnx.state(layer, nnx.BatchStat)\n    >>> assert (batch_stats2['mean'][...] == batch_stats3['mean'][...]).all()\n    >>> assert (batch_stats2['var'][...] == batch_stats3['var'][...]).all()\n\n  Args:\n    num_features: the number of input features.\n    use_running_average: if True, the stored batch statistics will be\n      used instead of computing the batch statistics on the input.\n    axis: the feature or non-batch axis of the input.\n    momentum: decay rate for the exponential moving average of\n      the batch statistics.\n    epsilon: a small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    use_bias:  if True, bias (beta) is added.\n    use_scale: if True, multiply by scale (gamma).\n      When the next layer is linear (also e.g. nn.relu), this can be disabled\n      since the scaling will be done by the next layer.\n    bias_init: initializer for bias, by default, zero.\n    scale_init: initializer for scale, by default, one.\n    axis_name: the axis name used to combine batch statistics from multiple\n      devices. See ``jax.pmap`` for a description of axis names (default: None).\n    axis_index_groups: groups of axis indices within that named axis\n      representing subsets of devices to reduce over (default: None). For\n      example, ``[[0, 1], [2, 3]]`` would independently batch-normalize over\n      the examples on the first two and last two devices. See ``jax.lax.psum``\n      for more details. This argument is currently not supported for SPMD jit.\n    use_fast_variance: If true, use a faster, but less numerically stable,\n      calculation for the variance.\n    promote_dtype: function to promote the dtype of all input array arguments\n      (including Variables accessed through ``self``) to the desired dtype. The\n      function should accept a tuple of ``(inputs, mean, var, scale, bias)`` and\n      a ``dtype`` keyword argument, and return a tuple of arrays with the promoted\n      dtype.\n    rngs: rng key.\n    bias_metadata: Optional metadata dictionary to set when initializing\n      the bias.\n    scale_metadata: Optional metadata dictionary to set when initializing\n      the scale.\n  \"\"\"\n\n  def __init__(\n    self,\n    num_features: int,\n    *,\n    use_running_average: bool = False,\n    axis: int = -1,\n    momentum: float = 0.99,\n    epsilon: float = 1e-5,\n    dtype: tp.Optional[Dtype] = None,\n    param_dtype: Dtype = jnp.float32,\n    use_bias: bool = True,\n    use_scale: bool = True,\n    bias_init: Initializer = initializers.zeros_init(),\n    scale_init: Initializer = initializers.ones_init(),\n    axis_name: tp.Optional[str] = None,\n    axis_index_groups: tp.Any = None,\n    use_fast_variance: bool = True,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    rngs: rnglib.Rngs,\n    bias_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n    scale_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    feature_shape = (num_features,)\n    self.mean = nnx.BatchStat(jnp.zeros(feature_shape, jnp.float32))\n    self.var = nnx.BatchStat(jnp.ones(feature_shape, jnp.float32))\n\n    self.scale: nnx.Param[jax.Array] | None\n    if use_scale:\n      key = rngs.params()\n      self.scale = nnx.Param(scale_init(key, feature_shape, param_dtype), **scale_metadata)\n    else:\n      self.scale = nnx.data(None)\n\n    self.bias: nnx.Param[jax.Array] | None\n    if use_bias:\n      key = rngs.params()\n      self.bias = nnx.Param(bias_init(key, feature_shape, param_dtype), **bias_metadata)\n    else:\n      self.bias = nnx.data(None)\n\n    self.num_features = num_features\n    self.use_running_average = use_running_average\n    self.axis = axis\n    self.momentum = momentum\n    self.epsilon = epsilon\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.use_bias = use_bias\n    self.use_scale = use_scale\n    self.axis_name = axis_name\n    self.axis_index_groups = axis_index_groups\n    self.use_fast_variance = use_fast_variance\n    self.promote_dtype = promote_dtype\n\n  def __call__(\n    self,\n    x,\n    use_running_average: tp.Optional[bool] = None,\n    *,\n    mask: tp.Optional[jax.Array] = None,\n  ):\n    \"\"\"Normalizes the input using batch statistics.\n\n    Args:\n      x: the input to be normalized.\n      use_running_average: if true, the stored batch statistics will be\n        used instead of computing the batch statistics on the input. The\n        ``use_running_average`` flag passed into the call method will take\n        precedence over the ``use_running_average`` flag passed into the\n        constructor.\n\n    Returns:\n      Normalized inputs (the same shape as inputs).\n    \"\"\"\n\n    use_running_average = first_from(\n      use_running_average,\n      self.use_running_average,\n      error_msg=\"\"\"No `use_running_average` argument was provided to BatchNorm\n        as either a __call__ argument, class attribute, or nnx.flag.\"\"\",\n    )\n    feature_axes = _canonicalize_axes(x.ndim, self.axis)\n    reduction_axes = tuple(i for i in range(x.ndim) if i not in feature_axes)\n\n    # Promote dtypes for input and all Variables\n    scale = self.scale[...] if self.scale is not None else None\n    bias = self.bias[...] if self.bias is not None else None\n    x, mean, var, scale, bias = self.promote_dtype(\n      (x, self.mean[...], self.var[...], scale, bias), dtype=self.dtype\n    )\n    if not use_running_average:\n      mean, var = _compute_stats(\n        x,\n        reduction_axes,\n        dtype=self.dtype,\n        axis_name=self.axis_name,\n        axis_index_groups=self.axis_index_groups,\n        use_fast_variance=self.use_fast_variance,\n        mask=mask,\n      )\n      # stop_gradient only for flax_array_ref\n      if self.mean._can_update or self.var._can_update:\n        stop_gradient = jax.lax.stop_gradient\n      else:\n        stop_gradient = lambda x: x\n\n      self.mean[...] = stop_gradient(\n        self.momentum * self.mean[...] + (1 - self.momentum) * mean\n      )\n      self.var[...] = stop_gradient(\n        self.momentum * self.var[...] + (1 - self.momentum) * var\n      )\n\n    return _normalize(\n      x,\n      mean,\n      var,\n      scale,\n      bias,\n      reduction_axes,\n      feature_axes,\n      self.dtype,\n      self.epsilon,\n    )\n\n  def set_view(\n      self,\n      use_running_average: bool | None = None,\n  ):\n    \"\"\"Class method used by ``nnx.view``.\n\n    Args:\n      use_running_average: if True, the stored batch statistics will be\n        used instead of computing the batch statistics on the input.\n    \"\"\"\n    if use_running_average is not None:\n      self.use_running_average = use_running_average\n\n\nclass LayerNorm(Module):\n  \"\"\"Layer normalization (https://arxiv.org/abs/1607.06450).\n\n  LayerNorm normalizes the activations of the layer for each given example in a\n  batch independently, rather than across a batch like Batch Normalization.\n  i.e. applies a transformation that maintains the mean activation within\n  each example close to 0 and the activation standard deviation close to 1.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax\n\n    >>> x = jax.random.normal(jax.random.key(0), (3, 4, 5, 6))\n    >>> layer = nnx.LayerNorm(num_features=6, rngs=nnx.Rngs(0))\n\n    >>> nnx.state(layer)\n    State({\n      'bias': Param( # 6 (24 B)\n        value=Array([0., 0., 0., 0., 0., 0.], dtype=float32)\n      ),\n      'scale': Param( # 6 (24 B)\n        value=Array([1., 1., 1., 1., 1., 1.], dtype=float32)\n      )\n    })\n\n    >>> y = layer(x)\n\n  Args:\n    num_features: the number of input features.\n    epsilon: A small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    use_bias:  If True, bias (beta) is added.\n    use_scale: If True, multiply by scale (gamma). When the next layer is linear\n        (also e.g. nnx.relu), this can be disabled since the scaling will be done\n        by the next layer.\n    bias_init: Initializer for bias, by default, zero.\n    scale_init: Initializer for scale, by default, one.\n    reduction_axes: Axes for computing normalization statistics.\n    feature_axes: Feature axes for learned bias and scaling.\n    axis_name: the axis name used to combine batch statistics from multiple\n        devices. See ``jax.pmap`` for a description of axis names (default: None).\n        This is only needed if the model is subdivided across devices, i.e. the\n        array being normalized is sharded across devices within a pmap.\n    axis_index_groups: groups of axis indices within that named axis\n        representing subsets of devices to reduce over (default: None). For\n        example, ``[[0, 1], [2, 3]]`` would independently batch-normalize over\n        the examples on the first two and last two devices. See ``jax.lax.psum``\n        for more details. This argument is currently not supported for SPMD jit.\n    use_fast_variance: If true, use a faster, but less numerically stable,\n        calculation for the variance.\n    promote_dtype: function to promote the dtype of all input array arguments\n        (including Variables accessed through ``self``) to the desired dtype. The\n        function should accept a tuple of ``(inputs, scale, bias)`` and a ``dtype``\n        keyword argument, and return a tuple of arrays with the promoted dtype.\n    rngs: rng key.\n    bias_metadata: Optional metadata dictionary to set when initializing\n      the bias.\n    scale_metadata: Optional metadata dictionary to set when initializing\n      the scale.\n  \"\"\"\n\n  def __init__(\n    self,\n    num_features: int,\n    *,\n    epsilon: float = 1e-6,\n    dtype: tp.Optional[Dtype] = None,\n    param_dtype: Dtype = jnp.float32,\n    use_bias: bool = True,\n    use_scale: bool = True,\n    bias_init: Initializer = initializers.zeros_init(),\n    scale_init: Initializer = initializers.ones_init(),\n    reduction_axes: Axes = -1,\n    feature_axes: Axes = -1,\n    axis_name: tp.Optional[str] = None,\n    axis_index_groups: tp.Any = None,\n    use_fast_variance: bool = True,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    rngs: rnglib.Rngs,\n    bias_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n    scale_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    feature_shape = (num_features,)\n\n    self.scale: nnx.Param[jax.Array] | None\n    if use_scale:\n      key = rngs.params()\n      self.scale = nnx.Param(scale_init(key, feature_shape, param_dtype), **scale_metadata)\n    else:\n      self.scale = nnx.data(None)\n\n    self.bias: nnx.Param[jax.Array] | None\n    if use_bias:\n      key = rngs.params()\n      self.bias = nnx.Param(bias_init(key, feature_shape, param_dtype), **bias_metadata)\n    else:\n      self.bias = nnx.data(None)\n\n    self.num_features = num_features\n    self.epsilon = epsilon\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.use_bias = use_bias\n    self.use_scale = use_scale\n    self.reduction_axes = reduction_axes\n    self.feature_axes = feature_axes\n    self.axis_name = axis_name\n    self.axis_index_groups = axis_index_groups\n    self.use_fast_variance = use_fast_variance\n    self.promote_dtype = promote_dtype\n\n  def __call__(self, x, *, mask: tp.Optional[jax.Array] = None):\n    \"\"\"Applies layer normalization on the input.\n\n    Args:\n      x: the inputs\n\n    Returns:\n      Normalized inputs (the same shape as inputs).\n    \"\"\"\n    # Promote dtypes for input and all Variables\n    scale = self.scale[...] if self.scale else None\n    bias = self.bias[...] if self.bias else None\n    x, scale, bias = self.promote_dtype(\n      (x, scale, bias), dtype=self.dtype\n    )\n    mean, var = _compute_stats(\n      x,\n      self.reduction_axes,\n      self.dtype,\n      self.axis_name,\n      self.axis_index_groups,\n      use_fast_variance=self.use_fast_variance,\n      mask=mask,\n    )\n\n    return _normalize(\n      x,\n      mean,\n      var,\n      scale,\n      bias,\n      self.reduction_axes,\n      self.feature_axes,\n      self.dtype,\n      self.epsilon,\n    )\n\n\nclass RMSNorm(Module):\n  \"\"\"RMS Layer normalization (https://arxiv.org/abs/1910.07467).\n\n  RMSNorm normalizes the activations of the layer for each given example in a\n  batch independently, rather than across a batch like Batch Normalization.\n  Unlike LayerNorm which re-centers the mean to be 0 and normalizes by the\n  standard deviation of the activations, RMSNorm does not re-center at all\n  and instead normalizes by the root mean square of the activations.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax\n\n    >>> x = jax.random.normal(jax.random.key(0), (5, 6))\n    >>> layer = nnx.RMSNorm(num_features=6, rngs=nnx.Rngs(0))\n\n    >>> nnx.state(layer)\n    State({\n      'scale': Param( # 6 (24 B)\n        value=Array([1., 1., 1., 1., 1., 1.], dtype=float32)\n      )\n    })\n\n    >>> y = layer(x)\n\n  Args:\n    num_features: the number of input features.\n    epsilon: A small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    use_scale: If True, multiply by scale (gamma). When the next layer is linear\n        (also e.g. nn.relu), this can be disabled since the scaling will be done\n        by the next layer.\n    scale_init: Initializer for scale, by default, one.\n    reduction_axes: Axes for computing normalization statistics.\n    feature_axes: Feature axes for learned bias and scaling.\n    axis_name: the axis name used to combine batch statistics from multiple\n        devices. See ``jax.pmap`` for a description of axis names (default: None).\n        This is only needed if the model is subdivided across devices, i.e. the\n        array being normalized is sharded across devices within a pmap.\n    axis_index_groups: groups of axis indices within that named axis\n        representing subsets of devices to reduce over (default: None). For\n        example, ``[[0, 1], [2, 3]]`` would independently batch-normalize over\n        the examples on the first two and last two devices. See ``jax.lax.psum``\n        for more details. This argument is currently not supported for SPMD jit.\n    use_fast_variance: If true, use a faster, but less numerically stable,\n        calculation for the variance.\n    promote_dtype: function to promote the dtype of all input array arguments\n      (including Variables accessed through ``self``) to the desired dtype. The\n      function should accept a tuple of ``(inputs, scale)`` and a ``dtype``\n      keyword argument, and return a tuple of arrays with the promoted dtype.\n    rngs: rng key.\n    scale_metadata: Optional metadata dictionary to set when initializing\n      the scale.\n  \"\"\"\n\n  def __init__(\n    self,\n    num_features: int,\n    *,\n    epsilon: float = 1e-6,\n    dtype: tp.Optional[Dtype] = None,\n    param_dtype: Dtype = jnp.float32,\n    use_scale: bool = True,\n    scale_init: Initializer = initializers.ones,\n    reduction_axes: Axes = -1,\n    feature_axes: Axes = -1,\n    axis_name: tp.Optional[str] = None,\n    axis_index_groups: tp.Any = None,\n    use_fast_variance: bool = True,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    rngs: rnglib.Rngs,\n    scale_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    feature_shape = (num_features,)\n\n    self.scale: nnx.Param[jax.Array] | None\n    if use_scale:\n      key = rngs.params()\n      self.scale = nnx.Param(scale_init(key, feature_shape, param_dtype), **scale_metadata)\n    else:\n      self.scale = nnx.data(None)\n\n    self.num_features = num_features\n    self.epsilon = epsilon\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.use_scale = use_scale\n    self.reduction_axes = reduction_axes\n    self.feature_axes = feature_axes\n    self.axis_name = axis_name\n    self.axis_index_groups = axis_index_groups\n    self.use_fast_variance = use_fast_variance\n    self.promote_dtype = promote_dtype\n\n  def __call__(self, x, mask: tp.Optional[jax.Array] = None):\n    \"\"\"Applies layer normalization on the input.\n\n    Args:\n      x: the inputs\n\n    Returns:\n      Normalized inputs (the same shape as inputs).\n    \"\"\"\n    # Promote dtypes for input and all Variables\n    scale = self.scale[...] if self.scale else None\n    x, scale = self.promote_dtype(\n      (x, scale), dtype=self.dtype\n    )\n    mean, var = _compute_stats(\n      x,\n      self.reduction_axes,\n      self.dtype,\n      self.axis_name,\n      self.axis_index_groups,\n      use_mean=False,\n      use_fast_variance=self.use_fast_variance,\n      mask=mask,\n    )\n\n    return _normalize(\n      x,\n      mean,\n      var,\n      scale,\n      None,\n      self.reduction_axes,\n      self.feature_axes,\n      self.dtype,\n      self.epsilon,\n    )\n\n\nclass GroupNorm(Module):\n  \"\"\"Group normalization (arxiv.org/abs/1803.08494).\n\n  This op is similar to batch normalization, but statistics are shared across\n  equally-sized groups of channels and not shared across batch dimension.\n  Thus, group normalization does not depend on the batch composition and does\n  not require maintaining internal state for storing statistics.\n  The user should either specify the total number of channel groups or the\n  number of channels per group.\n\n  .. note::\n    LayerNorm is a special case of GroupNorm where ``num_groups=1``.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax\n    >>> import numpy as np\n    ...\n    >>> x = jax.random.normal(jax.random.key(0), (3, 4, 5, 6))\n    >>> layer = nnx.GroupNorm(num_features=6, num_groups=3, rngs=nnx.Rngs(0))\n    >>> nnx.state(layer)\n    State({\n      'bias': Param( # 6 (24 B)\n        value=Array([0., 0., 0., 0., 0., 0.], dtype=float32)\n      ),\n      'scale': Param( # 6 (24 B)\n        value=Array([1., 1., 1., 1., 1., 1.], dtype=float32)\n      )\n    })\n    >>> y = layer(x)\n    ...\n    >>> y = nnx.GroupNorm(num_features=6, num_groups=1, rngs=nnx.Rngs(0))(x)\n    >>> y2 = nnx.LayerNorm(num_features=6, reduction_axes=(1, 2, 3), rngs=nnx.Rngs(0))(x)\n    >>> np.testing.assert_allclose(y, y2)\n\n  Args:\n    num_features: the number of input features/channels.\n    num_groups: the total number of channel groups. The default value of 32 is\n      proposed by the original group normalization paper.\n    group_size: the number of channels in a group.\n    epsilon: A small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    use_bias:  If True, bias (beta) is added.\n    use_scale: If True, multiply by scale (gamma). When the next layer is linear\n      (also e.g. nn.relu), this can be disabled since the scaling will be done\n      by the next layer.\n    bias_init: Initializer for bias, by default, zero.\n    scale_init: Initializer for scale, by default, one.\n    reduction_axes: List of axes used for computing normalization statistics.\n      This list must include the final dimension, which is assumed to be the\n      feature axis. Furthermore, if the input used at call time has additional\n      leading axes compared to the data used for initialisation, for example due\n      to batching, then the reduction axes need to be defined explicitly.\n    axis_name: the axis name used to combine batch statistics from multiple\n      devices. See ``jax.pmap`` for a description of axis names (default: None).\n      This is only needed if the model is subdivided across devices, i.e. the\n      array being normalized is sharded across devices within a pmap or shard\n      map. For SPMD jit, you do not need to manually synchronize. Just make sure\n      that the axes are correctly annotated and XLA:SPMD will insert the\n      necessary collectives.\n    axis_index_groups: groups of axis indices within that named axis\n      representing subsets of devices to reduce over (default: None). For\n      example, ``[[0, 1], [2, 3]]`` would independently batch-normalize over the\n      examples on the first two and last two devices. See ``jax.lax.psum`` for\n      more details. This argument is currently not supported for SPMD jit.\n    use_fast_variance: If true, use a faster, but less numerically stable,\n      calculation for the variance.\n    promote_dtype: function to promote the dtype of all input array arguments\n      (including Variables accessed through ``self``) to the desired dtype. The\n      function should accept a tuple of ``(inputs, scale, bias)`` and a ``dtype``\n      keyword argument, and return a tuple of arrays with the promoted dtype.\n    rngs: rng key.\n    bias_metadata: Optional metadata dictionary to set when initializing\n      the bias.\n    scale_metadata: Optional metadata dictionary to set when initializing\n      the scale.\n  \"\"\"\n\n  def __init__(\n    self,\n    num_features: int,\n    num_groups: tp.Optional[int] = 32,\n    group_size: tp.Optional[int] = None,\n    *,\n    epsilon: float = 1e-6,\n    dtype: tp.Optional[Dtype] = None,\n    param_dtype: Dtype = jnp.float32,\n    use_bias: bool = True,\n    use_scale: bool = True,\n    bias_init: Initializer = initializers.zeros_init(),\n    scale_init: Initializer = initializers.ones_init(),\n    reduction_axes: tp.Optional[Axes] = None,\n    axis_name: tp.Optional[str] = None,\n    axis_index_groups: tp.Any = None,\n    use_fast_variance: bool = True,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    rngs: rnglib.Rngs,\n    bias_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n    scale_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    self.feature_axis = -1\n\n    if (num_groups is None and group_size is None) or (\n      num_groups is not None and group_size is not None\n    ):\n      raise ValueError(\n        'Either `num_groups` or `group_size` should be '\n        'specified. If `group_size` is to be specified, '\n        'pass `num_groups=None` as argument to override '\n        'the default `num_groups` value of 32.'\n      )\n\n    if group_size is not None:\n      if num_features % group_size != 0:\n        raise ValueError(\n          'Number of features ({}) is not multiple of the '\n          'group size ({}).'.format(num_features, group_size)\n        )\n      self.num_groups = num_features // group_size\n      self.group_size = group_size\n    else:\n      if not isinstance(num_groups, int) or num_groups <= 0 or (\n        num_features % num_groups != 0\n      ):\n        raise ValueError(\n          'Number of groups ({}) does not divide the number'\n          ' of channels ({}).'.format(num_groups, num_features)\n        )\n      self.num_groups = num_groups\n      self.group_size = num_features // num_groups\n\n    feature_shape = (num_features,)\n    self.scale: nnx.Param[jax.Array] | None\n    if use_scale:\n      key = rngs.params()\n      self.scale = nnx.Param(scale_init(key, feature_shape, param_dtype), **scale_metadata)\n    else:\n      self.scale = nnx.data(None)\n\n    self.bias: nnx.Param[jax.Array] | None\n    if use_bias:\n      key = rngs.params()\n      self.bias = nnx.Param(bias_init(key, feature_shape, param_dtype), **bias_metadata)\n    else:\n      self.bias = nnx.data(None)\n\n    self.epsilon = epsilon\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.use_bias = use_bias\n    self.use_scale = use_scale\n    self.reduction_axes = reduction_axes\n    self.axis_name = axis_name\n    self.axis_index_groups = axis_index_groups\n    self.use_fast_variance = use_fast_variance\n    self.promote_dtype = promote_dtype\n\n  def __call__(self, x, *, mask: tp.Optional[jax.Array] = None):\n    \"\"\"Applies group normalization to the input (arxiv.org/abs/1803.08494).\n\n    Args:\n      x: the input of shape ``...self.num_features`` where ``self.num_features``\n        is a channels dimension and ``...`` represents an arbitrary number of\n        extra dimensions that can be used to accumulate statistics over. If no\n        reduction axes have been specified then all additional dimensions ``...``\n        will be used to accumulate statistics apart from the leading dimension\n        which is assumed to represent the batch.\n      mask: Binary array of shape broadcastable to ``inputs`` tensor, indicating\n        the positions for which the mean and variance should be computed.\n\n    Returns:\n      Normalized inputs (the same shape as inputs).\n    \"\"\"\n    if self.reduction_axes is not None:\n      reduction_axes = self.reduction_axes\n    else:\n      reduction_axes = list(range(1, x.ndim - 1)) + [-1]\n    reduction_axes = _canonicalize_axes(x.ndim, reduction_axes)\n\n    group_shape = x.shape[:-1] + (self.num_groups, self.group_size)\n    if mask is not None:\n      mask = mask.reshape(mask.shape[:-1] + (self.num_groups, self.group_size))\n\n    # Promote dtypes for input and all Variables\n    scale = self.scale[...] if self.scale else None\n    bias = self.bias[...] if self.bias else None\n    x, scale, bias = self.promote_dtype(\n      (x, scale, bias), dtype=self.dtype\n    )\n    mean, var = _compute_stats(\n      x.reshape(group_shape),\n      list(reduction_axes[:-1]) + [-1],\n      self.dtype,\n      self.axis_name,\n      self.axis_index_groups,\n      use_fast_variance=self.use_fast_variance,\n      mask=mask,\n    )\n    mean = jnp.repeat(mean, self.group_size, axis=1)\n    var = jnp.repeat(var, self.group_size, axis=1)\n\n    return _normalize(\n      x,\n      mean,\n      var,\n      scale,\n      bias,\n      reduction_axes[:-1],\n      (self.feature_axis,),\n      self.dtype,\n      self.epsilon,\n    )\n\nclass WeightNorm(nnx.Module):\n  \"\"\"L2 weight normalization (https://arxiv.org/abs/1602.07868).\n\n  Weight normalization normalizes the weight params so that the l2-norm of\n  the matrix is equal to 1. This is implemented as a layer wrapper where\n  each wrapped layer will have its params l2-normalized before computing\n  its ``__call__`` output.\n\n  Example usage::\n\n    >>> import jax\n    >>> import numpy as np\n    >>> from flax import nnx\n\n    >>> class Foo(nnx.Module):\n    ...   def __init__(self, rngs: nnx.Rngs):\n    ...     self.normed_linear = nnx.WeightNorm(\n    ...       nnx.Linear(8, 4, rngs=rngs),\n    ...       variable_filter=nnx.PathContains('kernel'),\n    ...       rngs=rngs,\n    ...     )\n    ...\n    ...   def __call__(self, x: jax.Array) -> jax.Array:\n    ...     return self.normed_linear(x)\n\n    >>> rng = jax.random.key(42)\n    >>> model = Foo(rngs=nnx.Rngs(rng))\n\n    >>> x = jax.random.normal(rng, (5, 8))\n    >>> y = model(x)\n    >>> y.shape\n    (5, 4)\n\n    >>> w = model.normed_linear.layer_instance.kernel[...]\n    >>> col_norms = np.linalg.norm(np.array(w), axis=0)\n    >>> np.testing.assert_allclose(col_norms, np.ones(4))\n\n  Args:\n    layer_instance: The layer instance to wrap.\n    feature_axes: The axes to normalize.\n    use_scale: Whether to use a scale parameter.\n    scale_init: The initializer for the scale parameter, by default ones.\n    epsilon: The epsilon value for the normalization, by default 1e-12.\n    dtype: The dtype of the result, by default infer from input and params.\n    param_dtype: The dtype of the parameters, by default float32.\n    variable_filter: The variable filter, by default ``nnx.PathContains('kernel')``.\n    promote_dtype: function to promote the dtype of all input array arguments\n      (including Variables accessed through ``self``) to the desired dtype. This\n      is used internally by WeightNorm when normalizing weights.\n    rngs: The rng key.\n  \"\"\"\n  def __init__(\n    self,\n    layer_instance: nnx.Module,\n    *,\n    feature_axes: Axes | None = -1,\n    use_scale: bool = True,\n    scale_init: Initializer = initializers.ones,\n    epsilon: float = 1e-12,\n    dtype: tp.Optional[Dtype] = None,\n    param_dtype: Dtype = jnp.float32,\n    variable_filter: nnx.filterlib.Filter = nnx.PathContains('kernel'),\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    rngs: rnglib.Rngs,\n  ):\n    self.layer_instance = layer_instance\n    self.feature_axes = () if feature_axes is None else feature_axes\n    self.use_scale = use_scale\n    self.scale_init = scale_init\n    self.epsilon = epsilon\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.variable_filter = nnx.filterlib.to_predicate(variable_filter)\n    self.promote_dtype = promote_dtype\n    self.scales : tp.Optional[dict] = None\n\n    if use_scale:\n      state = nnx.state(self.layer_instance, nnx.Param)\n      def init_scales(param):\n        feature_axes = _canonicalize_axes(param.ndim, self.feature_axes)\n        scale_shape = tuple(param.shape[ax] for ax in feature_axes)\n        return scale_init(rngs['params'], scale_shape)\n      self.scales = nnx.data({\n        path: init_scales(param) for path, param in nnx.to_flat_state(state)\n        if self.variable_filter(path, param)})\n\n  def _weightnorm_inplace(self, path, param):\n    if not self.variable_filter(path, param):\n      return\n\n    if self.feature_axes is None:\n      feature_axes = ()\n      reduction_axes = tuple(range(param.ndim))\n    else:\n      feature_axes = _canonicalize_axes(param.ndim, self.feature_axes)\n      reduction_axes = tuple(\n        i for i in range(param.ndim) if i not in feature_axes\n      )\n\n    value_bar = _l2_normalize(param, axis=reduction_axes, eps=self.epsilon)\n\n    if self.use_scale:\n      if path not in self.scales:\n        raise RuntimeError(\n          f'Could not find the scale corresponding to the param {path} '\n          'in scales dict. Parameters of the layer_instance should not change!'\n        )\n      scale_value = self.scales[path]\n\n      if len(feature_axes) < param.ndim:\n        broadcast_shape = [1] * param.ndim\n        for ax in feature_axes:\n          broadcast_shape[ax] = param.shape[ax]\n        scale_value = scale_value.reshape(broadcast_shape)\n      value_bar = value_bar * scale_value\n\n    cast_args = [param]\n    if self.use_scale:\n      cast_args.append(scale_value)\n\n    final_dtype = dtypes.canonicalize_dtype(*cast_args, dtype=self.dtype)\n    param.set_value(jnp.asarray(value_bar, final_dtype))\n\n  def __call__(self, x: Array, *args, **kwargs) -> Array:\n    \"\"\"Compute the l2-norm of the weights in ``self.layer_instance``\n    and normalize the weights using this value before computing the\n    ``__call__`` output.\n\n    Args:\n      *args: positional arguments to be passed into the call method of the\n        underlying layer instance in ``self.layer_instance``.\n      **kwargs: keyword arguments to be passed into the call method of the\n        underlying layer instance in ``self.layer_instance``.\n\n    Returns:\n      Output of the layer using l2-normalized weights.\n    \"\"\"\n    state = nnx.state(self.layer_instance)\n    for path, param in nnx.to_flat_state(state):\n      self._weightnorm_inplace(path, param)\n\n    return self.layer_instance(x, *args, **kwargs)  # type: ignore\n\nclass InstanceNorm(Module):\n  \"\"\"Instance normalization (https://arxiv.org/abs/1607.08022v3).\n  InstanceNorm normalizes the activations of the layer for each channel (rather\n  than across all channels like Layer Normalization), and for each given example\n  in a batch independently (rather than across an entire batch like Batch\n  Normalization). i.e. applies a transformation that maintains the mean activation\n  within each channel within each example close to 0 and the activation standard\n  deviation close to 1.\n\n  .. note::\n    This normalization operation is identical to LayerNorm and GroupNorm; the\n    difference is simply which axes are reduced and the shape of the feature axes\n    (i.e. the shape of the learnable scale and bias parameters).\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax\n    >>> import numpy as np\n    >>> # dimensions: (batch, height, width, channel)\n    >>> x = jax.random.normal(jax.random.key(0), (2, 3, 4, 5))\n    >>> layer = nnx.InstanceNorm(5, rngs=nnx.Rngs(0))\n    >>> nnx.state(layer, nnx.Param)\n    State({\n      'bias': Param( # 5 (20 B)\n        value=Array([0., 0., 0., 0., 0.], dtype=float32)\n      ),\n      'scale': Param( # 5 (20 B)\n        value=Array([1., 1., 1., 1., 1.], dtype=float32)\n      )\n    })\n    >>> y = layer(x)\n    >>> # having a channel_axis of -1 in InstanceNorm is identical to reducing all non-batch,\n    >>> # non-channel axes and using the feature_axes as the feature_axes in LayerNorm\n    >>> y2 = nnx.LayerNorm(5, reduction_axes=[1, 2], feature_axes=-1, rngs=nnx.Rngs(0))(x)\n    >>> np.testing.assert_allclose(y, y2, atol=1e-7)\n    >>> y3 = nnx.GroupNorm(5, num_groups=x.shape[-1], rngs=nnx.Rngs(0))(x)\n    >>> np.testing.assert_allclose(y, y3, atol=1e-7)\n\n  Args:\n    num_features: the number of input features/channels.\n    epsilon: A small float added to variance to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    use_bias:  If True, bias (beta) is added.\n    use_scale: If True, multiply by scale (gamma). When the next layer is linear\n      (also e.g. nn.relu), this can be disabled since the scaling will be done\n      by the next layer.\n    bias_init: Initializer for bias, by default, zero.\n    scale_init: Initializer for scale, by default, one.\n    feature_axes: Axes for features. The learned bias and scaling parameters will\n      be in the shape defined by the feature axes. All other axes except the batch\n      axes (which is assumed to be the leading axis) will be reduced.\n    axis_name: the axis name used to combine batch statistics from multiple\n      devices. See ``jax.pmap`` for a description of axis names (default: None).\n      This is only needed if the model is subdivided across devices, i.e. the\n      array being normalized is sharded across devices within a pmap or shard\n      map. For SPMD jit, you do not need to manually synchronize. Just make sure\n      that the axes are correctly annotated and XLA:SPMD will insert the\n      necessary collectives.\n    axis_index_groups: groups of axis indices within that named axis\n      representing subsets of devices to reduce over (default: None). For\n      example, ``[[0, 1], [2, 3]]`` would independently batch-normalize over the\n      examples on the first two and last two devices. See ``jax.lax.psum`` for\n      more details. This argument is currently not supported for SPMD jit.\n    use_fast_variance: If true, use a faster, but less numerically stable,\n      calculation for the variance.\n    promote_dtype: function to promote the dtype of all input array arguments\n      (including Variables accessed through ``self``) to the desired dtype. The\n      function should accept a tuple of ``(inputs, scale, bias)`` and a ``dtype``\n      keyword argument, and return a tuple of arrays with the promoted dtype.\n    rngs: The rng key.\n    bias_metadata: Optional metadata dictionary to set when initializing\n      the bias.\n    scale_metadata: Optional metadata dictionary to set when initializing\n      the scale.\n  \"\"\"\n\n  def __init__(\n    self,\n    num_features: int,\n    *,\n    epsilon: float = 1e-6,\n    dtype: tp.Optional[Dtype] = None,\n    param_dtype: Dtype = jnp.float32,\n    use_bias: bool = True,\n    use_scale: bool = True,\n    bias_init: Initializer = initializers.zeros,\n    scale_init: Initializer = initializers.ones,\n    feature_axes: Axes = -1,\n    axis_name: tp.Optional[str] = None,\n    axis_index_groups: tp.Any = None,\n    use_fast_variance: bool = True,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    rngs: rnglib.Rngs,\n    bias_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n    scale_metadata: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  ):\n    feature_shape = (num_features,)\n    self.scale: nnx.Param[jax.Array] | None\n    if use_scale:\n      key = rngs.params()\n      self.scale = nnx.Param(scale_init(key, feature_shape, param_dtype), **scale_metadata)\n    else:\n      self.scale = None\n\n    self.bias: nnx.Param[jax.Array] | None\n    if use_bias:\n      key = rngs.params()\n      self.bias = nnx.Param(bias_init(key, feature_shape, param_dtype), **bias_metadata)\n    else:\n      self.bias = None\n\n    self.num_features = num_features\n    self.epsilon = epsilon\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.use_bias = use_bias\n    self.use_scale = use_scale\n    self.feature_axes = feature_axes\n    self.axis_name = axis_name\n    self.axis_index_groups = axis_index_groups\n    self.use_fast_variance = use_fast_variance\n    self.promote_dtype = promote_dtype\n\n  def __call__(self, x, *, mask: tp.Optional[jax.Array] = None):\n    \"\"\"Applies instance normalization on the input.\n\n    Args:\n      x: the inputs\n      mask: Binary array of shape broadcastable to ``inputs`` array, indicating\n        the positions for which the mean and variance should be computed.\n\n    Returns:\n      Normalized inputs (the same shape as inputs).\n    \"\"\"\n    feature_axes = _canonicalize_axes(x.ndim, self.feature_axes)\n    if 0 in feature_axes:\n      raise ValueError('The channel axes cannot include the leading dimension '\n                       'as this is assumed to be the batch axis.')\n    reduction_axes = [i for i in range(1, x.ndim) if i not in feature_axes]\n\n    # Promote dtypes for input and all Variables\n    scale = self.scale[...] if self.scale else None\n    bias = self.bias[...] if self.bias else None\n    x, scale, bias = self.promote_dtype(\n      (x, scale, bias), dtype=self.dtype\n    )\n    mean, var = _compute_stats(\n      x,\n      reduction_axes,\n      self.dtype,\n      self.axis_name,\n      self.axis_index_groups,\n      use_fast_variance=self.use_fast_variance,\n      mask=mask,\n    )\n\n    return _normalize(\n      x,\n      mean,\n      var,\n      scale,\n      bias,\n      reduction_axes,\n      feature_axes,\n      self.dtype,\n      self.epsilon,\n    )\n\n\nclass SpectralNorm(Module):\n  \"\"\"Spectral normalization.\n\n  See:\n\n  - https://arxiv.org/abs/1802.05957\n  - https://arxiv.org/abs/1805.08318\n  - https://arxiv.org/abs/1809.11096\n\n  Spectral normalization normalizes the weight params so that the spectral\n  norm of the matrix is equal to 1. This is implemented as a layer wrapper\n  where each wrapped layer will have its params spectral normalized before\n  computing its ``__call__`` output.\n\n  .. note::\n    The initialized variables dict will contain, in addition to a 'params'\n    collection, a separate 'batch_stats' collection that will contain a\n    ``u`` vector and ``sigma`` value, which are intermediate values used\n    when performing spectral normalization. During training, we pass in\n    ``update_stats=True`` so that ``u`` and ``sigma`` are updated with\n    the most recently computed values using power iteration. This will\n    help the power iteration method approximate the true singular value\n    more accurately over time. During eval, we pass in ``update_stats=False``\n    to ensure we get deterministic behavior from the model.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax\n    >>> rngs = nnx.Rngs(0)\n    >>> x = jax.random.normal(jax.random.key(0), (3, 4))\n    >>> layer = nnx.SpectralNorm(nnx.Linear(4, 5, rngs=rngs), rngs=rngs)\n    >>> jax.tree.map(jax.numpy.shape, nnx.state(layer, nnx.Param))\n    State({\n      'layer_instance': {\n        'bias': Param(\n          value=(5,)\n        ),\n        'kernel': Param(\n          value=(4, 5)\n        )\n      }\n    })\n    >>> y = layer(x, update_stats=True)\n\n  Args:\n    layer_instance: Module instance that is wrapped with SpectralNorm\n    n_steps: How many steps of power iteration to perform to approximate the\n      singular value of the weight params.\n    epsilon: A small float added to l2-normalization to avoid dividing by zero.\n    dtype: the dtype of the result (default: infer from input and params).\n    param_dtype: the dtype passed to parameter initializers (default: float32).\n    error_on_non_matrix: Spectral normalization is only defined on matrices. By\n      default, this module will return scalars unchanged and flatten\n      higher-order tensors in their leading dimensions. Setting this flag to\n      True will instead throw an error if a weight tensor with dimension greater\n      than 2 is used by the layer.\n    update_stats: if True, the stored batch statistics will be\n      used instead of computing the batch statistics on the input.\n    rngs: rng key.\n  \"\"\"\n\n  def __init__(\n    self,\n    layer_instance: Module,\n    *,\n    n_steps: int = 1,\n    epsilon: float = 1e-12,\n    dtype: tp.Optional[Dtype] = None,\n    param_dtype: Dtype = jnp.float32,\n    error_on_non_matrix: bool = False,\n    update_stats: bool = True,\n    rngs: rnglib.Rngs,\n  ):\n    self.layer_instance = layer_instance\n    self.n_steps = n_steps\n    self.epsilon = epsilon\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.error_on_non_matrix = error_on_non_matrix\n\n    # We define here self.use_running_average attribute to make\n    # .train() and .eval() work. These methods internally change\n    # self.use_running_average to False in train mode and\n    # to True in eval mode\n    # update_stats flag has the opposite logic.\n    self.use_running_average = not update_stats\n\n    # Initialize batch stat variables:\n    state = nnx.state(self.layer_instance, nnx.Param)\n\n    def init_batch_stats(path, param):\n      if param.ndim <= 1 or self.n_steps < 1:\n        return None\n      elif param.ndim > 2:\n        if self.error_on_non_matrix:\n          raise ValueError(\n            f'Layer instance parameter is {param.ndim}D but error_on_non_matrix is True'\n          )\n        else:\n          param = jnp.reshape(param, (-1, param.shape[-1]))\n\n      path_u = path + (\"u\", )\n      path_sigma = path + (\"sigma\", )\n\n      key = rngs.params()\n      return [\n        (\n          path_u,\n          nnx.BatchStat(\n            initializers.normal()(key, (1, param.shape[-1]), self.param_dtype))\n          ),\n        (\n          path_sigma,\n          nnx.BatchStat(initializers.ones(key, (), self.param_dtype)),\n        )\n      ]\n\n    batch_stats: dict[tuple, nnx.BatchStat] = {}\n    for path, param in nnx.to_flat_state(state):\n      batch_stats_per_param = init_batch_stats(path, param)\n      if batch_stats_per_param is None:\n        continue\n      for new_path, bstat in batch_stats_per_param:\n        batch_stats[new_path] = bstat\n\n    self.batch_stats = nnx.data(batch_stats)\n\n  def __call__(\n    self,\n    x,\n    update_stats: tp.Optional[bool] = None,\n  ):\n    \"\"\"Compute the largest singular value of the weights in ``self.layer_instance``\n    using power iteration and normalize the weights using this value before\n    computing the ``__call__`` output.\n\n    Args:\n      x: the input array of the nested layer\n      update_stats: if True, update the internal ``u`` vector and ``sigma``\n        value after computing their updated values using power iteration. This\n        will help the power iteration method approximate the true singular value\n        more accurately over time.\n\n    Returns:\n      Output of the layer using spectral normalized weights.\n    \"\"\"\n    update_stats = first_from(\n      update_stats,\n      not self.use_running_average,\n      error_msg=\"\"\"No `update_stats` argument was provided to SpectralNorm\n        as either a __call__ argument or __init__ argument.\"\"\",\n    )\n    state = nnx.state(self.layer_instance, nnx.Param)\n    for path, param in nnx.to_flat_state(state):\n      self._spectral_normalize_inplace(path, param, update_stats=update_stats)\n    return self.layer_instance(x)\n\n  def _spectral_normalize_inplace(self, path, orig_param, update_stats):\n    param = orig_param\n    param_shape = param.shape\n    if param.ndim <= 1 or self.n_steps < 1:\n      return\n    elif param.ndim > 2:\n      if self.error_on_non_matrix:\n        raise ValueError(\n          f'Layer instance parameter is {param.ndim}D but error_on_non_matrix is True'\n        )\n      else:\n        param = jnp.reshape(param, (-1, param.shape[-1]))\n\n    path_u = path + (\"u\", )\n    path_sigma = path + (\"sigma\", )\n\n    if path_u not in self.batch_stats:\n      raise RuntimeError(\n        f\"Could not find the path for u batch stat corresponding to the param {path} \"\n        \"in the batch stats dict. Parameters of the layer_instance should not change!\"\n      )\n    if path_sigma not in self.batch_stats:\n      raise RuntimeError(\n        f\"Could not find the path for sigma batch stat corresponding to the param {path} \"\n        \"in the batch stats dict. Parameters of the layer_instance should not change!\"\n      )\n\n    u = self.batch_stats[path_u][...]\n\n    for _ in range(self.n_steps):\n      v = _l2_normalize(jnp.matmul(u, param.T), eps=self.epsilon)\n      u = _l2_normalize(jnp.matmul(v, param), eps=self.epsilon)\n\n    u = lax.stop_gradient(u)\n    v = lax.stop_gradient(v)\n\n    sigma = jnp.matmul(jnp.matmul(v, param), u.T)[0, 0]\n    param = param / jnp.where(sigma != 0, sigma, 1)\n    param = param.reshape(param_shape)\n\n    if update_stats:\n      self.batch_stats[path_u][...] = u\n      self.batch_stats[path_sigma][...] = sigma\n\n    dtype = dtypes.canonicalize_dtype(param, u, v, sigma, dtype=self.dtype)\n    orig_param[...] = jnp.asarray(param, dtype)\n"
  },
  {
    "path": "flax/nnx/nn/recurrent.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"RNN modules for Flax.\"\"\"\nimport warnings\nfrom typing import Any, TypeVar\nfrom collections.abc import Mapping\nfrom types import MappingProxyType\nfrom collections.abc import Mapping\nfrom collections.abc import Callable\nfrom functools import partial\nfrom typing_extensions import Protocol\nfrom absl import logging\n\nimport jax\nimport jax.numpy as jnp\n\nfrom flax import nnx\nfrom flax.nnx import filterlib, rnglib\nfrom flax.nnx.module import Module\nfrom flax.nnx.nn import initializers, dtypes\nfrom flax.nnx.nn.linear import Linear\nfrom flax.nnx.nn.activations import sigmoid\nfrom flax.nnx.nn.activations import tanh\nfrom flax.nnx.transforms import iteration\nfrom flax.typing import Dtype, Initializer, PromoteDtypeFn, Shape\n\ndefault_kernel_init = initializers.lecun_normal()\ndefault_bias_init = initializers.zeros_init()\n\nA = TypeVar(\"A\")\nArray = jax.Array\nOutput = Any\nCarry = Any\n\nclass RNNCellBase(Module):\n    \"\"\"RNN cell base class.\"\"\"\n\n    def initialize_carry(\n      self,\n      input_shape: tuple[int, ...],\n      rngs: rnglib.Rngs | rnglib.RngStream | None = None,\n      carry_init: Initializer | None = None,\n    ) -> Carry:\n        \"\"\"Initialize the RNN cell carry.\n\n        Args:\n          input_shape: a tuple providing the shape of the input to the cell.\n          rngs: random number generator passed to the init_fn.\n          carry_init: optional carry initializer.\n\n        Returns:\n          An initialized carry for the given RNN cell.\n        \"\"\"\n        raise NotImplementedError\n\n    def __call__(\n        self,\n        carry: Carry,\n        inputs: Array\n    ) -> tuple[Carry, Array]:\n        \"\"\"Run the RNN cell.\n\n        Args:\n          carry: the hidden state of the RNN cell.\n          inputs: an ndarray with the input for the current time step.\n            All dimensions except the final are considered batch dimensions.\n\n        Returns:\n          A tuple with the new carry and the output.\n        \"\"\"\n        raise NotImplementedError\n\n    @property\n    def num_feature_axes(self) -> int:\n        \"\"\"Returns the number of feature axes of the RNN cell.\"\"\"\n        raise NotImplementedError\n\ndef modified_orthogonal(key: Array, shape: Shape, dtype: Dtype = jnp.float32) -> Array:\n    \"\"\"Modified orthogonal initializer for compatibility with half precision.\"\"\"\n    initializer = initializers.orthogonal()\n    return initializer(key, shape).astype(dtype)\n\nclass LSTMCell(RNNCellBase):\n  r\"\"\"LSTM cell.\n\n  The mathematical definition of the cell is as follows\n\n  .. math::\n      \\begin{array}{ll}\n      i = \\sigma(W_{ii} x + W_{hi} h + b_{hi}) \\\\\n      f = \\sigma(W_{if} x + W_{hf} h + b_{hf}) \\\\\n      g = \\tanh(W_{ig} x + W_{hg} h + b_{hg}) \\\\\n      o = \\sigma(W_{io} x + W_{ho} h + b_{ho}) \\\\\n      c' = f * c + i * g \\\\\n      h' = o * \\tanh(c') \\\\\n      \\end{array}\n\n  where x is the input, h is the output of the previous time step, and c is\n  the memory.\n  \"\"\"\n\n  def __init__(\n    self,\n    in_features: int,\n    hidden_features: int,\n    *,\n    gate_fn: Callable[..., Any] = sigmoid,\n    activation_fn: Callable[..., Any] = tanh,\n    kernel_init: Initializer = default_kernel_init,\n    recurrent_kernel_init: Initializer = modified_orthogonal,\n    bias_init: Initializer = initializers.zeros_init(),\n    dtype: Dtype | None = None,\n    param_dtype: Dtype = jnp.float32,\n    carry_init: Initializer | None = None,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    keep_rngs: bool = False,\n    rngs: rnglib.Rngs,\n    kernel_metadata: Mapping[str, Any] = MappingProxyType({}),\n    recurrent_kernel_metadata: Mapping[str, Any] = MappingProxyType({}),\n    bias_metadata: Mapping[str, Any] = MappingProxyType({}),\n  ):\n    self.in_features = in_features\n    self.hidden_features = hidden_features\n    self.gate_fn = gate_fn\n    self.activation_fn = activation_fn\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.promote_dtype = promote_dtype\n    self.rngs: rnglib.RngStream | None\n    if keep_rngs:\n      self.rngs = rngs.carry.fork()\n    else:\n      self.rngs = nnx.data(None)\n\n    # input and recurrent layers are summed so only one needs a bias.\n    dense_i = partial(\n      Linear,\n      in_features=in_features,\n      out_features=hidden_features,\n      use_bias=False,\n      kernel_init=kernel_init,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      promote_dtype=self.promote_dtype,\n      rngs=rngs,\n      kernel_metadata=kernel_metadata,\n    )\n\n    dense_h = partial(\n      Linear,\n      in_features=hidden_features,\n      out_features=hidden_features,\n      use_bias=True,\n      kernel_init=recurrent_kernel_init,\n      bias_init=bias_init,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      promote_dtype=self.promote_dtype,\n      rngs=rngs,\n      kernel_metadata=recurrent_kernel_metadata,\n      bias_metadata=bias_metadata,\n    )\n\n    self.ii = dense_i()\n    self.if_ = dense_i()\n    self.ig = dense_i()\n    self.io = dense_i()\n    self.hi = dense_h()\n    self.hf = dense_h()\n    self.hg = dense_h()\n    self.ho = dense_h()\n\n    if carry_init:\n      warnings.warn(\n        \"carry_init is provided in __init__. \"\n        \"Please, use carry_init argument in `initialize_carry` method instead to initialize the carry. \"\n        \"Otherwise, two instances with same configuration but different carry_init \"\n        \"functions will have different graphdefs.\"\n      )\n\n    self.carry_init = carry_init\n\n  def __call__(\n    self, carry: tuple[Array, Array], inputs: Array\n  ) -> tuple[tuple[Array, Array], Array]:  # type: ignore[override]\n    r\"\"\"A long short-term memory (LSTM) cell.\n\n    Args:\n      carry: the hidden state of the LSTM cell,\n        initialized using ``LSTMCell.initialize_carry``.\n      inputs: an ndarray with the input for the current time step.\n        All dimensions except the final are considered batch dimensions.\n\n    Returns:\n      A tuple with the new carry and the output.\n    \"\"\"\n    c, h = carry\n    i = self.gate_fn(self.ii(inputs) + self.hi(h))\n    f = self.gate_fn(self.if_(inputs) + self.hf(h))\n    g = self.activation_fn(self.ig(inputs) + self.hg(h))\n    o = self.gate_fn(self.io(inputs) + self.ho(h))\n    new_c = f * c + i * g\n    new_h = o * self.activation_fn(new_c)\n    return (new_c, new_h), new_h\n\n  def initialize_carry(\n    self,\n    input_shape: tuple[int, ...],\n    rngs: rnglib.Rngs | rnglib.RngStream | None = None,\n    carry_init: Initializer | None = None,\n  ) -> tuple[Array, Array]:  # type: ignore[override]\n    \"\"\"Initialize the RNN cell carry.\n\n    Args:\n      rng: random number generator passed to the init_fn.\n      input_shape: a tuple providing the shape of the input to the cell.\n    Returns:\n      An initialized carry for the given RNN cell.\n    \"\"\"\n    batch_dims = input_shape[:-1]\n    if rngs is None:\n      rngs = self.rngs\n    if isinstance(rngs, rnglib.Rngs):\n      rngs = rngs.carry\n    if rngs is None:\n      raise ValueError('RNGs must be provided to initialize the cell carry.')\n\n    if self.carry_init is None and carry_init is None:\n      carry_init = initializers.zeros_init()\n    elif carry_init is None:\n      carry_init = self.carry_init\n    assert carry_init is not None  # just to please mypy\n\n    mem_shape = batch_dims + (self.hidden_features,)\n    c = carry_init(rngs(), mem_shape, self.param_dtype)\n    h = carry_init(rngs(), mem_shape, self.param_dtype)\n    return (c, h)\n\n  @property\n  def num_feature_axes(self) -> int:\n    return 1\n\n\nclass OptimizedLSTMCell(RNNCellBase):\n  r\"\"\"More efficient LSTM Cell that concatenates state components before matmul.\n\n    The parameters are compatible with ``LSTMCell``. Note that this cell is often\n    faster than ``LSTMCell`` as long as the hidden size is roughly <= 2048 units.\n\n    The mathematical definition of the cell is the same as ``LSTMCell`` and as\n    follows:\n\n    .. math::\n\n        \\begin{array}{ll}\n        i = \\sigma(W_{ii} x + W_{hi} h + b_{hi}) \\\\\n        f = \\sigma(W_{if} x + W_{hf} h + b_{hf}) \\\\\n        g = \\tanh(W_{ig} x + W_{hg} h + b_{hg}) \\\\\n        o = \\sigma(W_{io} x + W_{ho} h + b_{ho}) \\\\\n        c' = f * c + i * g \\\\\n        h' = o * \\tanh(c') \\\\\n        \\end{array}\n\n    where x is the input, h is the output of the previous time step, and c is\n    the memory.\n\n    Args:\n        gate_fn: activation function used for gates (default: sigmoid).\n        activation_fn: activation function used for output and memory update\n          (default: tanh).\n        kernel_init: initializer function for the kernels that transform\n          the input (default: lecun_normal).\n        recurrent_kernel_init: initializer function for the kernels that transform\n          the hidden state (default: initializers.orthogonal()).\n        bias_init: initializer for the bias parameters (default: initializers.zeros_init()).\n        dtype: the dtype of the computation (default: infer from inputs and params).\n        param_dtype: the dtype passed to parameter initializers (default: float32).\n        keep_rngs: whether to store the input rngs as attribute (i.e. `self.rngs = rngs`)\n          (default: True). If rngs is stored, we should split the module as\n          `graphdef, params, nondiff = nnx.split(module, nnx.Param, ...)` where `nondiff`\n          contains RNG object associated with stored `self.rngs`.\n        rngs: rng key.\n        kernel_metadata: Optional metadata dictionary to set when initializing\n          the kernels that transform the input.\n        recurrent_kernel_metadata: Optional metadata dictionary to set when initializing\n          the kernels that transform the hidden state.\n        bias_metadata: Optional metadata dictionary to set when initializing\n          the bias of layers that transform the hidden state.\n    \"\"\"\n\n  def __init__(\n    self,\n    in_features: int,\n    hidden_features: int,\n    *,\n    gate_fn: Callable[..., Any] = sigmoid,\n    activation_fn: Callable[..., Any] = tanh,\n    kernel_init: Initializer = default_kernel_init,\n    recurrent_kernel_init: Initializer = initializers.orthogonal(),\n    bias_init: Initializer = initializers.zeros_init(),\n    dtype: Dtype | None = None,\n    param_dtype: Dtype = jnp.float32,\n    carry_init: Initializer | None = None,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    keep_rngs: bool = False,\n    rngs: rnglib.Rngs,\n    kernel_metadata: Mapping[str, Any] = MappingProxyType({}),\n    recurrent_kernel_metadata: Mapping[str, Any] = MappingProxyType({}),\n    bias_metadata: Mapping[str, Any] = MappingProxyType({}),\n  ):\n    self.in_features = in_features\n    self.hidden_features = hidden_features\n    self.gate_fn = gate_fn\n    self.activation_fn = activation_fn\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.promote_dtype = promote_dtype\n    self.rngs: rnglib.RngStream | None\n    if keep_rngs:\n      self.rngs = rngs.carry.fork()\n    else:\n      self.rngs = nnx.data(None)\n\n    # input and recurrent layers are summed so only one needs a bias.\n    self.dense_i = Linear(\n      in_features=in_features,\n      out_features=4 * hidden_features,\n      use_bias=False,\n      kernel_init=kernel_init,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      promote_dtype=self.promote_dtype,\n      rngs=rngs,\n      kernel_metadata=kernel_metadata,\n    )\n\n    self.dense_h = Linear(\n      in_features=hidden_features,\n      out_features=4 * hidden_features,\n      use_bias=True,\n      kernel_init=recurrent_kernel_init,\n      bias_init=bias_init,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      promote_dtype=self.promote_dtype,\n      rngs=rngs,\n      kernel_metadata=recurrent_kernel_metadata,\n      bias_metadata=bias_metadata,\n    )\n\n    if carry_init:\n      warnings.warn(\n        \"carry_init is provided in __init__. \"\n        \"Please, use carry_init argument in `initialize_carry` method instead to initialize the carry. \"\n        \"Otherwise, two instances with same configuration but different carry_init \"\n        \"functions will have different graphdefs.\"\n      )\n\n    self.carry_init = carry_init\n\n  def __call__(\n    self, carry: tuple[Array, Array], inputs: Array\n  ) -> tuple[tuple[Array, Array], Array]:  # type: ignore[override]\n    r\"\"\"An optimized long short-term memory (LSTM) cell.\n\n    Args:\n      carry: the hidden state of the LSTM cell, initialized using\n        ``LSTMCell.initialize_carry``.\n      inputs: an ndarray with the input for the current time step.\n        All dimensions except the final are considered batch dimensions.\n\n    Returns:\n      A tuple with the new carry and the output.\n    \"\"\"\n    c, h = carry\n\n    # Compute combined transformations for inputs and hidden state\n    y = self.dense_i(inputs) + self.dense_h(h)\n\n    # Split the combined transformations into individual gates\n    i, f, g, o = jnp.split(y, indices_or_sections=4, axis=-1)\n\n    # Apply gate activations\n    i = self.gate_fn(i)\n    f = self.gate_fn(f)\n    g = self.activation_fn(g)\n    o = self.gate_fn(o)\n\n    # Update cell state and hidden state\n    new_c = f * c + i * g\n    new_h = o * self.activation_fn(new_c)\n    return (new_c, new_h), new_h\n\n  def initialize_carry(\n    self,\n    input_shape: tuple[int, ...],\n    rngs: rnglib.Rngs | rnglib.RngStream | None = None,\n    carry_init: Initializer | None = None,\n  ) -> tuple[Array, Array]:  # type: ignore[override]\n    \"\"\"Initialize the RNN cell carry.\n\n    Args:\n      rngs: random number generator passed to the init_fn.\n      input_shape: a tuple providing the shape of the input to the cell.\n\n    Returns:\n      An initialized carry for the given RNN cell.\n    \"\"\"\n    batch_dims = input_shape[:-1]\n    if rngs is None:\n      rngs = self.rngs\n    if isinstance(rngs, rnglib.Rngs):\n      rngs = rngs.carry\n    if rngs is None:\n      raise ValueError('RNGs must be provided to initialize the cell carry.')\n    mem_shape = batch_dims + (self.hidden_features,)\n\n    if self.carry_init is None and carry_init is None:\n      carry_init = initializers.zeros_init()\n    elif carry_init is None:\n      carry_init = self.carry_init\n    assert carry_init is not None  # just to please mypy\n\n    c = carry_init(rngs(), mem_shape, self.param_dtype)\n    h = carry_init(rngs(), mem_shape, self.param_dtype)\n    return (c, h)\n\n  @property\n  def num_feature_axes(self) -> int:\n    return 1\n\n\nclass SimpleCell(RNNCellBase):\n  r\"\"\"Simple cell.\n\n  The mathematical definition of the cell is as follows\n\n  .. math::\n\n      \\begin{array}{ll}\n      h' = \\tanh(W_i x + b_i + W_h h)\n      \\end{array}\n\n  where x is the input and h is the output of the previous time step.\n\n  If `residual` is `True`,\n\n  .. math::\n\n      \\begin{array}{ll}\n      h' = \\tanh(W_i x + b_i + W_h h + h)\n      \\end{array}\n  \"\"\"\n\n  def __init__(\n    self,\n    in_features: int,\n    hidden_features: int,  # not inferred from carry for now\n    *,\n    dtype: Dtype = jnp.float32,\n    param_dtype: Dtype = jnp.float32,\n    carry_init: Initializer | None = None,\n    residual: bool = False,\n    activation_fn: Callable[..., Any] = tanh,\n    kernel_init: Initializer = initializers.lecun_normal(),\n    recurrent_kernel_init: Initializer = initializers.orthogonal(),\n    bias_init: Initializer = initializers.zeros_init(),\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    keep_rngs: bool = False,\n    rngs: rnglib.Rngs,\n    kernel_metadata: Mapping[str, Any] = MappingProxyType({}),\n    recurrent_kernel_metadata: Mapping[str, Any] = MappingProxyType({}),\n    bias_metadata: Mapping[str, Any] = MappingProxyType({}),\n  ):\n    self.in_features = in_features\n    self.hidden_features = hidden_features\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.residual = residual\n    self.activation_fn = activation_fn\n    self.promote_dtype = promote_dtype\n    self.rngs: rnglib.RngStream | None\n    if keep_rngs:\n      self.rngs = rngs.carry.fork()\n    else:\n      self.rngs = nnx.data(None)\n\n    # self.hidden_features = carry.shape[-1]\n    # input and recurrent layers are summed so only one needs a bias.\n    self.dense_h = Linear(\n      in_features=self.hidden_features,\n      out_features=self.hidden_features,\n      use_bias=False,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      kernel_init=recurrent_kernel_init,\n      promote_dtype=self.promote_dtype,\n      rngs=rngs,\n      kernel_metadata=recurrent_kernel_metadata,\n    )\n    self.dense_i = Linear(\n      in_features=self.in_features,\n      out_features=self.hidden_features,\n      use_bias=True,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      kernel_init=kernel_init,\n      bias_init=bias_init,\n      promote_dtype=self.promote_dtype,\n      rngs=rngs,\n      kernel_metadata=kernel_metadata,\n      bias_metadata=bias_metadata,\n    )\n\n    if carry_init:\n      warnings.warn(\n        \"carry_init is provided in __init__. \"\n        \"Please, use carry_init argument in `initialize_carry` method instead to initialize the carry. \"\n        \"Otherwise, two instances with same configuration but different carry_init \"\n        \"functions will have different graphdefs.\"\n      )\n\n    self.carry_init = carry_init\n\n  def __call__(self, carry: Array, inputs: Array) -> tuple[Array, Array]:  # type: ignore[override]\n    new_carry = self.dense_i(inputs) + self.dense_h(carry)\n    if self.residual:\n      new_carry += carry\n    new_carry = self.activation_fn(new_carry)\n    return new_carry, new_carry\n\n  def initialize_carry(\n    self,\n    input_shape: tuple[int, ...],\n    rngs: rnglib.Rngs | rnglib.RngStream | None = None,\n    carry_init: Initializer | None = None,\n  ) -> Array:  # type: ignore[override]\n    \"\"\"Initialize the RNN cell carry.\n\n    Args:\n      rng: random number generator passed to the init_fn.\n      input_shape: a tuple providing the shape of the input to the cell.\n\n    Returns:\n      An initialized carry for the given RNN cell.\n    \"\"\"\n    if rngs is None:\n      rngs = self.rngs\n    if isinstance(rngs, rnglib.Rngs):\n      rngs = rngs.carry\n    if rngs is None:\n      raise ValueError('RNGs must be provided to initialize the cell carry.')\n\n    batch_dims = input_shape[:-1]\n    mem_shape = batch_dims + (self.hidden_features,)\n\n    if self.carry_init is None and carry_init is None:\n      carry_init = initializers.zeros_init()\n    elif carry_init is None:\n      carry_init = self.carry_init\n    assert carry_init is not None  # just to please mypy\n\n    return carry_init(rngs(), mem_shape, self.param_dtype)\n\n  @property\n  def num_feature_axes(self) -> int:\n    return 1\n\n\nclass GRUCell(RNNCellBase):\n  r\"\"\"GRU cell.\n\n    The mathematical definition of the cell is as follows\n\n    .. math::\n\n        \\begin{array}{ll}\n        r = \\sigma(W_{ir} x + b_{ir} + W_{hr} h) \\\\\n        z = \\sigma(W_{iz} x + b_{iz} + W_{hz} h) \\\\\n        n = \\tanh(W_{in} x + b_{in} + r * (W_{hn} h + b_{hn})) \\\\\n        h' = (1 - z) * n + z * h \\\\\n        \\end{array}\n\n    where x is the input and h is the output of the previous time step.\n\n    Args:\n        in_features: number of input features.\n        hidden_features: number of output features.\n        gate_fn: activation function used for gates (default: sigmoid).\n        activation_fn: activation function used for output and memory update\n          (default: tanh).\n        kernel_init: initializer function for the kernels that transform\n          the input (default: lecun_normal).\n        recurrent_kernel_init: initializer function for the kernels that transform\n          the hidden state (default: initializers.orthogonal()).\n        bias_init: initializer for the bias parameters (default: initializers.zeros_init()).\n        dtype: the dtype of the computation (default: None).\n        param_dtype: the dtype passed to parameter initializers (default: float32).\n        keep_rngs: whether to store the input rngs as attribute (i.e. `self.rngs = rngs`)\n          (default: True). If rngs is stored, we should split the module as\n          `graphdef, params, nondiff = nnx.split(module, nnx.Param, ...)` where `nondiff`\n          contains RNG object associated with stored `self.rngs`.\n        rngs: rng key.\n        kernel_metadata: Optional metadata dictionary to set when initializing\n          the kernels that transform the input.\n        recurrent_kernel_metadata: Optional metadata dictionary to set when initializing\n          the kernels that transform the hidden state.\n        bias_metadata: Optional metadata dictionary to set when initializing\n          the bias of layers that transform the input.\n    \"\"\"\n\n  def __init__(\n    self,\n    in_features: int,\n    hidden_features: int,\n    *,\n    gate_fn: Callable[..., Any] = sigmoid,\n    activation_fn: Callable[..., Any] = tanh,\n    kernel_init: Initializer = default_kernel_init,\n    recurrent_kernel_init: Initializer = initializers.orthogonal(),\n    bias_init: Initializer = initializers.zeros_init(),\n    dtype: Dtype | None = None,\n    param_dtype: Dtype = jnp.float32,\n    carry_init: Initializer | None = None,\n    promote_dtype: PromoteDtypeFn = dtypes.promote_dtype,\n    keep_rngs: bool = False,\n    rngs: rnglib.Rngs,\n    kernel_metadata: Mapping[str, Any] = MappingProxyType({}),\n    recurrent_kernel_metadata: Mapping[str, Any] = MappingProxyType({}),\n    bias_metadata: Mapping[str, Any] = MappingProxyType({}),\n  ):\n    self.in_features = in_features\n    self.hidden_features = hidden_features\n    self.gate_fn = gate_fn\n    self.activation_fn = activation_fn\n    self.dtype = dtype\n    self.param_dtype = param_dtype\n    self.promote_dtype = promote_dtype\n    self.rngs: rnglib.RngStream | None\n    if keep_rngs:\n      self.rngs = rngs.carry.fork()\n    else:\n      self.rngs = nnx.data(None)\n\n    # Combine input transformations into a single linear layer\n    self.dense_i = Linear(\n      in_features=in_features,\n      out_features=3 * hidden_features,  # r, z, n\n      use_bias=True,\n      kernel_init=kernel_init,\n      bias_init=bias_init,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      promote_dtype=self.promote_dtype,\n      rngs=rngs,\n      kernel_metadata=kernel_metadata,\n      bias_metadata=bias_metadata,\n    )\n\n    self.dense_h = Linear(\n      in_features=hidden_features,\n      out_features=3 * hidden_features,  # r, z, n\n      use_bias=False,\n      kernel_init=recurrent_kernel_init,\n      dtype=self.dtype,\n      param_dtype=self.param_dtype,\n      promote_dtype=self.promote_dtype,\n      rngs=rngs,\n      kernel_metadata=recurrent_kernel_metadata,\n    )\n\n    if carry_init:\n      warnings.warn(\n        \"carry_init is provided in __init__. \"\n        \"Please, use carry_init argument in `initialize_carry` method instead to initialize the carry. \"\n        \"Otherwise, two instances with same configuration but different carry_init \"\n        \"functions will have different graphdefs.\"\n      )\n\n    self.carry_init = carry_init\n\n  def __call__(self, carry: Array, inputs: Array) -> tuple[Array, Array]:  # type: ignore[override]\n    \"\"\"Gated recurrent unit (GRU) cell.\n\n    Args:\n        carry: the hidden state of the GRU cell,\n          initialized using ``GRUCell.initialize_carry``.\n        inputs: an ndarray with the input for the current time step.\n          All dimensions except the final are considered batch dimensions.\n\n    Returns:\n        A tuple with the new carry and the output.\n    \"\"\"\n    h = carry\n\n    # Compute combined transformations for inputs and hidden state\n    x_transformed = self.dense_i(inputs)\n    h_transformed = self.dense_h(h)\n\n    # Split the combined transformations into individual components\n    xi_r, xi_z, xi_n = jnp.split(x_transformed, 3, axis=-1)\n    hh_r, hh_z, hh_n = jnp.split(h_transformed, 3, axis=-1)\n\n    # Compute gates\n    r = self.gate_fn(xi_r + hh_r)\n    z = self.gate_fn(xi_z + hh_z)\n\n    # Compute n with an additional linear transformation on h\n    n = self.activation_fn(xi_n + r * hh_n)\n\n    # Update hidden state\n    new_h = (1.0 - z) * n + z * h\n    return new_h, new_h\n\n  def initialize_carry(\n    self,\n    input_shape: tuple[int, ...],\n    rngs: rnglib.Rngs | rnglib.RngStream | None = None,\n    carry_init: Initializer | None = None,\n  ) -> Array:  # type: ignore[override]\n    \"\"\"Initialize the RNN cell carry.\n\n    Args:\n        rngs: random number generator passed to the init_fn.\n        input_shape: a tuple providing the shape of the input to the cell.\n\n    Returns:\n        An initialized carry for the given RNN cell.\n    \"\"\"\n    batch_dims = input_shape[:-1]\n    if rngs is None:\n      rngs = self.rngs\n    if isinstance(rngs, rnglib.Rngs):\n      rngs = rngs.carry\n    if rngs is None:\n      raise ValueError('RNGs must be provided to initialize the cell carry.')\n\n    mem_shape = batch_dims + (self.hidden_features,)\n\n    if self.carry_init is None and carry_init is None:\n      carry_init = initializers.zeros_init()\n    elif carry_init is None:\n      carry_init = self.carry_init\n    assert carry_init is not None  # just to please mypy\n\n    h = carry_init(rngs(), mem_shape, self.param_dtype)\n    return h\n\n  @property\n  def num_feature_axes(self) -> int:\n    return 1\n\n\nclass RNN(Module):\n  \"\"\"The ``RNN`` module takes any :class:`RNNCellBase` instance and applies it over a sequence\n\n  using :func:`flax.nnx.scan`.\n  \"\"\"\n\n  state_axes: dict[str, int | type[iteration.Carry] | None]\n\n  def __init__(\n    self,\n    cell: RNNCellBase,\n    *,\n    time_major: bool = False,\n    return_carry: bool = False,\n    reverse: bool = False,\n    keep_order: bool = False,\n    unroll: int = 1,\n    state_axes: Mapping[str, int | type[iteration.Carry] | None] | None = None,\n    broadcast_rngs: filterlib.Filter = None,\n    rngs: rnglib.Rngs | rnglib.RngStream | bool = True,\n  ):\n    self.cell = cell\n    self.time_major = time_major\n    self.return_carry = return_carry\n    self.reverse = reverse\n    self.keep_order = keep_order\n    self.unroll = unroll\n    self.rngs: rnglib.RngStream | None\n    if rngs is True:\n      self.rngs = rnglib.RngStream(0, tag='carry')\n    elif isinstance(rngs, rnglib.Rngs):\n      self.rngs = rngs.carry.fork()\n    elif rngs is False:\n      self.rngs = nnx.data(None)\n    else:\n      raise ValueError(\n        'Expected rngs to be a jax.Array, int, Rngs, or bool. '\n        f'Got {type(rngs)}.'\n      )\n    self.state_axes = state_axes or nnx.StateAxes({...: iteration.Carry})  # type: ignore\n    self.broadcast_rngs = broadcast_rngs\n\n  def __call__(\n    self,\n    inputs: Array,\n    *,\n    initial_carry: Carry | None = None,\n    seq_lengths: Array | None = None,\n    return_carry: bool | None = None,\n    time_major: bool | None = None,\n    reverse: bool | None = None,\n    keep_order: bool | None = None,\n    rngs: rnglib.Rngs | rnglib.RngStream | None = None,\n  ):\n    if return_carry is None:\n      return_carry = self.return_carry\n    if time_major is None:\n      time_major = self.time_major\n    if reverse is None:\n      reverse = self.reverse\n    if keep_order is None:\n      keep_order = self.keep_order\n\n    # Infer the number of batch dimensions from the input shape.\n    # Cells like ConvLSTM have additional spatial dimensions.\n    time_axis = 0 if time_major else inputs.ndim - (self.cell.num_feature_axes + 1)\n\n    # make time_axis positive\n    if time_axis < 0:\n      time_axis += inputs.ndim\n\n    if time_major:\n      # we add +1 because we moved the time axis to the front\n      batch_dims = inputs.shape[1 : -self.cell.num_feature_axes]\n    else:\n      batch_dims = inputs.shape[:time_axis]\n\n    # maybe reverse the sequence\n    if reverse:\n      inputs = jax.tree_util.tree_map(\n        lambda x: flip_sequences(\n          x,\n          seq_lengths,\n          num_batch_dims=len(batch_dims),\n          time_major=time_major,  # type: ignore\n        ),\n        inputs,\n      )\n    if rngs is None:\n      rngs = self.rngs\n    if isinstance(rngs, rnglib.Rngs):\n      rngs = rngs.carry.fork()\n\n    carry: Carry = (\n      self.cell.initialize_carry(\n        inputs.shape[:time_axis] + inputs.shape[time_axis + 1 :], rngs\n      )\n      if initial_carry is None\n      else initial_carry\n    )\n\n    slice_carry = seq_lengths is not None and return_carry\n    broadcast_rngs = nnx.All(nnx.RngState, self.broadcast_rngs)\n    state_axes = iteration.StateAxes({broadcast_rngs: None, **self.state_axes})  # type: ignore[misc]\n\n    # we use split_rngs with splits=1 and squeeze=True to get unique rngs\n    # every time RNN is called\n    @nnx.split_rngs(splits=1, only=self.broadcast_rngs, squeeze=True)\n    @nnx.scan(\n      in_axes=(state_axes, iteration.Carry, time_axis),\n      out_axes=(iteration.Carry, (0, time_axis))\n      if slice_carry\n      else (iteration.Carry, time_axis),\n      unroll=self.unroll,\n    )\n    def scan_fn(\n      cell: RNNCellBase, carry: Carry, x: Array\n    ) -> tuple[Carry, Array] | tuple[Carry, tuple[Carry, Array]]:\n      carry, y = cell(carry, x)\n      if slice_carry:\n        return carry, (carry, y)\n      return carry, y\n\n    scan_output = scan_fn(self.cell, carry, inputs)\n\n    # Next we select the final carry. If a segmentation mask was provided and\n    # return_carry is True we slice the carry history and select the last valid\n    # carry for each sequence. Otherwise we just use the last carry.\n    if slice_carry:\n      assert seq_lengths is not None\n      _, (carries, outputs) = scan_output\n      # seq_lengths[None] expands the shape of the mask to match the\n      # number of dimensions of the carry.\n      carry = _select_last_carry(carries, seq_lengths)\n    else:\n      carry, outputs = scan_output\n\n    if reverse and keep_order:\n      outputs = jax.tree_util.tree_map(\n                lambda x: flip_sequences(\n                    x,\n                    seq_lengths,\n                    num_batch_dims=len(batch_dims),\n                    time_major=time_major,  # type: ignore\n                ),\n                outputs,\n            )\n\n    if return_carry:\n      return carry, outputs\n    else:\n      return outputs\n\n\ndef _select_last_carry(sequence: A, seq_lengths: jnp.ndarray) -> A:\n    last_idx = seq_lengths - 1\n\n    def _slice_array(x: jnp.ndarray):\n        return x[last_idx, jnp.arange(x.shape[1])]\n\n    return jax.tree_util.tree_map(_slice_array, sequence)\n\n\ndef _expand_dims_like(x, target):\n    \"\"\"Expands the shape of `x` to match `target`'s shape by adding singleton dimensions.\"\"\"\n    return x.reshape(list(x.shape) + [1] * (target.ndim - x.ndim))\n\n\ndef flip_sequences(\n    inputs: Array,\n    seq_lengths: Array | None,\n    num_batch_dims: int,\n    time_major: bool,\n) -> Array:\n    \"\"\"Flips a sequence of inputs along the time axis.\n\n    This function can be used to prepare inputs for the reverse direction of a\n    bidirectional LSTM. It solves the issue that, when naively flipping multiple\n    padded sequences stored in a matrix, the first elements would be padding\n    values for those sequences that were padded. This function keeps the padding\n    at the end, while flipping the rest of the elements.\n\n    Example::\n\n      >>> from flax.nnx.nn.recurrent import flip_sequences\n      >>> from jax import numpy as jnp\n      >>> inputs = jnp.array([[1, 0, 0], [2, 3, 0], [4, 5, 6]])\n      >>> lengths = jnp.array([1, 2, 3])\n      >>> flip_sequences(inputs, lengths, 1, False)\n      Array([[1, 0, 0],\n             [3, 2, 0],\n             [6, 5, 4]], dtype=int32)\n\n\n    Args:\n      inputs: An array of input IDs <int>[batch_size, seq_length].\n      lengths: The length of each sequence <int>[batch_size].\n\n    Returns:\n      An ndarray with the flipped inputs.\n    \"\"\"\n    # Compute the indices to put the inputs in flipped order as per above example.\n    time_axis = 0 if time_major else num_batch_dims\n    max_steps = inputs.shape[time_axis]\n\n    if seq_lengths is None:\n        # reverse inputs and return\n        inputs = jnp.flip(inputs, axis=time_axis)\n        return inputs\n\n    seq_lengths = jnp.expand_dims(seq_lengths, axis=time_axis)\n\n    # create indexes\n    idxs = jnp.arange(max_steps - 1, -1, -1)  # [max_steps]\n    if time_major:\n        idxs = jnp.reshape(idxs, [max_steps] + [1] * num_batch_dims)\n    else:\n        idxs = jnp.reshape(\n            idxs, [1] * num_batch_dims + [max_steps]\n        )  # [1, ..., max_steps]\n    idxs = (idxs + seq_lengths) % max_steps  # [*batch, max_steps]\n    idxs = _expand_dims_like(idxs, target=inputs)  # [*batch, max_steps, *features]\n    # Select the inputs in flipped order.\n    outputs = jnp.take_along_axis(inputs, idxs, axis=time_axis)\n\n    return outputs\n\n\ndef _concatenate(a: Array, b: Array) -> Array:\n    \"\"\"Concatenates two arrays along the last dimension.\"\"\"\n    return jnp.concatenate([a, b], axis=-1)\n\n\nclass RNNBase(Protocol):\n  def __call__(\n    self,\n    inputs: Array,\n    *,\n    initial_carry: Carry | None = None,\n    rngs: rnglib.Rngs | rnglib.RngStream | None = None,\n    seq_lengths: Array | None = None,\n    return_carry: bool | None = None,\n    time_major: bool | None = None,\n    reverse: bool | None = None,\n    keep_order: bool | None = None,\n  ) -> Output | tuple[Carry, Output]: ...\n\n\nclass Bidirectional(Module):\n  \"\"\"Processes the input in both directions and merges the results.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax\n    >>> import jax.numpy as jnp\n\n    >>> # Define forward and backward RNNs\n    >>> forward_rnn = RNN(GRUCell(in_features=3, hidden_features=4, rngs=nnx.Rngs(0)))\n    >>> backward_rnn = RNN(GRUCell(in_features=3, hidden_features=4, rngs=nnx.Rngs(0)))\n\n    >>> # Create Bidirectional layer\n    >>> layer = Bidirectional(forward_rnn=forward_rnn, backward_rnn=backward_rnn)\n\n    >>> # Input data\n    >>> x = jnp.ones((2, 3, 3))\n\n    >>> # Apply the layer\n    >>> out = layer(x)\n    >>> print(out.shape)\n    (2, 3, 8)\n\n  \"\"\"\n\n  forward_rnn: RNNBase\n  backward_rnn: RNNBase\n  merge_fn: Callable[[Array, Array], Array] = _concatenate\n  time_major: bool = False\n  return_carry: bool = False\n\n  def __init__(\n    self,\n    forward_rnn: RNNBase,\n    backward_rnn: RNNBase,\n    *,\n    merge_fn: Callable[[Array, Array], Array] = _concatenate,\n    time_major: bool = False,\n    return_carry: bool = False,\n    rngs: rnglib.Rngs | rnglib.RngStream | bool = True,\n  ):\n    self.forward_rnn = forward_rnn\n    self.backward_rnn = backward_rnn\n    self.merge_fn = merge_fn\n    self.time_major = time_major\n    self.return_carry = return_carry\n    self.rngs: rnglib.RngStream | None\n    if rngs is True:\n      self.rngs = rnglib.RngStream(0, tag='carry')\n    elif rngs is False:\n      self.rngs = None\n    elif isinstance(rngs, rnglib.Rngs):\n      self.rngs = rngs.carry.fork()\n    elif isinstance(rngs, rnglib.RngStream):\n      self.rngs = rngs\n    else:\n      raise TypeError(\n        f'rngs must be a Rngs, jax.Array, int, or bool, but got {type(rngs)}.'\n      )\n\n  def __call__(\n    self,\n    inputs: Array,\n    *,\n    initial_carry: tuple[Carry, Carry] | None = None,\n    rngs: rnglib.Rngs | rnglib.RngStream | None = None,\n    seq_lengths: Array | None = None,\n    return_carry: bool | None = None,\n    time_major: bool | None = None,\n    reverse: bool | None = None,  # unused\n    keep_order: bool | None = None,  # unused\n  ) -> Output | tuple[tuple[Carry, Carry], Output]:\n    if time_major is None:\n      time_major = self.time_major\n    if return_carry is None:\n      return_carry = self.return_carry\n    if rngs is None:\n      rngs = self.rngs\n    if isinstance(rngs, rnglib.Rngs):\n      rngs = rngs.carry\n\n    if initial_carry is not None:\n      initial_carry_forward, initial_carry_backward = initial_carry\n    else:\n      initial_carry_forward = None\n      initial_carry_backward = None\n    # Throw a warning in case the user accidentally re-uses the forward RNN\n    # for the backward pass and does not intend for them to share parameters.\n    if self.forward_rnn is self.backward_rnn:\n      logging.warning(\n        'forward_rnn and backward_rnn is the same object, so '\n        'they will share parameters.'\n      )\n\n    # Encode in the forward direction.\n    carry_forward, outputs_forward = self.forward_rnn(\n      inputs,\n      initial_carry=initial_carry_forward,\n      rngs=rngs,\n      seq_lengths=seq_lengths,\n      return_carry=True,\n      time_major=time_major,\n      reverse=False,\n    )\n\n    # Encode in the backward direction.\n    carry_backward, outputs_backward = self.backward_rnn(\n      inputs,\n      initial_carry=initial_carry_backward,\n      rngs=rngs,\n      seq_lengths=seq_lengths,\n      return_carry=True,\n      time_major=time_major,\n      reverse=True,\n      keep_order=True,\n    )\n\n    carry = (carry_forward, carry_backward) if return_carry else None\n    outputs = jax.tree_util.tree_map(\n      self.merge_fn, outputs_forward, outputs_backward\n    )\n\n    if return_carry:\n      return carry, outputs\n    else:\n      return outputs\n"
  },
  {
    "path": "flax/nnx/nn/stochastic.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom __future__ import annotations\n\nfrom collections.abc import Sequence\n\nimport jax\nimport jax.numpy as jnp\nfrom jax import lax, random\n\nfrom flax.nnx import rnglib\nfrom flax.nnx.module import Module, first_from\nfrom flax import nnx\n\n\nclass Dropout(Module):\n  \"\"\"Create a dropout layer.\n\n  To use dropout, call the :func:`train` method (or pass in\n  ``deterministic=False`` in the constructor or during call time).\n\n  To disable dropout, call the :func:`eval` method (or pass in\n  ``deterministic=True`` in the constructor or during call time).\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n\n    >>> class MLP(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.linear = nnx.Linear(in_features=3, out_features=4, rngs=rngs)\n    ...     self.dropout = nnx.Dropout(0.5, rngs=rngs)\n    ...   def __call__(self, x):\n    ...     x = self.linear(x)\n    ...     x = self.dropout(x)\n    ...     return x\n\n    >>> model = MLP(rngs=nnx.Rngs(0))\n    >>> x = jnp.ones((1, 3))\n\n    >>> model.train() # use dropout\n    >>> model(x)\n    Array([[ 2.1067007, -2.5359864, -1.592019 , -2.5238838]], dtype=float32)\n\n    >>> model.eval() # don't use dropout\n    >>> model(x)\n    Array([[ 1.0533503, -1.2679932, -0.7960095, -1.2619419]], dtype=float32)\n\n  Args:\n    rate: the dropout probability.  (_not_ the keep rate!)\n    broadcast_dims: dimensions that will share the same dropout mask\n    deterministic: if false the inputs are scaled by ``1 / (1 - rate)`` and\n      masked, whereas if true, no mask is applied and the inputs are returned\n      as is.\n    rng_collection: the rng collection name to use when requesting an rng key.\n    rngs: rng key.\n  \"\"\"\n\n  def __init__(\n    self,\n    rate: float,\n    *,\n    broadcast_dims: Sequence[int] = (),\n    deterministic: bool = False,\n    rng_collection: str = 'dropout',\n    rngs: rnglib.Rngs | rnglib.RngStream | None = None,\n  ):\n    self.rate = rate\n    self.broadcast_dims = broadcast_dims\n    self.deterministic = deterministic\n    self.rng_collection = rng_collection\n\n    if isinstance(rngs, rnglib.Rngs):\n      self.rngs = rngs[self.rng_collection].fork()\n    elif isinstance(rngs, rnglib.RngStream):\n      self.rngs = rngs.fork()\n    elif rngs is None:\n      self.rngs = nnx.data(None)\n    else:\n      raise TypeError(\n        f'rngs must be a Rngs, RngStream or None, but got {type(rngs)}.'\n      )\n\n  def __call__(\n    self,\n    inputs,\n    *,\n    deterministic: bool | None = None,\n    rngs: rnglib.Rngs | rnglib.RngStream | jax.Array | None = None,\n  ) -> jax.Array:\n    \"\"\"Applies a random dropout mask to the input.\n\n    Args:\n      inputs: the inputs that should be randomly masked.\n      deterministic: if false the inputs are scaled by ``1 / (1 - rate)`` and\n        masked, whereas if true, no mask is applied and the inputs are returned\n        as is. The ``deterministic`` flag passed into the call method will take\n        precedence over the ``deterministic`` flag passed into the constructor.\n      rngs: an optional key, RngStream, or Rngs object used to generate the dropout mask.\n        If given it will take precedence over the rngs passed into the constructor.\n\n    Returns:\n      The masked inputs reweighted to preserve mean.\n    \"\"\"\n    deterministic = first_from(\n      deterministic,\n      self.deterministic,\n      error_msg=\"\"\"No `deterministic` argument was provided to Dropout\n          as either a __call__ argument or class attribute\"\"\",\n    )\n\n    if (self.rate == 0.0) or deterministic:\n      return inputs\n\n    # Prevent gradient NaNs in 1.0 edge-case.\n    if self.rate == 1.0:\n      return jnp.zeros_like(inputs)\n\n    rngs = first_from(  # type: ignore[assignment]\n      rngs,\n      self.rngs,\n      error_msg=\"\"\"`deterministic` is False, but no `rngs` argument was provided to Dropout\n          as either a __call__ argument or class attribute.\"\"\",\n    )\n\n    if isinstance(rngs, rnglib.Rngs):\n      key = rngs[self.rng_collection]()\n    elif isinstance(rngs, rnglib.RngStream):\n      key = rngs()\n    elif isinstance(rngs, jax.Array):\n      key = rngs\n    else:\n      raise TypeError(\n        f'rngs must be a Rngs, RngStream or jax.Array, but got {type(rngs)}.'\n      )\n\n    keep_prob = 1.0 - self.rate\n    broadcast_shape = list(inputs.shape)\n    for dim in self.broadcast_dims:\n      broadcast_shape[dim] = 1\n    mask = random.bernoulli(\n      key, p=keep_prob, shape=broadcast_shape, out_sharding=jax.typeof(inputs).sharding\n    )\n    mask = jnp.broadcast_to(mask, inputs.shape)\n    return lax.select(mask, inputs / keep_prob, jnp.zeros_like(inputs))\n\n  def set_view(\n      self,\n      deterministic: bool | None = None,\n  ):\n    \"\"\"Class method used by ``nnx.view``.\n\n    Args:\n      deterministic: if True, disables dropout masking.\n    \"\"\"\n    if deterministic is not None:\n      self.deterministic = deterministic\n"
  },
  {
    "path": "flax/nnx/proxy_caller.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom __future__ import annotations\n\nimport dataclasses\nimport typing as tp\n\nimport jax\n\n\nA = tp.TypeVar('A', covariant=True)  # type: ignore[not-supported-yet]\n\n\ndef _identity(x):\n  return x\n\n@dataclasses.dataclass(frozen=True)\nclass GetItem:\n  key: tp.Any\n\n\n@dataclasses.dataclass(frozen=True)\nclass GetAttr:\n  name: str\n\n\n@dataclasses.dataclass(frozen=True)\nclass DelayedAccessor:\n  actions: tuple[GetItem | GetAttr, ...] = ()\n\n  def __call__(self, x):\n    for action in self.actions:\n      if isinstance(action, GetItem):\n        x = x[action.key]\n      elif isinstance(action, GetAttr):\n        x = getattr(x, action.name)\n    return x\n\n  def __getattr__(self, name):\n    return DelayedAccessor(self.actions + (GetAttr(name),))\n\n  def __getitem__(self, key):\n    return DelayedAccessor(self.actions + (GetItem(key),))\n\njax.tree_util.register_static(DelayedAccessor)\n\n\nclass _AccessorCall(tp.Protocol):\n  def __call__(self, accessor: DelayedAccessor, /, *args, **kwargs) -> tp.Any:\n    ...\n\nclass CallableProxy:\n  def __init__(\n    self, callable: _AccessorCall, accessor: DelayedAccessor | None = None\n  ):\n    self._callable = callable\n    self._accessor = DelayedAccessor() if accessor is None else accessor\n\n  def __call__(self, *args, **kwargs):\n    return self._callable(self._accessor, *args, **kwargs)\n\n  def __getattr__(self, name) -> CallableProxy:\n    return CallableProxy(self._callable, getattr(self._accessor, name))\n\n  def __getitem__(self, key) -> CallableProxy:\n    return CallableProxy(self._callable, self._accessor[key])\n\n\nclass ApplyCaller(tp.Protocol, tp.Generic[A]):\n  def __getattr__(self, __name) -> ApplyCaller[A]:\n    ...\n\n  def __getitem__(self, __name) -> ApplyCaller[A]:\n    ...\n\n  def __call__(self, *args, **kwargs) -> tuple[tp.Any, A]:\n    ...\n"
  },
  {
    "path": "flax/nnx/pytreelib.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom __future__ import annotations\n\nimport dataclasses\nimport inspect\nimport os\nimport threading\nimport typing as tp\nfrom abc import ABCMeta\nfrom copy import deepcopy\nimport warnings\n\nfrom flax.nnx import variablelib\nimport jax\nimport numpy as np\nimport treescope  # type: ignore[import-untyped]\nfrom treescope import rendering_parts\n\nfrom flax import errors, nnx\nfrom flax.nnx import (\n  graphlib,\n  reprlib,\n  tracers,\n  visualization,\n)\nfrom flax import config\nfrom flax.nnx.variablelib import Variable\nfrom flax.typing import MISSING, Missing, SizeBytes\n\nBUILDING_DOCS = 'FLAX_DOC_BUILD' in os.environ\n\nA = tp.TypeVar('A')\nP = tp.TypeVar('P', bound='Pytree')\nT = tp.TypeVar('T', bound=type)\n\nDataAnnotation = '__data__'\nData = tp.Annotated[A, DataAnnotation]\nData.__doc__ = \"\"\"Data marks attributes of a class as pytree data using type annotations.\n\nData annotations must be used at the class level and will apply to all instances.\nThe usage of Data is recommended when type annotations are used already present\nor required e.g. for dataclasses.\n\"\"\"\nDATA_REGISTRY: set[type] = set()\n\n@tp.overload\ndef data(value: A, /) -> A: ...\n@tp.overload\ndef data(\n  *,\n  default: A = dataclasses.MISSING,  # type: ignore[assignment]\n  default_factory: tp.Callable[[], A] | None = None,  # type: ignore[assignment]\n  init: bool = True,\n  repr: bool = True,\n  hash: bool | None = None,\n  compare: bool = True,\n  metadata: tp.Mapping[str, tp.Any] | None = None,\n  kw_only: bool = False,\n) -> tp.Any: ...\ndef data(value: tp.Any = MISSING, /, **kwargs) -> tp.Any:\n  \"\"\"Annotates a an attribute as pytree data.\n\n  The return value from `data` must be directly assigned to an Object attribute\n  which will be registered as a pytree data attribute.\n\n  Example::\n\n    from flax import nnx\n    import jax\n\n    class Foo(nnx.Pytree):\n      def __init__(self):\n        self.data_attr = nnx.data(42)  # pytree data\n        self.static_attr = \"hello\"     # static attribute\n\n    foo = Foo()\n\n    assert jax.tree.leaves(foo) == [42]\n\n  Args:\n    value: The value to annotate as data.\n\n  Returns:\n    A value which will register the attribute as data on assignment.\n\n  \"\"\"\n  if not isinstance(value, Missing) and kwargs:\n    raise TypeError(\n      'nnx.data() accepts either a single positional argument or keyword'\n      ' arguments, but not both.'\n    )\n  metadata = {'nnx_value': value}\n  if 'metadata' in kwargs and kwargs['metadata'] is not None:\n    if 'static' in kwargs['metadata']:\n      raise ValueError(\n        \"Cannot use 'static' key in metadata argument for nnx.data.\"\n      )\n    metadata.update(kwargs.pop('metadata'))\n  metadata['static'] = False\n  return dataclasses.field(**kwargs, metadata=metadata)  # type: ignore[return-value]\n\n\ndef register_data_type(type_: T, /) -> T:\n  \"\"\"Registers a type as pytree data type recognized by Object.\n\n  Custom types registered as data will be automatically recognized\n  as data attributes when assigned to an Object attribute. This means\n  that values of this type do not need to be wrapped in `nnx.data(...)`\n  for Object to mark the attribute its being assigned to as data.\n\n  Example::\n\n    from flax import nnx\n    from dataclasses import dataclass\n\n    @dataclass(frozen=True)\n    class MyType:\n      value: int\n\n    nnx.register_data_type(MyType)\n\n    class Foo(nnx.Pytree):\n      def __init__(self, a):\n        self.a = MyType(a)  # Automatically registered as data\n        self.b = \"hello\"     # str not registered as data\n\n    foo = Foo(42)\n\n    assert nnx.is_data(foo.a)  # True\n    assert jax.tree.leaves(foo) == [MyType(value=42)]\n\n  Can also be used as a decorator::\n\n    @nnx.register_data_type\n    @dataclass(frozen=True)\n    class MyType:\n      value: int\n  \"\"\"\n  DATA_REGISTRY.add(type_)\n  return type_\n\n\ndef is_data(value: tp.Any, /) -> bool:\n  \"\"\"Checks if a value is a registered data type.\n\n  This function checks a the value is registered data type, which means it is\n  automatically recognized as data when assigned a :class:`flax.nnx.Pytree` attribute.\n\n  Data types are:\n\n  - ``jax.Array``\n  - ``np.ndarray``\n  - ``ArrayRef``\n  - Variables (:class:`flax.nnx.Param`, :class:`flax.nnx.BatchStat`, `nnx.RngState`, etc.)\n  - All graph nodes (:class:`flax.nnx.Object`, :class:`flax.nnx.Module`, :class:`flax.nnx.Rngs`, etc.)\n  - Any type registered with :func:`flax.nnx.register_data_type`\n  - Any pytree that contains at least one node or leaf element of the above\n\n\n  Example::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n    ... # ------ DATA ------------\n    >>> assert nnx.is_data( jnp.array(0) )                      # Arrays\n    >>> assert nnx.is_data( nnx.Param(1) )                      # Variables\n    >>> assert nnx.is_data( nnx.Rngs(2) )                       # nnx.Pytrees\n    >>> assert nnx.is_data( nnx.Linear(1, 1,rngs=nnx.Rngs(0)) ) # Modules\n    ... # ------ STATIC ------------\n    >>> assert not nnx.is_data( 'hello' )                       # strings, arbitrary objects\n    >>> assert not nnx.is_data( 42 )                            # int, float, bool, complex, etc.\n    >>> assert not nnx.is_data( [1, 2.0, 3j, jnp.array(1)] )    # list, dict, tuple, pytrees\n\n\n  Args:\n    value: The value to check.\n\n  Returns:\n    A string representing the attribute status.\n  \"\"\"\n  return (\n    graphlib.is_node_leaf(value)\n    or graphlib.is_graph_node(value)\n    or type(value) in DATA_REGISTRY\n  )\n\n\ndef has_data(value: tp.Any, /) -> list[tp.Any]:\n  visited: set[int] = set()\n  def _is_leaf(x):\n    if id(x) in visited:\n      return True\n    visited.add(id(x))\n    return is_data(x)\n  leaves = jax.tree.leaves(value, is_leaf=_is_leaf)\n  return [leaf for leaf in leaves if is_data(leaf)]\n\n\nStaticAnnotation = '__static__'\nStatic = tp.Annotated[A, StaticAnnotation]\nStatic.__doc__ = \"\"\"Static marks attributes of a class as static using type annotations.\nStatic annotations must be used at the class level and will apply to all instances.\nThe usage of Static is recommended when type annotations are used already present\nor required e.g. for dataclasses.\n\"\"\"\n\n@tp.overload\ndef static(value: A, /) -> A: ...\n@tp.overload\ndef static(\n  *,\n  default: A = dataclasses.MISSING,  # type: ignore[assignment]\n  default_factory: tp.Callable[[], A] | None = None,\n  init: bool = True,\n  repr: bool = True,\n  hash: bool | None = None,\n  compare: bool = True,\n  metadata: tp.Mapping[str, tp.Any] | None = None,\n  kw_only: bool = False,\n) -> tp.Any: ...\ndef static(value: tp.Any = MISSING, /, **kwargs) -> tp.Any:\n  \"\"\"Annotates a an attribute as static.\n\n  The return value from `static` must be directly assigned to an Object\n  attribute\n  which will be registered as static attribute.\n\n  Example::\n\n    from flax import nnx\n\n    class Foo(nnx.Pytree):\n      def __init__(self, a, b):\n        self.a = nnx.static(a)  # pytree metadata\n        self.b = nnx.data(b)    # pytree data\n\n    foo = Foo(\"one\", \"two\")\n\n    assert jax.tree.leaves(foo) == [\"two\"]\n\n  By default ``nnx.Pytree`` will ...\n  \"\"\"\n  if not isinstance(value, Missing) and kwargs:\n    raise TypeError(\n      'nnx.static() accepts either a single positional argument or keyword'\n      ' arguments, but not both.'\n    )\n  metadata = {'nnx_value': value}\n  if 'metadata' in kwargs and kwargs['metadata'] is not None:\n    if 'static' in kwargs['metadata']:\n      raise ValueError(\n        \"Cannot use 'static' key in metadata argument for nnx.static.\"\n      )\n    metadata.update(kwargs.pop('metadata'))\n  metadata['static'] = True\n  return dataclasses.field(**kwargs, metadata=metadata)  # type: ignore[return-value]\n\n@tp.overload\ndef dataclass(cls: type[A], /) -> type[A]: ...\n@tp.overload\ndef dataclass(\n  *,\n  init: bool = True,\n  eq: bool = True,\n  order: bool = False,\n  unsafe_hash: bool = False,\n  match_args: bool = True,\n  kw_only: bool = False,\n  slots: bool = False,\n  weakref_slot: bool = False,\n) -> tp.Callable[[type[A]], type[A]]: ...\n\n@tp.dataclass_transform(field_specifiers=(dataclasses.field, data, static))\ndef dataclass(\n  cls=None,\n  /,\n  *,\n  init: bool = True,\n  eq: bool = True,\n  order: bool = False,\n  unsafe_hash: bool = False,\n  match_args: bool = True,\n  kw_only: bool = False,\n  slots: bool = False,\n  weakref_slot: bool = False,\n) -> tp.Any:\n  return dataclasses.dataclass(\n    cls,\n    init=init,\n    eq=eq,\n    order=order,\n    unsafe_hash=unsafe_hash,\n    match_args=match_args,\n    kw_only=kw_only,\n    slots=slots,\n    weakref_slot=weakref_slot,\n  )\n\ndef _collect_stats(\n  node: tp.Any, node_stats: dict[int, dict[type[Variable], SizeBytes]]\n):\n  if not graphlib.is_node(node) and not isinstance(node, Variable):\n    raise ValueError(f'Expected a graph node or Variable, got {type(node)!r}.')\n\n  if id(node) in node_stats:\n    return\n\n  stats: dict[type[Variable], SizeBytes] = {}\n  node_stats[id(node)] = stats\n\n  if isinstance(node, Variable):\n    var_type = node.var_type\n    if issubclass(var_type, nnx.RngState):\n      var_type = nnx.RngState\n    size_bytes = SizeBytes.from_any(node.get_raw_value())\n    if size_bytes:\n      stats[var_type] = size_bytes\n\n  else:\n    node_impl = graphlib.get_node_impl(node)\n    assert node_impl is not None\n    node_dict = node_impl.node_dict(node)\n    for key, value in node_dict.items():\n      if id(value) in node_stats:\n        continue\n      if graphlib.is_node(value) or isinstance(value, Variable):\n        _collect_stats(value, node_stats)\n        child_stats = node_stats[id(value)]\n        for var_type, size_bytes in child_stats.items():\n          if var_type in stats:\n            stats[var_type] += size_bytes\n          else:\n            stats[var_type] = size_bytes\n\n\n@dataclasses.dataclass\nclass ObjectContext(threading.local):\n  seen_modules_repr: set[int] | None = None\n  node_stats: dict[int, dict[type[Variable], SizeBytes]] | None = None\n\n\nOBJECT_CONTEXT = ObjectContext()\n\n\nclass PytreeState(reprlib.Representable):\n  __slots__ = ('_trace_state', '_initializing', '_is_setup')\n\n  def __init__(self, initializing: bool = False, is_setup: bool = False):\n    self._trace_state = tracers.TraceState()\n    self._initializing = initializing\n    self._is_setup = is_setup\n\n  @property\n  def trace_state(self) -> tracers.TraceState:\n    return self._trace_state\n\n  @property\n  def initializing(self) -> bool:\n    return self._initializing\n\n  @property\n  def is_setup(self) -> bool:\n    return self._is_setup\n\n  def __nnx_repr__(self):\n    yield reprlib.Object(type(self))\n    yield reprlib.Attr('trace_state', self._trace_state)\n\n  def __treescope_repr__(self, path, subtree_renderer):\n    return visualization.render_object_constructor(\n      object_type=type(self),\n      attributes={'trace_state': self._trace_state},\n      path=path,\n      subtree_renderer=subtree_renderer,\n    )\n\n\ndef _flatten_pytree_state(state: PytreeState):\n  return (), (state.initializing, state.is_setup)\n\n\ndef _unflatten_pytree_state(static: tuple[bool, bool], _):\n  initializing, setup = static\n  return PytreeState(initializing, setup)\n\n\njax.tree_util.register_pytree_node(\n  PytreeState,\n  _flatten_pytree_state,\n  _unflatten_pytree_state,\n)\n\n\ndef check_pytree(pytree):\n  \"\"\"Checks if a pytree is valid.\"\"\"\n  if not isinstance(pytree, Pytree):\n    raise TypeError(f'Expected a Pytree, got {type(pytree)}.')\n\n  for name, value in vars(pytree).items():\n    pytree._check_value(name, value, new_status=None)\n\n\nclass PytreeMeta(ABCMeta):\n  if not tp.TYPE_CHECKING:\n\n    def __call__(cls, *args: Any, **kwargs: Any) -> Any:\n      return _graph_node_meta_call(cls, *args, **kwargs)\n\n  def _pytree_meta_construct(cls, self, *args, **kwargs):\n    self.__init__(*args, **kwargs)\n\nObjectMeta = PytreeMeta\n\ndef _graph_node_meta_call(cls: tp.Type[P], *args, **kwargs) -> P:\n  node = cls.__new__(cls, *args, **kwargs)\n  object.__setattr__(node, '_pytree__state', PytreeState())\n  object.__setattr__(node, '_pytree__nodes', cls._pytree__nodes)\n  cls._pytree_meta_construct(node, *args, **kwargs)\n  if cls._pytree__is_pytree:\n    missing: dict[str, bool] = {}\n    for name, value in vars(node).items():\n      if name not in node._pytree__nodes:\n        missing[name] = is_data(value)\n    if missing:\n      object.__setattr__(\n        node, '_pytree__nodes', node._pytree__nodes.update(missing)\n      )\n    check_pytree(node)\n\n  return node\n\n\n@dataclasses.dataclass(frozen=True, repr=False)\nclass ArrayRepr(reprlib.Representable):\n  shape: tp.Tuple[int, ...]\n  dtype: tp.Any\n\n  @staticmethod\n  def from_array(array: jax.Array | np.ndarray) -> ArrayRepr:\n    return ArrayRepr(array.shape, array.dtype)\n\n  def __nnx_repr__(self):\n    yield reprlib.Object(type='Array', same_line=True)\n    yield reprlib.Attr('shape', self.shape)\n    yield reprlib.Attr('dtype', self.dtype)\n\n@dataclasses.dataclass(frozen=True, repr=False)\nclass VariableRepr(reprlib.Representable):\n  var_type: type[Variable]\n  value: tp.Any\n  metadata: dict[str, tp.Any]\n\n  def __nnx_repr__(self):\n    variable = self.var_type._new(self.value, self.metadata)\n    yield from variable.__nnx_repr__()\n\n\n@dataclasses.dataclass(frozen=True, repr=False)\nclass MutableArrayRepr(reprlib.Representable):\n  shape: tp.Tuple[int, ...]\n  dtype: tp.Any\n\n  @staticmethod\n  def from_array(array: jax.Array | np.ndarray) -> MutableArrayRepr:\n    return MutableArrayRepr(array.shape, array.dtype)\n\n  def __nnx_repr__(self):\n    yield reprlib.Object(type='ArrayRef', same_line=True)\n    yield reprlib.Attr('shape', self.shape)\n    yield reprlib.Attr('dtype', self.dtype)\n\ndef _to_shape_dtype(x):\n  if isinstance(x, Variable):\n    value = x.get_raw_value()\n    metadata = x.get_metadata()\n    value = jax.tree.map(_to_shape_dtype, value)\n    return VariableRepr(x.var_type, value, metadata)\n  elif variablelib.is_array_ref(x) and np.prod(x.shape) > 1:\n    return MutableArrayRepr(x.shape, x.dtype)\n  elif (\n    isinstance(x, (np.ndarray, jax.Array))\n    and np.prod(x.shape) > 1\n  ):\n    return ArrayRepr(x.shape, x.dtype)\n  return x\n\nclass AttributeStatus(tp.NamedTuple):\n  is_data: bool\n  explicit: bool\n\n\nclass Pytree(reprlib.Representable, metaclass=PytreeMeta):\n  \"\"\"Base class for all NNX objects.\"\"\"\n\n  if tp.TYPE_CHECKING:\n    _pytree__nodes: graphlib.HashableMapping[tp.Any, bool]\n    _pytree__state: PytreeState\n    _pytree__is_pytree: bool\n\n  def __init_subclass__(\n      cls,\n      *,\n      pytree: bool = config.flax_pytree_module,\n      **kwargs,\n  ) -> None:\n    super().__init_subclass__(**kwargs)\n    if slots := getattr(cls, '__slots__', ()):\n      raise TypeError(\n        'Pytree currently does not support __slots__, '\n        f\"found __slots__={slots} in '{cls.__name__}'.\"\n      )\n    cls._pytree__is_pytree = pytree\n\n    graphlib.register_graph_node_type(\n      type=cls,\n      flatten=cls._graph_node_flatten,\n      set_key=cls._graph_node_set_key,  # type: ignore\n      pop_key=cls._graph_node_pop_key,  # type: ignore\n      create_empty=cls._graph_node_create_empty,\n      clear=cls._graph_node_clear,\n      init=cls._graph_node_init,  # type: ignore\n    )\n\n    nodes: dict[str, bool] = dict(getattr(cls, '_pytree__nodes', ()))\n    nodes['_pytree__state'] = True\n    try:\n      type_hints = tp.get_type_hints(\n          cls, globals(), {cls.__name__: cls}, include_extras=True\n      )\n    except NameError:\n      type_hints = cls.__annotations__\n    # add annotation attributes\n    for name, type_ in type_hints.items():\n      if isinstance(type_, str):\n        if type_.startswith('nnx.Data'):\n          warnings.warn(\n            f\"'Data' is deprecated, please replace:\\n\\n\"\n            '  some_field: nnx.Data[SomeType]\\n\\n'\n            f'with:\\n\\n'\n            '  some_field: SomeType = nnx.data()\\n\\n',\n            DeprecationWarning,\n            stacklevel=2,\n          )\n          nodes[name] = True\n        elif type_.startswith('nnx.Static'):\n          warnings.warn(\n            f\"'Static' is deprecated, please replace:\\n\\n\"\n            '  some_field: nnx.Static[SomeType]\\n\\n'\n            f'with:\\n\\n'\n            '  some_field: SomeType = nnx.static()\\n\\n',\n            DeprecationWarning,\n            stacklevel=2,\n          )\n          nodes[name] = False\n      else:\n        type_metadata = getattr(type_, '__metadata__', ())\n        if DataAnnotation in type_metadata:\n          warnings.warn(\n            f\"'Data' is deprecated, please replace:\\n\\n\"\n            '  some_field: nnx.Data[SomeType]\\n\\n'\n            f'with:\\n\\n'\n            '  some_field: SomeType = nnx.data()\\n\\n',\n            DeprecationWarning,\n            stacklevel=2,\n          )\n          nodes[name] = True\n        elif StaticAnnotation in type_metadata:\n          warnings.warn(\n            f\"'Static' is deprecated, please replace:\\n\\n\"\n            '  some_field: nnx.Static[SomeType]\\n\\n'\n            f'with:\\n\\n'\n            '  some_field: SomeType = nnx.static()\\n\\n',\n            DeprecationWarning,\n            stacklevel=2,\n          )\n          nodes[name] = False\n\n    for name, value in vars(cls).items():\n      if isinstance(value, dataclasses.Field) and 'static' in value.metadata:\n        if not isinstance(value.metadata['static'], bool):\n          raise ValueError(\n            f\"Invalid 'static' metadata for attribute\"\n            f\" '{cls.__name__}.{name}': expected bool, got\"\n            f' {type(value.metadata[\"static\"]).__name__}.'\n          )\n        is_node = not value.metadata['static']\n        if name in nodes and nodes[name] != is_node:\n          raise ValueError(\n            f'Conflicting pytree annotation for attribute'\n            f\" '{cls.__name__}.{name}': previously registered as\"\n            f' {\"data\" if nodes[name] else \"static\"}, but found'\n            f' nnx.{\"data\" if is_node else \"static\"}(...) annotation.'\n          )\n        nodes[name] = is_node\n\n    cls._pytree__nodes = graphlib.HashableMapping(nodes, copy=False)\n\n    if pytree:\n      jax.tree_util.register_pytree_with_keys(\n        cls,\n        flatten_with_keys=cls._pytree__flatten_with_paths,\n        unflatten_func=cls._pytree__unflatten,\n        flatten_func=cls._pytree__flatten,\n      )\n\n    if BUILDING_DOCS:\n      # set correct signature for sphinx\n      cls.__signature__ = inspect.signature(cls.__init__)\n\n  # Backward compatibility with PR #4863\n  @property\n  def _object__nodes(self):\n    warnings.warn(\n      \"'_object__nodes' is deprecated, use '_pytree__nodes' instead.\",\n      DeprecationWarning,\n      stacklevel=2,\n    )\n    return self._pytree__nodes\n\n  @property\n  def _object__state(self):\n    warnings.warn(\n      \"'_object__state' is deprecated, use '_pytree__state' instead.\",\n      DeprecationWarning,\n      stacklevel=2,\n    )\n    return self._pytree__state\n\n  if not tp.TYPE_CHECKING:\n\n    def __setattr__(self, name: str, value: Any) -> None:\n      self._setattr(name, value)\n\n  def _setattr(self, name, value: tp.Any) -> None:\n    self._check_valid_context(\n      lambda: f\"Cannot mutate '{type(self).__name__}' from different trace level\"\n    )\n    data: bool = False\n    explicit: bool = False\n    if isinstance(value, dataclasses.Field) and 'nnx_value' in value.metadata:\n      is_static = value.metadata['static']\n      value = value.metadata['nnx_value']\n      if self._pytree__is_pytree:\n        data = not is_static\n        explicit = True\n    elif self._pytree__is_pytree:\n      data = is_data(value)\n    if self._pytree__is_pytree:\n      self._check_value(name, value, AttributeStatus(data, explicit))\n      if name not in self._pytree__nodes or (\n          explicit and self._pytree__nodes[name] != data\n      ):\n        object.__setattr__(\n          self, '_pytree__nodes', self._pytree__nodes.update({name: data})\n        )\n    object.__setattr__(self, name, value)\n\n  def _check_value(self, key, value, new_status: AttributeStatus | None):\n\n    def _get_annotations(leaves):\n      return {\n        'static' if leaf.metadata['static'] else 'data'\n        for leaf in leaves\n        if isinstance(leaf, dataclasses.Field) and 'nnx_value' in leaf.metadata\n      }\n\n    def _has_visited(x):\n      if id(x) in visited:\n        return True\n      visited.add(id(x))\n      return False\n\n    current_is_data = (\n        self._pytree__nodes[key] if key in self._pytree__nodes else False\n    )\n    existing_attr = key in vars(self)\n    if (\n        new_status is not None\n        and not new_status.explicit\n        and new_status.is_data\n        and existing_attr\n        and not current_is_data\n    ):\n      raise ValueError(\n          f\"Cannot assign data value of type '{type(value)}' to static\"\n          f\" attribute '{key}' of Pytree type '{type(self)}'. To override the\"\n          ' status explicitly wrap the value with nnx.data on assignment:\\n\\n '\n          f' _.{key} = nnx.data(...)\\n\\n'\n      )\n\n    visited: set[int] = set()\n    leaves = jax.tree.leaves(\n      value, is_leaf=lambda x: _has_visited(x) or is_data(x)\n    )\n    data_leaves = [leaf for leaf in leaves if is_data(leaf)]\n    if data_leaves:\n      # check no data in nnx.static assignments\n      if new_status is not None:\n        if not new_status.is_data and new_status.explicit:\n          raise ValueError(\n              f\"Found data in value of type '{type(value)}' annotated with \"\n              f\"nnx.static(...) when setting attribute '{key}' of Pytree type \"\n              f\"'{type(self)}'.\"\n          )\n        if not new_status.is_data and not current_is_data:\n          base_pytree_type = Pytree\n          for t in type(self).mro()[1:]:\n            if issubclass(t, nnx.Pytree):\n              base_pytree_type = t\n              break\n          data_leaves_type_names = {type(leaf).__name__ for leaf in data_leaves}\n          raise ValueError(\n            f\"Found data on value of type '{type(value)}' assigned to\"\n            f\" static attribute '{key}' of Pytree type '{type(self)}'. Static\"\n            ' attributes should not contain data values. Consider one of'\n            ' the following options:\\n\\n1. If the attribute is meant to be'\n            f' static, remove the data values of type(s):\\n\\n  {\", \".join(data_leaves_type_names)}'\n            f'\\n\\n2. If the attribute is meant to be data, wrap the value with nnx.data '\n            f' on assignment:\\n\\n  _.{key} = nnx.data(...)\\n\\n3. Alternatively,'\n            ' annotate the class attribute with nnx.data:\\n\\n  class'\n            f' {type(self).__name__}({base_pytree_type.__name__}):\\n   '\n            f' {key}: {type(value).__name__} = nnx.data()\\n\\n4. If the'\n            ' container is a list or dict, try using nnx.List(...) or'\n            ' nnx.Dict(...) instead.\\n\\n5. Disable pytree'\n            ' for this class:\\n\\n  class'\n            f' {type(self).__name__}({base_pytree_type.__name__},'\n            ' pytree=False):\\n\\n'\n          )\n      # check no data in static attributes after __init__\n      elif not current_is_data:\n        base_pytree_type = Pytree\n        for t in type(self).mro()[1:]:\n          if issubclass(t, nnx.Pytree):\n            base_pytree_type = t\n            break\n        raise ValueError(\n          f'Found unexpected data on value of type {type(value)} in static'\n          f\" attribute '{key}' of Pytree type '{type(self)}'. This is an\"\n          ' error starting from Flax version 0.12.0.\\nConsider one of the'\n          ' following options:\\n\\n1. If the attribute is meant to be static,'\n          ' either remove the data value or wrap it in a static'\n          ' container.\\n2. Wrap the value with nnx.data on'\n          f' assignment:\\n\\n  _.{key} = nnx.data(...)\\n\\n3. Annotate the'\n          ' class attribute with nnx.data:\\n\\n  class'\n          f' {type(self).__name__}({base_pytree_type.__name__}):\\n    {key}:'\n          f' {type(value).__name__} = nnx.data()\\n\\n4. If the container is a'\n          ' list or dict, try using nnx.List(...) or nnx.Dict(...)'\n          ' instead.\\n5. Disable pytree for this class:\\n\\n  class'\n          f' {type(self).__name__}({base_pytree_type.__name__},'\n          f' pytree=False):\\n\\n'\n        )\n    if tags := _get_annotations(leaves):\n      raise ValueError(\n          f'Found unexpected tags {tags} on attribute'\n          f\" '{type(self).__name__}.{key}'. Values from nnx.data(...)\"\n          ' and\\nnnx.static(...) should be assigned to nnx.Pytree attributes'\n          ' directly, they should not be inside other structures. Got value of'\n          f\" type '{type(value)}' on Pytree of type '{type(self)}'.\"\n      )\n\n  def _check_valid_context(self, error_msg: tp.Callable[[], str]) -> None:\n    if not self._pytree__state.trace_state.is_valid():\n      raise errors.TraceContextError(error_msg())\n\n  def __deepcopy__(self: P, memo=None) -> P:\n    graphdef, state = graphlib.split(self, graph=True)\n    graphdef = deepcopy(graphdef)\n    state = deepcopy(state)\n    return graphlib.merge(graphdef, state)\n\n  def __nnx_repr__(self):\n    if OBJECT_CONTEXT.node_stats is None or id(self) not in OBJECT_CONTEXT.node_stats:\n      node_stats: dict[int, dict[type[Variable], SizeBytes]] = {}\n      _collect_stats(self, node_stats)\n      OBJECT_CONTEXT.node_stats = node_stats\n      stats = node_stats[id(self)]\n      clear_node_stats = True\n    else:\n      stats = OBJECT_CONTEXT.node_stats[id(self)]\n      clear_node_stats = False\n\n    if OBJECT_CONTEXT.seen_modules_repr is None:\n      OBJECT_CONTEXT.seen_modules_repr = set()\n      clear_seen = True\n    else:\n      clear_seen = False\n\n    if id(self) in OBJECT_CONTEXT.seen_modules_repr:\n      yield reprlib.Object(type=type(self), empty_repr='...')\n      return\n\n    try:\n      if stats:\n        stats_repr = ' # ' + ', '.join(\n          f'{var_type.__name__}: {size_bytes}'\n          for var_type, size_bytes in stats.items()\n        )\n        if len(stats) > 1:\n          total_bytes = sum(stats.values(), SizeBytes(0, 0))\n          stats_repr += f', Total: {total_bytes}'\n      else:\n        stats_repr = ''\n\n      yield reprlib.Object(type=type(self), comment=stats_repr)\n      OBJECT_CONTEXT.seen_modules_repr.add(id(self))\n\n      for name, value in vars(self).items():\n        if str(name).startswith('_pytree__'):\n          continue\n        if str(name).startswith('_') and not self._pytree__nodes.get(str(name), False):\n          continue\n\n        value = jax.tree.map(_to_shape_dtype, value, is_leaf=graphlib.is_graph_node)\n        yield reprlib.Attr(name, value)\n    finally:\n      if clear_seen:\n        OBJECT_CONTEXT.seen_modules_repr = None\n      if clear_node_stats:\n        OBJECT_CONTEXT.node_stats = None\n\n  def __treescope_repr__(self, path, subtree_renderer):\n    from flax import nnx\n\n    if OBJECT_CONTEXT.node_stats is None or id(self) not in OBJECT_CONTEXT.node_stats:\n      node_stats: dict[int, dict[type[Variable], SizeBytes]] = {}\n      _collect_stats(self, node_stats)\n      OBJECT_CONTEXT.node_stats = node_stats\n      stats = node_stats[id(self)]\n      clear_node_stats = True\n    else:\n      stats = OBJECT_CONTEXT.node_stats[id(self)]\n      clear_node_stats = False\n\n    try:\n      if stats:\n        stats_repr = ' # ' + ', '.join(\n          f'{var_type.__name__}: {size_bytes}'\n          for var_type, size_bytes in stats.items()\n        )\n        if len(stats) > 1:\n          total_bytes = sum(stats.values(), SizeBytes(0, 0))\n          stats_repr += f', Total: {total_bytes}'\n\n        first_line_annotation = rendering_parts.comment_color(\n          rendering_parts.text(f'{stats_repr}')\n        )\n      else:\n        first_line_annotation = None\n      children = {}\n      for name, value in vars(self).items():\n        if str(name).startswith('_pytree__'):\n          continue\n        if str(name).startswith('_') and not self._pytree__nodes.get(str(name), False):\n          continue\n        children[name] = value\n\n      if isinstance(self, nnx.Module):\n        color = treescope.formatting_util.color_from_string(\n          type(self).__qualname__\n        )\n      else:\n        color = None\n      return visualization.render_object_constructor(\n        object_type=type(self),\n        attributes=children,\n        path=path,\n        subtree_renderer=subtree_renderer,\n        first_line_annotation=first_line_annotation,\n        color=color,\n      )\n    finally:\n      if clear_node_stats:\n        OBJECT_CONTEXT.node_stats = None\n\n  # pickle support\n  def __getstate__(self):\n    return vars(self).copy()\n\n  def __setstate__(self, state):\n    vars(self).update(state)\n\n  # -------------------------\n  # Pytree Definition\n  # -------------------------\n  _pytree__has_int_keys: bool = False\n\n  def _pytree__flatten_with_paths(self):\n    obj_items = vars(self).items()\n    if self._pytree__has_int_keys:\n      obj_items = ((_maybe_int(name), value) for name, value in obj_items)\n      key_fn = graphlib._type_aware_sort\n    else:\n      key_fn = None\n    node_attributes = self._pytree__nodes\n    node_keys: list[str | int] = []\n    node_attrs: list[tuple[tp.Any, tp.Any]] = []\n    static_keys: list[str | int] = []\n    static_attrs: list[tp.Any] = []\n    for key, value in sorted(obj_items, key=key_fn):\n      # get string representation of the key because\n      # node_attributes keys are strings\n      key_str = _get_str(key)\n      if key_str in node_attributes and node_attributes[key_str]:\n        node_keys.append(key)\n        node_attrs.append((\n            jax.tree_util.GetAttrKey(key)\n            if isinstance(key, str)\n            else jax.tree_util.SequenceKey(key),\n            value,\n        ))\n      else:\n        static_keys.append(key)\n        static_attrs.append(value)\n\n    return (\n        node_attrs,\n        (tuple(node_keys), tuple(static_keys), tuple(static_attrs)),\n    )\n\n  def _pytree__flatten(self):\n    obj_items = vars(self).items()\n    if self._pytree__has_int_keys:\n      obj_items = ((_maybe_int(name), value) for name, value in obj_items)\n      key_fn = graphlib._type_aware_sort\n    else:\n      key_fn = None\n    node_attributes = self._pytree__nodes\n    node_keys: list[str | int] = []\n    node_attrs: list[tp.Any] = []\n    static_keys: list[str | int] = []\n    static_attrs: list[tp.Any] = []\n    for key, value in sorted(obj_items, key=key_fn):\n      # get string representation of the key because\n      # node_attributes keys are strings\n      key_str = _get_str(key)\n      if key_str in node_attributes and node_attributes[key_str]:\n        node_keys.append(key)\n        node_attrs.append(value)\n      else:\n        static_keys.append(key)\n        static_attrs.append(value)\n\n    return (\n        node_attrs,\n        (tuple(node_keys), tuple(static_keys), tuple(static_attrs)),\n    )\n\n  @classmethod\n  def _pytree__unflatten(\n    cls,\n    static: tuple[tp.Iterable[str | int], tp.Iterable[str | int], tp.Iterable[tp.Any]],\n    node_attrs: tp.Iterable[tp.Any],\n  ):\n    node_keys, static_keys, static_attrs = static\n    obj = object.__new__(cls)\n    for name, value in zip(node_keys, node_attrs, strict=True):\n      object.__setattr__(obj, _get_str(name), value)\n    for name, value in zip(static_keys, static_attrs, strict=True):\n      object.__setattr__(obj, _get_str(name), value)\n    return obj\n\n  # -------------------------\n  # Graph Definition\n  # -------------------------\n  def _graph_node_flatten(self):\n    obj_items = vars(self).items()\n    if self._pytree__is_pytree:\n      pytree_nodes = self._pytree__nodes\n      obj_items = (\n          (\n              name,\n              nnx.graphlib.DataElem(value)\n              if name in pytree_nodes and pytree_nodes[name]\n              else nnx.graphlib.StaticElem(value),\n          )\n          for name, value in obj_items\n      )\n    if self._pytree__has_int_keys:\n      obj_items = ((_maybe_int(name), value) for name, value in obj_items)\n      key_fn = graphlib._type_aware_sort\n    else:\n      key_fn = None\n    nodes = sorted(obj_items, key=key_fn)\n    return nodes, type(self)\n\n  def _graph_node_set_key(self, key, value: tp.Any):\n    if self._pytree__has_int_keys and isinstance(key, int):\n      key = str(key)\n    if not isinstance(key, str):\n      raise KeyError(f'Invalid key: {key!r}')\n    elif (\n      hasattr(self, key)\n      and isinstance(variable := getattr(self, key), Variable)\n      and isinstance(value, Variable)\n    ):\n      variable.update_from_state(value)\n    else:\n      setattr(self, key, value)\n\n  def _graph_node_pop_key(self, key):\n    if self._pytree__has_int_keys and isinstance(key, int):\n      key = str(key)\n    value = getattr(self, key)\n    delattr(self, key)\n    return value\n\n  def __delattr__(self, name: str) -> None:\n    if name in self._pytree__nodes:\n      mapping = {k: v for k, v in self._pytree__nodes.items() if k != name}\n      object.__setattr__(\n        self, '_pytree__nodes', graphlib.HashableMapping(mapping, copy=False)\n      )\n\n    super().__delattr__(name)\n\n  @staticmethod\n  def _graph_node_create_empty(node_type: tp.Type[P]) -> P:\n    node = object.__new__(node_type)\n    return node\n\n  def _graph_node_clear(self):\n    vars(self).clear()\n\n  def _graph_node_init(self, attributes: tp.Iterable[tuple[str | int, tp.Any]]):\n    for name, value in attributes:\n      object.__setattr__(self, _get_str(name), value)\n\n  if tp.TYPE_CHECKING:\n    def __call__(self, *args: tp.Any, **kwargs: tp.Any) -> tp.Any: ...\n\n\nclass Object(Pytree, pytree=False):\n  \"\"\"Base class for NNX objects that are not pytrees.\"\"\"\n\n  def __init_subclass__(cls, **kwargs):\n    pytree = kwargs.pop('pytree', False)\n    if pytree is not False:\n      raise ValueError(\n        \"Object is not a pytree, but 'pytree' was explicitly set to \"\n        f'{pytree!r} for type {cls}.'\n      )\n    super().__init_subclass__(pytree=pytree, **kwargs)\n\ndef _maybe_int(x):\n  try:\n    return int(x)\n  except (ValueError, TypeError):\n    return x\n\ndef _get_str(x):\n  return x if isinstance(x, str) else str(x)"
  },
  {
    "path": "flax/nnx/reprlib.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport dataclasses\nimport os\nimport sys\nimport threading\nimport typing as tp\nfrom flax import config as flax_config\n\nA = tp.TypeVar('A')\nB = tp.TypeVar('B')\n\n\ndef supports_color() -> bool:\n  \"\"\"\n  Returns True if the running system's terminal supports color, and False otherwise.\n  \"\"\"\n  try:\n    from IPython import get_ipython\n\n    ipython_available = get_ipython() is not None\n  except ImportError:\n    ipython_available = False\n\n  supported_platform = sys.platform != 'win32' or 'ANSICON' in os.environ\n  is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()\n  return (supported_platform and is_a_tty) or ipython_available\n\n\nclass Color(tp.NamedTuple):\n  TYPE: str\n  ATTRIBUTE: str\n  SEP: str\n  PAREN: str\n  COMMENT: str\n  INT: str\n  STRING: str\n  FLOAT: str\n  BOOL: str\n  NONE: str\n  END: str\n\n\nNO_COLOR = Color(\n  TYPE='',\n  ATTRIBUTE='',\n  SEP='',\n  PAREN='',\n  COMMENT='',\n  INT='',\n  STRING='',\n  FLOAT='',\n  BOOL='',\n  NONE='',\n  END='',\n)\n\n\n# Use python vscode theme colors\nif supports_color():\n  COLOR = Color(\n    TYPE='\\x1b[38;2;79;201;177m',\n    ATTRIBUTE='\\033[38;2;156;220;254m',\n    SEP='\\x1b[38;2;212;212;212m',\n    PAREN='\\x1b[38;2;255;213;3m',\n    # COMMENT='\\033[38;2;87;166;74m',\n    COMMENT='\\033[38;2;105;105;105m',  # Dark gray\n    INT='\\x1b[38;2;182;207;169m',\n    STRING='\\x1b[38;2;207;144;120m',\n    FLOAT='\\x1b[38;2;182;207;169m',\n    BOOL='\\x1b[38;2;86;156;214m',\n    NONE='\\x1b[38;2;86;156;214m',\n    END='\\x1b[0m',\n  )\nelse:\n  COLOR = NO_COLOR\n\n\n@dataclasses.dataclass\nclass ReprContext(threading.local):\n  current_color: Color = COLOR\n  depth: int = 0\n\n\nREPR_CONTEXT = ReprContext()\n\n\ndef colorized(x, /):\n  c = REPR_CONTEXT.current_color\n  if isinstance(x, list):\n    return f'{c.PAREN}[{c.END}{\", \".join(map(lambda i: colorized(i), x))}{c.PAREN}]{c.END}'\n  elif isinstance(x, tuple):\n    if len(x) == 1:\n      return f'{c.PAREN}({c.END}{colorized(x[0])},{c.PAREN}){c.END}'\n    return f'{c.PAREN}({c.END}{\", \".join(map(lambda i: colorized(i), x))}{c.PAREN}){c.END}'\n  elif isinstance(x, dict):\n    open, close = '{', '}'\n    return f'{c.PAREN}{open}{c.END}{\", \".join(f\"{c.STRING}{k!r}{c.END}: {colorized(v)}\" for k, v in x.items())}{c.PAREN}{close}{c.END}'\n  elif isinstance(x, set):\n    open, close = '{', '}'\n    return f'{c.PAREN}{open}{c.END}{\", \".join(map(lambda i: colorized(i), x))}{c.PAREN}{close}{c.END}'\n  elif isinstance(x, type):\n    return f'{c.TYPE}{x.__name__}{c.END}'\n  elif isinstance(x, bool):\n    return f'{c.BOOL}{x}{c.END}'\n  elif isinstance(x, int):\n    return f'{c.INT}{x}{c.END}'\n  elif isinstance(x, str):\n    return f'{c.STRING}{x!r}{c.END}'\n  elif isinstance(x, float):\n    return f'{c.FLOAT}{x}{c.END}'\n  elif x is None:\n    return f'{c.NONE}{x}{c.END}'\n  elif isinstance(x, Representable):\n    return get_repr(x)\n  else:\n    return repr(x)\n\n\n@dataclasses.dataclass\nclass Object:\n  type: tp.Union[str, type]\n  start: str = '('\n  end: str = ')'\n  kv_sep: str = '='\n  indent: str = '  '\n  empty_repr: str = ''\n  comment: str = ''\n  same_line: bool = False\n\n  @property\n  def elem_sep(self):\n    return ', ' if self.same_line else ',\\n'\n\n\n@dataclasses.dataclass\nclass Attr:\n  key: str\n  value: tp.Union[str, tp.Any]\n  start: str = ''\n  end: str = ''\n  use_raw_value: bool = False\n  use_raw_key: bool = False\n\n\nclass Representable:\n  __slots__ = ()\n\n  def __nnx_repr__(self) -> tp.Iterator[tp.Union[Object, Attr]]:\n    raise NotImplementedError\n\n  def __repr__(self) -> str:\n    current_color = REPR_CONTEXT.current_color\n    REPR_CONTEXT.current_color = NO_COLOR\n    try:\n      return get_repr(self)\n    finally:\n      REPR_CONTEXT.current_color = current_color\n\n  def __str__(self) -> str:\n    return get_repr(self)\n\n\ndef get_repr(obj: Representable) -> str:\n  REPR_CONTEXT.depth += 1\n  try:\n    if not isinstance(obj, Representable):\n      raise TypeError(f'Object {obj!r} is not representable')\n\n    c = REPR_CONTEXT.current_color\n    iterator = obj.__nnx_repr__()\n    config = next(iterator)\n\n    if not isinstance(config, Object):\n      raise TypeError(f'First item must be Config, got {type(config).__name__}')\n\n    kv_sep = f'{c.SEP}{config.kv_sep}{c.END}'\n\n    def _repr_elem(elem: tp.Any) -> str:\n      if not isinstance(elem, Attr):\n        raise TypeError(f'Item must be Elem, got {type(elem).__name__}')\n\n      value_repr = elem.value if elem.use_raw_value else colorized(elem.value)\n      value_repr = value_repr.replace('\\n', '\\n' + config.indent)\n      key = elem.key if elem.use_raw_key else f'{c.ATTRIBUTE}{elem.key}{c.END}'\n      indent = '' if config.same_line else config.indent\n\n      return f'{indent}{elem.start}{key}{kv_sep}{value_repr}{elem.end}'\n\n    max_depth_reached = (\n      flax_config.flax_max_repr_depth is not None\n      and REPR_CONTEXT.depth > flax_config.flax_max_repr_depth\n    )\n    if max_depth_reached:\n      elems = '...'\n    else:\n      elems = config.elem_sep.join(map(_repr_elem, iterator))\n\n    if elems:\n      if config.same_line or max_depth_reached:\n        elems_repr = elems\n        comment = ''\n      else:\n        elems_repr = '\\n' + elems + '\\n'\n        comment = f'{c.COMMENT}{config.comment}{c.END}'\n    else:\n      elems_repr = config.empty_repr\n      comment = ''\n\n    type_repr = (\n      config.type if isinstance(config.type, str) else config.type.__name__\n    )\n    type_repr = f'{c.TYPE}{type_repr}{c.END}' if type_repr else ''\n    start = f'{c.PAREN}{config.start}{c.END}' if config.start else ''\n    end = f'{c.PAREN}{config.end}{c.END}' if config.end else ''\n\n    out = f'{type_repr}{start}{comment}{elems_repr}{end}'\n    return out\n  finally:\n    REPR_CONTEXT.depth -= 1\n\nclass MappingReprMixin(Representable):\n  def __nnx_repr__(self):\n    yield Object(type=type(self), kv_sep=': ', start='({', end='})')\n\n    for key, value in self.items():  # type: ignore\n      yield Attr(colorized(key), value, use_raw_key=True)\n\n@dataclasses.dataclass(repr=False)\nclass PrettyMapping(Representable):\n  mapping: tp.Mapping\n\n  def __nnx_repr__(self):\n    yield Object(type=type(self), kv_sep=': ', start='({', end='})')\n\n    for key, value in self.mapping.items():\n      yield Attr(colorized(key), value, use_raw_key=True)\n\n\n@dataclasses.dataclass(repr=False)\nclass SequenceReprMixin(Representable):\n  def __nnx_repr__(self):\n    yield Object(type=type(self), kv_sep='', start='([', end='])')\n\n    for value in self:  # type: ignore\n      yield Attr('', value, use_raw_key=True)\n\n\n@dataclasses.dataclass(repr=False)\nclass PrettySequence(Representable):\n  sequence: tp.Sequence\n\n  def __nnx_repr__(self):\n    yield Object(type=type(self), kv_sep='', start='([', end='])')\n\n    for value in self.sequence:\n      yield Attr('', value, use_raw_key=True)"
  },
  {
    "path": "flax/nnx/rnglib.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom __future__ import annotations\n\nimport functools\nimport typing as tp\n\nimport jax\nfrom jax import random\nimport jax.numpy as jnp\n\nfrom flax import struct\nfrom flax import typing\nfrom flax.nnx import graphlib\nfrom flax.nnx.nn import initializers\nfrom flax.nnx.variablelib import Variable\nfrom flax.nnx import filterlib\nfrom flax.nnx.pytreelib import Pytree\nfrom flax.typing import MISSING, Key, Missing\nimport warnings\n\nF = tp.TypeVar('F', bound=tp.Callable[..., tp.Any])\nA = tp.TypeVar('A')\nCounts = list[int]\nAxesValue = tp.Union[int, None]\nSplitPattern = tp.Union[AxesValue, tuple[AxesValue, ...]]\nOutShardingType: tp.TypeAlias = (\n  jax.sharding.NamedSharding | jax.sharding.PartitionSpec | None\n)\nFargs = tp.ParamSpec('Fargs')\n\n\n@tp.runtime_checkable\nclass KeylessInitializer(tp.Protocol):\n  def __call__(\n    self,\n    shape: typing.Shape,\n    dtype: tp.Any | None = None,\n    out_sharding: OutShardingType = None,\n  ) -> jax.Array:\n    raise NotImplementedError\n\n\ndef _to_keyless(\n  initializer_constructor: tp.Callable[Fargs, jax.nn.initializers.Initializer],\n) -> tp.Callable[Fargs, KeylessInitializer]:\n  raise NotImplementedError\n\n\ndef _function_to_method(random_f):\n  @functools.wraps(random_f)\n  def rngs_random_method(self: Rngs | RngStream, *args, **kwargs) -> jax.Array:\n    return random_f(self(), *args, **kwargs)\n\n  return rngs_random_method\n\n\ndef _initializer_to_method(\n  initializer_constructor: tp.Callable[Fargs, jax.nn.initializers.Initializer],\n):\n  def rngs_initializer_method(\n    self: Rngs | RngStream, *args: Fargs.args, **kwargs: Fargs.kwargs\n  ) -> KeylessInitializer:\n    init_fn = initializer_constructor(*args, **kwargs)\n\n    def rngs_keyless_initializer(*init_args, **init_kwargs):\n      return init_fn(self(), *init_args, **init_kwargs)\n\n    return rngs_keyless_initializer\n\n  return rngs_initializer_method\n\n\nclass RngState(Variable[jax.Array]):\n  tag: str\n\n\nclass RngCount(RngState): ...\n\n\nclass RngKey(RngState): ...\n\n\nNotKey = filterlib.All(RngState, filterlib.Not(RngKey))\n\n\nclass RngStream(Pytree):\n\n  def __init__(\n    self,\n    key: jax.Array | int,\n    *,\n    tag: str,\n  ):\n    if isinstance(key, int):\n      key = random.key(key)\n    elif isinstance(key, jax.Array) and key.dtype == jnp.uint32:\n      key = random.wrap_key_data(key)\n\n    if not isinstance(key, jax.Array) or not jnp.issubdtype(key.dtype, jax.dtypes.prng_key):\n      raise ValueError(f'Invalid rng value: {key}, expected a '\n                       f'jax.Array of jax.dtypes.prng_key sub-dtype')\n\n    count = jnp.zeros(key.shape, dtype=jnp.uint32)\n    self.tag = tag\n    self.key = RngKey(key, tag=tag)\n    self.count = RngCount(count, tag=tag)\n\n  def __call__(self) -> jax.Array:\n    self.count._check_can_update()\n    key = random.fold_in(self.key[...], self.count[...])\n    self.count[...] += 1\n    return key\n\n  def split(self, k: int | tuple[int, ...]):\n      key = random.split(self(), k)\n      return type(self)(key, tag=self.tag)\n\n  def fork(self, *, split: int | tuple[int, ...] | None = None):\n    if split is not None:\n      warnings.warn(\n        \"The 'split' argument of 'fork' is deprecated; use the 'split' method instead.\",\n        DeprecationWarning,\n        stacklevel=2,\n      )\n    key = self()\n    if split is not None:\n      key = random.split(key, split)\n    return type(self)(key, tag=self.tag)\n\n  # ----------------------------------------------------------\n  # random functions\n  # ----------------------------------------------------------\n  if tp.TYPE_CHECKING:\n    bits = staticmethod(functools.partial(random.bits, random.key(0)))\n    uniform = staticmethod(\n      functools.partial(random.uniform, random.key(0))\n    )\n    randint = staticmethod(\n      functools.partial(random.randint, random.key(0))\n    )\n    permutation = staticmethod(\n      functools.partial(random.permutation, random.key(0))\n    )\n    choice = staticmethod(functools.partial(random.choice, random.key(0)))\n    normal = staticmethod(functools.partial(random.normal, random.key(0)))\n    multivariate_normal = staticmethod(\n      functools.partial(random.multivariate_normal, random.key(0))\n    )\n    truncated_normal = staticmethod(\n      functools.partial(random.truncated_normal, random.key(0))\n    )\n    bernoulli = staticmethod(\n      functools.partial(random.bernoulli, random.key(0))\n    )\n    beta = staticmethod(functools.partial(random.beta, random.key(0)))\n    cauchy = staticmethod(functools.partial(random.cauchy, random.key(0)))\n    dirichlet = staticmethod(\n      functools.partial(random.dirichlet, random.key(0))\n    )\n    exponential = staticmethod(\n      functools.partial(random.exponential, random.key(0))\n    )\n    gamma = staticmethod(functools.partial(random.gamma, random.key(0)))\n    loggamma = staticmethod(\n      functools.partial(random.loggamma, random.key(0))\n    )\n    poisson = staticmethod(\n      functools.partial(random.poisson, random.key(0))\n    )\n    gumbel = staticmethod(functools.partial(random.gumbel, random.key(0)))\n    categorical = staticmethod(\n      functools.partial(random.categorical, random.key(0))\n    )\n    laplace = staticmethod(\n      functools.partial(random.laplace, random.key(0))\n    )\n    logistic = staticmethod(\n      functools.partial(random.logistic, random.key(0))\n    )\n    pareto = staticmethod(functools.partial(random.pareto, random.key(0)))\n    t = staticmethod(functools.partial(random.t, random.key(0)))\n    chisquare = staticmethod(\n      functools.partial(random.chisquare, random.key(0))\n    )\n    f = staticmethod(functools.partial(random.f, random.key(0)))\n    rademacher = staticmethod(\n      functools.partial(random.rademacher, random.key(0))\n    )\n    maxwell = staticmethod(\n      functools.partial(random.maxwell, random.key(0))\n    )\n    double_sided_maxwell = staticmethod(\n      functools.partial(random.double_sided_maxwell, random.key(0))\n    )\n    weibull_min = staticmethod(\n      functools.partial(random.weibull_min, random.key(0))\n    )\n    orthogonal = staticmethod(\n      functools.partial(random.orthogonal, random.key(0))\n    )\n    generalized_normal = staticmethod(\n      functools.partial(random.generalized_normal, random.key(0))\n    )\n    ball = staticmethod(functools.partial(random.ball, random.key(0)))\n    rayleigh = staticmethod(\n      functools.partial(random.rayleigh, random.key(0))\n    )\n    wald = staticmethod(functools.partial(random.wald, random.key(0)))\n    geometric = staticmethod(\n      functools.partial(random.geometric, random.key(0))\n    )\n    triangular = staticmethod(\n      functools.partial(random.triangular, random.key(0))\n    )\n    lognormal = staticmethod(\n      functools.partial(random.lognormal, random.key(0))\n    )\n    binomial = staticmethod(\n      functools.partial(random.binomial, random.key(0))\n    )\n    multinomial = staticmethod(\n      functools.partial(random.multinomial, random.key(0))\n    )\n  else:\n    bits = _function_to_method(random.bits)\n    uniform = _function_to_method(random.uniform)\n    randint = _function_to_method(random.randint)\n    permutation = _function_to_method(random.permutation)\n    choice = _function_to_method(random.choice)\n    normal = _function_to_method(random.normal)\n    multivariate_normal = _function_to_method(random.multivariate_normal)\n    truncated_normal = _function_to_method(random.truncated_normal)\n    bernoulli = _function_to_method(random.bernoulli)\n    beta = _function_to_method(random.beta)\n    cauchy = _function_to_method(random.cauchy)\n    dirichlet = _function_to_method(random.dirichlet)\n    exponential = _function_to_method(random.exponential)\n    gamma = _function_to_method(random.gamma)\n    loggamma = _function_to_method(random.loggamma)\n    poisson = _function_to_method(random.poisson)\n    gumbel = _function_to_method(random.gumbel)\n    categorical = _function_to_method(random.categorical)\n    laplace = _function_to_method(random.laplace)\n    logistic = _function_to_method(random.logistic)\n    pareto = _function_to_method(random.pareto)\n    t = _function_to_method(random.t)\n    chisquare = _function_to_method(random.chisquare)\n    f = _function_to_method(random.f)\n    rademacher = _function_to_method(random.rademacher)\n    maxwell = _function_to_method(random.maxwell)\n    double_sided_maxwell = _function_to_method(random.double_sided_maxwell)\n    weibull_min = _function_to_method(random.weibull_min)\n    orthogonal = _function_to_method(random.orthogonal)\n    generalized_normal = _function_to_method(random.generalized_normal)\n    ball = _function_to_method(random.ball)\n    rayleigh = _function_to_method(random.rayleigh)\n    wald = _function_to_method(random.wald)\n    geometric = _function_to_method(random.geometric)\n    triangular = _function_to_method(random.triangular)\n    lognormal = _function_to_method(random.lognormal)\n    binomial = _function_to_method(random.binomial)\n    multinomial = _function_to_method(random.multinomial)\n\n  # ----------------------------------------------------------\n  # initializers\n  # ----------------------------------------------------------\n  if tp.TYPE_CHECKING:\n    # skip constant\n    delta_orthogonal = staticmethod(_to_keyless(initializers.delta_orthogonal))\n    glorot_normal = staticmethod(_to_keyless(initializers.glorot_normal))\n    glorot_uniform = staticmethod(_to_keyless(initializers.glorot_uniform))\n    he_normal = staticmethod(_to_keyless(initializers.he_normal))\n    he_uniform = staticmethod(_to_keyless(initializers.he_uniform))\n    kaiming_normal = staticmethod(_to_keyless(initializers.kaiming_normal))\n    kaiming_uniform = staticmethod(_to_keyless(initializers.kaiming_uniform))\n    lecun_normal = staticmethod(_to_keyless(initializers.lecun_normal))\n    lecun_uniform = staticmethod(_to_keyless(initializers.lecun_uniform))\n    # skip normal as it conflicts with jax.random.normal\n    # skip ones\n    # skip orthogonal as it conflicts with jax.random.orthogonal\n    # skip truncated_normal as it conflicts with jax.random.truncated_normal\n    # skip uniform as it conflicts with jax.random.uniform\n    variance_scaling = staticmethod(_to_keyless(initializers.variance_scaling))\n    xavier_normal = staticmethod(_to_keyless(initializers.xavier_normal))\n    xavier_uniform = staticmethod(_to_keyless(initializers.xavier_uniform))\n    # skip zeros\n  else:\n    # skip constant\n    delta_orthogonal = _initializer_to_method(initializers.delta_orthogonal)\n    glorot_normal = _initializer_to_method(initializers.glorot_normal)\n    glorot_uniform = _initializer_to_method(initializers.glorot_uniform)\n    he_normal = _initializer_to_method(initializers.he_normal)\n    he_uniform = _initializer_to_method(initializers.he_uniform)\n    kaiming_normal = _initializer_to_method(initializers.kaiming_normal)\n    kaiming_uniform = _initializer_to_method(initializers.kaiming_uniform)\n    lecun_normal = _initializer_to_method(initializers.lecun_normal)\n    lecun_uniform = _initializer_to_method(initializers.lecun_uniform)\n    # skip normal as it conflicts with jax.random.normal\n    # skip ones\n    # skip orthogonal as it conflicts with jax.random.orthogonal\n    # skip truncated_normal as it conflicts with jax.random.truncated_normal\n    # skip uniform as it conflicts with jax.random.uniform\n    variance_scaling = _initializer_to_method(initializers.variance_scaling)\n    xavier_normal = _initializer_to_method(initializers.xavier_normal)\n    xavier_uniform = _initializer_to_method(initializers.xavier_uniform)\n    # skip zeros\n\n\nRngValue = tp.Union[int, jax.Array]\n\nclass Rngs(Pytree):\n  \"\"\"A small abstraction to manage RNG state.\n\n  ``Rngs`` allows the creation of ``RngStream`` which are used to easily generate new unique\n  random keys on demand. An ``RngStream`` is a wrapper around a JAX random ``key``, and a\n  ``counter``. Every time a key is requested, the counter is incremented and the key is\n  generated from the seed key and the counter by using ``jax.random.fold_in``.\n\n  To create an ``Rngs`` pass in an integer or ``jax.random.key`` to the\n  constructor as a keyword argument with the name of the stream. The key will be used as the\n  starting seed for the stream, and the counter will be initialized to zero. Then call the\n  stream to get a key::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n\n    >>> rngs = nnx.Rngs(params=0, dropout=1)\n\n    >>> param_key1 = rngs.params()\n    >>> param_key2 = rngs.params()\n    >>> dropout_key1 = rngs.dropout()\n    >>> dropout_key2 = rngs.dropout()\n    ...\n    >>> assert param_key1 != dropout_key1\n\n  Trying to generate a key for a stream that was not specified during construction\n  will result in an error being raised::\n\n    >>> rngs = nnx.Rngs(params=0, dropout=1)\n    >>> try:\n    ...   key = rngs.unkown_stream()\n    ... except AttributeError as e:\n    ...   print(e)\n    No RngStream named 'unkown_stream' found in Rngs.\n\n  The ``default`` stream can be created by passing in a key to the constructor without\n  specifying a stream name. When the ``default`` stream is set the ``rngs`` object can be\n  called directly to get a key, and calling streams that were not specified during\n  construction will fallback to ``default``::\n\n    >>> rngs = nnx.Rngs(0, params=1)\n    ...\n    >>> key1 = rngs.default()       # uses 'default'\n    >>> key2 = rngs()               # uses 'default'\n    >>> key3 = rngs.params()        # uses 'params'\n    >>> key4 = rngs.dropout()       # uses 'default'\n    >>> key5 = rngs.unkown_stream() # uses 'default'\n  \"\"\"\n\n  def __init__(\n    self,\n    default: RngValue\n    | RngStream\n    | tp.Mapping[str, RngValue | RngStream]\n    | None = None,\n    **rngs: RngValue | RngStream,\n  ):\n    \"\"\"\n    Args:\n      default: the starting seed for the ``default`` stream, defaults to None.\n      **rngs: keyword arguments specifying the starting seed for each stream.\n        The key can be an integer or a ``jax.random.key``.\n    \"\"\"\n    if default is not None:\n      if isinstance(default, tp.Mapping):\n        rngs = {**default, **rngs}\n      else:\n        rngs['default'] = default\n\n    for tag, key in rngs.items():\n      if isinstance(key, RngStream):\n        key = key.key.get_value()\n      stream = RngStream(\n        key=key,\n        tag=tag,\n      )\n      setattr(self, tag, stream)\n\n  def _get_stream(self, name: str, error_type: type[Exception]) -> RngStream:\n    stream_vars = vars(self)\n    if name not in stream_vars:\n      if 'default' not in stream_vars:\n        raise error_type(f\"No RngStream named '{name}' found in Rngs.\")\n      stream = stream_vars['default']\n    else:\n      stream = stream_vars[name]\n\n    return stream\n\n  def __getitem__(self, name: str):\n    return self._get_stream(name, KeyError)\n\n  def __getattr__(self, name: str):\n    return self._get_stream(name, AttributeError)\n\n  def __call__(self):\n    return self.default()\n\n  def __iter__(self) -> tp.Iterator[str]:\n    for name, stream in vars(self).items():\n      if isinstance(stream, RngStream):\n        yield name\n\n  def __len__(self) -> int:\n    return sum(\n      1 for stream in vars(self).values() if isinstance(stream, RngStream)\n    )\n\n  def __contains__(self, name: tp.Any) -> bool:\n    return name in vars(self)\n\n  def items(self):\n    for name, stream in vars(self).items():\n      if isinstance(stream, RngStream):\n        yield name, stream\n\n  def split(self, k: tp.Mapping[filterlib.Filter, int | tuple[int, ...]] | int | tuple[int, ...]):\n    \"\"\"\n    Splits the keys of the newly created ``Rngs`` object.\n\n    Example::\n      >>> from flax import nnx\n      ...\n      >>> rngs = nnx.Rngs(params=1, dropout=2)\n      >>> new_rngs = rngs.split(5)\n      ...\n      >>> assert new_rngs.params.key.shape == (5,)\n      >>> assert new_rngs.dropout.key.shape == (5,)\n\n    ``split`` also accepts a mapping of\n    `Filters <https://flax.readthedocs.io/en/latest/guides/filters_guide.html>`__  to\n    split sizes or None to control which streams are split and how they are split::\n\n      >>> rngs = nnx.Rngs(params=1, dropout=2, noise=3)\n      >>> new_rngs = rngs.split({\n      ...  'params': 5,      # split params into 5 keys\n      ...  'dropout': None,  # don't split dropout\n      ...  ...: (2, 5),      # split anything else into 2x5 keys\n      ... })\n      ...\n      >>> assert new_rngs.params.key.shape == (5,)\n      >>> assert new_rngs.dropout.key.shape == ()\n      >>> assert new_rngs.noise.key.shape == (2, 5)\n    \"\"\"\n    if isinstance(k, int):\n      k = {...: k}\n    elif isinstance(k, tuple):\n      k = {...: k}\n\n    split_predicates = {filterlib.to_predicate(k): v for k, v in k.items()}\n    keys: dict[str, RngStream] = {}\n    for name, stream in self.items():\n      for predicate, num_splits in split_predicates.items():\n        if predicate((), stream):\n          if num_splits is None:\n            keys[name] = stream\n          else:\n            keys[name] = stream.split(num_splits)\n          break\n        else:\n          keys[name] = stream\n\n    return Rngs(**keys)\n\n  def fork(\n    self,\n    /,\n    *,\n    split: tp.Mapping[filterlib.Filter, int | tuple[int, ...]]\n    | int\n    | tuple[int, ...]\n    | None = None,\n  ):\n    \"\"\"Returns a new Rngs object with new unique RNG keys.\n\n    Example::\n      >>> from flax import nnx\n      ...\n      >>> rngs = nnx.Rngs(params=1, dropout=2)\n      >>> new_rngs = rngs.fork()\n      ...\n      >>> assert rngs.params() != new_rngs.params()\n    \"\"\"\n    if split is not None:\n      warnings.warn(\n        \"The 'split' argument of 'fork' is deprecated; use the 'split' method instead.\",\n        DeprecationWarning,\n        stacklevel=2,\n      )\n\n    if split is None:\n      split = {}\n    elif isinstance(split, int):\n      split = {...: split}\n    elif isinstance(split, tuple):\n      split = {...: split}\n\n    split_predicates = {filterlib.to_predicate(k): v for k, v in split.items()}\n    keys: dict[str, RngStream] = {}\n    for name, stream in self.items():\n      for predicate, num_splits in split_predicates.items():\n        if predicate((), stream):\n          keys[name] = stream.fork(split=num_splits)\n          break\n      else:\n        keys[name] = stream.fork()\n\n    return Rngs(**keys)\n\n  # ----------------------------------------------------------\n  # random functions\n  # ----------------------------------------------------------\n  if tp.TYPE_CHECKING:\n    bits = staticmethod(functools.partial(random.bits, random.key(0)))\n    uniform = staticmethod(\n      functools.partial(random.uniform, random.key(0))\n    )\n    randint = staticmethod(\n      functools.partial(random.randint, random.key(0))\n    )\n    permutation = staticmethod(\n      functools.partial(random.permutation, random.key(0))\n    )\n    choice = staticmethod(functools.partial(random.choice, random.key(0)))\n    normal = staticmethod(functools.partial(random.normal, random.key(0)))\n    multivariate_normal = staticmethod(\n      functools.partial(random.multivariate_normal, random.key(0))\n    )\n    truncated_normal = staticmethod(\n      functools.partial(random.truncated_normal, random.key(0))\n    )\n    bernoulli = staticmethod(\n      functools.partial(random.bernoulli, random.key(0))\n    )\n    beta = staticmethod(functools.partial(random.beta, random.key(0)))\n    cauchy = staticmethod(functools.partial(random.cauchy, random.key(0)))\n    dirichlet = staticmethod(\n      functools.partial(random.dirichlet, random.key(0))\n    )\n    exponential = staticmethod(\n      functools.partial(random.exponential, random.key(0))\n    )\n    gamma = staticmethod(functools.partial(random.gamma, random.key(0)))\n    loggamma = staticmethod(\n      functools.partial(random.loggamma, random.key(0))\n    )\n    poisson = staticmethod(\n      functools.partial(random.poisson, random.key(0))\n    )\n    gumbel = staticmethod(functools.partial(random.gumbel, random.key(0)))\n    categorical = staticmethod(\n      functools.partial(random.categorical, random.key(0))\n    )\n    laplace = staticmethod(\n      functools.partial(random.laplace, random.key(0))\n    )\n    logistic = staticmethod(\n      functools.partial(random.logistic, random.key(0))\n    )\n    pareto = staticmethod(functools.partial(random.pareto, random.key(0)))\n    t = staticmethod(functools.partial(random.t, random.key(0)))\n    chisquare = staticmethod(\n      functools.partial(random.chisquare, random.key(0))\n    )\n    f = staticmethod(functools.partial(random.f, random.key(0)))\n    rademacher = staticmethod(\n      functools.partial(random.rademacher, random.key(0))\n    )\n    maxwell = staticmethod(\n      functools.partial(random.maxwell, random.key(0))\n    )\n    double_sided_maxwell = staticmethod(\n      functools.partial(random.double_sided_maxwell, random.key(0))\n    )\n    weibull_min = staticmethod(\n      functools.partial(random.weibull_min, random.key(0))\n    )\n    orthogonal = staticmethod(\n      functools.partial(random.orthogonal, random.key(0))\n    )\n    generalized_normal = staticmethod(\n      functools.partial(random.generalized_normal, random.key(0))\n    )\n    ball = staticmethod(functools.partial(random.ball, random.key(0)))\n    rayleigh = staticmethod(\n      functools.partial(random.rayleigh, random.key(0))\n    )\n    wald = staticmethod(functools.partial(random.wald, random.key(0)))\n    geometric = staticmethod(\n      functools.partial(random.geometric, random.key(0))\n    )\n    triangular = staticmethod(\n      functools.partial(random.triangular, random.key(0))\n    )\n    lognormal = staticmethod(\n      functools.partial(random.lognormal, random.key(0))\n    )\n    binomial = staticmethod(\n      functools.partial(random.binomial, random.key(0))\n    )\n    multinomial = staticmethod(\n      functools.partial(random.multinomial, random.key(0))\n    )\n  else:\n    bits = _function_to_method(random.bits)\n    uniform = _function_to_method(random.uniform)\n    randint = _function_to_method(random.randint)\n    permutation = _function_to_method(random.permutation)\n    choice = _function_to_method(random.choice)\n    normal = _function_to_method(random.normal)\n    multivariate_normal = _function_to_method(random.multivariate_normal)\n    truncated_normal = _function_to_method(random.truncated_normal)\n    bernoulli = _function_to_method(random.bernoulli)\n    beta = _function_to_method(random.beta)\n    cauchy = _function_to_method(random.cauchy)\n    dirichlet = _function_to_method(random.dirichlet)\n    exponential = _function_to_method(random.exponential)\n    gamma = _function_to_method(random.gamma)\n    loggamma = _function_to_method(random.loggamma)\n    poisson = _function_to_method(random.poisson)\n    gumbel = _function_to_method(random.gumbel)\n    categorical = _function_to_method(random.categorical)\n    laplace = _function_to_method(random.laplace)\n    logistic = _function_to_method(random.logistic)\n    pareto = _function_to_method(random.pareto)\n    t = _function_to_method(random.t)\n    chisquare = _function_to_method(random.chisquare)\n    f = _function_to_method(random.f)\n    rademacher = _function_to_method(random.rademacher)\n    maxwell = _function_to_method(random.maxwell)\n    double_sided_maxwell = _function_to_method(random.double_sided_maxwell)\n    weibull_min = _function_to_method(random.weibull_min)\n    orthogonal = _function_to_method(random.orthogonal)\n    generalized_normal = _function_to_method(random.generalized_normal)\n    ball = _function_to_method(random.ball)\n    rayleigh = _function_to_method(random.rayleigh)\n    wald = _function_to_method(random.wald)\n    geometric = _function_to_method(random.geometric)\n    triangular = _function_to_method(random.triangular)\n    lognormal = _function_to_method(random.lognormal)\n    binomial = _function_to_method(random.binomial)\n    multinomial = _function_to_method(random.multinomial)\n\n  # ----------------------------------------------------------\n  # initializers\n  # ----------------------------------------------------------\n  if tp.TYPE_CHECKING:\n    # skip constant\n    delta_orthogonal = staticmethod(_to_keyless(initializers.delta_orthogonal))\n    glorot_normal = staticmethod(_to_keyless(initializers.glorot_normal))\n    glorot_uniform = staticmethod(_to_keyless(initializers.glorot_uniform))\n    he_normal = staticmethod(_to_keyless(initializers.he_normal))\n    he_uniform = staticmethod(_to_keyless(initializers.he_uniform))\n    kaiming_normal = staticmethod(_to_keyless(initializers.kaiming_normal))\n    kaiming_uniform = staticmethod(_to_keyless(initializers.kaiming_uniform))\n    lecun_normal = staticmethod(_to_keyless(initializers.lecun_normal))\n    lecun_uniform = staticmethod(_to_keyless(initializers.lecun_uniform))\n    # skip normal as it conflicts with jax.random.normal\n    # skip ones\n    # skip orthogonal as it conflicts with jax.random.orthogonal\n    # skip truncated_normal as it conflicts with jax.random.truncated_normal\n    # skip uniform as it conflicts with jax.random.uniform\n    variance_scaling = staticmethod(_to_keyless(initializers.variance_scaling))\n    xavier_normal = staticmethod(_to_keyless(initializers.xavier_normal))\n    xavier_uniform = staticmethod(_to_keyless(initializers.xavier_uniform))\n    # skip zeros\n  else:\n    # skip constant\n    delta_orthogonal = _initializer_to_method(initializers.delta_orthogonal)\n    glorot_normal = _initializer_to_method(initializers.glorot_normal)\n    glorot_uniform = _initializer_to_method(initializers.glorot_uniform)\n    he_normal = _initializer_to_method(initializers.he_normal)\n    he_uniform = _initializer_to_method(initializers.he_uniform)\n    kaiming_normal = _initializer_to_method(initializers.kaiming_normal)\n    kaiming_uniform = _initializer_to_method(initializers.kaiming_uniform)\n    lecun_normal = _initializer_to_method(initializers.lecun_normal)\n    lecun_uniform = _initializer_to_method(initializers.lecun_uniform)\n    # skip normal as it conflicts with jax.random.normal\n    # skip ones\n    # skip orthogonal as it conflicts with jax.random.orthogonal\n    # skip truncated_normal as it conflicts with jax.random.truncated_normal\n    # skip uniform as it conflicts with jax.random.uniform\n    variance_scaling = _initializer_to_method(initializers.variance_scaling)\n    xavier_normal = _initializer_to_method(initializers.xavier_normal)\n    xavier_uniform = _initializer_to_method(initializers.xavier_uniform)\n    # skip zeros\n\n\nStreamBackup = (\n  tuple[RngStream, jax.Array, jax.Array] | tuple[RngStream, jax.Array]\n)\n\n\nclass SplitBackups(struct.PyTreeNode, tp.Iterable[StreamBackup]):\n  backups: list[StreamBackup]\n\n  def __iter__(self) -> tp.Iterator[StreamBackup]:\n    return iter(self.backups)\n\n  def __enter__(self):\n    return self\n\n  def __exit__(self, *args):\n    restore_rngs(self)\n\n\n@tp.overload\ndef split_rngs(\n  node: tp.Any,\n  /,\n  *,\n  splits: int | tuple[int, ...],\n  only: filterlib.Filter = ...,\n  squeeze: bool = False,\n  graph: tp.Literal[True] | None = None,\n) -> SplitBackups: ...\n@tp.overload\ndef split_rngs(\n  node: A,\n  /,\n  *,\n  splits: int | tuple[int, ...],\n  only: filterlib.Filter = ...,\n  squeeze: bool = False,\n  graph: tp.Literal[False],\n) -> A: ...\n@tp.overload\ndef split_rngs(\n  *,\n  splits: int | tuple[int, ...],\n  only: filterlib.Filter = ...,\n  squeeze: bool = False,\n  graph: bool | None = None,\n) -> tp.Callable[[F], F]: ...\ndef split_rngs(\n  node: tp.Any = MISSING,\n  /,\n  *,\n  splits: int | tuple[int, ...],\n  only: filterlib.Filter = ...,\n  squeeze: bool = False,\n  graph: bool | None = None,\n) -> SplitBackups | tp.Any | tp.Callable[[F], F]:\n  \"\"\"Splits the (nested) Rng states of the given node.\n\n  Args:\n    node: the base node containing the rng states to split.\n    splits: an integer or tuple of integers specifying the\n      shape of the split rng keys.\n    only: a Filter selecting which rng states to split.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n\n  Returns:\n    A SplitBackups iterable if ``node`` is provided, otherwise a\n    decorator that splits the rng states of the inputs to the\n    decorated function.\n\n  Example::\n\n    >>> from flax import nnx\n    ...\n    >>> rngs = nnx.Rngs(params=0, dropout=1)\n    >>> _ = nnx.split_rngs(rngs, splits=5)\n    >>> rngs.params.key.shape, rngs.dropout.key.shape\n    ((5,), (5,))\n\n    >>> rngs = nnx.Rngs(params=0, dropout=1)\n    >>> _ = nnx.split_rngs(rngs, splits=(2, 5))\n    >>> rngs.params.key.shape, rngs.dropout.key.shape\n    ((2, 5), (2, 5))\n\n\n    >>> rngs = nnx.Rngs(params=0, dropout=1)\n    >>> _ = nnx.split_rngs(rngs, splits=5, only='params')\n    >>> rngs.params.key.shape, rngs.dropout.key.shape\n    ((5,), ())\n\n  Once split, random state can be used with transforms like :func:`nnx.vmap`::\n\n    >>> class Model(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.linear = nnx.Linear(2, 3, rngs=rngs)\n    ...     self.dropout = nnx.Dropout(0.5, rngs=rngs)\n    ...\n    >>> rngs = nnx.Rngs(params=0, dropout=1)\n    >>> _ = nnx.split_rngs(rngs, splits=5, only='params')\n    ...\n    >>> state_axes = nnx.StateAxes({(nnx.Param, 'params'): 0, ...: None})\n    ...\n    >>> @nnx.vmap(in_axes=(state_axes,), out_axes=state_axes)\n    ... def create_model(rngs):\n    ...   return Model(rngs)\n    ...\n    >>> model = create_model(rngs)\n    >>> model.dropout.rngs.key.shape\n    ()\n\n  ``split_rngs`` returns a SplitBackups object that can be used to restore the\n  original unsplit rng states using :func:`nnx.restore_rngs`, this is useful\n  when you only want to split the rng states temporarily::\n\n    >>> rngs = nnx.Rngs(params=0, dropout=1)\n    ...\n    >>> backups = nnx.split_rngs(rngs, splits=5, only='params')\n    >>> model = create_model(rngs)\n    >>> nnx.restore_rngs(backups)\n    ...\n    >>> model.dropout.rngs.key.shape\n    ()\n\n  SplitBackups can also be used as a context manager to automatically restore\n  the rng states when exiting the context::\n\n    >>> rngs = nnx.Rngs(params=0, dropout=1)\n    ...\n    >>> with nnx.split_rngs(rngs, splits=5, only='params'):\n    ...   model = create_model(rngs)\n    ...\n    >>> model.dropout.rngs.key.shape\n    ()\n\n    >>> state_axes = nnx.StateAxes({(nnx.Param, 'params'): 0, ...: None})\n    ...\n    >>> @nnx.split_rngs(splits=5, only='params')\n    ... @nnx.vmap(in_axes=(state_axes,), out_axes=state_axes)\n    ... def create_model(rngs):\n    ...   return Model(rngs)\n    ...\n    >>> rngs = nnx.Rngs(params=0, dropout=1)\n    >>> model = create_model(rngs)\n    >>> model.dropout.rngs.key.shape\n    ()\n\n\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n\n  if isinstance(node, Missing):\n\n    def split_rngs_decorator(f: F) -> F:\n      @functools.wraps(f)\n      def split_rngs_wrapper(*args, **kwargs):\n        if graph:\n          with split_rngs(\n            (args, kwargs), splits=splits, only=only, squeeze=squeeze,\n            graph=True,\n          ):\n            return f(*args, **kwargs)\n        else:\n          args, kwargs = split_rngs(\n            (args, kwargs), splits=splits, only=only, squeeze=squeeze,\n            graph=False,\n          )\n          return f(*args, **kwargs)\n\n      return tp.cast(F, split_rngs_wrapper)\n\n    return split_rngs_decorator  # type: ignore[bad-return-type]\n\n  if squeeze and splits != 1:\n    raise ValueError('squeeze=True is only supported for splits=1')\n\n  if graph:\n    return _graph_split_rngs(\n      node, splits=splits, only=only, squeeze=squeeze,\n    )\n  else:\n    return _tree_split_rngs(\n      node, splits=splits, only=only, squeeze=squeeze,\n    )\n\n\ndef _graph_split_rngs(\n  node: tp.Any,\n  /,\n  *,\n  splits: int | tuple[int, ...],\n  only: filterlib.Filter = ...,\n  squeeze: bool = False,\n) -> SplitBackups:\n  predicate = filterlib.to_predicate(only)\n  backups: list[StreamBackup] = []\n  for path, stream in graphlib.iter_graph(node, graph=True):\n    if (\n      isinstance(stream, RngStream)\n      and predicate((*path, 'key'), stream.key)\n      and predicate((*path, 'count'), stream.count)\n    ):\n      key = stream()\n      backups.append((stream, stream.key[...], stream.count[...]))\n      key = random.split(key, splits)\n      if squeeze:\n        key = key[0]\n      stream.key.set_value(key)\n      if squeeze:\n        counts_shape = stream.count.shape\n      elif isinstance(splits, int):\n        counts_shape = (splits, *stream.count.shape)\n      else:\n        counts_shape = (*splits, *stream.count.shape)\n\n      stream.count.set_value(jnp.zeros(counts_shape, dtype=jnp.uint32))\n\n  return SplitBackups(backups)\n\n\ndef _tree_split_rngs(\n  node: tp.Any,\n  /,\n  *,\n  splits: int | tuple[int, ...],\n  only: filterlib.Filter = ...,\n  squeeze: bool = False,\n) -> tp.Any:\n  predicate = filterlib.to_predicate(only)\n\n  def _split_stream(path, node):\n    if (\n      isinstance(node, RngStream)\n      and predicate((*path, 'key'), node.key)\n      and predicate((*path, 'count'), node.count)\n    ):\n      key = random.split(node(), splits)\n      if squeeze:\n        key = key[0]\n      if squeeze:\n        counts_shape = node.count.shape\n      elif isinstance(splits, int):\n        counts_shape = (splits, *node.count.shape)\n      else:\n        counts_shape = (*splits, *node.count.shape)\n\n      node.key = RngKey(key, tag=node.tag)\n      node.count = RngCount(\n        jnp.zeros(counts_shape, dtype=jnp.uint32), tag=node.tag\n      )\n    return node\n\n  return graphlib.recursive_map(_split_stream, node, graph=False)\n\n@tp.overload\ndef fork_rngs(\n  node: tp.Any,\n  /,\n  *,\n  split: tp.Mapping[filterlib.Filter, int | tuple[int, ...] | None]\n    | int\n    | None = None,\n  graph: bool | None = None,\n) -> SplitBackups: ...\n@tp.overload\ndef fork_rngs(\n  *,\n  split: tp.Mapping[filterlib.Filter, int | tuple[int, ...] | None]\n    | int\n    | None = None,\n  graph: bool | None = None,\n) -> tp.Callable[[F], F]: ...\ndef fork_rngs(\n  node: tp.Any = MISSING,\n  /,\n  *,\n  split: tp.Mapping[filterlib.Filter, int | tuple[int, ...] | None]\n    | int\n    | None = None,\n  graph: bool | None = None,\n) -> SplitBackups | tp.Callable[[F], F]:\n  \"\"\"Forks the (nested) Rng states of the given node.\n\n  Args:\n    node: the base node containing the rng states to fork.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n\n  Returns:\n    A SplitBackups iterable if ``node`` is provided, otherwise a\n    decorator that forks the rng states of the inputs to the\n    decorated function.\n\n  Example::\n\n    >>> from flax import nnx\n    ...\n    >>> rngs = nnx.Rngs(params=0, dropout=1)\n    >>> _ = nnx.fork_rngs(rngs)\n\n  ``fork_rngs`` returns a SplitBackups object that can be used to restore the\n  original unforked rng states using :func:`nnx.restore_rngs`, this is useful\n  when you only want to fork the rng states temporarily::\n\n    >>> rngs = nnx.Rngs(params=0, dropout=1)\n    ...\n    >>> backups = nnx.fork_rngs(rngs)\n    >>> model = nnx.Linear(2, 3, rngs=rngs)\n    >>> nnx.restore_rngs(backups)\n    ...\n\n  SplitBackups can also be used as a context manager to automatically restore\n  the rng states when exiting the context::\n\n    >>> rngs = nnx.Rngs(params=0, dropout=1)\n    ...\n    >>> with nnx.fork_rngs(rngs):\n    ...   model = nnx.Linear(2, 3, rngs=rngs)\n\n  \"\"\"\n  if isinstance(node, Missing):\n\n    def fork_rngs_decorator(f: F) -> F:\n      @functools.wraps(f)\n      def fork_rngs_wrapper(*args, **kwargs):\n        with fork_rngs((args, kwargs), split=split):\n          return f(*args, **kwargs)\n\n      return tp.cast(F, fork_rngs_wrapper)\n\n    return fork_rngs_decorator  # type: ignore[bad-return-type]\n\n  if split is None:\n    split = {...: None}\n  elif isinstance(split, int | tuple):\n    split = {...: split}\n\n  predicate_splits = {\n    filterlib.to_predicate(k): v for k, v in split.items()\n  }\n  backups: list[StreamBackup] = []\n  for path, stream in graphlib.iter_graph(node, graph=graph):\n    for predicate, splits in predicate_splits.items():\n      if (\n        isinstance(stream, RngStream)\n        and predicate((*path, 'key'), stream.key)\n        and predicate((*path, 'count'), stream.count)\n      ):\n        forked_stream = stream.fork(split=splits)\n        # backup the original stream state\n        backups.append((stream, stream.key[...], stream.count[...]))\n        # apply the forked key and count to the original stream\n        stream.key.set_value(forked_stream.key.get_value())\n        stream.count.set_value(forked_stream.count.get_value())\n\n  return SplitBackups(backups)\n\n\ndef backup_keys(node: tp.Any, /, *, graph: bool | None = None):\n  backups: list[StreamBackup] = []\n  for _, stream in graphlib.iter_graph(node, graph=graph):\n    if isinstance(stream, RngStream):\n      backups.append((stream, stream.key[...]))\n  return backups\n\ndef _scalars_only(\n  path: tuple[Key, ...], scalar_key: jax.Array, target_shape: tuple[int, ...]\n) -> jax.Array:\n  if target_shape != ():\n    raise ValueError(\n      f'Cannot reseed stream at path {path!r} becuase it has a non-scalar key, '\n      f'found key with shape {target_shape}. If all your multi-dimensional '\n      'keys have unique values on all dimensions, set policy=\"match_shape\", '\n      'else provide a custom reseed policy.'\n    )\n  return scalar_key\n\n\ndef _match_shape(\n  path: tuple[Key, ...], scalar_key: jax.Array, target_shape: tuple[int, ...]\n) -> jax.Array:\n  if target_shape == ():\n    return scalar_key\n  return random.split(scalar_key, target_shape)\n\n\ndef reseed(\n  node,\n  /,\n  *,\n  graph: bool | None = None,\n  policy: tp.Literal['scalars_only', 'match_shape']\n  | tp.Callable[\n    [tuple, jax.Array, tuple[int, ...]], jax.Array\n  ] = 'scalars_only',\n  **stream_keys: RngValue,\n):\n  \"\"\"Update the keys of the specified RNG streams with new keys.\n\n  Args:\n    node: the node to reseed the RNG streams in.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references. If ``False``, uses\n      tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol.\n    policy: defines how the the new scalar key is for each RngStream is used to\n      reseed the stream. If ``'scalars_only'`` is given (the default), an error is raised\n      if the target stream key is not a scalar. If ``'match_shape'`` is given, the new\n      scalar key is split to match the shape of the target stream key. A callable\n      of the form ``(path, scalar_key, target_shape) -> new_key`` can be passed to\n      define a custom reseeding policy.\n    **stream_keys: a mapping of stream names to new keys. The keys can be\n      either integers or ``jax.random.key``.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n    ...\n    >>> class Model(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.linear = nnx.Linear(2, 3, rngs=rngs)\n    ...     self.dropout = nnx.Dropout(0.5, rngs=rngs)\n    ...   def __call__(self, x):\n    ...     return self.dropout(self.linear(x))\n    ...\n    >>> model = Model(nnx.Rngs(params=0, dropout=42))\n    >>> x = jnp.ones((1, 2))\n    ...\n    >>> y1 = model(x)\n    ...\n    >>> # reset the ``dropout`` stream key to 42\n    >>> nnx.reseed(model, dropout=42)\n    >>> y2 = model(x)\n    ...\n    >>> jnp.allclose(y1, y2)\n    Array(True, dtype=bool)\n  \"\"\"\n  if policy == 'scalars_only':\n    policy = _scalars_only\n  elif policy == 'match_shape':\n    policy = _match_shape\n  elif not callable(policy):\n    raise ValueError(\n      f'policy must be \"scalars_only\", \"match_shape\" or a callable, '\n      f'got {policy!r}'\n    )\n  rngs = Rngs(**stream_keys)\n  for path, stream in graphlib.iter_graph(node, graph=graph):\n    if isinstance(stream, RngStream):\n      if stream.key.tag in stream_keys:\n        key = rngs[stream.key.tag]()\n        key = policy(path, key, stream.key.shape)\n        stream.key.set_value(key)\n        stream.count.set_value(jnp.zeros(key.shape, dtype=jnp.uint32))\n\n\ndef restore_rngs(backups: tp.Iterable[StreamBackup], /):\n  for backup in backups:\n    stream = backup[0]\n    stream.key.set_value(backup[1])\n    if len(backup) == 3:\n      stream.count.set_value(backup[2])  # count\n"
  },
  {
    "path": "flax/nnx/scripts/requirements.txt",
    "content": "datasets>=2.12.0\n"
  },
  {
    "path": "flax/nnx/scripts/run-all-examples.bash",
    "content": "set -e\n\nsource .venv/bin/activate\n\nfor f in $(find examples/nnx_toy_examples -name \"*.py\" -maxdepth 1); do\n    echo -e \"\\n---------------------------------\"\n    echo \"$f\"\n    echo \"---------------------------------\"\n    MPLBACKEND=Agg python \"$f\"\ndone\n"
  },
  {
    "path": "flax/nnx/spmd.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing as tp\n\nimport flax.core.spmd as core_spmd\nfrom flax.nnx import variablelib, graphlib\nfrom flax.nnx.transforms.transforms import eval_shape\nfrom flax.typing import (\n  Sharding,\n)\nimport jax\nfrom jax.sharding import PartitionSpec\n\nA = tp.TypeVar('A')\nF = tp.TypeVar('F', bound=tp.Callable[..., tp.Any])\nPARTITION_NAME = 'partition_name'\n\n\n# Transform axis change helpers\n# ------------------------------------------------------------------------------\n\n\ndef add_axis(tree: A, index: int, transform_metadata: tp.Mapping) -> A:\n  axis_name, other_meta = _get_partition_name_and_metadata(transform_metadata)\n\n  def insert_field(fields, index, value):\n    iterable = list(fields)\n    while len(iterable) < index:\n      iterable.append(None)\n    iterable.insert(index, value)\n    return tuple(iterable)\n\n  def _add_axis(x: tp.Any):\n    if isinstance(x, variablelib.Variable):\n      metadata = x.get_metadata()\n      if 'out_sharding' in metadata and metadata['out_sharding']:\n        sharding = metadata['out_sharding']\n        x.set_metadata(out_sharding=insert_field(sharding, index, axis_name))\n\n      for k, v in other_meta.items():\n        if hasattr(x, k) and (t := getattr(x, k)) and isinstance(t, tuple):\n          x.set_metadata(k, insert_field(t, index, v))\n\n      assert isinstance(x, variablelib.Variable)\n      x.add_axis(index, axis_name)\n    return x\n\n  return jax.tree.map(\n    _add_axis, tree, is_leaf=lambda x: isinstance(x, variablelib.Variable)\n  )\n\n\ndef remove_axis(\n  tree: A, index: int, transform_metadata: tp.Mapping[tp.Any, tp.Any]\n) -> A:\n  axis_name, other_meta = _get_partition_name_and_metadata(transform_metadata)\n\n  def remove_field(fields, index, value):\n    iterable = list(fields)\n    removed = iterable.pop(index)\n    if removed != value:\n      raise ValueError(\n        f'Expected to remove {value!r} at index {index} from '\n        f'{fields!r}, but found {removed!r}.'\n      )\n    return tuple(iterable)\n\n  def _remove_axis(x: tp.Any):\n    if isinstance(x, variablelib.Variable):\n      if hasattr(x, 'out_sharding') and x.out_sharding is not None:\n        x.set_metadata(\n          out_sharding=remove_field(x.out_sharding, index, axis_name)\n        )\n\n      for k, v in other_meta.items():\n        if hasattr(x, k) and (t := getattr(x, k)) and isinstance(t, tuple):\n          x.set_metadata(k, remove_field(t, index, v))\n\n      x.remove_axis(index, axis_name)\n    return x\n\n  return jax.tree.map(\n    _remove_axis,\n    tree,\n    is_leaf=lambda x: isinstance(x, variablelib.Variable),\n  )\n\n\ndef _get_partition_name_and_metadata(\n  transform_metadata: tp.Mapping[tp.Any, tp.Any],\n) -> tuple[str, tp.Mapping[tp.Any, tp.Any]]:\n  if PARTITION_NAME not in transform_metadata:\n    raise ValueError(\n      'Trying to transform a Partitioned variable but \"partition_name\" '\n      f'is not specified in transform_metadata: {transform_metadata}'\n    )\n  other_meta = dict(transform_metadata)  # shallow copy\n  other_meta.pop(PARTITION_NAME)\n  return transform_metadata[PARTITION_NAME], other_meta\n\n\n# Annotation handling\n# ------------------------------------------------------------------------------\n\n\ndef with_partitioning(\n  initializer: F,\n  sharding: Sharding,\n  mesh: tp.Optional[jax.sharding.Mesh] = None,\n  **metadata: tp.Any,\n) -> F:\n  \"\"\"A wrapper over any initializer to add sharding annotation data to a `Variable`.\"\"\"\n  return variablelib.with_metadata(\n    initializer,\n    out_sharding=sharding,\n    mesh=mesh,\n    **metadata,\n  )\n\n\ndef get_var_pspec(v: variablelib.Variable) -> PartitionSpec | None:\n  \"\"\"Given an `nnx.Variable`, return its `PartitionSpec`.\"\"\"\n  metadata = v.get_metadata()\n  if 'out_sharding' in metadata and metadata['out_sharding']:\n    sharding = metadata['out_sharding']\n    if core_spmd.get_logical_axis_rules() or 'sharding_rules' in metadata:\n      context_rules = core_spmd.get_logical_axis_rules()\n      local_rules = metadata.get('sharding_rules', ())\n      rules = core_spmd.composite_rules(context_rules, local_rules)\n      return PartitionSpec(*core_spmd.from_sharding_rules(sharding, rules))\n    return PartitionSpec(*sharding)\n  elif hasattr(v, 'shape'):\n      return PartitionSpec()\n  return None\n\n\ndef get_partition_spec(tree: A) -> A:\n  \"\"\"Extracts a PartitionSpec tree from a PyTree containing ``Variable`` values.\"\"\"\n\n  def f(x):\n    if isinstance(x, variablelib.Variable):\n      return x.replace(get_var_pspec(x))\n    elif hasattr(x, 'shape'):\n        return PartitionSpec()\n    return None\n\n  return jax.tree.map(\n    f, tree, is_leaf=lambda x: isinstance(x, variablelib.Variable)\n  )\n\n\ndef get_named_sharding(tree: A, mesh: jax.sharding.Mesh) -> A:\n  spec = get_partition_spec(tree)\n  sharding = jax.tree.map(lambda p: jax.sharding.NamedSharding(mesh, p), spec)\n  return sharding\n\n\n# Other utilities\n# ------------------------------------------------------------------------------\n\n\ndef get_abstract_model(init_fn, mesh, *, graph: bool | None = None):\n  with jax.set_mesh(mesh):\n    abs_model = eval_shape(init_fn, graph=graph)\n    gdef, abs_state = graphlib.split(abs_model, graph=graph)\n    abs_state = jax.tree.map(\n      lambda a, s: jax.ShapeDtypeStruct(a.shape, a.dtype, sharding=s),\n      abs_state, get_named_sharding(abs_state, mesh)\n    )\n  return gdef, abs_state\n\n\ndef abstract_with_sharding(\n    tree: A, graph: bool | None = None\n) -> A:\n  \"\"\"Add sharding information to abstract Variables.\n\n  When creating models with :func:`eval_shape`, Variables are abstract\n  (backed by ``jax.ShapeDtypeStruct``) and may not carry sharding\n  information, especially when using meshes with\n  :attr:`jax.sharding.AxisType.Auto` axes. ``abstract_with_sharding`` inspects each\n  Variable in ``tree`` and, if it has ``out_sharding`` metadata but no\n  sharding already set, attaches a :class:`jax.sharding.NamedSharding`\n  derived from the Variable's ``out_sharding`` and either its ``mesh``\n  metadata or the current abstract mesh (``jax.sharding.get_abstract_mesh``).\n\n  Example usage::\n\n    from flax import nnx\n    import jax\n\n    mesh = jax.make_mesh((2, 2), ('a', 'b'),\n      axis_types=(jax.sharding.AxisType.Auto,) * 2)\n    with jax.set_mesh(mesh):\n      abs_model = nnx.eval_shape(\n        lambda: nnx.Linear(4, 8, rngs=nnx.Rngs(0),\n          kernel_metadata={'out_sharding': ('a', 'b')}))\n      abs_model = nnx.abstract_with_sharding(abs_model)\n    assert abs_model.kernel.sharding.spec == jax.P('a', 'b')\n\n  Args:\n    tree: A graph node (e.g. an :class:`nnx.Module`) whose Variables should\n      be annotated with sharding (via ``out_sharding`` metadata).\n    graph: Forwarded to :func:`nnx.map`. If ``True``, uses graph-mode;\n      if ``False``, uses tree-mode.\n  Returns:\n    A tree with sharding-annotated ShapeDtypeStruct values inside Variables.\n  \"\"\"\n  def add_sharding(_path, x):\n    if (\n        isinstance(x, variablelib.Variable)\n        and hasattr(value := x.get_value(), 'shape')\n        and hasattr(value, 'dtype')\n        and getattr(value, 'sharding', None) is None\n        and x.has_metadata('out_sharding')\n    ):\n      if x.has_metadata('mesh'):\n        mesh = x.get_metadata('mesh')\n      else:\n        mesh = jax.sharding.get_abstract_mesh()\n      specs = get_var_pspec(x)\n      sharding = jax.sharding.NamedSharding(mesh, specs)\n      abs_var = x.replace(\n          jax.ShapeDtypeStruct(value.shape, value.dtype, sharding=sharding)\n      )\n      return abs_var\n    return x\n  return graphlib.map(add_sharding, tree, graph=graph)\n"
  },
  {
    "path": "flax/nnx/statelib.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# pytype: skip-file\nfrom __future__ import annotations\n\nimport typing as tp\nfrom collections.abc import MutableMapping\nfrom functools import partial\nimport warnings\n\nimport jax\nimport jax.tree_util as jtu\nimport treescope  # type: ignore[import-not-found,import-untyped]\n\nfrom flax.nnx import filterlib, reprlib, traversals, variablelib\nfrom flax.typing import Key, PathParts\n\nA = tp.TypeVar('A')\nK = tp.TypeVar('K', bound=tp.Hashable)\nS = tp.TypeVar('S', bound='State')\nV = tp.TypeVar('V')\n\nExtractValueFn = tp.Callable[[tp.Any], tp.Any]\nSetValueFn = tp.Callable[[V, tp.Any], V]\n\n\nclass NestedStateRepr(reprlib.Representable):\n  def __init__(self, state: State):\n    self.state = state\n\n  def __nnx_repr__(self):\n    yield reprlib.Object('', kv_sep=': ', start='{', end='}')\n\n    for r in self.state.__nnx_repr__():\n      if isinstance(r, reprlib.Object):\n        continue\n      yield r\n\n  def __treescope_repr__(self, path, subtree_renderer):\n    children = {}\n    for k, v in self.state.items():\n      if isinstance(v, State):\n        v = NestedStateRepr(v)\n      children[k] = v\n    # Render as the dictionary itself at the same path.\n    return subtree_renderer(children, path=path)\n\nclass FlatState(tp.Sequence[tuple[PathParts, V]], reprlib.Representable):\n  __slots__ = ('_keys', '_values')\n\n  _keys: tuple[PathParts, ...]\n  _values: list[V]\n\n  def __init__(self, items: tp.Iterable[tuple[PathParts, V]], /, *, sort: bool):\n    keys, values = [], []\n    if sort:\n      items = sorted(items)\n    for key, value in items:\n      keys.append(key)\n      values.append(value)\n    self._keys = tuple(keys)\n    self._values = values\n\n  @staticmethod\n  def from_sorted_keys_values(\n    keys: tuple[PathParts, ...], values: list[V], /\n  ) -> FlatState[V]:\n    flat_state = object.__new__(FlatState)\n    flat_state._keys = keys\n    flat_state._values = values\n    return flat_state\n\n  @property\n  def paths(self) -> tp.Tuple[PathParts, ...]:\n    return self._keys\n\n  @property\n  def leaves(self) -> list[V]:\n    return self._values\n\n  def __nnx_repr__(self):\n    yield reprlib.Object(type='FlatState', kv_sep='', start='([', end='])')\n\n    for value in self:\n      yield reprlib.Attr('', value)\n\n  @tp.overload\n  def __getitem__(self, index: int) -> tuple[PathParts, V]: ...\n  @tp.overload\n  def __getitem__(self, index: slice) -> FlatState[V]: ...\n  def __getitem__(\n    self, index: int | slice\n  ) -> tuple[PathParts, V] | FlatState[V]:\n    if isinstance(index, int):\n      return self._keys[index], self._values[index]\n    return FlatState(zip(self._keys[index], self._values[index]), sort=False)\n\n  def __len__(self) -> int:\n    return len(self._keys)\n\n  def __iter__(self) -> tp.Iterator[tuple[PathParts, V]]:\n    return iter(zip(self._keys, self._values))\n\n  def to_nested_state(self) -> State[Key, V]:\n    return from_flat_state(self)\n\n  @tp.overload\n  def split(self, first: filterlib.Filter, /) -> FlatState[V]: ...\n\n  @tp.overload\n  def split(\n    self,\n    first: filterlib.Filter,\n    second: filterlib.Filter,\n    /,\n    *filters: filterlib.Filter,\n  ) -> tuple[FlatState[V], ...]: ...\n\n  @tp.overload\n  def split(\n    self, /, *filters: filterlib.Filter\n  ) -> tp.Union[FlatState[V], tuple[FlatState[V], ...]]: ...\n\n  def split(  # type: ignore[misc]\n    self, first: filterlib.Filter, /, *filters: filterlib.Filter\n  ) -> tp.Union[FlatState[V], tuple[FlatState[V], ...]]:\n    filters = (first, *filters)\n    *flat_states_, rest = _split_state(self, *filters)\n\n    if rest:\n      raise ValueError(\n        'Non-exhaustive filters, got a non-empty remainder: '\n        f'{rest}.\\nUse `...` to match all remaining elements.'\n      )\n\n    flat_states: FlatState[V] | tuple[FlatState[V], ...]\n    if len(flat_states_) == 1:\n      flat_states = flat_states_[0]\n    else:\n      flat_states = tuple(flat_states_)\n    return flat_states  # type: ignore\n\n  @tp.overload\n  def filter(self, first: filterlib.Filter, /) -> FlatState[V]: ...\n\n  @tp.overload\n  def filter(\n    self,\n    first: filterlib.Filter,\n    second: filterlib.Filter,\n    /,\n    *filters: filterlib.Filter,\n  ) -> tuple[FlatState[V], ...]: ...\n\n  def filter(\n    self,\n    first: filterlib.Filter,\n    /,\n    *filters: filterlib.Filter,\n  ) -> tp.Union[FlatState[V], tuple[FlatState[V], ...]]:\n    *flat_states_, _rest = _split_state(self, first, *filters)\n\n    assert len(flat_states_) == len(filters) + 1\n\n    flat_states: FlatState[V] | tuple[FlatState[V], ...]\n    if len(flat_states_) == 1:\n      flat_states = flat_states_[0]\n    else:\n      flat_states = tuple(flat_states_)\n\n    return flat_states  # type: ignore\n\n  @staticmethod\n  def merge(\n    flat_state: tp.Iterable[tuple[PathParts, V]],\n    /,\n    *flat_states: tp.Iterable[tuple[PathParts, V]],\n  ) -> FlatState[V]:\n    if not flat_states:\n      if isinstance(flat_state, FlatState):\n        return flat_state\n      return FlatState(flat_state, sort=True)\n    flat_states = (flat_state, *flat_states)\n\n    return FlatState(\n      (elem for flat_state in flat_states for elem in flat_state), sort=True\n    )\n\n\ndef _flat_state_pytree_flatten(x: FlatState[V]):\n  return x._values, x._keys\n\n\ndef _flat_state_pytree_unflatten(\n  keys: tuple[PathParts, ...], values: list[V]\n) -> FlatState[V]:\n  flat_state = object.__new__(FlatState)\n  flat_state._keys = keys\n  flat_state._values = values\n  return flat_state\n\n\njax.tree_util.register_pytree_node(\n  FlatState,\n  _flat_state_pytree_flatten,\n  _flat_state_pytree_unflatten,\n)\n\n\nclass State(MutableMapping[K, V], reprlib.Representable):\n  \"\"\"A pytree-like ``Mapping`` with hashable and comparable keys.\n  \"\"\"\n\n  def __init__(\n    self,\n    mapping: tp.Union[\n      tp.Mapping[K, tp.Mapping | V],\n      tp.Iterator[tuple[K, tp.Mapping | V]],\n    ],\n    /,\n    *,\n    _copy: bool = True,\n  ):\n    if _copy:\n      _mapping = dict(mapping)\n    else:\n      if not isinstance(mapping, dict):\n        raise ValueError(\n          'Expected a dictionary when `_copy=False`, '\n          f'got {type(mapping)} instead.'\n        )\n      _mapping = mapping\n\n    if tp.TYPE_CHECKING:\n      self._mapping = _mapping\n    else:\n      super().__setattr__('_mapping', _mapping)\n\n  @property\n  def raw_mapping(self) -> dict[K, tp.Mapping[K, tp.Any] | V]:\n    return self._mapping  # type: ignore\n\n  def __contains__(self, key) -> bool:\n    return key in self._mapping\n\n  def __getitem__(self, key: K) -> State | V:  # type: ignore\n    value = self._mapping[key]\n    if isinstance(value, dict):\n      return type(self)(value, _copy=False)\n    return value  # type: ignore[return-value]\n\n  def __getattr__(self, key: K) -> State | V:  # type: ignore[misc]\n    if '_mapping' not in vars(self) or key not in self._mapping:\n      raise AttributeError(f\"No attribute '{key}' in State\")\n    return self[key]\n\n  def __setitem__(self, key: K, value: State | V) -> None:\n    if key == '__orig_class__':\n      object.__setattr__(self, key, value)  # type: ignore\n    elif isinstance(value, State):\n      self._mapping[key] = value._mapping\n    else:\n      self._mapping[key] = value\n\n  __setattr__ = __setitem__  # type: ignore\n\n  def __delitem__(self, key: K) -> None:\n    del self._mapping[key]\n\n  def __iter__(self) -> tp.Iterator[K]:\n    return iter(self._mapping)\n\n  def __len__(self) -> int:\n    return len(self._mapping)\n\n  def __nnx_repr__(self):\n    yield reprlib.Object(type(self), kv_sep=': ', start='({', end='})')\n\n    for k, v in self.items():\n      if isinstance(v, State):\n        v = NestedStateRepr(v)\n      yield reprlib.Attr(repr(k), v)\n\n  def __treescope_repr__(self, path, subtree_renderer):\n    children = {}\n    for k, v in self.items():\n      if isinstance(v, State):\n        v = NestedStateRepr(v)\n      children[k] = v\n    return treescope.repr_lib.render_dictionary_wrapper(\n      object_type=type(self),\n      wrapped_dict=children,\n      path=path,\n      subtree_renderer=subtree_renderer,\n    )\n\n  def map(self, f: tp.Callable[[tuple, V], V]) -> State[K, V]:\n    warnings.warn(\n      '`flax.nnx.State` will be deprecated and be replaced by the built-in '\n      'Python dict. Please use the equivalent `nnx.map_state` instead.',\n      DeprecationWarning,\n    )\n    return map_state(f, self)\n\n  def flat_state(self) -> FlatState[V]:\n    warnings.warn(\n      '`flax.nnx.State` will be deprecated and be replaced by the built-in '\n      'Python dict. Please use the equivalent `nnx.to_flat_state` instead.',\n      DeprecationWarning,\n    )\n    return to_flat_state(self)\n\n  @classmethod\n  def from_flat_path(\n    cls,\n    flat_state: tp.Mapping[PathParts, V] | tp.Iterable[tuple[PathParts, V]],\n    /,\n  ):\n    warnings.warn(\n      '`flax.nnx.State` will be deprecated and be replaced by the built-in '\n      'Python dict. Please use the equivalent `nnx.from_flat_state` instead.',\n      DeprecationWarning,\n    )\n    return from_flat_state(flat_state, cls=cls)\n\n  def to_pure_dict(self,\n                   extract_fn: ExtractValueFn | None = None\n                   ) -> dict[str, tp.Any]:\n    warnings.warn(\n      '`flax.nnx.State` will be deprecated and be replaced by the built-in '\n      'Python dict. Please use the equivalent `nnx.to_pure_dict` instead.',\n      DeprecationWarning,\n    )\n    return to_pure_dict(self, extract_fn)\n\n  def replace_by_pure_dict(self,\n                           pure_dict: dict[str, tp.Any],\n                           replace_fn: SetValueFn | None = None):\n    warnings.warn(\n      '`flax.nnx.State` will be deprecated and be replaced by the built-in '\n      'Python dict. Please use the equivalent `nnx.replace_by_pure_dict` '\n      'instead.',\n      DeprecationWarning,\n    )\n    return replace_by_pure_dict(self, pure_dict, replace_fn)\n\n  @tp.overload\n  def split(self, first: filterlib.Filter, /) -> State[K, V]: ...\n\n  @tp.overload\n  def split(\n    self,\n    first: filterlib.Filter,\n    second: filterlib.Filter,\n    /,\n    *filters: filterlib.Filter,\n  ) -> tuple[State[K, V], ...]: ...\n\n  @tp.overload\n  def split(\n    self, /, *filters: filterlib.Filter\n  ) -> tp.Union[State[K, V], tuple[State[K, V], ...]]: ...\n\n  def split(  # type: ignore[misc]\n    self, first: filterlib.Filter, /, *filters: filterlib.Filter\n  ) -> tp.Union[State[K, V], tuple[State[K, V], ...]]:\n    warnings.warn(\n      '`flax.nnx.State` will be deprecated and be replaced by the built-in '\n      'Python dict. Please use the equivalent `nnx.split_state` instead.',\n      DeprecationWarning,\n    )\n    return split_state(self, first, *filters)\n\n  @tp.overload\n  def filter(\n    self,\n    first: filterlib.Filter,\n    /,\n  ) -> State[K, V]: ...\n\n  @tp.overload\n  def filter(\n    self,\n    first: filterlib.Filter,\n    second: filterlib.Filter,\n    /,\n    *filters: filterlib.Filter,\n  ) -> tuple[State[K, V], ...]: ...\n\n  def filter(\n    self,\n    first: filterlib.Filter,\n    /,\n    *filters: filterlib.Filter,\n  ) -> tp.Union[State[K, V], tuple[State[K, V], ...]]:\n    warnings.warn(\n      '`flax.nnx.State` will be deprecated and be replaced by the built-in '\n      'Python dict. Please use the equivalent `nnx.filter_state` instead.',\n      DeprecationWarning,\n    )\n    return filter_state(self, first, *filters)\n\n  @classmethod\n  def merge(cls, state: tp.Mapping[K, V], /, *states: tp.Mapping[K, V]):\n    warnings.warn(\n      '`flax.nnx.State` will be deprecated and be replaced by the built-in '\n      'Python dict. Please use the equivalent `nnx.merge_state` instead.',\n      DeprecationWarning,\n    )\n    return merge_state(state, *states)\n\n  def __or__(self, other: State[K, V]) -> State[K, V]:\n    if not other:\n      return self\n    return merge_state(self, other)\n\n  def __sub__(self, other: State[K, V]) -> State[K, V]:\n    warnings.warn(\n      '`flax.nnx.State` will be deprecated and be replaced by the built-in '\n      'Python dict. Please use the equivalent `nnx.diff` instead.',\n      DeprecationWarning,\n    )\n    return diff(self, other)\n\n  def __init_subclass__(cls) -> None:\n    super().__init_subclass__()\n\n    jax.tree_util.register_pytree_with_keys(\n      cls,\n      _state_flatten_with_keys,\n      partial(_state_unflatten, cls),  # type: ignore[arg-type]\n    )\n\n\n\ndef _state_flatten_with_keys(x: State):\n  items = sorted(x._mapping.items())\n  children = tuple((jtu.DictKey(key), value) for key, value in items)\n  return children, tuple(key for key, _ in items)\n\n\ndef _state_unflatten(\n  cls: type[S],\n  static: tuple[K, ...],\n  leaves: tuple[V, ...] | tuple[dict[K, V]],\n):\n  return cls(zip(static, leaves))\n\n\njax.tree_util.register_pytree_with_keys(\n  State,\n  _state_flatten_with_keys,\n  partial(_state_unflatten, State),  # type: ignore[arg-type]\n)\n\ndef map_state(f: tp.Callable[[tuple, tp.Any], tp.Any], state: State) -> State:\n  \"\"\"Map ``f`` over :class:`State` object.\n\n  Arguments:\n    f: A function to be mapped\n    state: A :class:`State` object.\n  Returns:\n    New state :class:`State`.\n  \"\"\"\n  flat_state = to_flat_state(state)\n  result = [\n    (path, f(path, variable_state)) for path, variable_state in flat_state\n  ]\n  return from_flat_state(result)\n\n\ndef to_flat_state(state: State) -> FlatState:\n  \"\"\"Convert state into flat state\n\n  Arguments:\n    state: A :class:`State` object.\n  Returns:\n    Flat state :class:`FlatState`\n  \"\"\"\n  return FlatState(traversals.flatten_to_sequence(state._mapping), sort=True)\n\n\ndef from_flat_state(\n  flat_state: tp.Mapping[PathParts, V] | tp.Iterable[tuple[PathParts, V]],\n  *, cls = State,  # for compatibility with State subclasses\n) -> State:\n  \"\"\"Convert flat state object into :class:`State` object.\n\n  Arguments:\n    flat_state: A :class:`FlatState` object.\n  Returns:\n    State :class:`State` object.\n  \"\"\"\n  if not isinstance(flat_state, tp.Mapping):\n    flat_state = dict(flat_state)\n  nested_state = traversals.unflatten_mapping(flat_state)\n  return cls(nested_state)\n\n\ndef to_pure_dict(\n  state: State, extract_fn: ExtractValueFn | None = None\n) -> dict[str, tp.Any]:\n  \"\"\"Convert :class:`State` object into pure dictionary state.\n\n  Arguments:\n    state: A :class:`State` object.\n    extract_fn: optional extraction function.\n  Returns:\n    Pure dictionary.\n  \"\"\"\n  # Works for nnx.Variable\n  if extract_fn is None:\n    extract_fn = lambda x: x.get_value() if isinstance(x, variablelib.Variable) else x\n  flat_values = {k: extract_fn(x) for k, x in to_flat_state(state)}\n  return traversals.unflatten_mapping(flat_values)\n\n\ndef restore_int_paths(pure_dict: dict[str, tp.Any]):\n  \"\"\"Restore integer paths from string value in the dict.\n  This method can be helpful when restoring the state from a checkpoint as\n  pure dictionary:\n\n  Example::\n\n    >>> from flax import nnx\n    >>> import orbax.checkpoint as ocp\n    >>> import tempfile\n    ...\n    >>> model = nnx.List([nnx.Linear(10, 10, rngs=nnx.Rngs(0)) for _ in range(2)])\n    >>> pure_dict_state = nnx.to_pure_dict(nnx.state(model))\n    >>> list(pure_dict_state.keys())\n    [0, 1]\n    >>> checkpointer = ocp.StandardCheckpointer()\n    >>> with tempfile.TemporaryDirectory() as tmpdir:\n    ...   checkpointer.save(f'{tmpdir}/ckpt', pure_dict_state)\n    ...   restored_pure_dict = checkpointer.restore(f'{tmpdir}/ckpt')\n    ...   list(restored_pure_dict.keys())\n    ['0', '1']\n    >>> restored_pure_dict = nnx.restore_int_paths(restored_pure_dict)\n    >>> list(restored_pure_dict.keys())\n    [0, 1]\n\n  Arguments:\n    pure_dict: state as pure dictionary\n  Returns:\n    state as pure dictionary with restored integers paths\n  \"\"\"\n  def try_convert_int(x):\n    try:\n      return int(x)\n    except ValueError:\n      return x\n\n  fixed = {\n      tuple(map(try_convert_int, path)): value\n      for path, value in traversals.flatten_mapping(pure_dict).items()\n  }\n  return traversals.unflatten_mapping(fixed)\n\ndef replace_by_pure_dict(\n  state: State, pure_dict: dict[str, tp.Any], replace_fn: SetValueFn | None = None\n):\n  \"\"\"Replace input ``state`` values with ``pure_dict`` values.\n\n  Arguments:\n    state: A :class:`State` object.\n    pure_dict: pure dictionary with values to be used for replacement.\n    replace_fn: optional replace function.\n  \"\"\"\n  def try_convert_int(x):\n    try:\n      return int(x)\n    except ValueError:\n      return x\n\n  # Works for nnx.Variable\n  if replace_fn is None:\n    replace_fn = lambda x, v: x.replace(v) if hasattr(x, 'replace') else v\n  current_flat = dict(to_flat_state(state))\n  for kp, v in traversals.flatten_mapping(pure_dict).items():\n    # Try exact match first, then integer conversion\n    if kp in current_flat:\n      matched_key = kp\n    else:\n      int_kp = tuple(map(try_convert_int, kp))\n      if int_kp in current_flat:\n        matched_key = int_kp\n      else:\n        raise ValueError(f'key in pure_dict not available in state: {kp}')\n    current_flat[matched_key] = replace_fn(current_flat[matched_key], v)\n  state.update(traversals.unflatten_mapping(current_flat))\n\n\n@tp.overload\ndef split_state(state: State, first: filterlib.Filter, /) -> State: ...\n\n\n@tp.overload\ndef split_state(\n  state: State,\n  first: filterlib.Filter,\n  second: filterlib.Filter,\n  /,\n  *filters: filterlib.Filter,\n) -> tuple[State, ...]: ...\n\n\n@tp.overload\ndef split_state(\n  state: State, /, *filters: filterlib.Filter\n) -> tp.Union[State, tuple[State, ...]]: ...\n\n\ndef split_state(  # type: ignore[misc]\n  state: State, first: filterlib.Filter, /, *filters: filterlib.Filter\n) -> tp.Union[State, tuple[State, ...]]:\n  \"\"\"Split a :class:`State` into one or more :class:`State`'s. The\n  user must pass at least one ``Filter`` (i.e. :class:`Variable`),\n  and the filters must be exhaustive (i.e. they must cover all\n  :class:`Variable` types in the ``State``).\n\n  Example usage::\n\n    >>> from flax import nnx\n\n    >>> class Model(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.batchnorm = nnx.BatchNorm(2, rngs=rngs)\n    ...     self.linear = nnx.Linear(2, 3, rngs=rngs)\n    ...   def __call__(self, x):\n    ...     return self.linear(self.batchnorm(x))\n\n    >>> model = Model(rngs=nnx.Rngs(0))\n    >>> state = nnx.state(model)\n    >>> param, batch_stats = nnx.split_state(state, nnx.Param, nnx.BatchStat)\n\n  Arguments:\n    first: The first filter\n    *filters: The optional, additional filters to group the state into mutually exclusive substates.\n  Returns:\n    One or more ``States`` equal to the number of filters passed.\n  \"\"\"\n  filters = (first, *filters)\n  flat_states = _split_state(to_flat_state(state), *filters)\n  *states_, rest = (state.to_nested_state() for state in flat_states)\n\n  if rest:\n    raise ValueError(\n      'Non-exhaustive filters, got a non-empty remainder: '\n      f'{rest}.\\nUse `...` to match all remaining elements.'\n    )\n\n  states: State | tuple[State, ...]\n  if len(states_) == 1:\n    states = states_[0]\n  else:\n    states = tuple(states_)\n  return states  # type: ignore\n\n\n\n@tp.overload\ndef filter_state(\n  state: State,\n  first: filterlib.Filter,\n  /,\n) -> State: ...\n\n\n@tp.overload\ndef filter_state(\n  state: State,\n  first: filterlib.Filter,\n  second: filterlib.Filter,\n  /,\n  *filters: filterlib.Filter,\n) -> tuple[State, ...]: ...\n\n\ndef filter_state(\n  state: State,\n  first: filterlib.Filter,\n  /,\n  *filters: filterlib.Filter,\n) -> tp.Union[State, tuple[State, ...]]:\n  \"\"\"Filter a ``State`` into one or more ``State``'s. The\n  user must pass at least one ``Filter`` (i.e. :class:`Variable`).\n  This method is similar to :meth:`split() <flax.nnx.State.state.split>`,\n  except the filters can be non-exhaustive.\n\n  Example usage::\n\n    >>> from flax import nnx\n\n    >>> class Model(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.batchnorm = nnx.BatchNorm(2, rngs=rngs)\n    ...     self.linear = nnx.Linear(2, 3, rngs=rngs)\n    ...   def __call__(self, x):\n    ...     return self.linear(self.batchnorm(x))\n\n    >>> model = Model(rngs=nnx.Rngs(0))\n    >>> state = nnx.state(model)\n    >>> param = nnx.filter_state(state, nnx.Param)\n    >>> batch_stats = nnx.filter_state(state, nnx.BatchStat)\n    >>> param, batch_stats = nnx.filter_state(state, nnx.Param, nnx.BatchStat)\n\n  Arguments:\n    first: The first filter\n    *filters: The optional, additional filters to group the state into mutually exclusive substates.\n  Returns:\n    One or more ``States`` equal to the number of filters passed.\n  \"\"\"\n  flat_states = _split_state(to_flat_state(state), first, *filters)\n  *states_, _rest = (state.to_nested_state() for state in flat_states)\n\n  assert len(states_) == len(filters) + 1\n\n  states: State | tuple[State, ...]\n  if len(states_) == 1:\n    states = states_[0]\n  else:\n    states = tuple(states_)\n\n  return states  # type: ignore\n\n\ndef merge_state(state: tp.Mapping, /, *states: tp.Mapping,\n  cls = State  # for compatibility with State subclasses\n  ) -> State:\n  \"\"\"The inverse of :meth:`split() <flax.nnx.State.state.split>`.\n\n  ``merge`` takes one or more :class:`State`'s and creates\n  a new :class:`State`.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n\n    >>> class Model(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.batchnorm = nnx.BatchNorm(2, rngs=rngs)\n    ...     self.linear = nnx.Linear(2, 3, rngs=rngs)\n    ...   def __call__(self, x):\n    ...     return self.linear(self.batchnorm(x))\n\n    >>> model = Model(rngs=nnx.Rngs(0))\n    >>> params, batch_stats = nnx.state(model, nnx.Param, nnx.BatchStat)\n    >>> params['linear']['bias'][...] += 1\n\n    >>> state = nnx.merge_state(params, batch_stats)\n    >>> nnx.update(model, state)\n    >>> assert (model.linear.bias[...] == jnp.array([1, 1, 1])).all()\n\n  Args:\n    state: A :class:`State` object.\n    *states: Additional :class:`State` objects.\n  Returns:\n    The merged :class:`State`.\n  \"\"\"\n  if not states:\n    if isinstance(state, cls):\n      return state\n    return cls(state)\n\n  states = (state, *states)\n\n  new_state: dict[PathParts, tp.Any] = {}\n\n  for state in states:\n    new_state.update(traversals.flatten_mapping(state))  # type: ignore[attribute-error] # pytype is wrong here\n\n  return from_flat_state(new_state, cls=cls)\n\n\ndef diff(state: State, other: State) -> State:\n  if not other:\n    return state\n\n  self_flat = to_flat_state(state)\n  other_flat = to_flat_state(other)\n  diff = {k: v for k, v in self_flat if k not in other_flat.paths}\n\n  return from_flat_state(diff)\n\n\ndef _split_state(\n  flat_state: FlatState[V],\n  *filters: filterlib.Filter,\n) -> tuple[FlatState[V], ...]:\n  for i, filter_ in enumerate(filters):\n    if filter_ in (..., True) and i != len(filters) - 1:\n      remaining_filters = filters[i + 1 :]\n      if not all(f in (..., True) for f in remaining_filters):\n        raise ValueError(\n          '`...` or `True` can only be used as the last filters, '\n          f'got {filter_} it at index {i}.'\n        )\n  predicates = tuple(map(filterlib.to_predicate, filters))\n\n  # we have n + 1 states, where n is the number of predicates\n  # the last state is for values that don't match any predicate\n  flat_states: tuple[list[tuple[PathParts, V]], ...] = tuple(\n    [] for _ in range(len(predicates) + 1)\n  )\n\n  for path, value in flat_state:\n    for i, predicate in enumerate(predicates):\n      if predicate(path, value):\n        flat_states[i].append((path, value))  # type: ignore[index] # mypy is wrong here?\n        break\n    else:\n      # if we didn't break, set leaf to last state\n      flat_states[-1].append((path, value))  # type: ignore[index] # mypy is wrong here?\n\n  return tuple(FlatState(flat_state, sort=False) for flat_state in flat_states)\n\n\ndef create_path_filters(state: State):\n  flat_state = to_flat_state(state)\n  value_paths: dict[tp.Any, set[PathParts]] = {}\n  for path, value in flat_state:\n    if isinstance(value, variablelib.Variable):\n      value = value.get_value()\n    value_paths.setdefault(value, set()).add(path)\n  return {filterlib.PathIn(*value_paths[value]): value for value in value_paths}"
  },
  {
    "path": "flax/nnx/summary.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nfrom collections import defaultdict\nimport dataclasses\nimport inspect\nimport io\nimport typing as tp\nfrom types import MappingProxyType\nimport functools\nimport itertools\n\nimport jax\nimport numpy as np\nimport rich.console\nimport rich.table\nimport rich.text\nimport yaml\nimport jax.numpy as jnp\n\nfrom flax import nnx\nfrom flax import typing\nfrom flax.nnx import graphlib, statelib, variablelib\n\nfrom functools import wraps\n\n\ntry:\n  from IPython import get_ipython\n\n  in_ipython = get_ipython() is not None\nexcept ImportError:\n  in_ipython = False\n\n# Custom YAML dumper to represent None as 'None' string (not YAML 'null') for clarity\nclass NoneDumper(yaml.SafeDumper):\n  pass\n\nNoneDumper.add_representer(\n  type(None),\n  lambda dumper, data: dumper.represent_scalar('tag:yaml.org,2002:str', 'None'),\n)\n\nclass SizeBytes(typing.SizeBytes):\n  def __repr__(self) -> str:\n    bytes_repr = _bytes_repr(self.bytes)\n    return f'{self.size:,} [dim]({bytes_repr})[/dim]'\n\nclass ObjectInfo(tp.NamedTuple):\n  path: statelib.PathParts\n  stats: dict[type[variablelib.Variable], SizeBytes]\n  variable_groups: defaultdict[\n    type[variablelib.Variable], defaultdict[typing.Key, variablelib.Variable]\n  ]\n\nNodeStats = dict[int, ObjectInfo | None]\n\ndef _collect_stats(\n  path: statelib.PathParts,\n  node: tp.Any,\n  node_stats: NodeStats,\n  object_types: set[type],\n):\n  if not graphlib.is_node(node) and not isinstance(node, variablelib.Variable):\n    raise ValueError(f'Expected a graph node or Variable, got {type(node)!r}.')\n\n  if id(node) in node_stats:\n    return\n\n  stats: dict[type[variablelib.Variable], SizeBytes] = {}\n  variable_groups: defaultdict[\n    type[variablelib.Variable], defaultdict[typing.Key, variablelib.Variable]\n  ] = defaultdict(lambda: defaultdict())\n  node_stats[id(node)] = ObjectInfo(path, stats, variable_groups)\n\n  if isinstance(node, nnx.Pytree):\n    node._nnx_tabulate_id = id(node)  # type: ignore\n    object_types.add(type(node))\n\n  node_impl = graphlib.get_node_impl(node)\n  assert node_impl is not None\n  node_dict = node_impl.node_dict(node)\n  for key, value in node_dict.items():\n    if id(value) in node_stats:\n      continue\n    elif isinstance(value, variablelib.Variable):\n      var_type = type(value)\n      if issubclass(var_type, nnx.RngState):\n        var_type = nnx.RngState\n      size_bytes = SizeBytes.from_any(value.get_value())\n      if var_type in stats:\n        stats[var_type] += size_bytes\n      else:\n        stats[var_type] = size_bytes\n      variable_groups[var_type][key] = value\n      node_stats[id(value)] = None\n    elif graphlib.is_node(value):\n      _collect_stats((*path, key), value, node_stats, object_types)\n      # accumulate stats from children\n      child_info = node_stats[id(value)]\n      assert child_info is not None\n      for var_type, size_bytes in child_info.stats.items():\n        if var_type in stats:\n          stats[var_type] += size_bytes\n        else:\n          stats[var_type] = size_bytes\n\n@dataclasses.dataclass(frozen=True, repr=False)\nclass ArrayRepr:\n  shape: tuple[int, ...]\n  dtype: tp.Any\n\n  @classmethod\n  def from_array(cls, x: jax.Array | np.ndarray):\n    return cls(jnp.shape(x), jnp.result_type(x))\n\n  def __str__(self):\n    shape_repr = ','.join(str(x) for x in self.shape)\n    return f'[dim]{self.dtype}[/dim][{shape_repr}]'\n\n\n@dataclasses.dataclass\nclass CallInfo:\n  call_order: int\n  object_id: int\n  type: type\n  path: statelib.PathParts\n  inputs_repr: str\n  outputs: tp.Any\n  flops: int | None\n  vjp_flops: int | None\n\nclass SimpleObjectRepr:\n  def __init__(self, obj: tp.Any):\n    self.type = type(obj)\n\n  def __str__(self):\n    return f'{self.type.__name__}(...)'\n\n  def __repr__(self):\n    return f'{self.type.__name__}(...)'\n\n\ndef _to_dummy_array(x):\n  if isinstance(x,jax.ShapeDtypeStruct):\n    return ArrayRepr(x.shape, x.dtype)\n  elif isinstance(x, jax.Array | np.ndarray):\n    return ArrayRepr.from_array(x)\n  elif graphlib.is_graph_node(x):\n    return SimpleObjectRepr(x)\n  else:\n    return x\n\ndef _pure_nnx_vjp(f, model, *args, **kwargs):\n  \"Wrap nnx functional api around jax.vjp. Only handles pure method calls.\"\n  graphdef, state = nnx.split(model)\n  def inner(state, *args, **kwargs):\n    model = nnx.merge(graphdef, state)\n    return f(model, *args, **kwargs)\n  return jax.vjp(inner, state, *args, **kwargs)\n\ndef filter_rng_streams(row: CallInfo):\n  return not issubclass(row.type, nnx.RngStream)\n\ndef _create_obj_env(object_types):\n  \"Turn a set of object types into a dictionary mapping (type, method name) pairs to methods\"\n  result = {}\n  for obj_type in object_types:\n    for name, top_method in inspect.getmembers(obj_type, inspect.isfunction):\n      if not name.startswith('_') or name == '__call__':\n        result[(obj_type, name)] = top_method\n  return result\n\ndef _get_inputs_repr(args, kwargs):\n  input_args, input_kwargs = jax.tree.map(\n    _to_dummy_array, (args, kwargs)\n  )\n  inputs_repr = ''\n  if input_args:\n    if len(input_args) == 1 and not input_kwargs:\n      inputs_repr += _as_yaml_str(input_args[0])\n    else:\n      inputs_repr += _as_yaml_str(input_args)\n    if input_kwargs:\n      inputs_repr += '\\n'\n  if input_kwargs:\n    inputs_repr += _as_yaml_str(input_kwargs)\n  return inputs_repr\n\ndef _save_call_info(counter, tracer_args, f, node_stats, compute_flops, compute_vjp_flops, seen):\n  \"Wrap a function to save its arguments\"\n\n  # Used when computing vjp flops\n  def do_vjp(*args, **kwargs):\n    primals, f_vjp = jax.vjp(f, *args, **kwargs)\n    return f_vjp(primals)\n\n  method_name = f.__name__\n\n  @functools.partial(jax.jit)\n  def jit_f(graphdef, state):\n    args, kwargs = nnx.merge(graphdef, state)\n    return f(*args, **kwargs)\n\n  @wraps(f)\n  def wrapper(obj, *args, **kwargs):\n    inputs_repr = _get_inputs_repr(args, kwargs)\n    object_id = getattr(obj, '_nnx_tabulate_id')\n    node_info = node_stats[object_id]\n    path = node_info.path\n    if method_name != '__call__':\n      path = (*path, method_name)\n    identifier = (inputs_repr, object_id)\n    counter_val = next(counter)\n    graphdef, state = nnx.split(((obj, *args), kwargs))\n    if compute_flops:\n      lowered = jit_f.lower(graphdef, state)\n      flops = _get_flops(lowered)\n      outputs = lowered.out_info\n    else:\n      flops = None\n      outputs = jit_f(graphdef, state)\n    if identifier not in seen:\n      seen.add(identifier)\n      output_repr = jax.tree.map(_to_dummy_array, outputs)\n      vjp_flops = _get_flops(jax.jit(do_vjp).lower(\n        obj, *args, **kwargs)) if compute_vjp_flops else None\n      tracer_args.append(\n        CallInfo(counter_val, object_id, type(obj), path, inputs_repr,\n          output_repr, flops, vjp_flops))\n    return jit_f(graphdef, state)\n  return wrapper\n\ndef _overwrite_methods(env):\n  \"Overwrite methods with functions from an environment\"\n  for (obj_type, name), f in env.items():\n    setattr(obj_type, name, f)\n\ndef _get_flops(e) -> int:\n  cost = e.cost_analysis() or e.compile().cost_analysis()\n  return 0 if cost is None or 'flops' not in cost else int(cost['flops'])\n\ndef tabulate(\n  obj,\n  *input_args,\n  depth: int | None = None,\n  method: str = '__call__',\n  row_filter: tp.Callable[[CallInfo], bool] = filter_rng_streams,\n  table_kwargs: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  column_kwargs: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  console_kwargs: tp.Mapping[str, tp.Any] = MappingProxyType({}),\n  compute_flops: bool = False,\n  compute_vjp_flops: bool = False,\n  **input_kwargs,\n) -> str:\n  \"\"\"Creates a summary of the graph object represented as a table.\n\n  The table summarizes the object's state and metadata. The table is\n  structured as follows:\n\n  - The first column represents the path of the object in the graph.\n  - The second column represents the type of the object.\n  - The third column represents the input arguments passed to the object's\n    method.\n  - The fourth column represents the output of the object's method.\n  - The following columns provide information about the object's state,\n    grouped by Variable types.\n\n  Example::\n\n    >>> from flax import nnx\n    ...\n    >>> class Block(nnx.Module):\n    ...   def __init__(self, din, dout, rngs: nnx.Rngs):\n    ...     self.linear = nnx.Linear(din, dout, rngs=rngs)\n    ...     self.bn = nnx.BatchNorm(dout, rngs=rngs)\n    ...     self.dropout = nnx.Dropout(0.2, rngs=rngs)\n    ...\n    ...   def __call__(self, x):\n    ...     return nnx.relu(self.dropout(self.bn(self.linear(x))))\n    ...\n    >>> class Foo(nnx.Module):\n    ...   def __init__(self, rngs: nnx.Rngs):\n    ...     self.block1 = Block(32, 128, rngs=rngs)\n    ...     self.block2 = Block(128, 10, rngs=rngs)\n    ...\n    ...   def __call__(self, x):\n    ...     return self.block2(self.block1(x))\n    ...\n    >>> foo = Foo(nnx.Rngs(0))\n    >>> # print(nnx.tabulate(foo, jnp.ones((1, 32))))\n\n                                                          Foo Summary\n    ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓\n    ┃ path           ┃ type      ┃ inputs         ┃ outputs        ┃ BatchStat          ┃ Param                   ┃ RngState ┃\n    ┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩\n    │                │ Foo       │ float32[1,32]  │ float32[1,10]  │ 276 (1.1 KB)       │ 5,790 (23.2 KB)         │ 2 (12 B) │\n    ├────────────────┼───────────┼────────────────┼────────────────┼────────────────────┼─────────────────────────┼──────────┤\n    │ block1         │ Block     │ float32[1,32]  │ float32[1,128] │ 256 (1.0 KB)       │ 4,480 (17.9 KB)         │ 2 (12 B) │\n    ├────────────────┼───────────┼────────────────┼────────────────┼────────────────────┼─────────────────────────┼──────────┤\n    │ block1/linear  │ Linear    │ float32[1,32]  │ float32[1,128] │                    │ bias: float32[128]      │          │\n    │                │           │                │                │                    │ kernel: float32[32,128] │          │\n    │                │           │                │                │                    │                         │          │\n    │                │           │                │                │                    │ 4,224 (16.9 KB)         │          │\n    ├────────────────┼───────────┼────────────────┼────────────────┼────────────────────┼─────────────────────────┼──────────┤\n    │ block1/bn      │ BatchNorm │ float32[1,128] │ float32[1,128] │ mean: float32[128] │ bias: float32[128]      │          │\n    │                │           │                │                │ var: float32[128]  │ scale: float32[128]     │          │\n    │                │           │                │                │                    │                         │          │\n    │                │           │                │                │ 256 (1.0 KB)       │ 256 (1.0 KB)            │          │\n    ├────────────────┼───────────┼────────────────┼────────────────┼────────────────────┼─────────────────────────┼──────────┤\n    │ block1/dropout │ Dropout   │ float32[1,128] │ float32[1,128] │                    │                         │ 2 (12 B) │\n    ├────────────────┼───────────┼────────────────┼────────────────┼────────────────────┼─────────────────────────┼──────────┤\n    │ block2         │ Block     │ float32[1,128] │ float32[1,10]  │ 20 (80 B)          │ 1,310 (5.2 KB)          │          │\n    ├────────────────┼───────────┼────────────────┼────────────────┼────────────────────┼─────────────────────────┼──────────┤\n    │ block2/linear  │ Linear    │ float32[1,128] │ float32[1,10]  │                    │ bias: float32[10]       │          │\n    │                │           │                │                │                    │ kernel: float32[128,10] │          │\n    │                │           │                │                │                    │                         │          │\n    │                │           │                │                │                    │ 1,290 (5.2 KB)          │          │\n    ├────────────────┼───────────┼────────────────┼────────────────┼────────────────────┼─────────────────────────┼──────────┤\n    │ block2/bn      │ BatchNorm │ float32[1,10]  │ float32[1,10]  │ mean: float32[10]  │ bias: float32[10]       │          │\n    │                │           │                │                │ var: float32[10]   │ scale: float32[10]      │          │\n    │                │           │                │                │                    │                         │          │\n    │                │           │                │                │ 20 (80 B)          │ 20 (80 B)               │          │\n    ├────────────────┼───────────┼────────────────┼────────────────┼────────────────────┼─────────────────────────┼──────────┤\n    │ block2/dropout │ Dropout   │ float32[1,10]  │ float32[1,10]  │                    │                         │          │\n    ├────────────────┼───────────┼────────────────┼────────────────┼────────────────────┼─────────────────────────┼──────────┤\n    │                │           │                │          Total │ 276 (1.1 KB)       │ 5,790 (23.2 KB)         │ 2 (12 B) │\n    └────────────────┴───────────┴────────────────┴────────────────┴────────────────────┴─────────────────────────┴──────────┘\n\n                                                Total Parameters: 6,068 (24.3 KB)\n\n  Note that ``block2/dropout`` is not shown in the table because it shares the\n  same ``RngState`` with ``block1/dropout``.\n\n  Args:\n    obj: A object to summarize. It can a pytree or a graph objects\n      such as nnx.Module or nnx.Optimizer.\n    *input_args: Positional arguments passed to the object's method.\n    **input_kwargs: Keyword arguments passed to the object's method.\n    depth: The depth of the table.\n    method: The method to call on the object. Default is ``'__call__'``.\n    row_filter: A callable that filters the rows to be displayed in the table.\n      By default, it filters out rows with type ``nnx.RngStream``.\n    table_kwargs: An optional dictionary with additional keyword arguments\n      that are passed to ``rich.table.Table`` constructor.\n    column_kwargs: An optional dictionary with additional keyword arguments\n      that are passed to ``rich.table.Table.add_column`` when adding columns to\n      the table.\n    console_kwargs: An optional dictionary with additional keyword arguments\n      that are passed to `rich.console.Console` when rendering the table.\n      Default arguments are  ``'force_terminal': True``, and ``'force_jupyter'``\n      is set to ``True`` if the code is running in a Jupyter notebook, otherwise\n      it is set to ``False``.\n    compute_flops: whether to include a `flops` column in the table listing the\n      estimated FLOPs cost of each module forward pass. Does incur actual\n      on-device computation / compilation / memory allocation, but still\n      introduces overhead for large modules (e.g. extra 20 seconds for a\n      Stable Diffusion's UNet, whereas otherwise tabulation would finish in 5\n      seconds).\n    compute_vjp_flops: whether to include a `vjp_flops` column in the table\n      listing the estimated FLOPs cost of each module backward pass. Introduces\n      a compute overhead of about 2-3X of `compute_flops`.\n\n  Returns:\n    A string summarizing the object.\n  \"\"\"\n  _console_kwargs = {'force_terminal': True, 'force_jupyter': in_ipython}\n  _console_kwargs.update(console_kwargs)\n\n  obj = graphlib.clone(obj)  # create copy to avoid side effects\n  node_stats: NodeStats = {}\n  object_types: set[type] = set()\n  _collect_stats((), obj, node_stats, object_types)\n  _variable_types: set[type] = {\n    nnx.RngState  # type: ignore[misc]\n    if isinstance(leaf, nnx.RngState)\n    else type(leaf)\n    for _, leaf in nnx.to_flat_state(nnx.state(obj))\n  }\n  variable_types: list[type] = sorted(_variable_types, key=lambda t: t.__name__)\n\n  # Create a dictionary-version of the object's class. This makes\n  # iteration over methods easier.\n  env = _create_obj_env(object_types)\n\n  # Information is recorded in post-order, but should be presented as a pre-order traversal.\n  # This keeps track of the order of calls.\n  counter = itertools.count(0)\n\n  # Modify all the object's methods to save their lowered JIT representations.\n  rows : list[CallInfo] = []\n  seen : set = set()\n  jits = {k: _save_call_info(counter, rows, v, node_stats, compute_flops, compute_vjp_flops, seen)\n    for k, v in env.items()}\n  _overwrite_methods(jits)\n\n  # Trace the top function (which indirectly traces all the others)\n  jits[(type(obj), method)](obj, *input_args, **input_kwargs)\n\n  # Sort call info in pre-order traversal order\n  rows.sort(key=lambda x: x.call_order)\n\n  # Restore the object's original methods\n  _overwrite_methods(env)\n\n  if depth is not None:\n    rows = [row for row in rows if len(row.path) <= depth and row_filter(row)]\n  else:\n    rows = [row for row in rows if row_filter(row)]\n\n  rich_table = rich.table.Table(\n    show_header=True,\n    show_lines=True,\n    show_footer=True,\n    title=f'{type(obj).__name__} Summary',\n    **table_kwargs,\n  )\n\n  rich_table.add_column('path', **column_kwargs)\n  rich_table.add_column('type', **column_kwargs)\n  rich_table.add_column('inputs', **column_kwargs)\n  rich_table.add_column('outputs', **column_kwargs)\n  if compute_flops:\n    rich_table.add_column('flops', **column_kwargs)\n  if compute_vjp_flops:\n    rich_table.add_column('vjp_flops', **column_kwargs)\n\n  for var_type in variable_types:\n    rich_table.add_column(var_type.__name__, **column_kwargs)\n\n  for row in rows:\n    node_info = node_stats[row.object_id]\n    assert node_info is not None\n    col_reprs: list[str] = []\n    path_str = '/'.join(map(str, row.path))\n    col_reprs.append(path_str)\n    col_reprs.append(row.type.__name__)\n    col_reprs.append(row.inputs_repr)\n    col_reprs.append(_as_yaml_str(row.outputs))\n    if compute_flops:\n      col_reprs.append(str(row.flops))\n    if compute_vjp_flops:\n      col_reprs.append(str(row.vjp_flops))\n\n    for var_type in variable_types:\n      attributes = {}\n      variable: variablelib.Variable\n      for name, variable in node_info.variable_groups[var_type].items():\n        value = variable.get_value()\n        value_repr = _render_array(value) if _has_shape_dtype(value) else ''\n        metadata = variable.get_metadata()\n        for required_key in var_type.required_metadata:\n          metadata.pop(required_key, None)\n        if metadata:\n          attributes[name] = {\n            'value': value_repr,\n            **metadata,\n          }\n        elif value_repr:\n          attributes[name] = value_repr  # type: ignore[assignment]\n\n      if attributes:\n        col_repr = _as_yaml_str(attributes) + '\\n\\n'\n      else:\n        col_repr = ''\n\n      size_bytes = node_info.stats.get(var_type)  # type: ignore[call-overload]\n      if size_bytes:\n        col_repr += f'[bold]{size_bytes}[/bold]'\n      col_reprs.append(col_repr)\n\n    rich_table.add_row(*col_reprs)\n\n  total_offset = 3 + int(compute_flops) + int(compute_vjp_flops)\n  rich_table.columns[total_offset].footer = rich.text.Text.from_markup(\n    'Total', justify='right'\n  )\n  node_info = node_stats[id(obj)]\n  assert node_info is not None\n  for i, var_type in enumerate(variable_types):\n    size_bytes = node_info.stats[var_type]\n    rich_table.columns[i + total_offset + 1].footer = str(size_bytes)\n\n  rich_table.caption_style = 'bold'\n  total_size = sum(node_info.stats.values(), SizeBytes(0, 0))\n  rich_table.caption = f'\\nTotal Parameters: {total_size}'\n\n  return _get_rich_repr(rich_table, _console_kwargs)\n\n\ndef _get_rich_repr(obj, console_kwargs):\n  f = io.StringIO()\n  console = rich.console.Console(file=f, **console_kwargs)\n  console.print(obj)\n  return f.getvalue()\n\n\ndef _size_and_bytes(pytree: tp.Any) -> tuple[int, int]:\n  leaves = jax.tree.leaves(pytree)\n  size = sum(x.size for x in leaves if hasattr(x, 'size'))\n  num_bytes = sum(\n    x.size * x.dtype.itemsize for x in leaves if hasattr(x, 'size')\n  )\n  return size, num_bytes\n\n\ndef _size_and_bytes_repr(size: int, num_bytes: int) -> str:\n  if not size:\n    return ''\n  bytes_repr = _bytes_repr(num_bytes)\n  return f'{size:,} [dim]({bytes_repr})[/dim]'\n\n\ndef _bytes_repr(num_bytes):\n  count, units = (\n    (f'{num_bytes / 1e9:,.1f}', 'GB')\n    if num_bytes > 1e9\n    else (f'{num_bytes / 1e6:,.1f}', 'MB')\n    if num_bytes > 1e6\n    else (f'{num_bytes / 1e3:,.1f}', 'KB')\n    if num_bytes > 1e3\n    else (f'{num_bytes:,}', 'B')\n  )\n\n  return f'{count} {units}'\n\n\ndef _has_shape_dtype(value):\n  return hasattr(value, 'shape') and hasattr(value, 'dtype')\n\n\ndef _normalize_values(x):\n  if isinstance(x, type):\n    return f'type[{x.__name__}]'\n  elif isinstance(x, ArrayRepr | SimpleObjectRepr):\n    return str(x)\n  else:\n    return repr(x)\n\ndef _maybe_pytree_to_dict(pytree: tp.Any):\n  path_leaves = jax.tree_util.tree_flatten_with_path(pytree)[0]\n  path_leaves = [\n    (tuple(map(graphlib._key_path_to_key, path)), value)\n    for path, value in path_leaves\n  ]\n  if len(path_leaves) < 1:\n    return pytree\n  elif len(path_leaves) == 1 and path_leaves[0][0] == ():\n    return pytree\n  else:\n    return _unflatten_to_simple_structure(path_leaves, original=pytree)\n\n\ndef _unflatten_to_simple_structure(\n  xs: list[tuple[tuple[tp.Any, ...], tp.Any]], *, original: tp.Any\n):\n  \"\"\"Rebuild a simple Python structure from path/value leaves.\n\n  This variant is aware of the original object so it can:\n  - Preserve empty containers that were elided by JAX flattening.\n  - Pad trailing missing list/tuple items using the original length.\n  - Distinguish placeholders for empty dict/list vs None.\n  \"\"\"\n\n  def _get_by_path(x, path: tuple[tp.Any, ...]):\n    cur = x\n    for k in path:\n      cur = cur[k]\n    return cur\n\n  def _to_simple(x):\n    # Convert to display-friendly simple structures\n    if isinstance(x, (list, tuple)):\n      return [_to_simple(e) for e in x]\n    if isinstance(x, dict):\n      return {k: _to_simple(v) for k, v in x.items()}\n    return x\n\n  result: list | dict = (\n    [] if len(xs) > 0 and isinstance(xs[0][0][0], int) else {}\n  )\n  for path, value in xs:\n    cursor = result\n    for i, key in enumerate(path[:-1]):\n      if isinstance(cursor, list):\n        # Ensure list has slot for current key; infer placeholder from original\n        while len(cursor) <= key:\n          # path to the slot we are about to create\n          slot_path = path[:i] + (len(cursor),)\n          try:\n            orig_slot = _get_by_path(original, slot_path)\n          except Exception:\n            orig_slot = None\n          if isinstance(orig_slot, (list, tuple)):\n            cursor.append([])\n          elif isinstance(orig_slot, dict):\n            cursor.append({})\n          else:\n            cursor.append(None)\n      else:\n        if key not in cursor:\n          next_key = path[i + 1]\n          if isinstance(next_key, int):\n            cursor[key] = []\n          else:\n            cursor[key] = {}\n      cursor = cursor[key]\n    if isinstance(cursor, list):\n      # Handle gaps in indices caused by JAX flattening eliding empty containers\n      while len(cursor) <= path[-1]:\n        slot_path = path[:-1] + (len(cursor),)\n        try:\n          orig_slot = _get_by_path(original, slot_path)\n        except Exception:\n          orig_slot = None\n        if isinstance(orig_slot, (list, tuple)):\n          cursor.append([])\n        elif isinstance(orig_slot, dict):\n          cursor.append({})\n        else:\n          cursor.append(None)\n      cursor[path[-1]] = value\n    else:\n      assert isinstance(cursor, dict)\n      cursor[path[-1]] = value\n  # If original is a sequence and result is a list, pad trailing items\n  if isinstance(original, (list, tuple)) and isinstance(result, list):\n    for i in range(len(result), len(original)):\n      slot = original[i]\n      result.append(_to_simple(slot))\n  return result\n\ndef _as_yaml_str(value) -> str:\n  if (hasattr(value, '__len__') and len(value) == 0) or value is None:\n    return ''\n\n  value = jax.tree.map(_normalize_values, value)\n  value = _maybe_pytree_to_dict(value)\n\n  file = io.StringIO()\n  yaml.dump(\n    value,\n    file,\n    Dumper=NoneDumper,\n    default_flow_style=False,\n    indent=2,\n    sort_keys=False,\n    explicit_end=False,\n  )\n  return file.getvalue().replace('\\n...', '').replace(\"'\", '').strip()\n\n\ndef _render_array(x):\n  shape, dtype = jnp.shape(x), jnp.result_type(x)\n  shape_repr = ','.join(str(x) for x in shape)\n  return f'[dim]{dtype}[/dim][{shape_repr}]'\n\n\ndef _sort_variable_types(types: tp.Iterable[type]) -> list[type]:\n  def _variable_parents_count(t: type):\n    return sum(1 for p in t.mro() if issubclass(p, variablelib.Variable))\n\n  type_sort_key = {t: (-_variable_parents_count(t), t.__name__) for t in types}\n  return sorted(types, key=lambda t: type_sort_key[t])\n"
  },
  {
    "path": "flax/nnx/tracers.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Taken from flax/core/tracer.py 🏴‍☠️\n\n\nimport jax\nimport jax.core\nimport treescope  # type: ignore[import-not-found,import-untyped]\n\nfrom flax.nnx import reprlib\n\n\ndef current_jax_trace():\n  \"\"\"Returns the Jax tracing state.\"\"\"\n  if jax.__version_info__ <= (0, 4, 33):\n    return jax.core.thread_local_state.trace_state.trace_stack.dynamic\n  return jax.core.get_opaque_trace_state(convention=\"nnx\")\n\n\nclass TraceState(reprlib.Representable):\n  __slots__ = ['_jax_trace']\n\n  def __init__(self):\n    self._jax_trace = current_jax_trace()\n\n  @property\n  def jax_trace(self):\n    return self._jax_trace\n\n  def is_valid(self) -> bool:\n    return self._jax_trace == current_jax_trace()\n\n  def __nnx_repr__(self):\n    yield reprlib.Object(f'{type(self).__name__}')\n    yield reprlib.Attr('jax_trace', self._jax_trace)\n\n  def __treescope_repr__(self, path, subtree_renderer):\n    return treescope.repr_lib.render_object_constructor(\n        object_type=type(self),\n        attributes={'jax_trace': self._jax_trace},\n        path=path,\n        subtree_renderer=subtree_renderer,\n    )\n\n  def __eq__(self, other):\n    if jax.__version_info__ <= (0, 4, 33):\n      return isinstance(other, TraceState) and self._jax_trace is other._jax_trace\n\n    return isinstance(other, TraceState) and self._jax_trace == other._jax_trace\n\n  # pickle support\n  def __getstate__(self):\n    return {}\n\n  def __setstate__(self, state):\n    self._jax_trace = current_jax_trace()\n\ndef _flatten_trace_state(trace_state: TraceState):\n  return (), None\n\n\ndef _unflatten_trace_state(_1, _2):\n  return TraceState()\n\n\njax.tree_util.register_pytree_node(\n  TraceState,\n  _flatten_trace_state,\n  _unflatten_trace_state,\n)"
  },
  {
    "path": "flax/nnx/training/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n"
  },
  {
    "path": "flax/nnx/training/metrics.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom __future__ import annotations\n\nimport typing as tp\n\nimport numpy as np\n\nfrom flax import struct\nfrom flax.nnx import filterlib, graphlib\nfrom flax.nnx.pytreelib import Pytree\nfrom flax.nnx.variablelib import Variable\nimport jax, jax.numpy as jnp\n\n# TODO: add tests and docstrings\n\n\nclass MetricState(Variable):\n  \"\"\"Wrapper class for Metric Variables.\"\"\"\n\n  pass\n\n\nclass Metric(Pytree):\n  \"\"\"Base class for metrics. Any class that subclasses ``Metric`` should\n  implement a ``compute``, ``reset`` and ``update`` method.\"\"\"\n\n  def __init__(self):\n    raise NotImplementedError('Must override `__init__()` method.')\n\n  def reset(self) -> None:\n    \"\"\"In-place reset the ``Metric``.\"\"\"\n    raise NotImplementedError('Must override `reset()` method.')\n\n  def update(self, **kwargs) -> None:\n    \"\"\"In-place update the ``Metric``.\"\"\"\n    raise NotImplementedError('Must override `update()` method.')\n\n  def compute(self):\n    \"\"\"Compute and return the value of the ``Metric``.\"\"\"\n    raise NotImplementedError('Must override `compute()` method.')\n\n  def split(self, *filters: filterlib.Filter):\n    return graphlib.split(self, *filters)\n\n\nclass Average(Metric):\n  \"\"\"Average metric.\n\n  Example usage::\n\n    >>> import jax.numpy as jnp\n    >>> from flax import nnx\n\n    >>> batch_loss = jnp.array([1, 2, 3, 4])\n    >>> batch_loss2 = jnp.array([3, 2, 1, 0])\n\n    >>> metrics = nnx.metrics.Average()\n    >>> metrics.compute()\n    Array(nan, dtype=float32)\n    >>> metrics.update(values=batch_loss)\n    >>> metrics.compute()\n    Array(2.5, dtype=float32)\n    >>> metrics.update(values=batch_loss2)\n    >>> metrics.compute()\n    Array(2., dtype=float32)\n    >>> metrics.reset()\n    >>> metrics.compute()\n    Array(nan, dtype=float32)\n  \"\"\"\n\n  def __init__(self, argname: str = 'values'):\n    \"\"\"Pass in a string denoting the key-word argument that :func:`update` will use to derive the new value.\n    For example, constructing the metric as ``avg = Average('test')`` would allow you to make updates with\n    ``avg.update(test=new_value)``.\n\n    Args:\n      argname: an optional string denoting the key-word argument that\n        :func:`update` will use to derive the new value. Defaults to\n        ``'values'``.\n    \"\"\"\n    self.argname = argname\n    self.total = MetricState(jnp.array(0, dtype=jnp.float32))\n    self.count = MetricState(jnp.array(0, dtype=jnp.int32))\n\n  def reset(self) -> None:\n    \"\"\"Reset this ``Metric``.\"\"\"\n    self.total[...] = jnp.array(0, dtype=jnp.float32)\n    self.count[...] = jnp.array(0, dtype=jnp.int32)\n\n  def update(self, mask: jax.Array | None = None, **kwargs) -> None:\n    \"\"\"In-place update this ``Metric``. This method will use the value from\n    ``kwargs[self.argname]`` to update the metric, where ``self.argname`` is\n    defined on construction.\n\n    Args:\n      mask: optional mask to ignore the values from the computation.\n        We use `values * mask` rule and mask shape should be broadcastable\n        to the shape of values array.\n      **kwargs: the key-word arguments that contains a ``self.argname``\n        entry that maps to the value we want to use to update this metric.\n    \"\"\"\n    if self.argname not in kwargs:\n      raise TypeError(f\"Expected keyword argument '{self.argname}'\")\n    values: tp.Union[int, float, jax.Array] = kwargs[self.argname]\n    if mask is not None and isinstance(values, (int, float)):\n      raise ValueError(f\"If mask is provided, {self.argname} should be a jax array\")\n    if isinstance(values, (int, float)):\n      self.total[...] += values\n      self.count[...] += 1\n    elif mask is None:\n      self.total[...] += values.sum()\n      self.count[...] += values.size\n    else:\n      self.total[...] += (values * mask.astype(values.dtype)).sum()\n      self.count[...] += mask.sum().astype(self.count.dtype)\n\n  def compute(self) -> jax.Array:\n    \"\"\"Compute and return the average.\"\"\"\n    return self.total / self.count\n\n\n@struct.dataclass\nclass Statistics:\n  mean: jnp.float32\n  standard_error_of_mean: jnp.float32\n  standard_deviation: jnp.float32\n\n\nclass Welford(Metric):\n  \"\"\"Uses Welford's algorithm to compute the mean and variance of a stream of data.\n\n  Example usage::\n\n    >>> import jax.numpy as jnp\n    >>> from flax import nnx\n\n    >>> batch_loss = jnp.array([1, 2, 3, 4])\n    >>> batch_loss2 = jnp.array([3, 2, 1, 0])\n\n    >>> metrics = nnx.metrics.Welford()\n    >>> metrics.compute()\n    Statistics(mean=Array(0., dtype=float32), standard_error_of_mean=Array(nan, dtype=float32), standard_deviation=Array(nan, dtype=float32))\n    >>> metrics.update(values=batch_loss)\n    >>> metrics.compute()\n    Statistics(mean=Array(2.5, dtype=float32), standard_error_of_mean=Array(0.559017, dtype=float32), standard_deviation=Array(1.118034, dtype=float32))\n    >>> metrics.update(values=batch_loss2)\n    >>> metrics.compute()\n    Statistics(mean=Array(2., dtype=float32), standard_error_of_mean=Array(0.43301272, dtype=float32), standard_deviation=Array(1.2247449, dtype=float32))\n    >>> metrics.reset()\n    >>> metrics.compute()\n    Statistics(mean=Array(0., dtype=float32), standard_error_of_mean=Array(nan, dtype=float32), standard_deviation=Array(nan, dtype=float32))\n  \"\"\"\n\n  def __init__(self, argname: str = 'values'):\n    \"\"\"Pass in a string denoting the key-word argument that :func:`update` will use to derive the new value.\n    For example, constructing the metric as ``wf = Welford('test')`` would allow you to make updates with\n    ``wf.update(test=new_value)``.\n\n    Args:\n      argname: an optional string denoting the key-word argument that\n        :func:`update` will use to derive the new value. Defaults to\n        ``'values'``.\n    \"\"\"\n    self.argname = argname\n    self.count = MetricState(jnp.array(0, dtype=jnp.int32))\n    self.mean = MetricState(jnp.array(0, dtype=jnp.float32))\n    self.m2 = MetricState(jnp.array(0, dtype=jnp.float32))\n\n  def reset(self) -> None:\n    \"\"\"Reset this ``Metric``.\"\"\"\n    self.count[...] = jnp.array(0, dtype=jnp.uint32)\n    self.mean[...] = jnp.array(0, dtype=jnp.float32)\n    self.m2[...] = jnp.array(0, dtype=jnp.float32)\n\n  def update(self, **kwargs) -> None:\n    \"\"\"In-place update this ``Metric``. This method will use the value from\n    ``kwargs[self.argname]`` to update the metric, where ``self.argname`` is\n    defined on construction.\n\n    Args:\n      **kwargs: the key-word arguments that contains a ``self.argname``\n        entry that maps to the value we want to use to update this metric.\n    \"\"\"\n    if self.argname not in kwargs:\n      raise TypeError(f\"Expected keyword argument '{self.argname}'\")\n    values: tp.Union[int, float, jax.Array] = kwargs[self.argname]\n    count = 1 if isinstance(values, (int, float)) else values.size\n    original_count = self.count[...]\n    self.count[...] += count\n    delta = (\n      values if isinstance(values, (int, float)) else values.mean()\n    ) - self.mean\n    self.mean[...] += delta * count / self.count\n    m2 = 0.0 if isinstance(values, (int, float)) else values.var() * count\n    self.m2[...] += m2 + delta * delta * count * original_count / self.count\n\n  def compute(self) -> Statistics:\n    \"\"\"Compute and return the mean and variance statistics in a\n    ``Statistics`` dataclass object.\n    \"\"\"\n    variance = self.m2 / self.count\n    standard_deviation = variance**0.5\n    sem = standard_deviation / (self.count**0.5)\n    return Statistics(\n      mean=self.mean[...],\n      standard_error_of_mean=sem,\n      standard_deviation=standard_deviation,\n    )\n\n\nclass Accuracy(Average):\n  \"\"\"Accuracy metric. This metric subclasses :class:`Average`,\n  and so they share the same ``reset`` and ``compute`` method\n  implementations. Unlike :class:`Average`, no string needs to\n  be passed to ``Accuracy`` during construction.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n\n    >>> logits = jax.random.normal(jax.random.key(0), (5, 2))\n    >>> labels = jnp.array([0, 1, 1, 1, 0])\n    >>> logits2 = jax.random.normal(jax.random.key(1), (5, 2))\n    >>> labels2 = jnp.array([0, 1, 1, 1, 1])\n\n    >>> metrics = nnx.metrics.Accuracy()\n    >>> metrics.compute()\n    Array(nan, dtype=float32)\n    >>> metrics.update(logits=logits, labels=labels)\n    >>> metrics.compute()\n    Array(0.6, dtype=float32)\n    >>> metrics.update(logits=logits2, labels=labels2)\n    >>> metrics.compute()\n    Array(0.4, dtype=float32)\n    >>> metrics.reset()\n    >>> metrics.compute()\n    Array(nan, dtype=float32)\n\n    >>> logits3 = jax.random.normal(jax.random.key(2), (5,))\n    >>> labels3 = jnp.array([0, 1, 0, 1, 1])\n    >>> accuracy = nnx.metrics.Accuracy(threshold=0.5)\n    >>> accuracy.update(logits=logits3, labels=labels3)\n    >>> accuracy.compute()\n    Array(0.8, dtype=float32)\n  \"\"\"\n\n  def __init__(self, threshold: float | None = None, *args, **kwargs):\n    \"\"\"For binary classification, pass in a float denoting a threshold to determine if a\n    prediction is positive. For example, constructing the metric as\n    ``acc = Accuracy(threshold=0.5)`` would cause any logit greater than or equal to 0.5\n    to be interpreted as a positive classification. For multi-class classification, do\n    not pass in a threshold.\n\n    Args:\n      threshold: for binary classification, determines if a prediction is\n        positive. Defaults to None.\n    \"\"\"\n    if (threshold is not None) and (not isinstance(threshold, float)):\n      raise TypeError(f'Expected threshold to be a float, got {type(threshold)}')\n\n    self.threshold = threshold\n    super().__init__(*args, **kwargs)\n\n  def update(  # type: ignore[override]\n      self, *, logits: jax.Array, labels: jax.Array, mask: jax.Array | None = None, **_\n  ) -> None:\n    \"\"\"In-place update this ``Metric``.\n\n    Args:\n      logits: the outputted predicted activations. For multi-class\n        classification, these values are argmax-ed (on the trailing\n        dimension), before comparing them to the labels. For binary\n        classification, these values are compared to the labels directly.\n      labels: the ground truth integer labels.\n      mask: optional mask to ignore the logits and labels values from the computation.\n        We use `array * mask` rule and mask shape should be broadcastable\n        to the shape of labels array.\n    \"\"\"\n    if self.threshold is not None:  # Binary classification case\n      if logits.ndim != labels.ndim:\n        raise ValueError(\n          'For binary classification, expected logits.ndim==labels.ndim, got '\n          f'{logits.ndim} and {labels.ndim}'\n        )\n    elif logits.ndim != labels.ndim + 1:  # Multi-class classification case\n      raise ValueError(\n        'For multi-class classification, expected logits.ndim==labels.ndim+1, '\n        f'got {logits.ndim} and {labels.ndim}'\n      )\n\n    if labels.dtype in (jnp.int64, np.int32, np.int64):\n      labels = jnp.astype(labels, jnp.int32)\n    elif labels.dtype != jnp.int32:\n      raise ValueError(f'Expected labels.dtype==jnp.int32, got {labels.dtype}')\n\n    if self.threshold is not None:  # Binary classification case\n      super().update(values=((logits >= self.threshold) == (labels > 0)), mask=mask)\n      return\n\n    # Multi-class classification case\n    super().update(values=(logits.argmax(axis=-1) == labels), mask=mask)\n\n\nclass MultiMetric(Metric):\n  \"\"\"MultiMetric class to store multiple metrics and update them in a single call.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n\n    >>> metrics = nnx.MultiMetric(\n    ...   accuracy=nnx.metrics.Accuracy(), loss=nnx.metrics.Average()\n    ... )\n\n    >>> metrics\n    MultiMetric( # MetricState: 4 (16 B)\n      accuracy=Accuracy( # MetricState: 2 (8 B)\n        threshold=None,\n        argname='values',\n        total=MetricState( # 1 (4 B)\n          value=Array(0., dtype=float32)\n        ),\n        count=MetricState( # 1 (4 B)\n          value=Array(0, dtype=int32)\n        )\n      ),\n      loss=Average( # MetricState: 2 (8 B)\n        argname='values',\n        total=MetricState( # 1 (4 B)\n          value=Array(0., dtype=float32)\n        ),\n        count=MetricState( # 1 (4 B)\n          value=Array(0, dtype=int32)\n        )\n      )\n    )\n\n    >>> metrics.accuracy\n    Accuracy( # MetricState: 2 (8 B)\n      threshold=None,\n      argname='values',\n      total=MetricState( # 1 (4 B)\n        value=Array(0., dtype=float32)\n      ),\n      count=MetricState( # 1 (4 B)\n        value=Array(0, dtype=int32)\n      )\n    )\n\n    >>> metrics.loss\n    Average( # MetricState: 2 (8 B)\n      argname='values',\n      total=MetricState( # 1 (4 B)\n        value=Array(0., dtype=float32)\n      ),\n      count=MetricState( # 1 (4 B)\n        value=Array(0, dtype=int32)\n      )\n    )\n\n    >>> logits = jax.random.normal(jax.random.key(0), (5, 2))\n    >>> labels = jnp.array([0, 1, 1, 1, 0])\n    >>> logits2 = jax.random.normal(jax.random.key(1), (5, 2))\n    >>> labels2 = jnp.array([0, 1, 1, 1, 1])\n\n    >>> batch_loss = jnp.array([1, 2, 3, 4])\n    >>> batch_loss2 = jnp.array([3, 2, 1, 0])\n\n    >>> metrics.compute()\n    {'accuracy': Array(nan, dtype=float32), 'loss': Array(nan, dtype=float32)}\n    >>> metrics.update(logits=logits, labels=labels, values=batch_loss)\n    >>> metrics.compute()\n    {'accuracy': Array(0.6, dtype=float32), 'loss': Array(2.5, dtype=float32)}\n    >>> metrics.update(logits=logits2, labels=labels2, values=batch_loss2)\n    >>> metrics.compute()\n    {'accuracy': Array(0.4, dtype=float32), 'loss': Array(2., dtype=float32)}\n    >>> metrics.reset()\n    >>> metrics.compute()\n    {'accuracy': Array(nan, dtype=float32), 'loss': Array(nan, dtype=float32)}\n  \"\"\"\n\n  def __init__(self, **metrics):\n    \"\"\"Pass in key-word arguments to the constructor, e.g.\n    ``MultiMetric(keyword1=Average(), keyword2=Accuracy(), ...)``.\n\n    Args:\n      **metrics: the key-word arguments that will be used to access\n        the corresponding ``Metric``.\n    \"\"\"\n    # TODO: raise error if a kwarg is passed that is in ('reset', 'update', 'compute'), since these names are reserved for methods\n    self._metric_names = []\n    for metric_name, metric in metrics.items():\n      self._metric_names.append(metric_name)\n      setattr(self, metric_name, metric)\n\n  def reset(self) -> None:\n    \"\"\"Reset all underlying ``Metric``'s.\"\"\"\n    for metric_name in self._metric_names:\n      getattr(self, metric_name).reset()\n\n  def update(self, **updates) -> None:\n    \"\"\"In-place update all underlying ``Metric``'s in this ``MultiMetric``. All\n    ``**updates`` will be passed to the ``update`` method of all underlying\n    ``Metric``'s.\n\n    Args:\n      **updates: the key-word arguments that will be passed to the underlying ``Metric``'s\n        ``update`` method. It can contain ``mask`` argument as mask to be passed to the underlying metrics.\n        ``mask`` can be a ``jax.Array` and it will be passed to all the metrics. ``mask`` can be a dictionary\n        with metric name as keys and ``jax.Array`` as values, e.g ``{metric_name1: metric_mask1, ...}``\n    \"\"\"\n    # TODO: should we give the option of updating only some of the metrics and not all? e.g. if for some kwargs==None, don't do update\n    # TODO: should we raise an error if a kwarg is passed into **updates that has no match with any underlying metric? e.g. user typo\n    mask = updates.pop(\"mask\", None)\n    for metric_name in self._metric_names:\n      metric_mask_kwarg = {}\n      metric_mask = mask.get(metric_name, None) if isinstance(mask, dict) else mask\n      if metric_mask is not None:\n        metric_mask_kwarg = {\"mask\": metric_mask}\n      getattr(self, metric_name).update(**(updates | metric_mask_kwarg))\n\n  def compute(self) -> dict[str, tp.Any]:\n    \"\"\"Compute and return the value of all underlying ``Metric``'s. This method\n    will return a dictionary, mapping strings (defined by the key-word arguments\n    ``**metrics`` passed to the constructor) to the corresponding metric value.\n    \"\"\"\n    return {\n        f'{metric_name}': getattr(self, metric_name).compute()\n        for metric_name in self._metric_names\n    }\n"
  },
  {
    "path": "flax/nnx/training/optimizer.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom __future__ import annotations\n\nimport functools\nimport typing as tp\n\nimport jax\nimport jax.numpy as jnp\nimport optax\n\nfrom flax import nnx\nfrom flax.nnx import filterlib\nfrom flax.nnx.pytreelib import Pytree\nfrom flax.nnx.variablelib import Variable\n\nM = tp.TypeVar('M', bound=nnx.Module)\nF = tp.TypeVar('F', bound=tp.Callable[..., tp.Any])\n\nclass OptState(Variable):\n  \"\"\"Any optimizer state\"\"\"\n\n  pass\n\n\nclass OptArray(OptState):\n  \"\"\"Optimizer state for an array.\"\"\"\n\n  pass\n\n\nclass OptVariable(OptState):\n  \"\"\"Optimizer state for a Variable.\"\"\"\n\n  pass\n\n\ndef to_opt_state(tree):\n  def _to_opt_state(x):\n    if isinstance(x, Variable):\n      opt_state = OptVariable(x.get_value(), **x.get_metadata())  # type: ignore\n    else:\n      opt_state = OptArray(x)\n    return opt_state\n\n  tree = jax.tree.map(\n    _to_opt_state,\n    tree,\n    is_leaf=lambda x: isinstance(x, Variable),\n  )\n  return tree\n\nclass _Missing:\n  pass\n\nMISSING = _Missing()\n\ndef _check_grads_arg_passed(f: F) -> F:\n  @functools.wraps(f)\n  def _check_grads_wrapper(self, model, grads=MISSING, **kwargs):\n    if isinstance(grads, _Missing):\n      raise TypeError(\n        'Missing required argument `grads`. As of Flax 0.11.0 update requires both (model, grads) arguments '\n        'to be passed. If you want to keep the previous use nnx.ModelAndOptimizer instead of nnx.Optimizer.'\n      )\n    return f(self, model, grads, **kwargs)\n  return _check_grads_wrapper # type: ignore\n\ndef _check_wrt_arg_passed(f: F) -> F:\n  @functools.wraps(f)\n  def _check_wrt_wrapper(*args, wrt=MISSING, **kwargs):\n    if isinstance(wrt, _Missing):\n      raise TypeError(\n        'Missing required argument `wrt`. As of Flax 0.11.0 the `wrt` argument is required, '\n        'if you want to keep the previous use nnx.ModelAndOptimizer instead of nnx.Optimizer.'\n      )\n    return f(*args, wrt=wrt, **kwargs)\n  return _check_wrt_wrapper  # type: ignore\n\nclass Optimizer(Pytree, tp.Generic[M]):\n  \"\"\"Simple train state for the common case with a single Optax optimizer.\n\n  Example usage::\n\n    >>> import jax, jax.numpy as jnp\n    >>> from flax import nnx\n    >>> import optax\n    ...\n    >>> class Model(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.linear1 = nnx.Linear(2, 3, rngs=rngs)\n    ...     self.linear2 = nnx.Linear(3, 4, rngs=rngs)\n    ...   def __call__(self, x):\n    ...     return self.linear2(self.linear1(x))\n    ...\n    >>> x = jax.random.normal(jax.random.key(0), (1, 2))\n    >>> y = jnp.ones((1, 4))\n    ...\n    >>> model = Model(nnx.Rngs(0))\n    >>> tx = optax.adam(1e-3)\n    >>> optimizer = nnx.Optimizer(model, tx, wrt=nnx.Param)\n    ...\n    >>> loss_fn = lambda model: ((model(x) - y) ** 2).mean()\n    >>> loss_fn(model)\n    Array(2.3359997, dtype=float32)\n    >>> grads = nnx.grad(loss_fn)(model)\n    >>> optimizer.update(model, grads)\n    >>> loss_fn(model)\n    Array(2.310461, dtype=float32)\n\n  Attributes:\n    step: An ``OptState`` :class:`Variable` that tracks the step count.\n    tx: An Optax gradient transformation.\n    opt_state: The Optax optimizer state.\n  \"\"\"\n\n  @_check_wrt_arg_passed\n  def __init__(\n    self,\n    model: M,\n    tx: optax.GradientTransformation,\n    *,\n    wrt: filterlib.Filter,  # type: ignore\n  ):\n    \"\"\"\n    Instantiate the class and wrap the :class:`Module` and Optax gradient\n    transformation. Instantiate the optimizer state to keep track of\n    :class:`Variable` types specified in ``wrt``. Set the step count to 0.\n\n    Args:\n      model: An NNX Module.\n      tx: An Optax gradient transformation.\n      wrt: filter to specify for which :class:`Variable`'s to keep\n        track of in the optimizer state. These should be the :class:`Variable`'s\n        that you plan on updating; i.e. this argument value should match the\n        ``wrt``  argument passed to the ``nnx.grad`` call that will generate the\n        gradients that will be passed into the ``grads`` argument of the\n        :func:`update` method. The filter should match the filter used in nnx.grad.\n    \"\"\"\n    self.step = OptState(jnp.array(0, dtype=jnp.uint32))\n    self.tx = tx\n    self.opt_state = nnx.data(\n      to_opt_state(tx.init(nnx.state(model, wrt)))\n    )\n    self.wrt = wrt\n\n  if not tp.TYPE_CHECKING:\n    def __getattribute__(self, name: str) -> tp.Any:\n      if name == 'model' and name not in vars(self):\n        raise AttributeError(\n          f\"{type(self).__name__} does not have attribute 'model' since Flax 0.11.0. \"\n          \"To keep the previous behavior, use nnx.ModelAndOptimizer instead of nnx.Optimizer.\"\n        )\n      return super().__getattribute__(name)\n\n  @_check_grads_arg_passed\n  def update(self, model: M, grads, /, **kwargs):\n    \"\"\"Updates the optimizer state and model parameters given the gradients.\n\n    Example::\n\n      >>> from flax import nnx\n      >>> import jax, jax.numpy as jnp\n      >>> import optax\n      ...\n      >>> class Model(nnx.Module):\n      ...   def __init__(self, rngs):\n      ...     self.linear = nnx.Linear(2, 3, rngs=rngs)\n      ...     self.count = nnx.Variable(jnp.array(0))\n      ...\n      ...   def __call__(self, x):\n      ...     self.count[...] += 1\n      ...     return self.linear(x)\n      ...\n      >>> model = Model(rngs=nnx.Rngs(0))\n      ...\n      >>> loss_fn = lambda model, x, y: ((model(x) - y) ** 2).mean()\n      >>> optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\n      >>> grads = nnx.grad(loss_fn, argnums=nnx.DiffState(0, nnx.Param))(\n      ...   model, jnp.ones((1, 2)), jnp.ones((1, 3))\n      ... )\n      >>> optimizer.update(model, grads)\n\n    Note that internally this function calls ``.tx.update()`` followed by a call\n    to ``optax.apply_updates()`` to update ``params`` and ``opt_state``.\n\n    Args:\n      grads: the gradients derived from ``nnx.grad``.\n      **kwargs: additional keyword arguments passed to the tx.update, to support\n      ``GradientTransformationExtraArgs``, such as ``optax.scale_by_backtracking_linesearch``.\n    \"\"\"\n    param_arrays = nnx.pure(nnx.state(model, self.wrt))\n    grad_arrays = nnx.pure(nnx.state(grads, self.wrt))\n    opt_state_arrays = nnx.pure(self.opt_state)\n    kwargs_arrays = nnx.pure(kwargs)\n\n    updates, new_opt_state = self.tx.update(\n      grad_arrays, opt_state_arrays, param_arrays, **kwargs_arrays\n    )\n    new_params = optax.apply_updates(param_arrays, updates)\n\n    nnx.update(model, new_params)\n    nnx.update(self.opt_state, nnx.state(new_opt_state))\n    self.step[...] += 1\n\nclass ModelAndOptimizer(Optimizer[M]):\n  \"\"\"A convenience class that combines a model and an optimizer.\n\n  This class is deprecated and will be removed in a future release.\n  Use :class:`Optimizer` instead.\n  \"\"\"\n\n  def __init__(self, model: M, tx: optax.GradientTransformation, *, wrt: filterlib.Filter = nnx.Param):\n    super().__init__(model, tx, wrt=wrt)\n    self.model = model\n\n  def update(self, grads, /, **kwargs): # type: ignore\n    return super().update(self.model, grads, **kwargs)\n"
  },
  {
    "path": "flax/nnx/transforms/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "flax/nnx/transforms/autodiff.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom collections import deque\nimport dataclasses\nimport functools\nimport typing as tp\n\n\nfrom flax import struct\nfrom flax.nnx import (\n  extract,\n  filterlib,\n  graphlib,\n  variablelib,\n)\nfrom flax.nnx.statelib import State\nimport jax\n\nfrom flax.nnx.transforms import general\nfrom flax.nnx.transforms.transforms import (\n  resolve_kwargs,\n  _resolve_bound_callable,\n  _raise_bound_method_error,\n)\nfrom flax.typing import MISSING, Missing\n\n\nA = tp.TypeVar('A')\n# C = tp.TypeVar('C')\n# B = tp.TypeVar('B')\nF = tp.TypeVar('F', bound=tp.Callable[..., tp.Any])\n# G = tp.TypeVar('G', bound=tp.Callable[..., tp.Any])\n# M = tp.TypeVar('M', bound=Module)\n# MA = tp.TypeVar('MA', bound=Module)\n# N = tp.TypeVar('N', bound=Module)\n# StrInt = tp.TypeVar('StrInt', str, int)\nAxisName = tp.Hashable\n# Leaves = tp.List[Leaf]\n# Index = int\n\n\n# -------------------------------\n# grad\n# -------------------------------\n\n\n@dataclasses.dataclass(frozen=True)\nclass DiffState:\n  argnum: int\n  filter: filterlib.Filter\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleGradFn:\n  f: tp.Callable[..., tp.Any]\n  has_aux: bool\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args, **kwargs):\n    updates, snapshot = extract.updates_and_snapshot((args, kwargs))\n    if self.graph:\n      args, kwargs = extract.from_tree2((args, kwargs))\n    out = self.f(*args, **kwargs)\n    if self.graph:\n      out = extract.to_tree2(out)\n    extract.check_no_aliases('grad', args=updates[0], kwargs=updates[1], out=out)\n    updates = extract.mask_variable_updates(updates, snapshot)\n\n    if self.has_aux:\n      loss, aux = out\n      return loss, (updates, aux)\n    else:\n      return out, updates\n\n\n@dataclasses.dataclass(eq=False)\nclass GradFn:\n  f: tp.Callable[..., tp.Any]\n  has_aux: bool\n  nondiff_states: deque[State | None]\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f)\n\n  def __call__(self, *pure_args):\n    # rebuild diff_state from substates in args\n\n    def _grad_merge_fn(\n      ctx: graphlib.MergeContext, path, prefix, value: extract.NodeStates\n    ):\n      nondiff = self.nondiff_states.popleft()\n      if nondiff is None:\n        return ctx.merge(value.graphdef, value.state)\n      else:\n        return ctx.merge(value.graphdef, value.state, nondiff)\n\n    args = extract.from_tree(\n      pure_args, merge_fn=_grad_merge_fn, ctxtag='grad', is_inner=True\n    )\n\n    out = self.f(*args)\n\n    args_out = extract.clear_non_graph_nodes(args)\n    pure_args_out, pure_out = extract.to_tree((args_out, out), ctxtag='grad')\n\n    if self.has_aux:\n      loss, pure_aux = pure_out\n      fn_out = (loss, (pure_args_out, pure_aux))\n    else:\n      loss = pure_out\n      fn_out = (loss, pure_args_out)\n\n    return fn_out\n\n\ndef _grad_general(\n    f: tp.Callable[..., tp.Any],\n    argnums: int | DiffState | tp.Sequence[int | DiffState],\n    has_aux: bool,\n    holomorphic: bool,\n    allow_int: bool,\n    return_value: bool,\n    graph: bool,\n    graph_updates: bool,\n) -> tp.Callable[..., tp.Any]:\n\n  transform = jax.value_and_grad if return_value else jax.grad\n\n  if not graph or not graph_updates:\n    if any(isinstance(x, DiffState) for x in jax.tree.leaves(argnums)):\n      raise ValueError(\n        '`argnums` cannot contain `DiffState` objects '\n        'when `graph=False`. '\n        + graphlib._tree_mode_suggestion_transform('grad')\n      )\n\n    gradded_fn = transform(\n        SimpleGradFn(f, has_aux, graph=graph),\n        argnums=argnums,  # type: ignore[arg-type]\n        has_aux=True,\n        holomorphic=holomorphic,\n        allow_int=allow_int,\n    )\n\n    def tree_grad_wrapper(*args, **kwargs):\n      if graph:\n        diff_argnums = (argnums,) if isinstance(argnums, int) else argnums\n        args_prefix = tuple(\n          i in diff_argnums for i in range(len(args))\n        )\n        args, kwargs = extract.to_tree2(\n          (args, kwargs), prefix=(args_prefix, False),\n        )\n\n      extract.check_no_aliases('grad', args=args, kwargs=kwargs)\n\n      fn_out = gradded_fn(*args, **kwargs)\n\n      if return_value:\n        if has_aux:\n          (loss, (updates, aux)), grads = fn_out\n          if graph: grads, aux = extract.from_tree2((grads, aux))\n          result = (loss, aux), grads\n        else:\n          (loss, updates), grads = fn_out\n          if graph: grads = extract.from_tree2(grads)\n          result = loss, grads\n      else:\n        if has_aux:\n          grads, (updates, aux) = fn_out\n          if graph: grads, aux = extract.from_tree2((grads, aux))\n          result = grads, aux\n        else:\n          grads, updates = fn_out\n          if graph: grads = extract.from_tree2(grads)\n          result = grads\n\n      extract.apply_variable_updates((args, kwargs), updates)\n      return result\n\n    return tree_grad_wrapper\n\n  jax_argnums: int | tuple[int, ...]\n  if isinstance(argnums, (int, DiffState)):\n    jax_argnums = argnums.argnum if isinstance(argnums, DiffState) else argnums\n  else:\n    jax_argnums = tuple(\n      x.argnum if isinstance(x, DiffState) else x for x in argnums\n    )\n\n  _argnums = (argnums,) if isinstance(argnums, (int, DiffState)) else argnums\n  index_filter: dict[int, DiffState] = {}\n  for argnum in _argnums:\n    index = argnum.argnum if isinstance(argnum, DiffState) else argnum\n    if index in index_filter:\n      raise ValueError(f'argnum {index} is repeated in argnums')\n    index_filter[index] = (\n      dataclasses.replace(argnum, argnum=-1)\n      if isinstance(argnum, DiffState)\n      else DiffState(-1, variablelib.Param)\n    )\n\n  @graphlib.update_context('grad')\n  def grad_wrapper(*args, **kwargs):\n    args = resolve_kwargs(f, args, kwargs)\n    del kwargs\n    nondiff_states: deque[State | variablelib.Variable | None] = deque()\n\n    def _grad_split_fn(\n      ctx: graphlib.SplitContext, path, prefix: DiffState | None, value\n    ):\n      if prefix is None or (prefix.argnum == -1 and isinstance(value, variablelib.Variable)):\n        nondiff_states.append(None)\n        return extract.NodeStates.from_split(*ctx.split(value))\n      else:\n        graphdef, diff, nondiff = ctx.split(value, prefix.filter, ...)  # type: ignore[misc]\n        nondiff_states.append(nondiff)  # type: ignore[container-type-mismatch]\n        return extract.NodeStates.from_split(graphdef, diff)\n\n    arg_filters = tuple(index_filter.get(i) for i in range(len(args)))\n    pure_args = extract.to_tree(\n      args, prefix=arg_filters, split_fn=_grad_split_fn, ctxtag='grad'\n    )\n\n    gradded_fn = transform(\n      GradFn(f, has_aux, nondiff_states),\n      argnums=jax_argnums,\n      has_aux=True,\n      holomorphic=holomorphic,\n      allow_int=allow_int,\n    )\n\n    fn_out = gradded_fn(*pure_args)\n\n    def process_grads(grads):\n      return jax.tree.map(\n        lambda x: x.state if isinstance(x, extract.NodeStates) else x,\n        grads,\n        is_leaf=lambda x: isinstance(x, extract.NodeStates),\n      )\n\n    def process_out(pure_out: A, /) -> A:\n      return extract.from_tree(pure_out, ctxtag='grad', is_inner=False)\n\n    if return_value:\n      # unpack value_and_grad output\n      if has_aux:\n        (loss, (pure_args_out, pure_aux)), grads = fn_out\n        grads = process_grads(grads)\n        _args_out, aux = process_out((pure_args_out, pure_aux))\n        return (loss, aux), grads\n      else:\n        (loss, pure_args_out), grads = fn_out\n        grads = process_grads(grads)\n        _args_out = process_out(pure_args_out)\n        return loss, grads\n    else:\n      # unpack grad output\n      if has_aux:\n        grads, (pure_args_out, pure_aux) = fn_out\n        grads = process_grads(grads)\n        _args_out, aux = process_out((pure_args_out, pure_aux))\n        return grads, aux\n      else:\n        grads, pure_args_out = fn_out\n        grads = process_grads(grads)\n        _args_out = process_out(pure_args_out)\n        return grads\n\n  return grad_wrapper\n\n\n@tp.overload\ndef grad(\n  f: tp.Callable[..., tp.Any],\n  *,\n  argnums: int | DiffState | tp.Sequence[int | DiffState] = 0,\n  has_aux: bool = False,\n  holomorphic: bool = False,\n  allow_int: bool = False,\n  reduce_axes: tp.Sequence[AxisName] = (),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tp.Callable[..., tp.Any]: ...\n@tp.overload\ndef grad(\n  *,\n  argnums: int | DiffState | tp.Sequence[int | DiffState] = 0,\n  has_aux: bool = False,\n  holomorphic: bool = False,\n  allow_int: bool = False,\n  reduce_axes: tp.Sequence[AxisName] = (),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tp.Callable[[tp.Callable[..., tp.Any]], tp.Callable[..., tp.Any]]: ...\ndef grad(\n  f: tp.Callable[..., tp.Any] | Missing = MISSING,\n  *,\n  argnums: int | DiffState | tp.Sequence[int | DiffState] = 0,\n  has_aux: bool = False,\n  holomorphic: bool = False,\n  allow_int: bool = False,\n  reduce_axes: tp.Sequence[AxisName] = (),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> (\n  tp.Callable[..., tp.Any]\n  | tp.Callable[[tp.Callable[..., tp.Any]], tp.Callable[..., tp.Any]]\n):\n  \"\"\"Object-aware version of ``jax.grad`` that can handle Modules / graph nodes as\n  arguments.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n    ...\n    >>> m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    >>> x = jnp.ones((1, 2))\n    >>> y = jnp.ones((1, 3))\n    ...\n    >>> loss_fn = lambda m, x, y: jnp.mean((m(x) - y) ** 2)\n    >>> grad_fn = nnx.grad(loss_fn)\n    ...\n    >>> grads = grad_fn(m, x, y)\n    >>> jax.tree.map(jnp.shape, grads)\n    State({\n      'bias': Param(\n        value=(3,)\n      ),\n      'kernel': Param(\n        value=(2, 3)\n      )\n    })\n\n  By default, NNX objects are differentiated with respect to all their ``nnx.Param``\n  Variables. You can specify which substates are differentiable by passing a ``DiffState``\n  object to the ``argnums`` argument. For example, if you want to differentiate only the\n  ``kernel`` attribute of the ``Linear`` class, you can use the ``PathContains`` filter::\n\n    >>> m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    ...\n    >>> kernel_attribute = nnx.PathContains('kernel')\n    >>> diff_state = nnx.DiffState(0, kernel_attribute)\n    ...\n    >>> loss_fn = lambda m, x, y: jnp.mean((m(x) - y) ** 2)\n    >>> grad_fn = nnx.grad(loss_fn, argnums=diff_state)\n    ...\n    >>> grads = grad_fn(m, x, y)\n    >>> jax.tree.map(jnp.shape, grads)\n    State({\n      'kernel': Param(\n        value=(2, 3)\n      )\n    })\n\n  For more information on how to create custom filters, see\n  `Using Filters <https://flax.readthedocs.io/en/latest/guides/filters_guide.html>`__\n  guide.\n\n  Args:\n    fun: Function to be differentiated. Its arguments at positions specified by\n      ``argnums`` should be arrays, scalars, graph nodes or standard Python\n      containers. Argument arrays in the positions specified by ``argnums`` must\n      be of inexact (i.e., floating-point or complex) type. It should return a\n      scalar (which includes arrays with shape ``()`` but not arrays with shape\n      ``(1,)`` etc.)\n    argnums: Optional, integer or sequence of integers. Specifies which\n      positional argument(s) to differentiate with respect to (default 0).\n    has_aux: Optional, bool. Indicates whether ``fun`` returns a pair where the\n      first element is considered the output of the mathematical function to be\n      differentiated and the second element is auxiliary data. Default False.\n    holomorphic: Optional, bool. Indicates whether ``fun`` is promised to be\n      holomorphic. If True, inputs and outputs must be complex. Default False.\n    allow_int: Optional, bool. Whether to allow differentiating with\n      respect to integer valued inputs. The gradient of an integer input will\n      have a trivial vector-space dtype (float0). Default False.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references and reference semantics.\n      If ``False``, uses tree-mode which treats Modules as regular JAX\n      pytrees, avoiding the overhead of the graph protocol. Tree-mode does\n      not support ``DiffState`` or shared ``Variable`` references.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``. When ``False``, using ``DiffState``\n      is not supported.\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if reduce_axes:\n    raise NotImplementedError('reduce_axes argument to grad is deprecated')\n  del reduce_axes\n\n  if isinstance(f, Missing):\n    return functools.partial(\n      grad,\n      argnums=argnums,\n      has_aux=has_aux,\n      holomorphic=holomorphic,\n      allow_int=allow_int,\n      graph=graph,\n      graph_updates=graph_updates,\n    )\n  # Detect bound nnx.Module methods and raise error.\n  f_unbound, _, was_bound = _resolve_bound_callable(f)\n\n  if was_bound:\n    _raise_bound_method_error('grad')\n\n  return _grad_general(\n    f_unbound,\n    argnums,\n    has_aux,\n    holomorphic,\n    allow_int,\n    return_value=False,\n    graph=graph,\n    graph_updates=graph_updates,\n  )\n\n\n@tp.overload\ndef value_and_grad(\n  f: tp.Callable[..., tp.Any],\n  *,\n  argnums: int | DiffState | tp.Sequence[int | DiffState] = 0,\n  has_aux: bool = False,\n  holomorphic: bool = False,\n  allow_int: bool = False,\n  reduce_axes: tp.Sequence[AxisName] = (),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tp.Callable[..., tp.Any]: ...\n@tp.overload\ndef value_and_grad(\n  *,\n  argnums: int | DiffState | tp.Sequence[int | DiffState] = 0,\n  has_aux: bool = False,\n  holomorphic: bool = False,\n  allow_int: bool = False,\n  reduce_axes: tp.Sequence[AxisName] = (),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tp.Callable[[tp.Callable[..., tp.Any]], tp.Callable[..., tp.Any]]: ...\ndef value_and_grad(\n  f: tp.Callable[..., tp.Any] | type[Missing] = Missing,\n  *,\n  argnums: int | DiffState | tp.Sequence[int | DiffState] = 0,\n  has_aux: bool = False,\n  holomorphic: bool = False,\n  allow_int: bool = False,\n  reduce_axes: tp.Sequence[AxisName] = (),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> (\n  tp.Callable[..., tp.Any]\n  | tp.Callable[[tp.Callable[..., tp.Any]], tp.Callable[..., tp.Any]]\n):\n  \"\"\"Object-aware version of ``jax.value_and_grad``.\n\n  Like :func:`grad`, but returns both the value and the gradient of ``f``.\n\n  Args:\n    f: Function to be differentiated. Its arguments at positions specified by\n      ``argnums`` should be arrays, scalars, graph nodes or standard Python\n      containers. Argument arrays in the positions specified by ``argnums`` must\n      be of inexact (i.e., floating-point or complex) type. It should return a\n      scalar (which includes arrays with shape ``()`` but not arrays with shape\n      ``(1,)`` etc.)\n    argnums: Optional, integer or sequence of integers. Specifies which\n      positional argument(s) to differentiate with respect to (default 0).\n    has_aux: Optional, bool. Indicates whether ``f`` returns a pair where the\n      first element is considered the output of the mathematical function to be\n      differentiated and the second element is auxiliary data. Default False.\n    holomorphic: Optional, bool. Indicates whether ``f`` is promised to be\n      holomorphic. If True, inputs and outputs must be complex. Default False.\n    allow_int: Optional, bool. Whether to allow differentiating with\n      respect to integer valued inputs. The gradient of an integer input will\n      have a trivial vector-space dtype (float0). Default False.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references and reference semantics.\n      If ``False``, uses tree-mode which treats Modules as regular JAX\n      pytrees, avoiding the overhead of the graph protocol. Tree-mode does\n      not support ``DiffState`` or shared ``Variable`` references.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``. When ``False``, using ``DiffState``\n      is not supported.\n\n  Returns:\n    A function with the same arguments as ``f`` that evaluates both ``f``\n    and the gradient of ``f`` and returns them as a pair (a two-element\n    tuple). If ``argnums`` is an integer then the gradient has the same shape\n    and type as the positional argument indicated by that integer. If argnums is\n    a sequence of integers, the gradient is a tuple of values with the same\n    shapes and types as the corresponding arguments. If ``has_aux`` is True\n    then a tuple of ((value, auxiliary_data), gradient) is returned.\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if reduce_axes:\n    raise NotImplementedError(\n        'reduce_axes argument to value_and_grad is deprecated')\n  del reduce_axes\n\n  if f is Missing:\n    return functools.partial(\n      value_and_grad,\n      argnums=argnums,\n      has_aux=has_aux,\n      holomorphic=holomorphic,\n      allow_int=allow_int,\n      graph=graph,\n      graph_updates=graph_updates,\n    )\n  # Detect bound nnx.Module methods and raise error.\n  f_unbound, _, was_bound = _resolve_bound_callable(f)\n\n  if was_bound:\n    _raise_bound_method_error('value_and_grad')\n\n  return _grad_general(\n    f_unbound,\n    argnums,\n    has_aux,\n    holomorphic,\n    allow_int,\n    return_value=True,\n    graph=graph,\n    graph_updates=graph_updates,\n  )\n\n# -----------------------------------------------\n# vjp\n# -----------------------------------------------\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleVjpFn:\n  f: tp.Callable[..., tp.Any]\n  has_aux: bool\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args):\n    updates, snapshot = extract.updates_and_snapshot(args)\n    if self.graph:\n      args = extract.from_tree2(args)\n    out = self.f(*args)\n    if self.graph:\n      out = extract.to_tree2(out)\n    extract.check_no_aliases('vjp', args=updates, out=out)\n    updates = extract.mask_variable_updates(updates, snapshot)\n    if self.has_aux:\n      primals_out, aux = out\n      return primals_out, (updates, aux)\n    else:\n      return out, updates\n\n\n@tp.overload\ndef vjp(\n  f: tp.Callable[..., tp.Any],\n  *primals: tp.Any,\n  has_aux: bool = False,\n  reduce_axes: tp.Sequence[AxisName] = (),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tuple[tp.Any, tp.Callable] | tuple[tp.Any, tp.Callable, tp.Any]: ...\n@tp.overload\ndef vjp(\n  *,\n  has_aux: bool = False,\n  reduce_axes: tp.Sequence[AxisName] = (),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tp.Callable[[tp.Callable[..., tp.Any]], tp.Callable[..., tp.Any]]: ...\ndef vjp(\n  f: tp.Callable[..., tp.Any] | Missing = MISSING,\n  *primals: tp.Any,\n  has_aux: bool = False,\n  reduce_axes: tp.Sequence[AxisName] = (),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> (\n  tuple[tp.Any, tp.Callable]\n  | tuple[tp.Any, tp.Callable, tp.Any]\n  | tp.Callable[[tp.Callable[..., tp.Any]], tp.Callable[..., tp.Any]]\n):\n  \"\"\"Stateful version of ``jax.vjp`` that propagates NNX Variable updates.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n    ...\n    >>> m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    >>> x = jnp.ones((1, 2))\n    ...\n    >>> def loss_fn(m, x):\n    ...   return jnp.sum(m(x))\n    ...\n    >>> primals_out, vjp_fn = nnx.vjp(loss_fn, m, x, graph=False)\n    >>> m_grad, x_grad = vjp_fn(jnp.ones_like(primals_out))\n\n  Can also be used as a decorator::\n\n    >>> @nnx.vjp(graph=False)\n    ... def f(m, x):\n    ...   return jnp.sum(m(x))\n    ...\n    >>> primals_out, vjp_fn = f(m, x)\n\n  Args:\n    f: Function to be differentiated. Its arguments can be arrays, scalars,\n      or pytrees containing arrays and NNX Variables.\n    *primals: A sequence of primal values at which the Jacobian of ``f``\n      should be evaluated.\n    has_aux: Optional, bool. Indicates whether ``f`` returns a pair where the\n      first element is considered the output of the mathematical function to be\n      differentiated and the second element is auxiliary data. Default False.\n    reduce_axes: Deprecated, do not use.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references and reference semantics.\n      If ``False``, uses tree-mode which treats Modules as regular JAX\n      pytrees, avoiding the overhead of the graph protocol.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``.\n\n  Returns:\n    If ``has_aux`` is False, returns a ``(primals_out, vjp_fn)`` pair.\n    ``vjp_fn`` takes a cotangent with the same structure as ``primals_out``\n    and returns gradients for each primal argument.\n    If ``has_aux`` is True, returns ``(primals_out, vjp_fn, aux)``.\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if graph and graph_updates:\n    raise NotImplementedError(\n      'graph-mode with graph_updates is not supported for nnx.vjp. '\n      'Set graph=False or graph_updates=False.'\n    )\n  if reduce_axes:\n    raise NotImplementedError('reduce_axes argument to vjp is deprecated')\n  del reduce_axes\n\n  if isinstance(f, Missing):\n    return functools.partial(  # type: ignore[return-value]\n      vjp,\n      has_aux=has_aux,\n      graph=graph,\n      graph_updates=graph_updates,\n    )\n\n  f_unbound, _, was_bound = _resolve_bound_callable(f)\n  if was_bound:\n    _raise_bound_method_error('vjp')\n\n  if not primals:\n    return functools.partial(  # type: ignore[return-value]\n      vjp,\n      f,\n      has_aux=has_aux,\n      graph=graph,\n      graph_updates=graph_updates,\n    )\n\n  if graph:\n    primals = extract.to_tree2(primals)\n  extract.check_no_aliases('vjp', primals=primals)\n  primals_out, vjp_fn, aux = jax.vjp(\n    SimpleVjpFn(f_unbound, has_aux=has_aux, graph=graph),\n    *primals,\n    has_aux=True,\n  )\n  if has_aux:\n    updates, user_aux = aux\n  else:\n    updates = aux\n    user_aux = None\n  if graph:\n    primals_out = extract.from_tree2(primals_out)\n    raw_vjp_fn = vjp_fn\n    def vjp_fn(g):\n      return extract.from_tree2(raw_vjp_fn(g))\n  extract.apply_variable_updates(primals, updates)\n  if has_aux:\n    return primals_out, vjp_fn, user_aux\n  else:\n    return primals_out, vjp_fn\n\n\n# -----------------------------------------------\n# jvp\n# -----------------------------------------------\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleJvpFn:\n  f: tp.Callable[..., tp.Any]\n  has_aux: bool\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args):\n    updates, snapshot = extract.updates_and_snapshot(args)\n    if self.graph:\n      args = extract.from_tree2(args)\n    out = self.f(*args)\n    if self.graph:\n      out = extract.to_tree2(out)\n    extract.check_no_aliases('jvp', args=updates, out=out)\n    updates = extract.mask_variable_updates(updates, snapshot)\n    if self.has_aux:\n      primals_out, aux = out\n      return (primals_out, updates), aux\n    else:\n      return out, updates\n\n\n@tp.overload\ndef jvp(\n  f: tp.Callable[..., tp.Any],\n  primals: tuple[tp.Any, ...],\n  tangents: tuple[tp.Any, ...],\n  *,\n  has_aux: bool = False,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tuple[tp.Any, ...]: ...\n@tp.overload\ndef jvp(\n  *,\n  has_aux: bool = False,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tp.Callable[[tp.Callable[..., tp.Any]], tp.Callable[..., tp.Any]]: ...\n@tp.overload\ndef jvp(\n  f: tp.Callable[..., tp.Any],\n  *,\n  has_aux: bool = False,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tp.Callable[..., tp.Any]: ...\ndef jvp(\n  f: tp.Callable[..., tp.Any] | Missing = MISSING,\n  primals: tuple[tp.Any, ...] | Missing = MISSING,\n  tangents: tuple[tp.Any, ...] | Missing = MISSING,\n  *,\n  has_aux: bool = False,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> (\n  tuple[tp.Any, ...]\n  | tp.Callable[..., tp.Any]\n  | tp.Callable[[tp.Callable[..., tp.Any]], tp.Callable[..., tp.Any]]\n):\n  \"\"\"Stateful version of ``jax.jvp`` that propagates NNX Variable updates.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n    ...\n    >>> m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    >>> x = jnp.ones((1, 2))\n    ...\n    >>> def f(m, x):\n    ...   return jnp.sum(m(x))\n    ...\n    >>> m_tangent = jax.tree.map(jnp.zeros_like, m)\n    >>> x_tangent = jnp.ones_like(x)\n    >>> primals_out, tangent_out = nnx.jvp(\n    ...   f, (m, x), (m_tangent, x_tangent), graph=False\n    ... )\n\n  Can also be used as a decorator::\n\n    >>> @nnx.jvp(graph=False)\n    ... def f(m, x):\n    ...   return jnp.sum(m(x))\n    ...\n    >>> primals_out, tangent_out = f((m, x), (m_tangent, x_tangent))\n\n  Args:\n    f: Function to be differentiated. Its arguments can be arrays, scalars,\n      or pytrees containing arrays and NNX Variables.\n    primals: A tuple of primal values at which the Jacobian of ``f``\n      should be evaluated.\n    tangents: A tuple of tangent vectors, with the same structure as\n      ``primals``.\n    has_aux: Optional, bool. Indicates whether ``f`` returns a pair where the\n      first element is considered the output of the mathematical function to be\n      differentiated and the second element is auxiliary data. Default False.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references and reference semantics.\n      If ``False``, uses tree-mode which treats Modules as regular JAX\n      pytrees, avoiding the overhead of the graph protocol.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``.\n\n  Returns:\n    If ``has_aux`` is False, returns ``(primals_out, tangent_out)``.\n    If ``has_aux`` is True, returns ``(primals_out, tangent_out, aux)``.\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if graph and graph_updates:\n    raise NotImplementedError(\n      'graph-mode with graph_updates is not supported for nnx.jvp. '\n      'Set graph=False or graph_updates=False.'\n    )\n\n  if isinstance(f, Missing):\n    return functools.partial(\n      jvp,\n      has_aux=has_aux,\n      graph=graph,\n      graph_updates=graph_updates,\n    )\n\n  f_unbound, _, was_bound = _resolve_bound_callable(f)\n  if was_bound:\n    _raise_bound_method_error('jvp')\n\n  if isinstance(primals, Missing) or isinstance(tangents, Missing):\n    return functools.partial(\n      jvp,\n      f,\n      has_aux=has_aux,\n      graph=graph,\n      graph_updates=graph_updates,\n    )\n\n  if graph:\n    primals = extract.to_tree2(primals)\n    tangents = extract.to_tree2(tangents)\n  extract.check_no_aliases('jvp', primals=primals)\n  extract.check_no_aliases('jvp', tangents=tangents)\n  if has_aux:\n    (primals_out, updates), (tangent_out, _updates_tangent), aux = jax.jvp(\n      SimpleJvpFn(f_unbound, has_aux=True, graph=graph),\n      primals,\n      tangents,\n      has_aux=True,\n    )\n  else:\n    (primals_out, updates), (tangent_out, _updates_tangent) = jax.jvp(\n      SimpleJvpFn(f_unbound, has_aux=False, graph=graph),\n      primals,\n      tangents,\n    )\n  if graph:\n    primals_out = extract.from_tree2(primals_out)\n    tangent_out = extract.from_tree2(tangent_out)\n  extract.apply_variable_updates(primals, updates)\n  if has_aux:\n    return primals_out, tangent_out, aux\n  else:\n    return primals_out, tangent_out\n\n\n# -----------------------------------------------\n# custom_vjp\n# -----------------------------------------------\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleCustomVjpFn:\n  f: tp.Callable[..., tp.Any]\n  graph: bool\n  nondiff_argnums: tuple[int, ...]\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args):\n    updates, snapshot = extract.updates_and_snapshot(args)\n    if self.graph:\n      args = extract.from_tree2(args)\n    out = self.f(*args)\n    if self.graph:\n      out = extract.to_tree2(out)\n    extract.check_no_aliases('custom_vjp', args=updates, out=out)\n    diff_prefix = tuple(\n      i not in self.nondiff_argnums for i in range(len(args))\n    )\n    def keep_fn(path, diff_arg, cur, snap):\n      assert isinstance(diff_arg, bool)\n      changed = extract.variable_changed(cur, snap)\n      if diff_arg and changed:\n        raise ValueError(\n          f'Variables in differentiable argument were mutated inside '\n          f'custom_vjp at {jax.tree_util.keystr(path)}.\\n'\n          f'This is not supported when '\n          f'graph_updates=False because the gradient for the Variable '\n          f'updates would be silently dropped. Move the Variable mutation '\n          f'to a non-differentiable argument, or use graph_updates=True.'\n        )\n      return changed\n    updates = extract.mask_variable_updates(\n      updates, snapshot, prefix=diff_prefix, keep_fn=keep_fn,\n    )\n    return out, updates\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleFwdFn:\n  fwd: tp.Callable[..., tp.Any]\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.fwd, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args):\n    updates, snapshot = extract.updates_and_snapshot(args)\n    if self.graph:\n      args = extract.from_tree2(args)\n    out, residual = self.fwd(*args)\n    if self.graph:\n      out = extract.to_tree2(out)\n      residual = extract.to_tree2(residual)\n    extract.check_no_aliases('custom_vjp', args=updates, out=out)\n    updates = extract.mask_variable_updates(updates, snapshot)\n    return (out, updates), residual\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleBwdFn:\n  bwd: tp.Callable[..., tp.Any]\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.bwd, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args):\n    *nondiff, residual, (out_g, _updates_g) = args\n    if self.graph:\n      nondiff = extract.from_tree2(nondiff)\n      residual = extract.from_tree2(residual)\n    result = self.bwd(*nondiff, residual, out_g)\n    if self.graph:\n      result = extract.to_tree2(result)\n    return result\n\n\nclass SimpleCustomVjp(tp.Generic[A]):\n  def __init__(\n    self,\n    fun: tp.Callable[..., A],\n    nondiff_argnums: tuple[int, ...],\n    graph: bool,\n  ):\n    functools.update_wrapper(self, fun)\n    self.fun = fun\n    self.nondiff_argnums = nondiff_argnums\n    self.graph = graph\n    self.custom_vjp_fn = jax.custom_vjp(\n      fun=SimpleCustomVjpFn(fun, graph=graph, nondiff_argnums=nondiff_argnums),\n      nondiff_argnums=nondiff_argnums,\n    )\n\n  def __call__(\n    self, *args: tp.Any, **kwargs: tp.Any\n  ) -> A:\n    args = resolve_kwargs(self.fun, args, kwargs)\n    del kwargs\n    if self.graph:\n      prefix = tuple(\n        i not in self.nondiff_argnums for i in range(len(args))\n      )\n      args = extract.to_tree2(args, prefix=prefix)\n    extract.check_no_aliases('custom_vjp', args=args)\n    (out, updates) = self.custom_vjp_fn(*args)\n    if self.graph:\n      out = extract.from_tree2(out)\n    extract.apply_variable_updates(args, updates)\n    return out\n\n  def defvjp(\n    self,\n    fwd: tp.Callable[..., tuple[A, tp.Any]],\n    bwd: tp.Callable[..., tuple[tp.Any, ...]],\n    symbolic_zeros: bool = False,\n  ) -> None:\n    self.fwd = fwd\n    self.bwd = bwd\n    self.symbolic_zeros = symbolic_zeros\n    self.custom_vjp_fn.defvjp(\n      fwd=SimpleFwdFn(fwd, graph=self.graph),\n      bwd=SimpleBwdFn(bwd, graph=self.graph),\n      symbolic_zeros=symbolic_zeros,\n    )\n\n\n# custom_vjp is one of the most complicated transforms as it requires\n# to handle 4 different functions:\n# 1. CustomVJP: the main object that runs the outer logic, converts input graph nodes\n#    to pytrees and output pytrees to graph nodes.\n# 2. CustomVjpFnWrapper: function that wraps the user's function, it converts\n#    its input pytrees to graph nodes and output graph nodes to pytrees.\n# 3. FwdFn: wraps the user's fwd function, it converts its input pytrees to graph nodes\n#    and output graph nodes to pytrees. Since it might run by itself in a separate context,\n#    it needs to be aware if the update_context is active or not in order to update the outer\n#    referenes.\n# 4. BwdFn: wraps the user's bwd function, it converts its input pytrees to graph nodes\n#    and output graph nodes to pytrees. It doesn't need to be aware of the outer context\n#    since it will never update the outer references as it runs during the backward pass.\n\ndef _custom_vjp_merge_fn(\n  ctx: graphlib.MergeContext,\n  path,\n  prefix: bool | DiffState,\n  value: extract.NodeStates,\n  *,\n  nondiff_states: deque[extract.GraphDefState],\n):\n  nondiff = nondiff_states.popleft()\n  return ctx.merge(nondiff.graphdef, value.state, nondiff.state)\n\n\ndef _custom_vjp_split_fn(\n  ctx: graphlib.SplitContext,\n  path,\n  prefix: bool | DiffState,\n  value,\n  *,\n  nondiff_states: list[extract.GraphDefState],\n):\n  broadcast: graphlib.GraphState\n  if prefix is False:\n    # pure non-differentiable arg, not supported\n    raise TypeError(\n      'Passing integers to nondiff_argnums for graph nodes arguments in custom_vjp is not supported. '\n      f'Got {prefix} at path {jax.tree_util.keystr(path)} for value {value}'\n    )\n  elif prefix is True:\n    # pure differentiable arg, we pass all the state through\n    # but we return a TreeNode.from_states which doesn't have a graphdef\n    # in order to keep the gradients clean from any metadata\n    graphdef, passed = ctx.split(value)\n    broadcast = State({})\n    nondiff_states.append(extract.GraphDefState(graphdef, broadcast))\n    return extract.NodeStates.from_states(passed)\n  else:\n    # differentiable arg with DiffState filter, we use the filter to split the state\n    # as before we return a TreeNode.from_states to keep the gradients clean\n    # from any metadata, the non-differentiable state is stored in a deque\n    # which is broadcasted during the forward pass\n    graphdef, passed, broadcast = ctx.split(value, prefix.filter, ...)  # type: ignore[misc]\n    nondiff_states.append(extract.GraphDefState(graphdef, broadcast))\n    return extract.NodeStates.from_states(passed)\n\n\n  nondiff_argnums: tuple[int, ...] = struct.field(pytree_node=False)\n  tangent_tree_node_args: tuple[tp.Any, ...] = struct.field(pytree_node=False)\n\ndef _extract_nodedefs(x, *, nodedefs: deque[graphlib.GraphDef]):\n  if isinstance(x, graphlib.GraphDef):\n    nodedefs.append(x)\n    return x.with_no_outer_index()\n  return x\n\n@dataclasses.dataclass(eq=False)\nclass CustomVjpFnWrapper:\n  f: tp.Callable[..., tp.Any]\n  jax_nondiff_argnums: tuple[int, ...]\n  ctxtag: str\n  nondiff_states: list[extract.GraphDefState]\n  nodedefs: deque[graphlib.GraphDef]\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f)\n\n  def __call__(self, *pure_args):\n    nondiff_states = deque(self.nondiff_states)\n    args = extract.from_tree(\n      pure_args,\n      merge_fn=functools.partial(\n        _custom_vjp_merge_fn, nondiff_states=nondiff_states\n      ),\n      ctxtag=self.ctxtag,\n      is_inner=True,\n    )\n\n    out = self.f(*args)\n\n    # remove nondiff from pure_args_out_g\n    args_out = tuple(\n      x for i, x in enumerate(args) if i not in self.jax_nondiff_argnums\n    )\n    args_out = extract.clear_non_graph_nodes(args_out)\n    pure_args_out, pure_out = extract.to_tree(\n      (args_out, out), ctxtag=self.ctxtag\n    )\n    # remove outer_index from GraphDef's but store them in global context\n\n    pure_args_out, pure_out = jax.tree.map(\n      functools.partial(_extract_nodedefs, nodedefs=self.nodedefs),\n      (pure_args_out, pure_out),\n      is_leaf=lambda x: isinstance(x, graphlib.GraphDef),\n    )\n\n    return pure_args_out, pure_out\n\n\n@dataclasses.dataclass(eq=False)\nclass FwdFn:\n  fwd: tp.Callable[..., tp.Any]\n  nondiff_argnums: tuple[int, ...]\n  ctxtag: str\n  nondiff_states: list[extract.GraphDefState]\n  nodedefs: deque[graphlib.GraphDef]\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.fwd)\n\n  def __call__(self, *pure_args):\n    # here we need to be aware if the update_context is active or not\n    # when its not active, index_mappings will be None\n    # when its active, we will remove the index_mappings from the GraphDef's and store them\n    # in the index_mappings deque created by CustomVjp\n    update_context_active = (\n      self.ctxtag in graphlib.GRAPH_CONTEXT.update_context_stacks\n    )\n    nondiff_states = deque(self.nondiff_states)\n    args = extract.from_tree(\n      pure_args,\n      merge_fn=functools.partial(\n        _custom_vjp_merge_fn, nondiff_states=nondiff_states\n      ),\n      ctxtag=self.ctxtag if update_context_active else None,\n      is_inner=True,\n    )\n\n    out, residual = self.fwd(*args)\n\n    # remove nondiff from pure_args_out_g\n    args_out = tuple(\n      x for i, x in enumerate(args) if i not in self.nondiff_argnums\n    )\n    args_out = extract.clear_non_graph_nodes(args_out)\n    pure_args_out, pure_out = extract.to_tree(\n      (args_out, out),\n      ctxtag=self.ctxtag if update_context_active else None,\n    )\n    pure_residual = extract.to_tree(residual)\n\n    if update_context_active:\n      # remove outer_index from GraphDef's but store them in global context\n      pure_args_out, pure_out = jax.tree.map(\n        functools.partial(_extract_nodedefs, nodedefs=self.nodedefs),\n        (pure_args_out, pure_out),\n        is_leaf=lambda x: isinstance(x, graphlib.GraphDef),\n      )\n\n    return (pure_args_out, pure_out), pure_residual\n\n\n@dataclasses.dataclass(eq=False)\nclass BwdFn:\n  bwd: tp.Callable[..., tp.Any]\n  tree_node_args: tuple[tp.Any, ...]\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.bwd)\n\n  def __call__(self, *args):\n    *nondiff, pure_residual, (pure_args_out_g, pure_out_g) = args\n    residual = extract.from_tree(pure_residual, is_inner=True)\n    (pure_args_out_g, pure_out_g) = jax.tree.map(\n      lambda x: x.state if isinstance(x, extract.NodeStates) else x,\n      (pure_args_out_g, pure_out_g),\n      is_leaf=lambda x: isinstance(x, extract.NodeStates),\n    )\n\n    tangent = self.bwd(*nondiff, residual, (pure_args_out_g, pure_out_g))\n\n    def state_to_node_states(is_differentiable: bool, x):\n      if is_differentiable:\n        if isinstance(x, jax.Array):\n          return x\n        elif not isinstance(x, State | variablelib.Variable):\n          raise ValueError(f'Expected State or Variable, got {type(x)}')\n        return extract.NodeStates.from_states(x)\n      return x\n\n    pure_tangent = jax.tree.map(\n      state_to_node_states,\n      self.tree_node_args,\n      tangent,\n      is_leaf=lambda x: isinstance(x, State | variablelib.Variable),\n    )\n    return pure_tangent\n\n\nclass CustomVjp(tp.Generic[A]):\n  def __init__(\n    self,\n    fun: tp.Callable[..., A],\n    nondiff_argnums: tuple[int | DiffState, ...],\n  ):\n    functools.update_wrapper(self, fun)\n    # first argument is metadata\n    self.jax_nondiff_argnums = tuple(\n      x for x in nondiff_argnums if isinstance(x, int)\n    )\n    self.ctxtag = f'custom_vjp_{fun.__name__}_{id(fun)}'\n    self.fun = fun\n    self.fwd: tp.Callable | None = None\n    self.bwd: tp.Callable | None = None\n    self.symbolic_zeros: bool | None = None\n    self.nondiff_argnums = nondiff_argnums\n    self.diff_filter: dict[int, tp.Literal[False] | DiffState] = {}\n    for argnum in self.nondiff_argnums:\n      index = argnum.argnum if isinstance(argnum, DiffState) else argnum\n      if index in self.diff_filter:\n        raise ValueError(f'argnum {index} is repeated in nondiff_argnums')\n      self.diff_filter[index] = (\n        dataclasses.replace(argnum, argnum=-1)\n        if isinstance(argnum, DiffState)\n        else False\n      )\n\n  # def __getattr__(self, name: str) -> tp.Any:\n  #   if not hasattr(self.custom_vjp_fn, name):\n  #     raise AttributeError(f'{type(self).__name__} has no attribute {name}')\n  #   return getattr(self.custom_vjp_fn, name)\n\n  def __call__(\n    self, *args: tp.Any, **kwargs: tp.Any\n  ) -> A:  # pytype: disable=invalid-annotation\n    with graphlib.update_context(self.ctxtag):\n      args = resolve_kwargs(self.fun, args, kwargs)\n      del kwargs\n      nondiff_states: list[extract.GraphDefState] = []\n      arg_filters = tuple(\n        self.diff_filter.get(i, True) for i in range(len(args))\n      )\n      pure_args = extract.to_tree(\n        args,\n        prefix=arg_filters,\n        split_fn=functools.partial(\n          _custom_vjp_split_fn, nondiff_states=nondiff_states\n        ),\n        ctxtag=self.ctxtag,\n      )\n      tree_node_args = jax.tree.map(\n        lambda x: isinstance(x, extract.NodeStates),\n        pure_args,\n        is_leaf=lambda x: isinstance(x, extract.NodeStates),\n      )\n      tree_node_args = tuple(\n        x\n        for i, x in enumerate(tree_node_args)\n        if i not in self.jax_nondiff_argnums\n      )\n      nodedefs: deque[graphlib.GraphDef] = deque()\n      if self.fwd is None or self.bwd is None or self.symbolic_zeros is None:\n        raise ValueError()\n\n      custom_vjp_fn = jax.custom_vjp(\n        fun=CustomVjpFnWrapper(\n          f=self.fun,\n          jax_nondiff_argnums=self.jax_nondiff_argnums,\n          ctxtag=self.ctxtag,\n          nondiff_states=nondiff_states,\n          nodedefs=nodedefs,\n        ),\n        nondiff_argnums=self.jax_nondiff_argnums,\n      )\n      custom_vjp_fn.defvjp(\n        fwd=FwdFn(\n          fwd=self.fwd,\n          nondiff_argnums=self.jax_nondiff_argnums,\n          ctxtag=self.ctxtag,\n          nondiff_states=nondiff_states,\n          nodedefs=nodedefs,\n        ),\n        bwd=BwdFn(\n          bwd=self.bwd,\n          tree_node_args=tree_node_args,\n        ),\n        symbolic_zeros=self.symbolic_zeros,\n      )\n      pure_args_out, pure_out = custom_vjp_fn(*pure_args)\n\n      # insert index_mappings\n      def _insert_index_mappings(x):\n        if isinstance(x, graphlib.GraphDef):\n          nodedef = nodedefs.popleft()\n          return nodedef\n        return x\n\n      pure_args_out, pure_out = jax.tree_util.tree_map(\n        _insert_index_mappings,\n        (pure_args_out, pure_out),\n        is_leaf=lambda x: isinstance(x, graphlib.GraphDef),\n      )\n\n      args_out, out = extract.from_tree(\n        (pure_args_out, pure_out), ctxtag=self.ctxtag, is_inner=False\n      )\n\n      return out\n\n  def defvjp(\n    self,\n    fwd: tp.Callable[..., tuple[A, tp.Any]],\n    bwd: tp.Callable[..., tuple[tp.Any, ...]],\n    symbolic_zeros: bool = False,\n  ) -> None:\n    self.fwd = fwd\n    self.bwd = bwd\n    self.symbolic_zeros = symbolic_zeros\n\n\n@tp.overload\ndef custom_vjp(\n  fun: tp.Callable[..., A],\n  *,\n  nondiff_argnums: tuple[int | DiffState, ...] = (),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> CustomVjp[A] | SimpleCustomVjp[A]: ...\n@tp.overload\ndef custom_vjp(\n  *,\n  nondiff_argnums: tuple[int | DiffState, ...] = (),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tp.Callable[[tp.Callable[..., A]], CustomVjp[A] | SimpleCustomVjp[A]]: ...\ndef custom_vjp(\n  fun: tp.Callable[..., A] | Missing = MISSING,\n  *,\n  nondiff_argnums: tuple[int | DiffState, ...] = (),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> CustomVjp[A] | SimpleCustomVjp[A] | tp.Callable[[tp.Callable[..., A]], CustomVjp[A] | SimpleCustomVjp[A]]:\n  \"\"\"Reference aware version of\n  `jax.custom_vjp <https://jax.readthedocs.io/en/latest/_autosummary/jax.custom_vjp.html>`__.\n\n  ``nnx.custom_vjp`` accepts Modules and other Flax NNX objects as arguments. The main difference\n  with the JAX version is that, because Modules follow reference semantics, they propagate the State\n  updates for the inputs as auxiliary outputs. This means that the incoming gradients in the ``bwd`` function\n  will have the form ``(input_updates_g, out_g)`` where ``input_updates_g`` is the gradient updated state of\n  the inputs w.r.t. to the inputs. All Module terms on the inputs will an associated ``State`` term in\n  ``input_updates_g``, while all non-Module terms will appear as None. The shape of the tangent will be\n  expected to have the same shape as the input, with ``State`` terms in place of the corresponding Module terms.\n\n  Example::\n\n    >>> import jax\n    >>> import jax.numpy as jnp\n    >>> from flax import nnx\n    ...\n    >>> class Foo(nnx.Module):\n    ...   def __init__(self, x, y):\n    ...     self.x = nnx.Param(x)\n    ...     self.y = nnx.Param(y)\n    ...\n    >>> @nnx.custom_vjp\n    ... def f(m: Foo):\n    ...   return jnp.sin(m.x) * m.y\n    ...\n    >>> def f_fwd(m: Foo):\n    ...   return f(m), (jnp.cos(m.x), jnp.sin(m.x), m)\n    ...\n    >>> def f_bwd(res, g):\n    ...   input_updates_g, out_g = g\n    ...   cos_x, sin_x, m = res\n    ...   (m_updates_g,) = input_updates_g\n    ...   m_g = jax.tree.map(lambda x: x, m_updates_g) # create copy\n    ...\n    ...   m_g['x'][...] = cos_x * out_g * m.y\n    ...   m_g['y'][...] = sin_x * out_g\n    ...   return (m_g,)\n    ...\n    >>> f.defvjp(f_fwd, f_bwd)\n    ...\n    >>> m = Foo(x=jnp.array(1.), y=jnp.array(2.))\n    >>> grads = nnx.grad(f)(m)\n    ...\n    >>> jax.tree.map(jnp.shape, grads)\n    State({\n      'x': Param(\n        value=()\n      ),\n      'y': Param(\n        value=()\n      )\n    })\n\n  Note that the State objects that represent Module terms on ``input_updates_g`` have the\n  same shape as the State objects expected in the output tanget. This means that you can\n  usually just copy them from ``input_updates_g`` and update them with their corresponding\n  gradient values.\n\n  You can select which substates are differentiable (have a tangent) for Modules and other\n  graph nodes by passing a ``DiffState`` to ``nondiff_argnums``. For example, if you want to\n  differentiate only the ``x`` attribute of the ``Foo`` class, you can do the following::\n\n    >>> x_attribute = nnx.PathContains('x')\n    >>> diff_state = nnx.DiffState(0, x_attribute)\n    ...\n    >>> @nnx.custom_vjp(nondiff_argnums=(diff_state,))\n    ... def f(m: Foo):\n    ...   return jnp.sin(m.x) * m.y  # type: ignore\n\n    >>> def f_fwd(m: Foo):\n    ...   y = f(m)\n    ...   res = (jnp.cos(m.x), m)  # type: ignore\n    ...   return y, res\n    ...\n    >>> def f_bwd(res, g):\n    ...   input_updates_g, out_g = g\n    ...   cos_x, m = res\n    ...   (m_updates_g,) = input_updates_g\n    ...   m_g = jax.tree.map(lambda x: x, m_updates_g) # create copy\n    ...\n    ...   m_g.x[...] = cos_x * out_g * m.y\n    ...   del m_g['y'] # y is not differentiable\n    ...   return (m_g,)\n\n    >>> f.defvjp(f_fwd, f_bwd)\n    ...\n    >>> m = Foo(x=jnp.array(1.), y=jnp.array(2.))\n    >>> grad = nnx.grad(f, argnums=nnx.DiffState(0, x_attribute))(m)\n    ...\n    >>> jax.tree.map(jnp.shape, grad)\n    State({\n      'x': Param(\n        value=()\n      )\n    })\n\n  Note that ``grad`` cannot calculate gradients for states that don't have a tangent\n  defined by ``custom_vjp``, in the example above we reuse the same ``x_attribute``\n  filter to keep ``custom_vjp`` and ``grad`` in sync.\n\n  **graph_updates=False**\n\n  When ``graph_updates=False`` or ``graph=False``, the behavior is closer to\n  ``jax.custom_vjp``: the ``bwd`` function receives ``out_g`` directly, and\n  tangent types are the same as the input types, this means the tangent for a\n  Module is a Module instance with gradient values set on its attributes.\n  This mode does not support ``DiffState`` in ``nondiff_argnums``. Additionally,\n  Variables in differentiable arguments cannot be mutated inside ``f``. If\n  mutations are needed, pass the relevant Variables through a non-differentiable\n  argument instead.\n\n  Example::\n\n    >>> @nnx.custom_vjp(graph_updates=False)\n    ... def f(m: Foo):\n    ...   return jnp.sin(m.x) * m.y\n    ...\n    >>> def f_fwd(m: Foo):\n    ...   return f(m), (jnp.cos(m.x), jnp.sin(m.x), m)\n    ...\n    >>> def f_bwd(res, g):\n    ...   cos_x, sin_x, m = res\n    ...   m_g = nnx.clone(m)\n    ...   m_g.x[...] = cos_x * g * m.y\n    ...   m_g.y[...] = sin_x * g\n    ...   return (m_g,)\n    ...\n    >>> f.defvjp(f_fwd, f_bwd)\n\n  Args:\n    fun: Callable base function.\n    nondiff_argnums: Tuple of integers or DiffState objects specifying the\n      argument indices that are not differentiated. By default all arguments are\n      differentiated. Integers cannot be used to mark graph nodes such as Modules\n      as non-differentiable, in this case use a DiffState object. DiffState objects\n      define the set of differentiable substates, contrary to what the name of this\n      argument suggests, this is done for compatibility with ``grad``.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references and reference semantics.\n      If ``False``, uses tree-mode which treats Modules as regular JAX\n      pytrees, avoiding the overhead of the graph protocol. Tree-mode does\n      not support ``DiffState`` in ``nondiff_argnums``.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``. When ``False``, using ``DiffState``\n      is not supported.\n\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if isinstance(fun, Missing):\n    return functools.partial(\n      custom_vjp, nondiff_argnums=nondiff_argnums, graph=graph,\n      graph_updates=graph_updates,\n    )\n\n  # Detect bound nnx.Module methods and raise error.\n  fun_unbound, _, was_bound = _resolve_bound_callable(fun)\n  if was_bound:\n    _raise_bound_method_error('custom_vjp')\n\n  if not graph or not graph_updates:\n    if any(isinstance(x, DiffState) for x in nondiff_argnums):\n      raise ValueError(\n        '`nondiff_argnums` cannot contain `DiffState` objects '\n        'when `graph=False`. '\n        + graphlib._tree_mode_suggestion_transform('custom_vjp')\n      )\n    return SimpleCustomVjp(fun_unbound, nondiff_argnums, graph=graph)  # type: ignore[arg-type]\n\n  return CustomVjp(fun_unbound, nondiff_argnums)\n\n\n# -------------------------------\n# remat\n# -------------------------------\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleRematFn:\n  f: tp.Callable[..., tp.Any]\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args, **kwargs):\n    updates, snapshot = extract.updates_and_snapshot((args, kwargs))\n    if self.graph:\n      args, kwargs = extract.from_tree2((args, kwargs))\n    out = self.f(*args, **kwargs)\n    if self.graph:\n      out = extract.to_tree2(out)\n    extract.check_no_aliases('remat', args=updates[0], kwargs=updates[1], out=out)\n    updates = extract.mask_variable_updates(updates, snapshot)\n    return out, updates\n\n@tp.overload\ndef remat(\n  *,\n  prevent_cse: bool = True,\n  static_argnums: int | tuple[int, ...] = (),\n  policy: tp.Callable[..., bool] | None = None,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tp.Callable[[F], F]: ...\n@tp.overload\ndef remat(\n  f: F,\n  *,\n  prevent_cse: bool = True,\n  static_argnums: int | tuple[int, ...] = (),\n  policy: tp.Callable[..., bool] | None = None,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> F: ...\ndef remat(\n  f: F | Missing = MISSING,\n  *,\n  prevent_cse: bool = True,\n  static_argnums: int | tuple[int, ...] = (),\n  policy: tp.Callable[..., bool] | None = None,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> F | tp.Callable[[F], F]:\n  \"\"\"A 'lifted' version of the\n  `jax.checkpoint <https://jax.readthedocs.io/en/latest/_autosummary/jax.checkpoint.html>`__\n  (a.k.a. ``jax.remat``).\n\n  ``flax.nnx.remat``, similar to ``jax.checkpoint`` can provide control over, for\n    example, how ``flax.nnx.grad`` values are computed and saved during the forward pass versus\n    how they are recomputed during the backward pass, trading off memory and FLOPs.\n\n  Learn more in `Flax NNX vs JAX Transformations <https://flax.readthedocs.io/en/latest/guides/jax_and_nnx_transforms.html>`_.\n\n  To learn about ``jax.remat``, go to JAX's\n    `fundamentals of jax.checkpoint <https://jax.readthedocs.io/en/latest/notebooks/autodiff_remat.html#fundamentals-of-jax-checkpoint>`_\n    and `practical notes <https://jax.readthedocs.io/en/latest/notebooks/autodiff_remat.html#practical-notes>`_.\n\n  Args:\n    f: Function to be rematerialized.\n    prevent_cse: Optional, bool. If True, prevents common subexpression\n      elimination. Default True.\n    static_argnums: Optional, int or tuple of ints. Specifies which\n      positional arguments to treat as static.\n    policy: Optional, callable. A policy for which intermediates to save\n      during the forward pass.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references and reference semantics.\n      If ``False``, uses tree-mode which treats Modules as regular JAX\n      pytrees, avoiding the overhead of the graph protocol. Tree-mode does\n      not support shared ``Variable`` references.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``.\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if isinstance(f, Missing):\n    return functools.partial(\n      remat,\n      prevent_cse=prevent_cse,\n      static_argnums=static_argnums,\n      policy=policy,\n      graph=graph,\n      graph_updates=graph_updates,\n    )  # type: ignore[return-value]\n\n  f_unbound, _, was_bound = _resolve_bound_callable(f)\n\n  if was_bound:\n    _raise_bound_method_error('remat')\n\n  if not graph or not graph_updates:\n    checkpointed_fn = jax.checkpoint(\n      SimpleRematFn(f_unbound, graph=graph),\n      prevent_cse=prevent_cse,\n      static_argnums=static_argnums,\n      policy=policy,\n    )\n\n    @functools.wraps(f_unbound)\n    def simple_remat_wrapper(*args, **kwargs):\n      if graph:\n        args, kwargs = extract.to_tree2((args, kwargs))\n      extract.check_no_aliases('remat', args=args, kwargs=kwargs)\n      out, updates = checkpointed_fn(*args, **kwargs)\n      if graph:\n        out = extract.from_tree2(out)\n      extract.apply_variable_updates((args, kwargs), updates)\n      return out\n\n    return simple_remat_wrapper  # type: ignore[return-value]\n\n  # Unbound function path: preserve the concise composition used in NNX.\n  return resolve_kwargs()(  # type: ignore[return-value]\n    graphlib.update_context('remat')(\n      general.split_inputs(\n        jax.checkpoint(\n          general.merge_inputs(f_unbound, ctxtag='remat'),\n          prevent_cse=prevent_cse,\n          static_argnums=static_argnums,\n          policy=policy,\n        ),\n        ctxtag='remat',\n      ),\n    )\n  )\n"
  },
  {
    "path": "flax/nnx/transforms/compilation.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# pytype: skip-file\nfrom __future__ import annotations\n\nimport dataclasses\nimport functools\nimport inspect\nimport operator\nimport typing as tp\n\nimport jax\nfrom jax.sharding import AbstractMesh, Mesh, PartitionSpec\n\nfrom flax.nnx import (\n  extract,\n  filterlib,\n  graphlib,\n  statelib,\n  variablelib,\n)\nfrom flax.nnx.transforms.transforms import (\n  _resolve_bound_callable,\n  _raise_bound_method_error,\n)\nfrom flax.typing import MISSING, Missing, PathParts\n\nF = tp.TypeVar('F', bound=tp.Callable[..., tp.Any])\nP = tp.ParamSpec('P')\nR = tp.TypeVar('R')\nSpecs = tp.Any\nAxisName = tp.Hashable\n\n# -------------------------------\n# jit\n# -------------------------------\n\n\nclass StateSharding(extract.PrefixMapping):\n  def __init__(\n    self,\n    filter_sharding: statelib.State\n    | tp.Mapping[filterlib.Filter, tp.Any]\n    | tp.Iterable[tuple[filterlib.Filter, tp.Any]],\n    /,\n  ):\n    if isinstance(filter_sharding, statelib.State):\n      filter_sharding = statelib.create_path_filters(filter_sharding)  # type: ignore\n\n    iterable = tuple(\n      filter_sharding.items()\n      if isinstance(filter_sharding, tp.Mapping)\n      else filter_sharding\n    )\n    self._filters = tuple(filter for filter, _ in iterable)\n    self._shardings = tuple(axis for _, axis in iterable)\n\n  @property\n  def filters(self) -> tuple[filterlib.Filter, ...]:\n    return self._filters\n\n  @property\n  def shardings(self) -> tuple[tp.Any, ...]:\n    return self._shardings\n\n  def map_prefix(\n    self, path: PathParts, variable: variablelib.Variable\n  ) -> tp.Any:\n    for filter, sharding in zip(self.filters, self.shardings):\n      predicate = filterlib.to_predicate(filter)\n      if predicate(path, variable):\n        return sharding\n    raise ValueError(f'No axis found for {path=}, {variable=}')\n\n  def __repr__(self):\n    return f'StateSharding({dict(zip(self.filters, self.shardings))})'\n\n  def __eq__(self, other):\n    return (\n      isinstance(other, StateSharding)\n      and self.filters == other.filters\n      and self.shardings == other.shardings\n    )\n\n  def __hash__(self):\n    return hash((self.filters, self.shardings))\n\n\ndef _jit_split_fn(ctx: graphlib.SplitContext, path, prefix, x):\n  if isinstance(prefix, StateSharding):\n    graphdef, *states = ctx.flatten(x, *prefix.filters)\n    return extract.NodeStates.from_split(graphdef, *states, metadata=prefix)\n  return extract.NodeStates.from_split(*ctx.flatten(x, with_paths=False))\n\n\ndef _jit_merge_fn(ctx: graphlib.MergeContext, path, prefix, leaf) -> tp.Any:\n  if not isinstance(leaf, extract.NodeStates):\n    raise ValueError(f'Expected TreeNode, got {type(leaf)} at path {path}')\n  return ctx.unflatten(leaf.graphdef, *leaf.states)\n\n\n@dataclasses.dataclass(eq=False)\nclass JitFn:\n  f: tp.Callable[..., tp.Any]\n  in_shardings: tp.Any\n  out_shardings: tp.Any\n  kwarg_shardings: tp.Any\n  ctxtag: tp.Hashable\n\n  def __post_init__(self):\n    # Prevent overwriting our ctxtag info with the child function's\n    orig_ctxtag = self.ctxtag\n    functools.update_wrapper(self, self.f, updated=())\n    self.ctxtag = orig_ctxtag\n\n  def __call__(self, *pure_args, **pure_kwargs):\n    args, kwargs = extract.from_tree(\n      (pure_args, pure_kwargs),\n      merge_fn=_jit_merge_fn,\n      ctxtag=self.ctxtag,\n      is_inner=True,\n    )\n\n    out = self.f(*args, **kwargs)\n\n    args_out, kwargs_out = extract.clear_non_graph_nodes((args, kwargs))\n    pure_args_out, pure_kwargs_out, pure_out = extract.to_tree(\n      (args_out, kwargs_out, out),\n      prefix=(self.in_shardings, self.kwarg_shardings, self.out_shardings),\n      ctxtag=self.ctxtag,\n      split_fn=_jit_split_fn,\n    )\n\n    return pure_args_out, pure_kwargs_out, pure_out\n\n\n@tp.overload\ndef jit(\n  *,\n  in_shardings: tp.Any = None,\n  out_shardings: tp.Any = None,\n  static_argnums: int | tp.Sequence[int] | None = None,\n  static_argnames: str | tp.Iterable[str] | None = None,\n  donate_argnums: int | tp.Sequence[int] | None = None,\n  donate_argnames: str | tp.Iterable[str] | None = None,\n  keep_unused: bool = False,\n  device: tp.Optional[jax.Device] = None,\n  backend: tp.Optional[str] = None,\n  inline: bool = False,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tp.Callable[[tp.Callable[P, R]], JitWrapped[P, R]]: ...\n@tp.overload\ndef jit(\n  fun: tp.Callable[P, R],\n  *,\n  in_shardings: tp.Any = None,\n  out_shardings: tp.Any = None,\n  static_argnums: int | tp.Sequence[int] | None = None,\n  static_argnames: str | tp.Iterable[str] | None = None,\n  donate_argnums: int | tp.Sequence[int] | None = None,\n  donate_argnames: str | tp.Iterable[str] | None = None,\n  keep_unused: bool = False,\n  device: tp.Optional[jax.Device] = None,\n  backend: tp.Optional[str] = None,\n  inline: bool = False,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> JitWrapped[P, R]: ...\ndef jit(\n  fun: tp.Callable[P, R] | Missing = MISSING,\n  *,\n  in_shardings: tp.Any = None,\n  out_shardings: tp.Any = None,\n  static_argnums: int | tp.Sequence[int] | None = None,\n  static_argnames: str | tp.Iterable[str] | None = None,\n  donate_argnums: int | tp.Sequence[int] | None = None,\n  donate_argnames: str | tp.Iterable[str] | None = None,\n  keep_unused: bool = False,\n  device: tp.Optional[jax.Device] = None,\n  backend: tp.Optional[str] = None,\n  inline: bool = False,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> JitWrapped[P, R] | tp.Callable[[tp.Callable[P, R]], JitWrapped[P, R]]:\n  \"\"\"\n  Lifted version of ``jax.jit`` that can handle Modules / graph nodes as\n  arguments.\n\n  .. note::\n    If jitted function has a model and an optimizer as inputs, we can\n    reduce accelerator's memory usage if we specify them in\n    ``donate_argnums`` or ``donate_argnames``:\n\n      >>> from flax import nnx\n      >>>\n      >>> @nnx.jit(donate_argnames=(\"model\", \"optimizer\"))\n      ... def func(model: nnx.Module, optimizer: nnx.Optimizer, other_args):\n      ...   pass\n\n    For details please see `this discussion <https://github.com/google/flax/issues/5026>`_.\n\n  Args:\n    fun: Function to be jitted. ``fun`` should be a pure function, as\n      side-effects may only be executed once.\n\n      The arguments and return value of ``fun`` should be arrays,\n      scalars, or (nested) standard Python containers (tuple/list/dict) thereof.\n      Positional arguments indicated by ``static_argnums`` can be anything at\n      all, provided they are hashable and have an equality operation defined.\n      Static arguments are included as part of a compilation cache key, which is\n      why hash and equality operators must be defined.\n\n      JAX keeps a weak reference to ``fun`` for use as a compilation cache key,\n      so the object ``fun`` must be weakly-referenceable. Most :class:`Callable`\n      objects will already satisfy this requirement.\n\n      .. note::\n        Bound methods (e.g., ``module.method``) are not supported. Use the\n        decorator form ``@nnx.jit`` on the method definition or call\n        ``nnx.jit(MyClass.method)(instance, ...)`` with the unbound method.\n    in_shardings: Pytree of structure matching that of arguments to ``fun``,\n      with all actual arguments replaced by resource assignment specifications.\n      It is also valid to specify a pytree prefix (e.g. one value in place of a\n      whole subtree), in which case the leaves get broadcast to all values in\n      that subtree.\n\n      The ``in_shardings`` argument is optional. JAX will infer the shardings\n      from the input :py:class:`jax.Array`'s and defaults to replicating the input\n      if the sharding cannot be inferred.\n\n      The valid resource assignment specifications are:\n        - :py:class:`Sharding`, which will decide how the value\n            will be partitioned. With this, using a mesh context manager is not\n            required.\n        - :py:obj:`None`, will give JAX the freedom to choose whatever sharding\n          it wants.\n          For in_shardings, JAX will mark is as replicated but this behavior\n          can change in the future.\n          For out_shardings, we will rely on the XLA GSPMD partitioner to\n          determine the output shardings.\n\n      The size of every dimension has to be a multiple of the total number of\n      resources assigned to it. This is similar to pjit's in_shardings.\n    out_shardings: Like ``in_shardings``, but specifies resource\n      assignment for function outputs. This is similar to pjit's\n      out_shardings.\n\n      The ``out_shardings`` argument is optional. If not specified, :py:func:`jax.jit`\n      will use GSPMD's sharding propagation to figure out what the sharding of the\n      output(s) should be.\n    static_argnums: An optional int or collection of ints that specify which\n      positional arguments to treat as static (compile-time constant).\n      Operations that only depend on static arguments will be constant-folded in\n      Python (during tracing), and so the corresponding argument values can be\n      any Python object.\n\n      Static arguments should be hashable, meaning both ``__hash__`` and\n      ``__eq__`` are implemented, and immutable. Calling the jitted function\n      with different values for these constants will trigger recompilation.\n      Arguments that are not arrays or containers thereof must be marked as\n      static.\n\n      If neither ``static_argnums`` nor ``static_argnames`` is provided, no\n      arguments are treated as static. If ``static_argnums`` is not provided but\n      ``static_argnames`` is, or vice versa, JAX uses\n      :code:`inspect.signature(fun)` to find any positional arguments that\n      correspond to ``static_argnames``\n      (or vice versa). If both ``static_argnums`` and ``static_argnames`` are\n      provided, ``inspect.signature`` is not used, and only actual\n      parameters listed in either ``static_argnums`` or ``static_argnames`` will\n      be treated as static.\n    static_argnames: An optional string or collection of strings specifying\n      which named arguments to treat as static (compile-time constant). See the\n      comment on ``static_argnums`` for details. If not\n      provided but ``static_argnums`` is set, the default is based on calling\n      ``inspect.signature(fun)`` to find corresponding named arguments.\n    donate_argnums: Specify which positional argument buffers are \"donated\" to\n      the computation. It is safe to donate argument buffers if you no longer\n      need them once the computation has finished. In some cases XLA can make\n      use of donated buffers to reduce the amount of memory needed to perform a\n      computation, for example recycling one of your input buffers to store a\n      result. You should not reuse buffers that you donate to a computation, JAX\n      will raise an error if you try to. By default, no argument buffers are\n      donated.\n\n      If neither ``donate_argnums`` nor ``donate_argnames`` is provided, no\n      arguments are donated. If ``donate_argnums`` is not provided but\n      ``donate_argnames`` is, or vice versa, JAX uses\n      :code:`inspect.signature(fun)` to find any positional arguments that\n      correspond to ``donate_argnames``\n      (or vice versa). If both ``donate_argnums`` and ``donate_argnames`` are\n      provided, ``inspect.signature`` is not used, and only actual\n      parameters listed in either ``donate_argnums`` or ``donate_argnames`` will\n      be donated.\n\n      For more details on buffer donation see the\n      `FAQ <https://jax.readthedocs.io/en/latest/faq.html#buffer-donation>`_.\n    donate_argnames: An optional string or collection of strings specifying\n      which named arguments are donated to the computation. See the\n      comment on ``donate_argnums`` for details. If not\n      provided but ``donate_argnums`` is set, the default is based on calling\n      ``inspect.signature(fun)`` to find corresponding named arguments.\n    keep_unused: If `False` (the default), arguments that JAX determines to be\n      unused by `fun` *may* be dropped from resulting compiled XLA executables.\n      Such arguments will not be transferred to the device nor provided to the\n      underlying executable. If `True`, unused arguments will not be pruned.\n    device: This is an experimental feature and the API is likely to change.\n      Optional, the Device the jitted function will run on. (Available devices\n      can be retrieved via :py:func:`jax.devices`.) The default is inherited\n      from XLA's DeviceAssignment logic and is usually to use\n      ``jax.devices()[0]``.\n    backend: This is an experimental feature and the API is likely to change.\n      Optional, a string representing the XLA backend: ``'cpu'``, ``'gpu'``, or\n      ``'tpu'``.\n    inline: Specify whether this function should be inlined into enclosing\n      jaxprs (rather than being represented as an application of the xla_call\n      primitive with its own subjaxpr). Default False.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references, reference semantics, and\n      structural changes to Modules inside the jitted function. If ``False``,\n      uses tree-mode which treats Modules as regular JAX pytrees, avoiding\n      the overhead of the graph protocol. Tree-mode is faster but does not\n      support shared ``Variable`` references or returning mutable array\n      references from the jitted function.\n\n  Returns:\n    A wrapped version of ``fun``, set up for just-in-time compilation.\n  \"\"\"\n\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if isinstance(fun, Missing):\n    return functools.partial(\n      jit,\n      in_shardings=in_shardings,\n      out_shardings=out_shardings,\n      static_argnums=static_argnums,\n      static_argnames=static_argnames,\n      donate_argnums=donate_argnums,\n      donate_argnames=donate_argnames,\n      keep_unused=keep_unused,\n      device=device,\n      backend=backend,\n      inline=inline,\n      graph=graph,\n      graph_updates=graph_updates,\n    )  # type: ignore[return-value]\n  fun_unbound, _, was_bound = _resolve_bound_callable(fun)\n  if was_bound:\n    _raise_bound_method_error('jit')\n\n  if not graph:\n    if any(isinstance(x, StateSharding) for x in jax.tree.leaves(in_shardings)):\n      raise ValueError(\n        '`in_shardings` cannot contain `StateSharding` objects '\n        'when `graph=False`. '\n        + graphlib._tree_mode_suggestion_transform('jit')\n      )\n    if any(isinstance(x, StateSharding) for x in jax.tree.leaves(out_shardings)):\n      raise ValueError(\n        '`out_shardings` cannot contain `StateSharding` objects '\n        'when `graph=False`. '\n        + graphlib._tree_mode_suggestion_transform('jit')\n      )\n\n  if graph and not graph_updates:\n    if in_shardings is not None:\n      extract.check_prefix(in_shardings, 'in_shardings', 'jit')\n    if out_shardings is not None:\n      extract.check_prefix(out_shardings, 'out_shardings', 'jit')\n\n  wrapped_cls: tp.Any\n  if graph and graph_updates:\n    wrapped_cls = JitWrapped\n  else:\n    wrapped_cls = functools.partial(SimpleJitWrapped, graph=graph)\n  return wrapped_cls(\n    fun_unbound,\n    in_shardings=in_shardings,\n    out_shardings=out_shardings,\n    static_argnums=static_argnums,\n    static_argnames=static_argnames,\n    donate_argnums=donate_argnums,\n    donate_argnames=donate_argnames,\n    keep_unused=keep_unused,\n    device=device,\n    backend=backend,\n    inline=inline,\n  )\n\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass PartialState:\n  \"\"\"Container for a pre-flattened partial argument.\n\n  Stores the pytree structure (``treedef``) as static metadata and the\n  flattened leaves as dynamic data.  Variables within the original argument\n  are kept as leaves so their values can change between calls without\n  triggering recompilation.\n  \"\"\"\n  treedef: jax.tree_util.PyTreeDef\n  leaves: list[tp.Any]\n\njax.tree_util.register_dataclass(\n  PartialState,\n  data_fields=['leaves'],\n  meta_fields=['treedef'],\n)\n\n\ndef _flatten_to_partial_state(\n    arg: tp.Any,\n    ref_index: graphlib.RefMap | None,\n) -> PartialState:\n  if ref_index is not None:\n    graphdef, flat_state = graphlib.flatten(arg, ref_index=ref_index, graph=True)\n    return PartialState(treedef=graphdef, leaves=flat_state.leaves)\n  is_leaf = lambda x: isinstance(x, variablelib.Variable)\n  leaves, treedef = jax.tree.flatten(arg, is_leaf=is_leaf)\n  return PartialState(treedef=treedef, leaves=leaves)\n\n\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleJitFn:\n  f: tp.Callable[..., tp.Any]\n  out_shardings: tp.Any\n  donate_argnums: frozenset[int]\n  donate_argnames: frozenset[str]\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args, **kwargs):\n    updates, snapshot = extract.updates_and_snapshot((args, kwargs))\n    args_updates, kwargs_updates = updates\n    args_snapshot, kwargs_snapshot = snapshot\n    if self.graph:\n      args, kwargs = extract.from_tree2((args, kwargs))\n    out = self.f(*args, **kwargs)\n    if self.graph:\n      out = extract.to_tree2(out, prefix=self.out_shardings)\n    extract.check_no_aliases('jit', args=args_updates, kwargs=kwargs_updates, out=out)\n    def donated_arg(jax_path, prefix, c, s):\n      path = graphlib.jax_to_nnx_path(jax_path)\n      return path[0] in self.donate_argnums or extract.variable_changed(c, s)\n    args_updates = extract.mask_variable_updates(\n        args_updates, args_snapshot, keep_fn=donated_arg)\n    def donated_kwarg(jax_path, prefix, c, s):\n      path = graphlib.jax_to_nnx_path(jax_path)\n      return path[0] in self.donate_argnames or extract.variable_changed(c, s)\n    kwargs_updates = extract.mask_variable_updates(\n        kwargs_updates, kwargs_snapshot, keep_fn=donated_kwarg)\n    return out, (args_updates, kwargs_updates)\n\n\nclass SimpleJitWrapped(tp.Generic[P, R]):\n\n  def __init__(\n      self,\n      fun: tp.Callable[P, R],\n      in_shardings: tp.Any,\n      out_shardings: tp.Any,\n      static_argnums: int | tp.Sequence[int] | None = None,\n      static_argnames: str | tp.Iterable[str] | None = None,\n      donate_argnums: int | tp.Sequence[int] | None = None,\n      donate_argnames: str | tp.Iterable[str] | None = None,\n      keep_unused: bool = False,\n      device: tp.Optional[jax.Device] = None,\n      backend: tp.Optional[str] = None,\n      inline: bool = False,\n      partial_args: tuple[PartialState, ...] = (),\n      graph: bool = True,\n  ):\n    functools.update_wrapper(self, fun)\n    self.fun: tp.Callable[P, R] = fun\n    self.out_shardings = out_shardings\n    self.partial_args = partial_args\n    self.graph = graph\n\n    if in_shardings is not None and isinstance(in_shardings, (tuple, list)) and (\n        static_argnums or static_argnames\n    ):\n      resolved = _resolve_argnums(fun, static_argnums, static_argnames)\n      expanded = list(in_shardings)\n      for i in sorted(resolved):\n        expanded.insert(i, None)\n      self.in_shardings = tuple(expanded)\n    else:\n      self.in_shardings = in_shardings\n\n    jit_out_shardings: tp.Any\n    if in_shardings is not None or out_shardings is not None:\n      if isinstance(in_shardings, (tuple, list)) and (\n          static_argnums or static_argnames\n      ):\n        resolved = _resolve_argnums(fun, static_argnums, static_argnames)\n        expanded = list(in_shardings)\n        for i in sorted(resolved):\n          expanded.insert(i, None)\n        out_in_shardings = tuple(expanded)\n      else:\n        out_in_shardings = in_shardings\n      jit_out_shardings = (out_shardings, (out_in_shardings, None))\n    else:\n      jit_out_shardings = None\n\n    donate_argnums_set = frozenset(\n        (donate_argnums,) if isinstance(donate_argnums, int)\n        else donate_argnums or ()\n    )\n    donate_argnames_set = frozenset(\n        (donate_argnames,) if isinstance(donate_argnames, str)\n        else donate_argnames or ()\n    )\n    self.jitted_fn = jax.jit(\n        SimpleJitFn(fun, out_shardings, donate_argnums_set, donate_argnames_set, graph),\n        in_shardings=in_shardings,\n        out_shardings=jit_out_shardings,\n        static_argnums=static_argnums,\n        static_argnames=static_argnames,\n        donate_argnums=donate_argnums,\n        donate_argnames=donate_argnames,\n        keep_unused=keep_unused,\n        device=device,\n        backend=backend,\n        inline=inline,\n    )\n\n  def _maybe_to_tree(self, args, kwargs):\n    if self.graph:\n      args, kwargs = extract.to_tree2(\n          (args, kwargs),\n          prefix=(self.in_shardings, None)\n          if self.in_shardings is not None\n          else None,\n          check_aliasing=self.in_shardings is not None,\n      )\n    return args, kwargs\n\n  def _maybe_from_tree(self, out):\n    if self.graph:\n      out = extract.from_tree2(out)\n    return out\n\n  def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:\n    args = (*self.partial_args, *args)  # type: ignore[assignment]\n    args, kwargs = self._maybe_to_tree(args, kwargs)\n    if not self.graph:  # skip check for graph mode\n      extract.check_no_aliases('jit', args=args, kwargs=kwargs)\n    out, updates = self.jitted_fn(*args, **kwargs)\n    extract.apply_variable_updates((args, kwargs), updates)\n    return self._maybe_from_tree(out)\n\n  def __get__(self, obj, objtype=None):\n    if obj is None:\n      return self\n    return functools.partial(self, obj)\n\n  def eval_shape(self, *args, **kwargs):\n    args = (*self.partial_args, *args)\n    args, kwargs = self._maybe_to_tree(args, kwargs)\n    if not self.graph:\n      extract.check_no_aliases('jit', args=args, kwargs=kwargs)\n    out, updates = self.jitted_fn.eval_shape(*args, **kwargs)\n    return self._maybe_from_tree(out)\n\n  def trace(self, *args, **kwargs):\n    args = (*self.partial_args, *args)\n    args, kwargs = self._maybe_to_tree(args, kwargs)\n    if not self.graph:\n      extract.check_no_aliases('jit', args=args, kwargs=kwargs)\n    traced = self.jitted_fn.trace(*args, **kwargs)\n    return SimpleTraced(traced, self)\n\n  def lower(self, *args, **kwargs):\n    args = (*self.partial_args, *args)\n    args, kwargs = self._maybe_to_tree(args, kwargs)\n    if not self.graph:\n      extract.check_no_aliases('jit', args=args, kwargs=kwargs)\n    lowered = self.jitted_fn.lower(*args, **kwargs)\n    return SimpleLowered(lowered, self)\ndef jit_partial(\n    fun: tp.Callable[..., R],\n    *partial_args: tp.Any,\n    in_shardings: tp.Any = None,\n    out_shardings: tp.Any = None,\n    donate_argnums: int | tp.Sequence[int] | None = None,\n    donate_argnames: str | tp.Iterable[str] | None = None,\n    keep_unused: bool = False,\n    device: tp.Optional[jax.Device] = None,\n    backend: tp.Optional[str] = None,\n    inline: bool = False,\n    graph: bool | None = None,\n    graph_updates: bool | None = None,\n) -> SimpleJitWrapped[..., R]:\n  \"\"\"JIT-compile ``fun`` with pre-flattened partial arguments.\n\n  Similar to ``nnx.cached_partial`` but designed for tree-mode\n  (``graph=False``). Each ``partial_arg`` is flattened into a\n  ``PartialState`` whose pytree structure is fixed at construction time.\n  Variable values inside partial arguments can still change between calls\n  without triggering recompilation, and any mutations to Variables are\n  propagated back to the originals after each call.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n    >>> import optax\n    ...\n    >>> x, y = jnp.ones((4, 2)), jnp.ones((4, 3))\n    >>> model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    >>> optimizer = nnx.Optimizer(model, optax.adamw(1e-3), wrt=nnx.Param)\n    ...\n    >>> def train_step(model, optimizer, x, y):\n    ...   def loss_fn(model):\n    ...     return jnp.mean((model(x) - y) ** 2)\n    ...   loss, grads = nnx.value_and_grad(loss_fn)(model)\n    ...   optimizer.update(model, grads)\n    ...   return loss\n    ...\n    >>> train_step_fn = nnx.jit_partial(train_step, model, optimizer, graph=False)\n    ...\n    >>> loss = train_step_fn(x, y)\n\n  Args:\n    fun: The function to JIT-compile.\n    *partial_args: Arguments to be pre-flattened and bound. These must\n      appear as the first positional arguments of ``fun``.\n    in_shardings: Sharding specification for inputs. When a tuple/list,\n      the first ``len(partial_args)`` entries correspond to partial\n      arguments and are broadcast against their original pytree\n      structure. A non-tuple value (e.g. a single ``PartitionSpec``)\n      is passed through directly to ``jax.jit`` and broadcast across\n      all arguments uniformly.\n    out_shardings: Like ``in_shardings``, but for function outputs.\n    donate_argnums: Positional argument indices whose buffers may be\n      donated to the computation.\n    donate_argnames: Named arguments whose buffers may be donated.\n    keep_unused: If ``True``, unused arguments are not pruned.\n    device: Optional device to run on.\n    backend: Optional backend to use.\n    inline: If ``True``, inline the function.\n    graph: If ``None``, uses the ``nnx_graph_mode`` config value.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``. When ``False``, using\n      ``StateSharding`` is not supported.\n\n  Returns:\n    A callable expecting the remaining (runtime)\n    arguments.\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if graph_updates and graph:\n    raise ValueError(\n      '`graph_updates` not supported by `jit_partial`'\n    )\n  if any(isinstance(x, StateSharding) for x in jax.tree.leaves(in_shardings)):\n    raise ValueError(\n      '`in_shardings` cannot contain `StateSharding` objects '\n      'in `jit_partial`'\n    )\n  if any(isinstance(x, StateSharding) for x in jax.tree.leaves(out_shardings)):\n    raise ValueError(\n      '`out_shardings` cannot contain `StateSharding` objects '\n      'in `jit_partial`'\n    )\n\n  is_variable = lambda x: isinstance(x, variablelib.Variable)\n  ref_index = graphlib.RefMap() if graph else None\n  flat_partial_args = tuple(\n    _flatten_to_partial_state(arg, ref_index=ref_index)\n    for arg in partial_args\n  )\n\n  jit_in_shardings: tp.Any = None\n  if in_shardings is not None and isinstance(in_shardings, (tuple, list)) and not graph:\n    num_partial = len(partial_args)\n    partial_shardings = in_shardings[:num_partial]\n    runtime_shardings = in_shardings[num_partial:]\n\n    flat_partial_shardings = []\n    for flat_arg, orig_arg, sharding in zip(\n        flat_partial_args, partial_args, partial_shardings):\n      broadcasted = extract.broadcast_prefix(\n        sharding, orig_arg,\n        prefix_is_leaf=lambda x: x is None\n          or isinstance(x, variablelib.Variable),\n        tree_is_leaf=is_variable,\n      )\n      flat_partial_shardings.append(\n        PartialState(treedef=flat_arg.treedef, leaves=broadcasted)\n      )\n    jit_in_shardings = (*flat_partial_shardings, *runtime_shardings)\n  else:\n    jit_in_shardings = in_shardings\n\n  @functools.wraps(fun)\n  def wrapped_fun(*args, **kwargs):\n    index_ref = graphlib.IndexMap() if graph else None\n    def _unflatten(arg):\n      if not isinstance(arg, PartialState):\n        return arg\n      elif graph:\n        return graphlib.unflatten(\n            arg.treedef, arg.leaves, index_ref=index_ref,\n            copy_variables=False,\n        )\n      else:\n        return jax.tree.unflatten(arg.treedef, arg.leaves)\n    args = (_unflatten(a) for a in args)\n    return fun(*args, **kwargs)\n\n  return SimpleJitWrapped(\n    wrapped_fun,\n    in_shardings=jit_in_shardings,\n    out_shardings=out_shardings,\n    donate_argnums=donate_argnums,\n    donate_argnames=donate_argnames,\n    keep_unused=keep_unused,\n    device=device,\n    backend=backend,\n    inline=inline,\n    partial_args=flat_partial_args,\n    graph=graph,\n  )\n\n\nclass JitWrapped(tp.Generic[P, R]):\n  \"\"\"A function ready to be traced, lowered, and compiled.\n\n  This protocol reflects the output of functions such as\n  ``jax.jit``. Calling it results in JIT (just-in-time) lowering,\n  compilation, and execution. It can also be explicitly lowered prior\n  to compilation, and the result compiled prior to execution.\n  \"\"\"\n\n  def __init__(\n    self,\n    fun: tp.Callable[P, R],\n    in_shardings: tp.Any,\n    out_shardings: tp.Any,\n    static_argnums: int | tp.Sequence[int] | None = None,\n    static_argnames: str | tp.Iterable[str] | None = None,\n    donate_argnums: int | tp.Sequence[int] | None = None,\n    donate_argnames: str | tp.Iterable[str] | None = None,\n    keep_unused: bool = False,\n    device: tp.Optional[jax.Device] = None,\n    backend: tp.Optional[str] = None,\n    inline: bool = False,\n  ):\n    functools.update_wrapper(self, fun)\n    self.fun: tp.Callable[P, R] = fun\n    kwarg_shardings = None\n    self.jax_in_shardings = jax.tree.map(\n      lambda x: extract.NodeStates.from_prefixes(x.shardings, metadata=x)\n      if isinstance(x, StateSharding)\n      else x,\n      in_shardings,\n    )\n    self.jax_out_shardings = jax.tree.map(\n      lambda x: extract.NodeStates.from_prefixes(x.shardings, metadata=x)\n      if isinstance(x, StateSharding)\n      else x,\n      out_shardings,\n    )\n\n    if isinstance(in_shardings, (tuple, list)) and (static_argnums or static_argnames):\n      # We should reintroduce None values into in_shardings corresponding to static arguments\n      static_argnums = _resolve_argnums(fun, static_argnums, static_argnames)\n      in_shardings = list(in_shardings)\n      for static_arg_index in sorted(static_argnums):\n        in_shardings.insert(static_arg_index, None)\n      in_shardings = tuple(in_shardings)\n\n    jax_out_in_shardings = jax.tree.map(\n      lambda x: extract.NodeStates.from_prefixes(x.shardings, metadata=x)\n      if isinstance(x, StateSharding)\n      else x,\n      in_shardings,\n    )\n\n    self.jitted_fn = jax.jit(\n      JitFn(fun, in_shardings, out_shardings, kwarg_shardings, self),\n      in_shardings=self.jax_in_shardings,\n      out_shardings=(jax_out_in_shardings, kwarg_shardings, self.jax_out_shardings),\n      static_argnums=static_argnums,\n      static_argnames=static_argnames,\n      donate_argnums=donate_argnums,\n      donate_argnames=donate_argnames,\n      keep_unused=keep_unused,\n      device=device,\n      backend=backend,\n      inline=inline,\n    )\n    self.in_shardings = in_shardings\n    self.out_shardings = out_shardings\n    self.kwarg_shardings = kwarg_shardings\n    self.static_argnums = static_argnums\n\n  # implement descriptor protocol so that we can use this as a method\n  def __get__(self, obj, objtype=None):\n    if obj is None:\n      return self\n    return functools.partial(self, obj)\n\n  def _get_pure_args_kwargs(self, args, kwargs):\n    pure_args, pure_kwargs = extract.to_tree(\n      (args, kwargs),\n      prefix=(self.in_shardings, self.kwarg_shardings)\n      if self.in_shardings is not None or self.kwarg_shardings is not None\n      else None,\n      split_fn=_jit_split_fn,\n      check_aliasing=self.in_shardings is not None\n      or self.kwarg_shardings is not None,\n      ctxtag=self,\n    )\n    return pure_args, pure_kwargs\n\n  def _get_non_pure_out(self, pure_args_out, pure_kwargs_out, pure_out, /):\n    _args_out, _kwargs_out, out = extract.from_tree(\n      (pure_args_out, pure_kwargs_out, pure_out),\n      merge_fn=_jit_merge_fn,\n      is_inner=False,\n      ctxtag=self,\n    )\n    return out\n\n  def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:\n    # run dynamic_cache_context before update_context\n    with graphlib.update_context(self):\n      pure_args, pure_kwargs = self._get_pure_args_kwargs(args, kwargs)\n      pure_args_out, pure_kwargs_out, pure_out = self.jitted_fn(\n        *pure_args, **pure_kwargs\n      )\n      out = self._get_non_pure_out(pure_args_out, pure_kwargs_out, pure_out)\n    return out\n\n  def eval_shape(self, *args, **kwargs):\n    \"\"\"See ``jax.eval_shape``.\"\"\"\n    args, kwargs = graphlib.clone((args, kwargs))\n    with graphlib.update_context(self):\n      pure_args, pure_kwargs = self._get_pure_args_kwargs(args, kwargs)\n      pure_args_out, pure_kwargs_out, pure_out = self.jitted_fn.eval_shape(\n        *pure_args, **pure_kwargs\n      )\n      out = self._get_non_pure_out(pure_args_out, pure_kwargs_out, pure_out)\n    return out\n\n  def trace(self, *args, **kwargs) -> Traced:\n    \"\"\"Trace this function explicitly for the given arguments.\n\n    A traced function is staged out of Python and translated to a jaxpr. It is\n    ready for lowering but not yet lowered.\n\n    Returns:\n      A ``Traced`` instance representing the tracing.\n    \"\"\"\n    with graphlib.update_context(self):\n      pure_args, pure_kwargs = self._get_pure_args_kwargs(args, kwargs)\n      traced = self.jitted_fn.trace(*pure_args, **pure_kwargs)\n    return Traced(traced, self)\n\n  def lower(self, *args, **kwargs) -> Lowered:\n    \"\"\"Lower this function explicitly for the given arguments.\n\n    This is a shortcut for ``self.trace(*args, **kwargs).lower()``.\n\n    A lowered function is staged out of Python and translated to a\n    compiler's input language, possibly in a backend-dependent\n    manner. It is ready for compilation but not yet compiled.\n\n    Returns:\n      A ``Lowered`` instance representing the lowering.\n    \"\"\"\n    with graphlib.update_context(self):\n      pure_args, pure_kwargs = self._get_pure_args_kwargs(args, kwargs)\n      lowered = self.jitted_fn.lower(*pure_args, **pure_kwargs)\n    return Lowered(lowered, self)\n\n\nclass Stage:\n  args_info: tp.Any  # PyTree of ArgInfo\n\n  @property\n  def _inner_obj(self) -> tp.Any:\n    raise NotImplementedError\n\n  @property\n  def in_tree(self) -> jax.tree_util.PyTreeDef:\n    return self._inner_obj.in_tree\n\n  @property\n  def in_avals(self):\n    return self._inner_obj.in_avals\n\n  @property\n  def donate_argnums(self):\n    return self._inner_obj.donate_argnums\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass Compiled(Stage):\n  \"\"\"Compiled representation of a function specialized to types/values.\n\n  A compiled computation is associated with an executable and the\n  remaining information needed to execute it. It also provides a\n  common API for querying properties of compiled computations across\n  JAX's various compilation paths and backends.\n  \"\"\"\n\n  compiled: jax.stages.Compiled\n  jit_wrapped: JitWrapped\n\n  @property\n  def _inner_obj(self):\n    return self.compiled\n\n  @property\n  def args_info(self) -> tp.Any:  # PyTree of ArgInfo\n    raise self.compiled.args_info\n\n  @staticmethod\n  def call(*args, **kwargs):\n    raise NotImplementedError\n\n  def __call__(self, *args, **kwargs):\n    with graphlib.update_context(self.jit_wrapped):\n      pure_args, pure_kwargs = self.jit_wrapped._get_pure_args_kwargs(\n        args, kwargs\n      )\n      pure_args_out, pure_kwargs_out, pure_out = self.compiled(\n        *pure_args, **pure_kwargs\n      )\n      out = self.jit_wrapped._get_non_pure_out(\n        pure_args_out, pure_kwargs_out, pure_out\n      )\n    return out\n\n  @property\n  def out_tree(self) -> jax.tree_util.PyTreeDef:\n    return self.compiled.out_tree\n\n  def as_text(self) -> str | None:\n    \"\"\"A human-readable text representation of this executable.\n\n    Intended for visualization and debugging purposes. This is not a valid nor\n    reliable serialization.\n\n    Returns ``None`` if unavailable, e.g. based on backend, compiler, or\n    runtime.\n    \"\"\"\n    return self.compiled.as_text()\n\n  def cost_analysis(self) -> tp.Any | None:\n    \"\"\"A summary of execution cost estimates.\n\n    Intended for visualization and debugging purposes. The object output by\n    this is some simple data structure that can easily be printed or serialized\n    (e.g. nested dicts, lists, and tuples with numeric leaves). However, its\n    structure can be arbitrary: it may be inconsistent across versions of JAX\n    and jaxlib, or even across invocations.\n\n    Returns ``None`` if unavailable, e.g. based on backend, compiler, or\n    runtime.\n    \"\"\"\n    return self.compiled.cost_analysis()\n\n  def memory_analysis(self) -> tp.Any | None:\n    \"\"\"A summary of estimated memory requirements.\n\n    Intended for visualization and debugging purposes. The object output by\n    this is some simple data structure that can easily be printed or serialized\n    (e.g. nested dicts, lists, and tuples with numeric leaves). However, its\n    structure can be arbitrary: it may be inconsistent across versions of JAX\n    and jaxlib, or even across invocations.\n\n    Returns ``None`` if unavailable, e.g. based on backend, compiler, or\n    runtime.\n    \"\"\"\n    return self.compiled.memory_analysis()\n\n  def runtime_executable(self) -> tp.Any | None:\n    \"\"\"An arbitrary object representation of this executable.\n\n    Intended for debugging purposes. This is not valid nor reliable\n    serialization. The output has no guarantee of consistency across\n    invocations.\n\n    Returns ``None`` if unavailable, e.g. based on backend, compiler, or\n    runtime.\n    \"\"\"\n    return self.compiled.runtime_executable()\n\n  @property\n  def input_shardings(self):  # PyTree[sharding.Sharding]\n    return self.compiled.input_shardings\n\n  @property\n  def output_shardings(self):  # PyTree[sharding.Sharding]\n    return self.compiled.output_shardings\n\n  @property\n  def input_layouts(self):\n    return self.compiled.input_formats\n\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass Lowered(Stage):\n  \"\"\"Lowering of a function specialized to argument types and values.\n\n  A lowering is a computation ready for compilation. This class\n  carries a lowering together with the remaining information needed to\n  later compile and execute it. It also provides a common API for\n  querying properties of lowered computations across JAX's various\n  lowering paths (:func:`~jax.jit`, :func:`~jax.pmap`, etc.).\n  \"\"\"\n\n  lowered: jax.stages.Lowered\n  jit_wrapped: JitWrapped\n\n  @property\n  def _inner_obj(self):\n    return self.lowered\n\n  @property\n  def args_info(self) -> tp.Any:  # PyTree of ArgInfo\n    return self.lowered.args_info\n\n  @property\n  def out_tree(self):\n    return self.lowered.out_tree\n\n  @classmethod\n  def from_flat_info(\n    cls,\n    lowering: tp.Any,  # type: ignore[name-defined]\n    in_tree: jax.tree_util.PyTreeDef,\n    in_avals,\n    donate_argnums: tuple[int, ...],\n    out_tree: jax.tree_util.PyTreeDef,\n    no_kwargs: bool = False,\n  ):\n    raise NotImplementedError\n\n  def compile(\n    self, compiler_options: jax.stages.CompilerOptions | None = None\n  ) -> Compiled:\n    \"\"\"Compile, returning a corresponding ``Compiled`` instance.\"\"\"\n    compiled = self.lowered.compile(compiler_options)\n    return Compiled(compiled, self.jit_wrapped)\n\n  def as_text(\n    self, dialect: str | None = None, *, debug_info: bool = False\n  ) -> str:\n    \"\"\"A human-readable text representation of this lowering.\n\n    Intended for visualization and debugging purposes. This need not be a valid\n    nor reliable serialization.\n    Use `jax.export` if you want reliable and portable serialization.\n\n    Args:\n      dialect: Optional string specifying a lowering dialect (e.g. \"stablehlo\",\n        or \"hlo\").\n      debug_info: Whether to include debugging information,\n        e.g., source location.\n    \"\"\"\n    return self.lowered.as_text(dialect=dialect, debug_info=debug_info)\n\n  def compiler_ir(self, dialect: str | None = None) -> tp.Any | None:\n    \"\"\"An arbitrary object representation of this lowering.\n\n    Intended for debugging purposes. This is not a valid nor reliable\n    serialization. The output has no guarantee of consistency across\n    invocations.\n    Use `jax.export` if you want reliable and portable serialization.\n\n    Returns ``None`` if unavailable, e.g. based on backend, compiler, or\n    runtime.\n\n    Args:\n      dialect: Optional string specifying a lowering dialect (e.g. \"stablehlo\",\n        or \"hlo\").\n    \"\"\"\n    return self.lowered.compiler_ir(dialect=dialect)\n\n  def cost_analysis(self) -> tp.Any | None:\n    \"\"\"A summary of execution cost estimates.\n\n    Intended for visualization and debugging purposes. The object output by\n    this is some simple data structure that can easily be printed or serialized\n    (e.g. nested dicts, lists, and tuples with numeric leaves). However, its\n    structure can be arbitrary: it may be inconsistent across versions of JAX\n    and jaxlib, or even across invocations.\n\n    Returns ``None`` if unavailable, e.g. based on backend, compiler, or\n    runtime.\n    \"\"\"\n    return self.lowered.cost_analysis()\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass Traced(Stage):\n  \"\"\"Traced form of a function specialized to argument types and values.\n\n  A traced computation is ready for lowering. This class carries the\n  traced representation with the remaining information needed to later\n  lower, compile, and execute it.\n  \"\"\"\n\n  traced: jax.stages.Traced\n  jit_wrapped: JitWrapped\n\n  @property\n  def _inner_obj(self):\n    return self.traced\n\n  @property\n  def out_info(self):\n    return self.traced.out_info\n\n  def lower(\n    self, *, lowering_platforms: tuple[str, ...] | None = None\n  ) -> Lowered:\n    \"\"\"Lower to compiler input, returning a ``Lowered`` instance.\"\"\"\n    lowered = self.traced.lower(lowering_platforms=lowering_platforms)\n    return Lowered(lowered, self.jit_wrapped)\n\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass SimpleCompiled(Stage):\n  compiled: jax.stages.Compiled\n  jit_wrapped: SimpleJitWrapped\n\n  @property\n  def _inner_obj(self):\n    return self.compiled\n\n  @property\n  def args_info(self) -> tp.Any:\n    raise self.compiled.args_info\n\n  @staticmethod\n  def call(*args, **kwargs):\n    raise NotImplementedError\n\n  def __call__(self, *args, **kwargs):\n    args = (*self.jit_wrapped.partial_args, *args)\n    args, kwargs = self.jit_wrapped._maybe_to_tree(args, kwargs)\n    if not self.jit_wrapped.graph:\n      extract.check_no_aliases('jit', args=args, kwargs=kwargs)\n    out, updates = self.compiled(*args, **kwargs)\n    extract.apply_variable_updates((args, kwargs), updates)\n    return self.jit_wrapped._maybe_from_tree(out)\n\n  @property\n  def out_tree(self) -> jax.tree_util.PyTreeDef:\n    return self.compiled.out_tree\n\n  def as_text(self) -> str | None:\n    return self.compiled.as_text()\n\n  def cost_analysis(self) -> tp.Any | None:\n    return self.compiled.cost_analysis()\n\n  def memory_analysis(self) -> tp.Any | None:\n    return self.compiled.memory_analysis()\n\n  def runtime_executable(self) -> tp.Any | None:\n    return self.compiled.runtime_executable()\n\n  @property\n  def input_shardings(self):\n    return self.compiled.input_shardings\n\n  @property\n  def output_shardings(self):\n    return self.compiled.output_shardings\n\n  @property\n  def input_layouts(self):\n    return self.compiled.input_formats\n\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass SimpleLowered(Stage):\n  lowered: jax.stages.Lowered\n  jit_wrapped: SimpleJitWrapped\n\n  @property\n  def _inner_obj(self):\n    return self.lowered\n\n  @property\n  def args_info(self) -> tp.Any:\n    return self.lowered.args_info\n\n  @property\n  def out_tree(self):\n    return self.lowered.out_tree\n\n  def compile(\n    self, compiler_options: jax.stages.CompilerOptions | None = None\n  ) -> SimpleCompiled:\n    compiled = self.lowered.compile(compiler_options)\n    return SimpleCompiled(compiled, self.jit_wrapped)\n\n  def as_text(\n    self, dialect: str | None = None, *, debug_info: bool = False\n  ) -> str:\n    return self.lowered.as_text(dialect=dialect, debug_info=debug_info)\n\n  def compiler_ir(self, dialect: str | None = None) -> tp.Any | None:\n    return self.lowered.compiler_ir(dialect=dialect)\n\n  def cost_analysis(self) -> tp.Any | None:\n    return self.lowered.cost_analysis()\n\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass SimpleTraced(Stage):\n  traced: jax.stages.Traced\n  jit_wrapped: SimpleJitWrapped\n\n  @property\n  def _inner_obj(self):\n    return self.traced\n\n  @property\n  def out_info(self):\n    return self.traced.out_info\n\n  def lower(\n    self, *, lowering_platforms: tuple[str, ...] | None = None\n  ) -> SimpleLowered:\n    lowered = self.traced.lower(lowering_platforms=lowering_platforms)\n    return SimpleLowered(lowered, self.jit_wrapped)\n# -------------------------------\n# shard_map\n# -------------------------------\n\n# TODO: create StateSpec and consider enabling a mode that does\n# not use filters during split for performance. Overall there might\n# be performance limitations for using shard_map at a top-level\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleShardMapFn:\n  f: tp.Callable[..., tp.Any]\n  graph: bool\n  out_specs: tp.Any\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args):\n    updates, snapshot = extract.updates_and_snapshot(args)\n    if self.graph:\n      args = extract.from_tree2(args)\n    out = self.f(*args)\n    if self.graph:\n      out = extract.to_tree2(out, prefix=self.out_specs)\n    extract.check_no_aliases('shard_map', args=updates, out=out)\n    updates = extract.mask_variable_updates(updates, snapshot)\n    return out, updates\n\n\n@dataclasses.dataclass(eq=False)\nclass ShardMapFn:\n  f: tp.Callable[..., tp.Any]\n  in_specs: tp.Any\n  out_specs: tp.Any\n  kwarg_specs: tp.Any\n  ctxtag: tp.Hashable\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f)\n\n  def __call__(self, *pure_args, **pure_kwargs):\n    args, kwargs = extract.from_tree(\n      (pure_args, pure_kwargs),\n      merge_fn=_jit_merge_fn,\n      ctxtag=self.ctxtag,\n      is_inner=True,\n    )\n\n    out = self.f(*args, **kwargs)\n\n    args_out, kwargs_out = extract.clear_non_graph_nodes((args, kwargs))\n    pure_args_out, pure_kwargs_out, pure_out = extract.to_tree(\n      (args_out, kwargs_out, out),\n      prefix=(self.in_specs, self.kwarg_specs, self.out_specs),\n      ctxtag=self.ctxtag,\n      split_fn=_jit_split_fn,\n    )\n\n    return pure_args_out, pure_kwargs_out, pure_out\n\n\n@tp.overload\ndef shard_map(\n    f: F,\n    *,\n    mesh: Mesh | AbstractMesh,\n    in_specs: Specs,\n    out_specs: Specs,\n    axis_names: tp.AbstractSet[AxisName] = frozenset(),\n    check_vma: bool = True,\n    graph: bool | None = None,\n    graph_updates: bool | None = None,\n) -> F:\n  ...\n\n\n@tp.overload\ndef shard_map(\n    *,\n    mesh: Mesh | AbstractMesh,\n    in_specs: Specs,\n    out_specs: Specs,\n    axis_names: tp.AbstractSet[AxisName] = frozenset(),\n    check_vma: bool = True,\n    graph: bool | None = None,\n    graph_updates: bool | None = None,\n) -> tp.Callable[[F], F]:\n  ...\n\n\ndef shard_map(\n    f: F | type[Missing] = Missing,\n    *,\n    mesh: Mesh | AbstractMesh,\n    in_specs: Specs,\n    out_specs: Specs,\n    axis_names: tp.AbstractSet[AxisName] = frozenset(),\n    check_vma: bool = True,\n    graph: bool | None = None,\n    graph_updates: bool | None = None,\n) -> F | tp.Callable[[F], F]:\n  \"\"\"\n  Lifted version of\n  `jax.shard_map <https://docs.jax.dev/en/latest/_autosummary/jax.shard_map.html>`_\n  that can handle Modules / graph nodes as arguments.\n\n  Simple data parallel example::\n\n    import jax\n    import jax.numpy as jnp\n    from flax import nnx\n    from jax.sharding import PartitionSpec as P\n\n    mesh = jax.sharding.Mesh(jax.local_devices(), ('data',))\n\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    x = jnp.ones((32, 2))\n\n    @nnx.shard_map(\n      mesh=mesh, in_specs=(P(None), P('data')), out_specs=P('data')\n    )\n    def f(m, x):\n      return m(x)\n\n    y = f(m, x)\n\n    jax.debug.visualize_array_sharding(y)\n\n  Notice that here we simply used some ``PartitionSpec`` to define the spec\n  the the whole model and data. This works for simple cases but if we need\n  to assign different ``PartitionSpec`` to different parts of the model we\n  need to use ``StateSharding`` and create some filters that allow us to target\n  specific parts of the model. Here's an example of how to do tensor parallelism\n  for a simple MLP block using ``StateSharding`` and filters::\n\n    mesh = jax.sharding.Mesh(jax.local_devices(), ('model',))\n\n    class MLP(nnx.Module):\n      def __init__(self, din, dhidden, dout, *, rngs: nnx.Rngs):\n        self.linear1 = nnx.Linear(din, dhidden, use_bias=False, rngs=rngs)\n        self.linear2 = nnx.Linear(dhidden, dout, use_bias=False, rngs=rngs)\n\n      def __call__(self, x):\n        return self.linear2(jax.nn.relu(self.linear1(x)))\n\n    m = MLP(2, 64, 3, rngs=nnx.Rngs(0))\n    x = jnp.ones((32, 2))\n\n    def path_ends_with(*path_suffix): # custom filter\n      return lambda path, value: path[-len(path_suffix):] == path_suffix\n\n    model_spec = nnx.StateSharding({\n      path_ends_with('linear1', 'kernel'): P(None, 'model'),\n      path_ends_with('linear2', 'kernel'): P('model', None),\n    })\n\n    @nnx.shard_map(mesh=mesh, in_specs=(model_spec, P(None)), out_specs=P(None))\n    def f(m, x):\n      y = m(x)\n      return jax.lax.psum(y, 'model')\n\n    y = f(m, x)\n\n    jax.debug.visualize_array_sharding(m.linear1.kernel[...])\n    jax.debug.visualize_array_sharding(m.linear2.kernel[...])\n\n\n  Alternatively, a ``State`` object with the exact PartitionSpec for each\n  state then you can be passed to ``StateSharding``::\n\n    mesh = jax.sharding.Mesh(jax.local_devices(), ('model',))\n\n    class MLP(nnx.Module):\n      def __init__(self, din, dhidden, dout, *, rngs: nnx.Rngs):\n        self.linear1 = nnx.Linear(din, dhidden, use_bias=False, rngs=rngs)\n        self.linear2 = nnx.Linear(dhidden, dout, use_bias=False, rngs=rngs)\n\n      def __call__(self, x):\n        return self.linear2(jax.nn.relu(self.linear1(x)))\n\n    m = MLP(2, 64, 3, rngs=nnx.Rngs(0))\n    x = jnp.ones((32, 2))\n\n    model_spec = nnx.State(\n      {\n        'linear1': {'kernel': P(None, 'model')},\n        'linear2': {'kernel': P('model', None)},\n      }\n    )\n\n    @nnx.shard_map(\n      mesh=mesh,\n      in_specs=(nnx.StateSharding(model_spec), P(None)),\n      out_specs=P(None),\n    )\n    def f(m, x):\n      y = m(x)\n      return jax.lax.psum(y, 'model')\n\n    y = f(m, x)\n\n    jax.debug.visualize_array_sharding(m.linear1.kernel[...])\n    jax.debug.visualize_array_sharding(m.linear2.kernel[...])\n\n  Here ``model_spec`` was created manually but you can also automate\n  this process by using ``nnx.get_partition_spec`` to automatically\n  create it for you (see\n  `Scale up on multiple devices <https://flax.readthedocs.io/en/latest/guides/flax_gspmd.html>`_\n  ).\n\n  Args:\n    f: callable to be mapped. Each application of ``f``, or \"instance\" of ``f``,\n      takes as input a shard of the mapped-over arguments and produces a shard\n      of the output.\n    mesh: a ``jax.sharding.Mesh`` representing the array of devices over which\n      to shard the data and on which to execute instances of ``f``. The names of\n      the ``Mesh`` can be used in collective communication operations in ``f``.\n      This is typically created by a utility function like\n      :func:`jax.experimental.mesh_utils.create_device_mesh`.\n    in_specs: a pytree with ``jax.sharding.PartitionSpec``or ``nnx.StateSharding``\n      (mapping substates to ``PartitionSpec``s) instances as leaves,\n      with a tree structure that is a tree prefix of the\n      args tuple to be mapped over. Similar to ``jax.sharding.NamedSharding``,\n      each ``PartitionSpec`` represents how the corresponding argument (or subtree\n      of arguments) should be sharded along the named axes of ``mesh``. In each\n      ``PartitionSpec``, mentioning a ``mesh`` axis name at a position expresses sharding\n      the corresponding argument array axis along that positional axis; not\n      mentioning an axis name expresses replication. If an argument, or argument\n      subtree, has a corresponding spec of None, that argument is not sharded.\n    out_specs: a pytree with ``jax.sharding.PartitionSpec`` or ``nnx.StateSharding``\n      (mapping substates to ``PartitionSpec``s) instances as leaves, with a tree structure\n      that is a tree prefix of the output of ``f``.\n      Each ``PartitionSpec`` represents how the corresponding output shards should be\n      concatenated. In each ``PartitionSpec``, metioning a ``mesh`` axis name at\n      a position expresses concatenation of that mesh axis's shards along the\n      corresponding positional axis. Not mentioning a ``mesh`` axis name\n      expresses a promise that the output values are equal along that mesh axis,\n      and that rather than concatenating only a single value should be produced.\n    axis_names: optional set of axis names from ``mesh`` over which the function ``f``\n      is manual. If empty, ``f``, is manual over all mesh axes.\n    check_vma: optional boolean representing whether to enable additional validity\n      checks and automatic differentiation optimizations. The validity checks concern\n      whether any mesh axis names not mentioned in ``out_specs`` are consistent with\n      how the outputs of ``f`` are replicated.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references and reference semantics.\n      If ``False``, uses tree-mode which treats Modules as regular JAX\n      pytrees, avoiding the overhead of the graph protocol. Tree-mode does\n      not support ``StateSharding`` or shared ``Variable`` references.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``. When ``False``, using\n      ``StateSharding`` is not supported.\n\n  Returns:\n    A callable that applies the input function ``f`` across data sharded according to\n    the ``mesh`` and ``in_specs``.\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if f is Missing:\n    return functools.partial(\n        shard_map,\n        mesh=mesh,\n        in_specs=in_specs,\n        out_specs=out_specs,\n        axis_names=axis_names,\n        check_vma=check_vma,\n        graph=graph,\n        graph_updates=graph_updates,\n    )  # type: ignore[return-value]\n  assert not isinstance(f, type)\n\n  f_unbound, _, was_bound = _resolve_bound_callable(f)\n  if was_bound:\n    _raise_bound_method_error('shard_map')\n\n  if not graph or not graph_updates:\n    if any(isinstance(x, StateSharding) for x in jax.tree.leaves(in_specs)):\n      raise ValueError(\n        '`in_specs` cannot contain `StateSharding` objects '\n        'when `graph=False`'\n      )\n    if any(isinstance(x, StateSharding) for x in jax.tree.leaves(out_specs)):\n      raise ValueError(\n        '`out_specs` cannot contain `StateSharding` objects '\n        'when `graph=False`'\n      )\n    if graph:\n      extract.check_prefix(in_specs, 'in_specs', 'shard_map')\n      extract.check_prefix(out_specs, 'out_specs', 'shard_map')\n\n    shard_map_fn = jax.shard_map(\n        SimpleShardMapFn(f_unbound, graph=graph, out_specs=out_specs),\n        mesh=mesh,\n        in_specs=in_specs,\n        out_specs=(out_specs, in_specs),\n        axis_names=axis_names,\n        check_vma=check_vma,\n    )\n\n    @functools.wraps(f)\n    def shard_map_wrapper(*args, **kwargs):\n      if graph:\n        args = extract.to_tree2(\n            args,\n            prefix=in_specs,\n            check_aliasing=in_specs is not None,\n        )\n      extract.check_no_aliases('shard_map', args=args)\n      out, updates = shard_map_fn(*args, **kwargs)\n      extract.apply_variable_updates(args, updates)\n      if graph:\n        out = extract.from_tree2(out)\n      return out\n\n    shard_map_wrapper.inner = shard_map_fn  # type: ignore\n    return shard_map_wrapper  # type: ignore\n\n  kwarg_specs = PartitionSpec()\n  jax_in_specs = jax.tree.map(\n    lambda x: extract.NodeStates(\n      _graphdef=PartitionSpec(),  # type: ignore[arg-type]\n      states=x.shardings,\n      metadata=x,\n    )\n    if isinstance(x, StateSharding)\n    else x,\n    in_specs,\n  )\n  jax_out_specs = jax.tree.map(\n    lambda x: extract.NodeStates(\n      _graphdef=PartitionSpec(),  # type: ignore[arg-type]\n      states=x.shardings,\n      metadata=x,\n    )\n    if isinstance(x, StateSharding)\n    else x,\n    out_specs,\n  )\n\n  @functools.wraps(f)  # type: ignore[no-redef]\n  def shard_map_wrapper(*args, **kwargs):\n    with graphlib.update_context(shard_map_wrapper):\n      pure_args, pure_kwargs = extract.to_tree(\n        (args, kwargs),\n        prefix=(in_specs, kwarg_specs)\n        if in_specs is not None or kwarg_specs is not None\n        else None,\n        split_fn=_jit_split_fn,\n        check_aliasing=in_specs is not None or kwarg_specs is not None,\n        ctxtag=shard_map_wrapper,\n      )\n      pure_args_out, pure_kwargs_out, pure_out = shard_map_fn(\n        *pure_args, **pure_kwargs\n      )\n      _args_out, _kwargs_out, out = extract.from_tree(\n        (pure_args_out, pure_kwargs_out, pure_out),\n        merge_fn=_jit_merge_fn,\n        is_inner=False,\n        ctxtag=shard_map_wrapper,\n      )\n    return out\n\n  shard_map_fn = jax.shard_map(\n      ShardMapFn(f_unbound, in_specs, out_specs, kwarg_specs, shard_map_wrapper),\n      mesh=mesh,\n      in_specs=jax_in_specs,\n      out_specs=(jax_in_specs, kwarg_specs, jax_out_specs),  # type: ignore\n      axis_names=axis_names,\n      check_vma=check_vma,\n  )\n\n  shard_map_wrapper.inner = shard_map_fn  # type: ignore\n\n  return shard_map_wrapper  # type: ignore\n\n\n# We can't use private methods from jax._src.api_util\n# We copy the function: api_util.fun_signature\ndef _fun_signature(fun: tp.Callable) -> inspect.Signature | None:\n  try:\n    return inspect.signature(fun)\n  except (ValueError, TypeError):\n    return None\n\n# Adapted copy of private jax function from api_util: fun_signature\ndef _resolve_argnums(\n    fun: tp.Callable,\n    static_argnums: int | tp.Sequence[int] | None,\n    static_argnames: str | tp.Iterable[str] | None,\n) -> tuple[int, ...]:\n  def _ensure_index_tuple(x: tp.Any) -> tuple[int, ...]:\n    \"\"\"Convert x to a tuple of indices.\"\"\"\n    try:\n      return (operator.index(x),)\n    except TypeError:\n      return tuple(map(operator.index, x))\n\n  def _ensure_str(x: str) -> str:\n    if not isinstance(x, str):\n      raise TypeError(f\"argument is not a string: {x}\")\n    return x\n\n  def _ensure_str_tuple(x: str | tp.Iterable[str]) -> tuple[str, ...]:\n    \"\"\"Convert x to a tuple of strings.\"\"\"\n    if isinstance(x, str):\n      return (x,)\n    else:\n      return tuple(map(_ensure_str, x))\n\n  signature = _fun_signature(fun)\n  if signature is None:\n    # Some built-in functions don't support signature.\n    # See: https://github.com/python/cpython/issues/73485\n    # In this case no validation is done\n    static_argnums = () if static_argnums is None else _ensure_index_tuple(\n        static_argnums)\n  else:\n    # Infer argnums and argnames according to docstring\n    # If nums is None and names is not None, then nums are inferred from the\n    # names and vice-versa.\n    _POSITIONAL_OR_KEYWORD = inspect.Parameter.POSITIONAL_OR_KEYWORD\n    _POSITIONAL_ARGUMENTS = (\n      inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD\n    )\n\n    def infer_argnums_and_argnames(\n        sig: inspect.Signature,\n        argnums: int | tp.Iterable[int] | None,\n        argnames: str | tp.Iterable[str] | None,\n      ) -> tuple[tuple[int, ...], tuple[str, ...]]:\n      \"\"\"Infer missing argnums and argnames for a function with inspect.\"\"\"\n      if argnums is None and argnames is None:\n        return (), ()\n\n      if argnums is not None and argnames is not None:\n        argnums = _ensure_index_tuple(argnums)\n        argnames = _ensure_str_tuple(argnames)\n        return argnums, argnames\n\n      parameters = sig.parameters\n      if argnums is None:\n        assert argnames is not None\n        argnames = _ensure_str_tuple(argnames)\n        argnums = tuple(\n            i for i, (k, param) in enumerate(parameters.items())\n            if param.kind == _POSITIONAL_OR_KEYWORD and k in argnames\n        )\n      else:\n        argnums = _ensure_index_tuple(argnums)\n        argnames = tuple(\n            k for i, (k, param) in enumerate(parameters.items())\n            if param.kind == _POSITIONAL_OR_KEYWORD and i in argnums\n        )\n      return argnums, argnames\n\n    def _validate_argnums(sig: inspect.Signature, argnums: tuple[int, ...], argnums_name: str) -> None:\n      n_pos_args = 0\n      for param in sig.parameters.values():\n        if param.kind in _POSITIONAL_ARGUMENTS:\n          n_pos_args += 1\n\n        elif param.kind is inspect.Parameter.VAR_POSITIONAL:\n          # We can have any number of positional arguments\n          return\n\n      if argnums and (-min(argnums) > n_pos_args or max(argnums) >= n_pos_args):\n        raise ValueError(f\"Jitted function has {argnums_name}={argnums}, \"\n                        f\"but only accepts {n_pos_args} positional arguments.\")\n\n    static_argnums, static_argnames = infer_argnums_and_argnames(\n        signature, static_argnums, static_argnames)\n\n    # Validation\n    _validate_argnums(signature, static_argnums, \"static_argnums\")\n\n  return static_argnums\n"
  },
  {
    "path": "flax/nnx/transforms/general.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport functools\nimport typing as tp\n\nfrom flax.nnx import (\n  extract,\n  graphlib,\n)\nfrom flax.typing import MISSING, Missing\n\nA = tp.TypeVar('A')\nF = tp.TypeVar('F', bound=tp.Callable[..., tp.Any])\n\n\n# -------------------------------\n# (split|merge)_inputs\n# -------------------------------\n\n\n@tp.overload\ndef split_inputs(\n  *,\n  ctxtag: str = 'split_merge_inputs',\n) -> tp.Callable[[F], F]: ...\n@tp.overload\ndef split_inputs(\n  f: F,\n  *,\n  ctxtag: str = 'split_merge_inputs',\n) -> F: ...\ndef split_inputs(\n  f: F | Missing = MISSING,\n  *,\n  ctxtag: str = 'split_merge_inputs',\n) -> F | tp.Callable[[F], F]:\n  \"\"\"Takes in a function that contains graph nodes in the inputs and outputs, and\n  returns a function that replaces the graph nodes with some jax-compatible data\n  structures. Must be used in conjunction with :func:`merge_inputs`.\n\n  Args:\n    f: The function to be transformed.\n    ctxtag: The context tag to be used for the transformation. Defaults to\n      'split_merge_inputs'.\n\n  Returns:\n    The transformed function.\n\n  ``split_inputs`` and ``merge_inputs`` can be used to lift functions that operate\n  on jax datastructures (pytrees) to functions that operate on graph nodes. ``split_inputs``\n  will take graph nodes in the inputs and outputs and replace them with jax-compatible data\n  structures, usually before calling into the transformed function, while ``merge_inputs``\n  will convert the jax-compatible data structures back to graph nodes, usually inside the\n  transformed function. For common transforms like ``jax.jit`` and ``jax.vmap`` NNX will\n  provide a version that works with graph nodes, but for other transforms you can use\n  ``split_inputs`` and ``merge_inputs`` to manually lift the function.\n\n  The following example demonstrates how to use ``split_inputs`` and ``merge_inputs`` to\n  lift ``jax.jit`` to work over a silly function has a stateful operation that zeros out\n  the kernel of a linear layer::\n\n    >>> from flax import nnx\n    >>> import jax.numpy as jnp\n    >>> import jax\n    ...\n    >>> @split_inputs\n    ... @jax.jit\n    ... @merge_inputs\n    ... def forward_and_zero(model: nnx.Linear, x: jax.Array):\n    ...   y = model(x)\n    ...   model.kernel[...] *= 0  # zero out the kernel\n    ...   return y\n    ...\n    >>> model = nnx.Linear(2, 2, rngs=nnx.Rngs(0))\n    >>> y = forward_and_zero(model, jnp.ones((1, 2)))\n    >>> y.shape\n    (1, 2)\n    >>> assert jnp.allclose(model.kernel, 0)\n\n  As shown above, not only does the lifted function work with graph nodes, but it also\n  propagates the side effects of the original function. **Note**: in practice use ``nnx.jit``\n  instead.\n\n  Splitting and merging can also be applied to multiple functions in a pipeline. The following\n  example show how to lift ``jax.lax.cond`` by using ``split_inputs`` over ``cond`` and\n  ``merge_inputs`` over the branches::\n\n    >>> model = nnx.Linear(2, 2, rngs=nnx.Rngs(0))\n    >>> x = jnp.ones((1, 2))\n    ...\n    >>> true_fn = lambda m, x: m(x)\n    >>> false_fn = lambda m, x: x + 1\n    ...\n    >>> y = split_inputs(jax.lax.cond)(\n    ...   False,\n    ...   merge_inputs(true_fn),\n    ...   merge_inputs(false_fn), # <== gets called\n    ...   model,\n    ...   x,\n    ... )\n    >>> assert jnp.allclose(y, 2)\n\n  **Lifting functions with output semantics**\n\n  ``merge_inputs`` internally returns a ``(inputs, output)`` tuple, where ``inputs`` is the\n  tuple of the input arguments with non-graph node leaves set to ``None``, and ``output`` is\n  the output of the function. This is done to propage all the state changes in the function\n  to the graph nodes outside the function. If the transform function has output semantics\n  like e.g. ``jax.vmap``'s ``out_axes``, you must account for this in the by configuring\n  the arguments accordingly::\n\n    >>> from functools import partial\n    ...\n    >>> model = nnx.Linear(2, 2, rngs=nnx.Rngs(0))\n    ...\n    >>> in_axes = (None, 0)\n    >>> out_axes = (in_axes, 0)  # <== internal output arrangement\n    ...\n    >>> @split_inputs\n    ... @partial(jax.vmap, in_axes=in_axes, out_axes=out_axes)\n    ... @merge_inputs\n    ... def forward(model: nnx.Linear, x: jax.Array):\n    ...   return model(x)\n    ...\n    >>> x = jnp.ones((10, 2))\n    >>> y = forward(model, x)\n    >>> y.shape\n    (10, 2)\n\n  .. note::\n    If the transform has a rigid output structure like ``jax.grad`` or ``jax.lax.scan``\n    then ``split_inputs`` and ``merge_inputs`` will not work. In this case, use the\n    `Functional API <https://flax.readthedocs.io/en/latest/nnx/nnx_basics.html#the-functional-api>`__.\n  \"\"\"\n  if isinstance(f, Missing):\n    return functools.partial(split_inputs, ctxtag=ctxtag)  # type: ignore[return-value]\n\n  @graphlib.update_context(ctxtag)\n  @functools.wraps(f)\n  def split_inputs_wrapper(*args):\n    pure_args = extract.to_tree(args, ctxtag=ctxtag)\n    pure_args_out, pure_out = f(*pure_args)\n    args_out, out = extract.from_tree(\n      (pure_args_out, pure_out), ctxtag=ctxtag, is_inner=False\n    )\n    return out\n\n  return split_inputs_wrapper  # type: ignore\n\n@tp.overload\ndef merge_inputs(\n  *,\n  ctxtag: str = 'split_merge_inputs',\n) -> tp.Callable[[F], F]: ...\n@tp.overload\ndef merge_inputs(\n  f: F,\n  *,\n  ctxtag: str = 'split_merge_inputs',\n) -> F: ...\ndef merge_inputs(\n  f: F | Missing = MISSING,\n  *,\n  ctxtag: str = 'split_merge_inputs',\n) -> F | tp.Callable[[F], F]:\n  \"\"\"Takes in a function that contains jax-compatible data structures in the\n  inputs and outputs, and returns a function that replaces the jax-compatible\n  data structures the corresponding graph nodes. Must be used in conjunction\n  with :func:`split_inputs`.\n\n  Args:\n    f: The function to be transformed.\n    ctxtag: The context tag to be used for the transformation. Defaults to\n      'split_merge_inputs'.\n\n  Returns:\n    The transformed function.\n\n  For more information and examples, see :func:`split_inputs`.\n  \"\"\"\n  if isinstance(f, Missing):\n    return functools.partial(merge_inputs, ctxtag=ctxtag)  # type: ignore[return-value]\n\n  @functools.wraps(f)\n  def merge_inputs_wrapper(*pure_args):\n    args = extract.from_tree(pure_args, ctxtag=ctxtag, is_inner=True)\n    out = f(*args)\n    args_out = extract.clear_non_graph_nodes(args)\n    pure_args_out, pure_out = extract.to_tree((args_out, out), ctxtag=ctxtag)\n    return pure_args_out, pure_out\n\n  return merge_inputs_wrapper  # type: ignore\n"
  },
  {
    "path": "flax/nnx/transforms/iteration.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# pytype: skip-file\n\nfrom collections import deque\nimport dataclasses\nimport functools\nimport typing as tp\n\n\nfrom flax import struct\nfrom flax import typing\nfrom flax.core.frozen_dict import FrozenDict\nfrom flax.nnx import extract, filterlib, graphlib, spmd, variablelib\nfrom flax.nnx import statelib\nfrom flax.nnx.module import Module\nfrom flax.nnx.statelib import State\nfrom flax.nnx.transforms.transforms import (\n  resolve_kwargs,\n  _resolve_bound_callable,\n  _raise_bound_method_error,\n)\nfrom flax.typing import Leaf, Missing, PytreeDeque\nimport jax\nimport jax.core\nimport jax.numpy as jnp\nimport jax.stages\nimport numpy as np\n\nA = tp.TypeVar('A')\nC = tp.TypeVar('C')\nB = tp.TypeVar('B')\nF = tp.TypeVar('F', bound=tp.Callable[..., tp.Any])\nG = tp.TypeVar('G', bound=tp.Callable[..., tp.Any])\nM = tp.TypeVar('M', bound=Module)\nMA = tp.TypeVar('MA', bound=Module)\nN = tp.TypeVar('N', bound=Module)\nT = tp.TypeVar('T')\nStrInt = tp.TypeVar('StrInt', str, int)\nAxisName = tp.Hashable\nLeaves = list[Leaf]\nIndex = int\n\n\nclass Carry:\n  \"\"\"Helper class for :func:`flax.nnx.scan` function to mark input and output axis as carry.\n  \"\"\"\n  pass\n\n\n# -------------------------------\n# transform_metadata\n# -------------------------------\ndef _apply_axis_fn(\n    tree: tp.Any,\n    axes: tp.Any,\n    metadata: tp.Mapping[str, tp.Any],\n    axis_fn: tp.Callable[..., tp.Any],\n) -> None:\n  is_leaf = lambda x: x is None or isinstance(x, variablelib.Variable)\n  _, per_leaf_axes = extract.broadcast_prefix2(axes, tree, is_leaf=is_leaf)\n  leaves = jax.tree_util.tree_leaves(tree, is_leaf=is_leaf)\n  for leaf, axis in zip(leaves, per_leaf_axes):\n    if (axis is None or isinstance(axis, int)) and isinstance(\n        leaf, variablelib.Variable\n    ):\n      axis_fn(leaf, axis, metadata)\n\n\n@tp.overload\ndef transform_metadata(\n    *,\n    in_axes: tp.Any = 0,\n    out_axes: tp.Any = 0,\n    partition: str,\n    graph: bool | None = None,\n) -> tp.Callable[[F], F]:\n  ...\n\n\n@tp.overload\ndef transform_metadata(\n    f: F,\n    *,\n    in_axes: tp.Any = 0,\n    out_axes: tp.Any = 0,\n    graph: bool | None = None,\n    partition: str,\n) -> F:\n  ...\n\n\ndef transform_metadata(\n    f: F | type[Missing] = Missing,\n    *,\n    in_axes: tp.Any = 0,\n    out_axes: tp.Any = 0,\n    graph: bool | None = None,\n    partition: str,\n) -> F | tp.Callable[[F], F]:\n  if f is Missing:\n    return functools.partial(\n        transform_metadata,\n        in_axes=in_axes,\n        out_axes=out_axes,\n        partition=partition,\n        graph=graph,\n    )  # type: ignore[return-value]\n\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n\n  metadata: tp.Mapping[str, tp.Any] = {\n      spmd.PARTITION_NAME: partition,\n  }\n  if graph:\n    extract.check_prefix(in_axes, 'in_axes', 'transform_metadata')\n    extract.check_prefix(out_axes, 'out_axes', 'transform_metadata')\n\n  @functools.wraps(f)\n  def wrapper(*in_args, **in_kwargs):\n    in_args = resolve_kwargs(f, in_args, in_kwargs)\n    if graph:\n      in_args = extract.to_tree2(in_args, prefix=in_axes)\n    extract.check_no_aliases('transform_metadata', args=in_args)\n    args = graphlib.clone(in_args, graph=graph)\n    _apply_axis_fn(args, in_axes, metadata, spmd.remove_axis)\n    updates, snapshot = extract.updates_and_snapshot(args)\n    if graph:\n      args = extract.from_tree2(args)\n    out = f(*args)\n    if graph:\n      out = extract.to_tree2(out, prefix=out_axes)\n    extract.check_no_aliases('transform_metadata', args=updates, out=out)\n    _apply_axis_fn(args, in_axes, metadata, spmd.add_axis)\n    _apply_axis_fn(out, out_axes, metadata, spmd.add_axis)\n    updates = extract.mask_variable_updates(updates, snapshot)\n    extract.apply_variable_updates(in_args, updates)\n    if graph:\n      out = extract.from_tree2(out)\n    return out\n\n  return wrapper  # type: ignore[return-value]\n\n\n# -------------------------------\n# vmap\n# -------------------------------\n\n\nclass StateAxes(extract.PrefixMapping, tp.Mapping):\n\n  def __init__(\n    self,\n    filter_axes: (\n      statelib.State\n      | tp.Mapping[filterlib.Filter, Index | type[Carry] | None]\n      | tp.Iterable[tuple[filterlib.Filter, Index | type[Carry] | None]]\n    ),\n    /,\n  ):\n    if isinstance(filter_axes, statelib.State):\n      filter_axes = statelib.create_path_filters(filter_axes)  # type: ignore\n\n    iterable = tuple(\n        filter_axes.items()\n        if isinstance(filter_axes, tp.Mapping)\n        else filter_axes\n    )\n    self._filters = tuple(filter for filter, _ in iterable)\n    self._axes = tuple(axis for _, axis in iterable)\n\n  @property\n  def filters(self) -> tuple[filterlib.Filter, ...]:\n    return self._filters\n\n  @property\n  def axes(self) -> tuple[Index | type[Carry] | None, ...]:\n    return self._axes\n\n  def map_prefix(\n    self, path: typing.PathParts, variable: variablelib.Variable\n  ) -> tp.Any:\n    for filter, axis in zip(self.filters, self.axes):\n      predicate = filterlib.to_predicate(filter)\n      if predicate(path, variable):\n        return axis\n    raise ValueError(f'No axis found for {path=}, {variable=}')\n\n  def __repr__(self):\n    return f'StateAxes({dict(self.items())})'\n\n  def items(self):\n    return zip(self.filters, self.axes)\n\n  def __getitem__(self, key):\n    return self.axes[self.filters.index(key)]\n\n  def __iter__(self):\n    return iter(self.filters)\n\n  def __len__(self):\n    return len(self.filters)\n\n  def __eq__(self, other):\n    return (\n        isinstance(other, StateAxes)\n        and self.filters == other.filters\n        and self.axes == other.axes\n    )\n\n  def __hash__(self):\n    return hash((self.filters, self.axes))\n\n\nAxisFn = tp.Callable[\n  [graphlib.GraphState | variablelib.Variable, int, tp.Mapping],\n  graphlib.GraphState | variablelib.Variable,\n]\n\n\ndef _update_variable_sharding_metadata(\n    tree, transform_metadata, axis_fn: AxisFn\n):\n  def _update_axes_fn(node_states):\n    if isinstance(node_states, extract.NodeStates) and isinstance(\n      node_states.metadata, (StateAxes, int)\n    ):\n      if isinstance(node_states.metadata, int):\n        state = node_states.state\n        assert isinstance(state, State | variablelib.Variable)\n        state = axis_fn(state, node_states.metadata, transform_metadata)\n        return node_states.replace(states=(state,))\n      else:\n        states_out: list[graphlib.GraphState | variablelib.Variable] = []\n        for state, axis in zip(node_states.states, node_states.metadata.axes):\n          assert isinstance(state, graphlib.State | variablelib.Variable)\n          if isinstance(axis, int):\n            state = axis_fn(state, axis, transform_metadata)\n          states_out.append(state)\n        return node_states.replace(states=tuple(states_out))\n    return node_states\n\n  return jax.tree.map(\n    _update_axes_fn, tree, is_leaf=lambda x: isinstance(x, extract.NodeStates)\n  )\n\n\ndef _vmap_split_fn(ctx: graphlib.SplitContext, path, prefix, x):\n  if isinstance(prefix, StateAxes):\n    return extract.NodeStates.from_split(\n      *ctx.split(x, *prefix.filters), metadata=prefix\n    )\n  return extract.NodeStates.from_split(*ctx.split(x), metadata=prefix)\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleVmapFn:\n  f: tp.Callable[..., tp.Any]\n  graph: bool\n  out_axes: tp.Any\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args, **kwargs):\n    updates, snapshot = extract.updates_and_snapshot((args, kwargs))\n    if self.graph:\n      args, kwargs = extract.from_tree2((args, kwargs))\n    out = self.f(*args, **kwargs)\n    if self.graph:\n      out = extract.to_tree2(out, prefix=self.out_axes)\n    extract.check_no_aliases('vmap', args=updates[0], kwargs=updates[1], out=out)\n    updates = extract.mask_variable_updates(updates, snapshot)\n    return out, updates\n\n\n@dataclasses.dataclass(eq=False)\nclass SimplePmapFn:\n  f: tp.Callable[..., tp.Any]\n  graph: bool\n  out_axes: tp.Any\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args, **kwargs):\n    updates, snapshot = extract.updates_and_snapshot((args, kwargs))\n    if self.graph:\n      args, kwargs = extract.from_tree2((args, kwargs))\n    out = self.f(*args, **kwargs)\n    if self.graph:\n      out = extract.to_tree2(out, prefix=self.out_axes)\n    extract.check_no_aliases('pmap', args=updates[0], kwargs=updates[1], out=out)\n    updates = extract.mask_variable_updates(updates, snapshot)\n    return out, updates\n\n\n@dataclasses.dataclass(eq=False)\nclass VmapFn:\n  f: tp.Callable[..., tp.Any]\n  transform_metadata: tp.Mapping[str, tp.Any]\n  in_axes: tp.Any\n  out_axes: tp.Any\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f)\n\n  def __call__(self, *pure_args: tuple[tp.Any, ...]):\n    if spmd.PARTITION_NAME in self.transform_metadata:\n      pure_args = _update_variable_sharding_metadata(\n          pure_args, self.transform_metadata, spmd.remove_axis\n      )\n    args = extract.from_tree(pure_args, ctxtag='vmap', is_inner=True)\n\n    out = self.f(*args)\n\n    args_out = extract.clear_non_graph_nodes(args)\n    pure_args_out, pure_out = extract.to_tree(\n        (args_out, out),\n        prefix=(self.in_axes, self.out_axes),\n        split_fn=_vmap_split_fn,\n        ctxtag='vmap',\n    )\n    if spmd.PARTITION_NAME in self.transform_metadata:\n      pure_args_out, pure_out = _update_variable_sharding_metadata(\n          (pure_args_out, pure_out), self.transform_metadata, spmd.add_axis\n      )\n    return pure_args_out, pure_out\n\n\n@tp.overload\ndef vmap(\n    *,\n    in_axes: int | None | tp.Sequence[tp.Any] = 0,\n    out_axes: tp.Any = 0,\n    axis_name: AxisName | None = None,\n    axis_size: int | None = None,\n    spmd_axis_name: AxisName | tuple[AxisName, ...] | None = None,\n    # nnx specific\n    transform_metadata: tp.Mapping[str, tp.Any] = FrozenDict({}),\n    graph: bool | None = None,\n    graph_updates: bool | None = None,\n) -> tp.Callable[[F], F]:\n  ...\n\n\n@tp.overload\ndef vmap(\n    f: F,\n    *,\n    in_axes: int | None | tp.Sequence[tp.Any] = 0,\n    out_axes: tp.Any = 0,\n    axis_name: AxisName | None = None,\n    axis_size: int | None = None,\n    spmd_axis_name: AxisName | tuple[AxisName, ...] | None = None,\n    # nnx specific\n    transform_metadata: tp.Mapping[str, tp.Any] = FrozenDict({}),\n    graph: bool | None = None,\n    graph_updates: bool | None = None,\n) -> F:\n  ...\n\n\ndef vmap(\n  f: F | type[Missing] = Missing,\n  *,\n  in_axes: int | None | tp.Sequence[tp.Any] = 0,\n  out_axes: tp.Any = 0,\n  axis_name: AxisName | None = None,\n  axis_size: int | None = None,\n  spmd_axis_name: AxisName | tuple[AxisName, ...] | None = None,\n  # nnx specific\n  transform_metadata: tp.Mapping[str, tp.Any] = FrozenDict({}),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> F | tp.Callable[[F], F]:\n  \"\"\"Reference-aware version of `jax.vmap <https://jax.readthedocs.io/en/latest/_autosummary/jax.vmap.html>`__.\n\n  Args:\n    f: Function to be mapped over additional axes.\n    in_axes: An integer, None, or sequence of values specifying which input\n      array axes to map over (see `jax.vmap\n      <https://jax.readthedocs.io/en/latest/_autosummary/jax.vmap.html>`__). In\n      addition to integers and None, :class:`StateAxes`  can be used to control\n      how graph nodes like Modules are vectorized by specifying the axes to be\n      applied to substates of the graph node given a `Filter\n      <https://flax.readthedocs.io/en/latest/guides/filters_guide.html>`__.\n    out_axes: An integer, None, or pytree indicating where the mapped axis\n      should appear in the output (see `jax.vmap\n      <https://jax.readthedocs.io/en/latest/_autosummary/jax.vmap.html>`__).\n    axis_name: Optional, a hashable Python object used to identify the mapped\n      axis so that parallel collectives can be applied.\n    axis_size: Optional, an integer indicating the size of the axis to be\n      mapped. If not provided, the mapped axis size is inferred from arguments.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references and reference semantics.\n      If ``False``, uses tree-mode which treats Modules as regular JAX\n      pytrees, avoiding the overhead of the graph protocol. Tree-mode does\n      not support ``StateAxes`` or shared ``Variable`` references.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``. When ``False``, using ``StateAxes``\n      is not supported.\n\n  Returns:\n    Batched/vectorized version of ``f`` with arguments that correspond to\n    those of ``f``, but with extra array axes at positions indicated by\n    ``in_axes``, and a return value that corresponds to that of ``f``, but\n    with extra array axes at positions indicated by ``out_axes``.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> from jax import random, numpy as jnp\n    ...\n    >>> model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    >>> x = jnp.ones((5, 2))\n    ...\n    >>> @nnx.vmap(in_axes=(None, 0), out_axes=0)\n    ... def forward(model, x):\n    ...   return model(x)\n    ...\n    >>> y = forward(model, x)\n    >>> y.shape\n    (5, 3)\n\n  >>> class LinearEnsemble(nnx.Module):\n  ...   def __init__(self, num, rngs):\n  ...     self.w = nnx.Param(jax.random.uniform(rngs(), (num, 2, 3)))\n  ...\n  >>> model = LinearEnsemble(5, rngs=nnx.Rngs(0))\n  >>> x = jnp.ones((2,))\n  ...\n  >>> @nnx.vmap(in_axes=(0, None), out_axes=0)\n  ... def forward(model, x):\n  ...   return x @ model.w\n  ...\n  >>> y = forward(model, x)\n  >>> y.shape\n  (5, 3)\n\n  To control control how graph node substates are vectorized, ``StateAxes``\n  can be passed to ``in_axes`` and ``out_axes`` specifying the axes to be\n  applied to each substate given a filter. The following example shows how to\n  share the parameters between the ensemble members which keeping different\n  batch statistics and dropout random state::\n\n    >>> class Foo(nnx.Module):\n    ...   def __init__(self):\n    ...     self.a = nnx.Param(jnp.arange(4))\n    ...     self.b = nnx.BatchStat(jnp.arange(4))\n    ...\n    >>> state_axes = nnx.StateAxes({nnx.Param: 0, nnx.BatchStat: None})\n    >>> @nnx.vmap(in_axes=(state_axes,), out_axes=0)\n    ... def mul(foo):\n    ...   return foo.a * foo.b\n    ...\n    >>> foo = Foo()\n    >>> y = mul(foo)\n    >>> y\n    Array([[0, 0, 0, 0],\n           [0, 1, 2, 3],\n           [0, 2, 4, 6],\n           [0, 3, 6, 9]], dtype=int32)\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if f is Missing:\n    return functools.partial(\n        vmap,\n        in_axes=in_axes,\n        out_axes=out_axes,\n        axis_name=axis_name,\n        axis_size=axis_size,\n        spmd_axis_name=spmd_axis_name,\n        transform_metadata=transform_metadata,\n        graph=graph,\n        graph_updates=graph_updates,\n    )  # type: ignore[return-value]\n\n  f_unbound, _, was_bound = _resolve_bound_callable(f)\n  if was_bound:\n    _raise_bound_method_error('vmap')\n\n  if not graph or not graph_updates:\n    if any(isinstance(x, StateAxes) for x in jax.tree.leaves(in_axes)):\n      raise ValueError(\n        '`in_axes` cannot contain `StateAxes` objects '\n        'when `graph=False`. '\n        + graphlib._tree_mode_suggestion_transform('vmap')\n      )\n    if any(isinstance(x, StateAxes) for x in jax.tree.leaves(out_axes)):\n      raise ValueError(\n        '`out_axes` cannot contain `StateAxes` objects '\n        'when `graph=False`. '\n        + graphlib._tree_mode_suggestion_transform('vmap')\n      )\n    if graph:\n      extract.check_prefix(in_axes, 'in_axes', 'vmap')\n      extract.check_prefix(out_axes, 'out_axes', 'vmap')\n\n    vmapped_fn = jax.vmap(\n      SimpleVmapFn(f_unbound, graph=graph, out_axes=out_axes),\n      in_axes=in_axes,\n      out_axes=(out_axes, (in_axes, 0)),\n      axis_name=axis_name,\n      axis_size=axis_size,\n      spmd_axis_name=spmd_axis_name,\n    )\n\n    @functools.wraps(f_unbound)\n    def simple_vmap_wrapper(*args, **kwargs):\n      if graph:\n        args, kwargs = extract.to_tree2(\n            (args, kwargs),\n            prefix=(in_axes, None)\n            if in_axes is not None\n            else None,\n            check_aliasing=in_axes is not None,\n        )\n      extract.check_no_aliases('vmap', args=args, kwargs=kwargs)\n      out, updates = vmapped_fn(*args, **kwargs)\n      extract.apply_variable_updates((args, kwargs), updates)\n      if graph:\n        out = extract.from_tree2(out)\n      return out\n\n    return simple_vmap_wrapper  # type: ignore[return-value]\n\n\n  jax_in_axes = jax.tree.map(\n    lambda x: extract.NodeStates.from_prefixes(x.axes, metadata=x)\n    if isinstance(x, StateAxes)\n    else x,\n    in_axes,\n  )\n  jax_out_axes = jax.tree.map(\n    lambda x: extract.NodeStates.from_prefixes(x.axes, metadata=x)\n    if isinstance(x, StateAxes)\n    else x,\n    out_axes,\n  )\n  vmapped_fn = jax.vmap(  # type: ignore[assignment]\n      VmapFn(f_unbound, transform_metadata, in_axes, out_axes),\n      in_axes=jax_in_axes,\n      out_axes=(jax_in_axes, jax_out_axes),\n      axis_name=axis_name,\n      axis_size=axis_size,\n      spmd_axis_name=spmd_axis_name,\n  )\n\n  @functools.wraps(f)\n  @graphlib.update_context('vmap')\n  def vmap_wrapper(*args, **kwargs):\n    args = resolve_kwargs(f, args, kwargs)\n    pure_args = extract.to_tree(\n        args, prefix=in_axes, split_fn=_vmap_split_fn, ctxtag='vmap'\n    )\n    pure_args_out, pure_out = vmapped_fn(*pure_args)\n    _args_out, out = extract.from_tree(\n      (pure_args_out, pure_out), ctxtag='vmap', is_inner=False\n    )\n    return out\n\n  return vmap_wrapper  # type: ignore\n\n\n# -------------------------------\n# pmap\n# -------------------------------\n\n\n@dataclasses.dataclass(eq=False)\nclass PmapFn:\n  f: tp.Callable[..., tp.Any]\n  transform_metadata: tp.Mapping[str, tp.Any]\n  in_axes: tp.Any\n  out_axes: tp.Any\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f)\n\n  def __call__(self, *pure_args: tuple[tp.Any, ...]):\n    if spmd.PARTITION_NAME in self.transform_metadata:\n      pure_args = _update_variable_sharding_metadata(\n          pure_args, self.transform_metadata, spmd.remove_axis\n      )\n    args = extract.from_tree(pure_args, ctxtag='pmap', is_inner=True)\n\n    out = self.f(*args)\n\n    args_out = extract.clear_non_graph_nodes(args)\n    pure_args_out, pure_out = extract.to_tree(\n        (args_out, out),\n        prefix=(self.in_axes, self.out_axes),\n        split_fn=_vmap_split_fn,\n        ctxtag='pmap',\n    )\n    if spmd.PARTITION_NAME in self.transform_metadata:\n      pure_args_out, pure_out = _update_variable_sharding_metadata(\n          (pure_args_out, pure_out), self.transform_metadata, spmd.add_axis\n      )\n    return pure_args_out, pure_out\n\n\n@tp.overload\ndef pmap(\n    *,\n    axis_name: AxisName | None = None,\n    in_axes: tp.Any = 0,\n    out_axes: tp.Any = 0,\n    static_broadcasted_argnums: int | tp.Iterable[int] = (),\n    devices: tp.Sequence[jax.Device] | None = None,  # noqa: F811\n    backend: str | None = None,\n    axis_size: int | None = None,\n    donate_argnums: int | tp.Iterable[int] = (),\n    # nnx specific\n    transform_metadata: tp.Mapping[str, tp.Any] = FrozenDict({}),\n    graph: bool | None = None,\n    graph_updates: bool | None = None,\n) -> tp.Callable[[F], F]:\n  ...\n\n\n@tp.overload\ndef pmap(\n    f: F,\n    *,\n    axis_name: AxisName | None = None,\n    in_axes: tp.Any = 0,\n    out_axes: tp.Any = 0,\n    static_broadcasted_argnums: int | tp.Iterable[int] = (),\n    devices: tp.Sequence[jax.Device] | None = None,  # noqa: F811\n    backend: str | None = None,\n    axis_size: int | None = None,\n    donate_argnums: int | tp.Iterable[int] = (),\n    # nnx specific\n    transform_metadata: tp.Mapping[str, tp.Any] = FrozenDict({}),\n    graph: bool | None = None,\n    graph_updates: bool | None = None,\n) -> F:\n  ...\n\n\ndef pmap(\n  f: F | type[Missing] = Missing,\n  *,\n  axis_name: AxisName | None = None,\n  in_axes: tp.Any = 0,\n  out_axes: tp.Any = 0,\n  static_broadcasted_argnums: int | tp.Iterable[int] = (),\n  devices: tp.Sequence[jax.Device] | None = None,  # noqa: F811\n  backend: str | None = None,\n  axis_size: int | None = None,\n  donate_argnums: int | tp.Iterable[int] = (),\n  # nnx specific\n  transform_metadata: tp.Mapping[str, tp.Any] = FrozenDict({}),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> F | tp.Callable[[F], F]:\n  \"\"\"Reference-aware version of `jax.pmap <https://jax.readthedocs.io/en/latest/_autosummary/jax.pmap.html>`__.\n\n  Args:\n    f: Function to be mapped over argument axes. Its arguments and return\n      value should be arrays, scalars, graph nodes, or (nested) standard\n      Python containers (tuple/list/dict) thereof. Positional arguments\n      indicated by ``static_broadcasted_argnums`` can be anything at all,\n      provided they are hashable and have an equality operation defined.\n    axis_name: Optional, a hashable Python object used to identify the mapped\n      axis so that parallel collectives can be applied.\n    in_axes: A non-negative integer, None, or nested Python container thereof\n      that specifies which axes of positional arguments to map over. Arguments\n      passed as keywords are always mapped over their leading axis (i.e. axis\n      index 0). In addition to integers and None, :class:`StateAxes` can be\n      used to control how graph nodes like Modules are vectorized by specifying\n      the axes to be applied to substates of the graph node given a `Filter\n      <https://flax.readthedocs.io/en/latest/guides/filters_guide.html>`__.\n    out_axes: A non-negative integer, None, or nested Python container thereof\n      indicating where the mapped axis should appear in the output. All outputs\n      with a mapped axis must have a non-None ``out_axes`` specification.\n    static_broadcasted_argnums: An int or collection of ints specifying which\n      positional arguments to treat as static (compile-time constant).\n      Operations that only depend on static arguments will be constant-folded.\n      Calling the pmapped function with different values for these constants\n      will trigger recompilation. If the pmapped function is called with fewer\n      positional arguments than indicated by ``static_broadcasted_argnums`` then\n      an error is raised. Each of the static arguments will be broadcasted to\n      all devices. Arguments that are not arrays or containers thereof must be\n      marked as static. Defaults to ().\n      Static arguments must be hashable, meaning both ``__hash__`` and\n      ``__eq__`` are implemented, and should be immutable.\n    devices: This is an experimental feature and the API is likely to change.\n      Optional, a sequence of Devices to map over. (Available devices can be\n      retrieved via jax.devices()). Must be given identically for each process\n      in multi-process settings (and will therefore include devices across\n      processes). If specified, the size of the mapped axis must be equal to\n      the number of devices in the sequence local to the given process. Nested\n      ``pmap`` s with ``devices`` specified in either the inner or outer\n      ``pmap`` are not yet supported.\n    backend: This is an experimental feature and the API is likely to change.\n      Optional, a string representing the XLA backend. 'cpu', 'gpu', or 'tpu'.\n    axis_size: Optional; the size of the mapped axis.\n    donate_argnums: Specify which positional argument buffers are \"donated\" to\n      the computation. It is safe to donate argument buffers if you no longer\n      need them once the computation has finished. In some cases XLA can make\n      use of donated buffers to reduce the amount of memory needed to perform a\n      computation, for example recycling one of your input buffers to store a\n      result. You should not reuse buffers that you donate to a computation,\n      JAX will raise an error if you try to. Note that donate_argnums only\n      work for positional arguments, and keyword arguments will not be donated.\n    transform_metadata: Optional mapping of metadata for the transform.\n    graph: if True, use graph-mode (default). If False, use tree-mode.\n      If None, uses the value of ``nnx_graph_mode`` config.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``. When ``False``, using ``StateAxes``\n      is not supported.\n\n  Returns:\n    A parallelized version of ``f`` with arguments that correspond to those of\n    ``f`` but with extra array axes at positions indicated by ``in_axes`` and\n    with output that has an additional leading array axis (with the same size).\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if f is Missing:\n    return functools.partial(\n        pmap,\n        axis_name=axis_name,\n        in_axes=in_axes,\n        out_axes=out_axes,\n        static_broadcasted_argnums=static_broadcasted_argnums,\n        devices=devices,\n        backend=backend,\n        axis_size=axis_size,\n        donate_argnums=donate_argnums,\n        transform_metadata=transform_metadata,\n        graph=graph,\n        graph_updates=graph_updates,\n    )  # type: ignore[return-value]\n\n  f_unbound, _, was_bound = _resolve_bound_callable(f)\n  if was_bound:\n    _raise_bound_method_error('pmap')\n\n  if not graph or not graph_updates:\n    if any(isinstance(x, StateAxes) for x in jax.tree.leaves(in_axes)):\n      raise ValueError(\n        '`in_axes` cannot contain `StateAxes` objects '\n        'when `graph=False`. '\n        + graphlib._tree_mode_suggestion_transform('pmap')\n      )\n    if any(isinstance(x, StateAxes) for x in jax.tree.leaves(out_axes)):\n      raise ValueError(\n        '`out_axes` cannot contain `StateAxes` objects '\n        'when `graph=False`. '\n        + graphlib._tree_mode_suggestion_transform('pmap')\n      )\n    if graph:\n      extract.check_prefix(in_axes, 'in_axes', 'pmap')\n      extract.check_prefix(out_axes, 'out_axes', 'pmap')\n\n    pmapped_fn = jax.pmap(\n      SimplePmapFn(f_unbound, graph=graph, out_axes=out_axes),\n      axis_name=axis_name,\n      in_axes=in_axes,\n      out_axes=(out_axes, (in_axes, 0)),\n      static_broadcasted_argnums=static_broadcasted_argnums,\n      devices=devices,\n      backend=backend,\n      axis_size=axis_size,\n      donate_argnums=donate_argnums,\n    )\n\n    @functools.wraps(f_unbound)\n    def simple_pmap_wrapper(*args, **kwargs):\n      if graph:\n        args, kwargs = extract.to_tree2(\n            (args, kwargs),\n            prefix=(in_axes, None)\n            if in_axes is not None\n            else None,\n            check_aliasing=in_axes is not None,\n        )\n      extract.check_no_aliases('pmap', args=args, kwargs=kwargs)\n      out, updates = pmapped_fn(*args, **kwargs)\n      extract.apply_variable_updates((args, kwargs), updates)\n      if graph:\n        out = extract.from_tree2(out)\n      return out\n\n    return simple_pmap_wrapper  # type: ignore[return-value]\n\n  jax_in_axes = jax.tree.map(\n    lambda x: extract.NodeStates.from_prefixes(x.axes, metadata=x)\n    if isinstance(x, StateAxes)\n    else x,\n    in_axes,\n  )\n  jax_out_axes = jax.tree.map(\n    lambda x: extract.NodeStates.from_prefixes(x.axes, metadata=x)\n    if isinstance(x, StateAxes)\n    else x,\n    out_axes,\n  )\n  pmapped_fn = jax.pmap(\n      PmapFn(f_unbound, transform_metadata, in_axes, out_axes),\n      axis_name=axis_name,\n      in_axes=jax_in_axes,\n      out_axes=(jax_in_axes, jax_out_axes),\n      static_broadcasted_argnums=static_broadcasted_argnums,\n      devices=devices,\n      backend=backend,\n      axis_size=axis_size,\n      donate_argnums=donate_argnums,\n  )\n\n  @functools.wraps(f)\n  @graphlib.update_context('pmap')\n  def vmap_wrapper(*args):\n    pure_args = extract.to_tree(\n        args, prefix=in_axes, split_fn=_vmap_split_fn, ctxtag='pmap'\n    )\n    pure_args_out, pure_out = pmapped_fn(*pure_args)\n    _args_out, out = extract.from_tree(\n      (pure_args_out, pure_out), ctxtag='pmap', is_inner=False\n    )\n    return out\n\n  return vmap_wrapper  # type: ignore\n\n\n# -------------------------------\n# scan\n# -------------------------------\n\n\nclass Broadcasted(struct.PyTreeNode):\n  data: tp.Any\n\ndef _get_carry_argnum(axes, is_in_axes: bool):\n  if axes is Carry:\n    return 'all'\n  elif isinstance(axes, int) or axes is None:\n    return None\n\n  obj_repr = 'in_axes' if is_in_axes else 'out_axes'\n  carry_argnum: int | None = None\n  prev_key: tp.Any = None\n  for key, x in jax.tree_util.tree_leaves_with_path(axes):\n    if x is not Carry:\n      continue\n    assert isinstance(key[0], jax.tree_util.SequenceKey)\n    i = key[0].idx\n    if len(key) >= 2:\n      raise ValueError(\n        f'Carry must at the top-level, it cannot be nested. Found {axes=}'\n      )\n    if carry_argnum is not None:\n      raise ValueError(\n        f'Found multiple Carry definitions at '\n        f'{obj_repr}{jax.tree_util.keystr(prev_key)} and '\n        f'{obj_repr}{jax.tree_util.keystr(key)}'\n      )\n    carry_argnum = i\n    prev_key = key\n\n  return carry_argnum\n\n\ndef _check_out_axes(out_axes):\n  for key, x in jax.tree_util.tree_leaves_with_path(\n    out_axes, is_leaf=lambda x: x is None\n  ):\n    if x is None:\n      raise ValueError(\n        f'Cannot broadcast output state. '\n        f'Got out_axes=None at: out_axes{jax.tree_util.keystr(key)}'\n      )\n    elif isinstance(x, StateAxes):\n      for filter, value in x.items():\n        if value is None:\n          raise ValueError(\n            f'Cannot broadcast output state. '\n            f'Got StateAxes({{{filter}: None}}) at: out_axes'\n            f'{jax.tree_util.keystr(key)}'\n          )\n        elif value is Carry:\n          raise ValueError(\n            f'Cannot carry output state. '\n            f'Got StateAxes({{{filter}: Carry}}) at: out_axes'\n            f'{jax.tree_util.keystr(key)}'\n          )\ndef _check_carry_same_references(carry_arg, carry_arg_out):\n  def check_carry_same_references(key_path, arg, out):\n    if (\n      not isinstance(arg, jax.Array) or not isinstance(out, jax.Array)\n    ) and arg is not out:\n      raise ValueError(\n        'Carry references must be the same between iterations. '\n        f'Got {arg=} with id={id(arg)} and {out=} with id={id(out)} '\n        f'at carry{jax.tree_util.keystr(key_path)}'\n      )\n\n  jax.tree_util.tree_map_with_path(\n      check_carry_same_references,\n      carry_arg,\n      carry_arg_out,\n      is_leaf=lambda x: graphlib.is_graph_node(x)\n      and not isinstance(x, variablelib.Variable),\n  )\n\n\ndef _scan_split_in(\n  carry_deque: PytreeDeque[list[State | variablelib.Variable]],\n  graphdefs_deque: PytreeDeque[graphlib.GraphDef],\n  broadcast_deque: PytreeDeque[list[State | variablelib.Variable]],\n  broadcast_arrays: PytreeDeque[Broadcasted],\n  /,\n  ctx: graphlib.SplitContext,\n  path,\n  prefix,\n  x,\n):\n  if graphlib.is_graph_node(x) or isinstance(x, variablelib.Variable):\n    vectorized_states: list[State | variablelib.Variable] = []\n    carry_states: list[State | variablelib.Variable] = []\n    broadcast_states: list[State | variablelib.Variable] = []\n    if isinstance(prefix, StateAxes):\n      graphdef, *states = ctx.split(x, *prefix.filters)\n\n      for state, axis in zip(states, prefix.axes):\n        if axis is None:\n          broadcast_states.append(state)\n        elif isinstance(axis, int):\n          if axis != 0:\n            state = jax.tree.map(lambda x: jnp.moveaxis(x, axis, 0), state)\n          vectorized_states.append(state)\n        else:  # axis is Carry\n          carry_states.append(state)\n      if not vectorized_states:\n        vectorized_states.append(State({}))\n      carry_deque.append(carry_states)\n      graphdefs_deque.append(graphdef)\n      broadcast_deque.append(broadcast_states)\n      return extract.NodeStates.from_split(\n        None, *vectorized_states, metadata=prefix\n      )\n    elif isinstance(prefix, int):\n      graphdef, state = ctx.split(x)\n      if prefix != 0:\n        state = jax.tree.map(lambda x: jnp.moveaxis(x, prefix, 0), state)\n      vectorized_states.append(state)\n    elif prefix is None:\n      graphdef, state = ctx.split(x)\n      broadcast_states.append(state)\n      vectorized_states.append(State({}))\n    elif prefix is Carry:\n      graphdef, state = ctx.split(x)\n      carry_states.append(state)\n      vectorized_states.append(State({}))\n    else:\n      raise ValueError(\n        f'Invalid axes {prefix} args{jax.tree_util.keystr(path)}'\n      )\n\n    if not vectorized_states:\n      vectorized_states.append(State({}))\n    carry_deque.append(carry_states)\n    graphdefs_deque.append(graphdef)\n    broadcast_deque.append(broadcast_states)\n    return extract.NodeStates.from_split(\n      None, *vectorized_states, metadata=prefix\n    )\n  else:\n    if isinstance(prefix, StateAxes):\n      raise ValueError(\n        'Cannot use StateAxes on non-graph nodes, '\n        f'found {prefix} args{jax.tree_util.keystr(path)}'\n      )\n    elif prefix is Carry:\n      return x\n    elif prefix is None:\n      broadcast_arrays.append(Broadcasted(x))\n      return Broadcasted(None)\n    elif isinstance(prefix, int):\n      if not isinstance(x, (jax.Array, np.ndarray)):\n        raise ValueError(\n          f'Expected an array, got {type(x).__name__} args'\n          f'{jax.tree_util.keystr(path)}'\n        )\n      if prefix != 0:\n        x = jnp.moveaxis(x, prefix, 0)\n      return x\n    else:\n      raise ValueError(\n        f'Invalid axes {prefix} args{jax.tree_util.keystr(path)}'\n      )\n\n\ndef _scan_split_out(\n  carry_deque: PytreeDeque[list[State | variablelib.Variable]],\n  graphdefs_deque: PytreeDeque[graphlib.GraphDef],\n  broadcast_deque: PytreeDeque[list[State | variablelib.Variable]],\n  /,\n  ctx: graphlib.SplitContext,\n  path: extract.KeyPath,\n  prefix,\n  x,\n):\n  assert isinstance(path[0], jax.tree_util.SequenceKey)\n  is_input_arg = path[0].idx == 0\n\n  if graphlib.is_graph_node(x) or isinstance(x, variablelib.Variable):\n    vectorized_states: list[State | variablelib.Variable] = []\n    carry_states: list[State | variablelib.Variable] = []\n    broadcast_states: list[State | variablelib.Variable] = []\n    if isinstance(prefix, StateAxes):\n      graphdef, *states = ctx.split(x, *prefix.filters)\n\n      for state, filter, axis in zip(states, prefix.filters, prefix.axes):\n        if axis is None:\n          assert is_input_arg  # validated by _check_out_axes\n          broadcast_states.append(state)\n        elif isinstance(axis, int):\n          vectorized_states.append(state)\n        elif axis is Carry:\n          assert is_input_arg  # validated by _check_out_axes\n          carry_states.append(state)\n        else:\n          obj_repr = 'args' if is_input_arg else 'out'\n          raise ValueError(\n            f'Invalid axes {axis} for filter {filter} at '\n            f'{obj_repr}{jax.tree_util.keystr(path)}'\n          )\n\n      if not vectorized_states:\n        vectorized_states.append(State({}))\n      if is_input_arg:\n        carry_deque.append(carry_states)\n        broadcast_deque.append(broadcast_states)\n      graphdefs_deque.append(graphdef)\n      return extract.NodeStates.from_split(\n        None, *vectorized_states, metadata=prefix\n      )\n    elif isinstance(prefix, int):\n      graphdef, state = ctx.split(x)\n      vectorized_states.append(state)\n    elif prefix is None:\n      assert is_input_arg  # validated by _check_out_axes\n      graphdef, state = ctx.split(x)\n      broadcast_states.append(state)\n      vectorized_states.append(State({}))\n    elif prefix is Carry:\n      assert is_input_arg  # validated by _check_out_axes\n      graphdef, state = ctx.split(x)\n      carry_states.append(state)\n      vectorized_states.append(State({}))\n    else:\n      obj_repr = 'args' if is_input_arg else 'out'\n      raise ValueError(\n        f'Invalid axes {prefix} at {obj_repr}{jax.tree_util.keystr(path)}'\n      )\n    if not vectorized_states:\n      vectorized_states.append(State({}))\n    if is_input_arg:\n      carry_deque.append(carry_states)\n      broadcast_deque.append(broadcast_states)\n    graphdefs_deque.append(graphdef)\n    return extract.NodeStates.from_split(\n      None, *vectorized_states, metadata=prefix\n    )\n  else:\n    if isinstance(prefix, StateAxes):\n      obj_repr = 'args' if is_input_arg else 'out'\n      raise ValueError(\n        'Cannot use StateAxes on non-graph nodes, '\n        f'found {prefix} at {obj_repr}{jax.tree_util.keystr(path)}'\n      )\n    elif prefix is Carry:\n      return x\n    elif prefix is None:\n      assert not is_input_arg  # validated by _check_out_axes\n      return Broadcasted(None)\n    elif isinstance(prefix, int):\n      return x\n    else:\n      obj_repr = 'args' if is_input_arg else 'out'\n      raise ValueError(\n        f'Invalid axes {prefix} at {obj_repr}{jax.tree_util.keystr(path)}'\n      )\n\n\ndef _scan_merge_in(\n  carry_deque: PytreeDeque[list[State]],\n  graphdefs_deque: PytreeDeque[graphlib.GraphDef],\n  broadcast_deque: PytreeDeque[list[State]],\n  broadcast_arrays: PytreeDeque[Broadcasted],\n  /,\n  ctx: graphlib.MergeContext,\n  path,\n  prefix,\n  x,\n):\n  if isinstance(x, extract.NodeStates):\n    carry_states = carry_deque.popleft()\n    broadcast_states = broadcast_deque.popleft()\n    graphdef = graphdefs_deque.popleft()\n    return ctx.merge(graphdef, *x.states, *carry_states, *broadcast_states)\n  elif isinstance(x, Broadcasted):\n    assert x.data is None\n    return broadcast_arrays.popleft().data\n  else:\n    return x\n\n\ndef _scan_merge_out(\n  carry_deque: PytreeDeque[list[State]],\n  graphdefs_deque: PytreeDeque[graphlib.GraphDef],\n  broadcast_deque: PytreeDeque[list[State]],\n  /,\n  ctx: graphlib.MergeContext,\n  path,\n  prefix,\n  x,\n):\n  assert isinstance(path[0], jax.tree_util.SequenceKey)\n  is_input_arg = path[0].idx == 0\n\n  if isinstance(x, extract.NodeStates):\n    states: list[State] = []\n    graphdef = graphdefs_deque.popleft()\n    if is_input_arg:\n      carry_states = deque(carry_deque.popleft())\n      broadcast_states = deque(broadcast_deque.popleft())\n    else:\n      carry_states = deque[State]()\n      broadcast_states = deque[State]()\n    if isinstance(prefix, StateAxes):\n      vectorized_states = deque(x.states)\n      for axis in prefix.axes:\n        if isinstance(axis, int):\n          state = vectorized_states.popleft()\n          state = jax.tree.map(\n            lambda x: jnp.moveaxis(x, 0, axis) if axis != 0 else x,\n            state,\n          )\n          states.append(state)\n        elif axis is None:\n          states.append(broadcast_states.popleft())\n        else:  # axis is Carry\n          states.append(carry_states.popleft())\n      assert not carry_states and not broadcast_states\n      assert not vectorized_states or (\n        len(vectorized_states) == 1 and not vectorized_states[0]\n      )\n    elif isinstance(prefix, int):\n      state = jax.tree.map(\n        lambda x: jnp.moveaxis(x, 0, prefix) if prefix != 0 else x, x.state\n      )\n      states.extend((state, *carry_states, *broadcast_states))\n    elif prefix is None:\n      assert is_input_arg\n      states.extend(broadcast_states)\n    elif prefix is Carry:\n      assert is_input_arg\n      states.extend(carry_states)\n    else:\n      obj_repr = 'args' if is_input_arg else 'out'\n      raise ValueError(\n        f'Invalid axes {prefix} at {obj_repr}{jax.tree_util.keystr(path)}'\n      )\n    return ctx.merge(graphdef, *states)\n  else:\n    if isinstance(prefix, StateAxes):\n      obj_repr = 'args' if is_input_arg else 'out'\n      raise ValueError(\n        'Cannot use StateAxes on non-graph nodes, '\n        f'found {prefix} at {obj_repr}{jax.tree_util.keystr(path)}'\n      )\n    elif prefix is Carry:\n      return x\n    elif prefix is None:\n      return x\n    elif isinstance(prefix, int):\n      if not isinstance(x, (jax.Array, np.ndarray)):\n        obj_repr = 'args' if is_input_arg else 'out'\n        raise ValueError(\n          f'Expected an array, got {type(x).__name__} at '\n          f'{obj_repr}{jax.tree_util.keystr(path)}'\n        )\n      if prefix != 0:\n        x = jnp.moveaxis(x, 0, prefix)\n      return x\n    else:\n      obj_repr = 'args' if is_input_arg else 'out'\n      raise ValueError(\n        f'Invalid axes {prefix} at {obj_repr}{jax.tree_util.keystr(path)}'\n      )\n\n\n@dataclasses.dataclass(eq=False)\nclass ScanFn:\n  f: tp.Callable[..., tp.Any]\n  input_carry_argnum: int | None | tp.Literal['all']\n  output_carry_argnum: int | None | tp.Literal['all']\n  in_axes: tp.Any\n  out_axes: tp.Any\n  transform_metadata: tp.Mapping[str, tp.Any]\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f)\n\n  def __call__(\n    self,\n    carry: tuple[\n      tp.Any,  # carry_arg\n      PytreeDeque[list[State]],  # carry_deque\n      PytreeDeque[list[State]],  # broadcast_deque\n      PytreeDeque[Broadcasted],  # broadcast_arrays\n    ],\n    scan_in: tuple[tp.Any, ...],\n  ):\n    pure_carry_arg, carry_deque, broadcast_deque, broadcast_arrays = carry\n    broadcast_deque_out = PytreeDeque(broadcast_deque)\n    broadcast_arrays_out = PytreeDeque(broadcast_arrays)\n    graphdefs_deque, pure_args = scan_in\n\n    if self.input_carry_argnum == 'all':\n      assert pure_args == ()\n      pure_args = (pure_carry_arg,)\n    elif isinstance(self.input_carry_argnum, int):\n      assert pure_args[self.input_carry_argnum] is None\n      pure_args = extract.replace_at(pure_args, self.input_carry_argnum, pure_carry_arg)\n    else:\n      assert self.input_carry_argnum is None\n      assert pure_carry_arg is None\n\n    if spmd.PARTITION_NAME in self.transform_metadata:\n      pure_args = _update_variable_sharding_metadata(\n          pure_args, self.transform_metadata, spmd.remove_axis\n      )\n\n    args: tuple = extract.from_tree(\n      pure_args,\n      prefix=self.in_axes,\n      merge_fn=functools.partial(\n        _scan_merge_in, carry_deque, graphdefs_deque, broadcast_deque, broadcast_arrays\n      ),\n      is_leaf=lambda x: isinstance(x, (extract.NodeStates, Broadcasted)),\n      map_non_graph_nodes=True,\n      ctxtag='scan',\n      is_inner=True,\n    )\n    assert not carry_deque and not broadcast_deque and not broadcast_arrays\n\n    out = self.f(*args)\n\n    # extract the carry from the args\n    if self.input_carry_argnum == 'all':\n      carry_arg = args[0]\n    elif isinstance(self.input_carry_argnum, int):\n      carry_arg = args[self.input_carry_argnum]\n    else:\n      assert self.input_carry_argnum is None\n      carry_arg = None\n\n    # extract the carry from the output\n    if self.output_carry_argnum == 'all':\n      carry_arg_out = out\n      out = None\n    elif isinstance(self.output_carry_argnum, int):\n      assert isinstance(out, tuple)\n      carry_arg_out = out[self.output_carry_argnum]\n      out = extract.replace_at(out, self.output_carry_argnum, None)\n    else:\n      assert self.output_carry_argnum is None\n      carry_arg_out = None\n\n    # TODO(cgarciae): allowing new references might lead to inconsistencies with\n    # scan's looping semantics and we would also need to propagate the input\n    _check_carry_same_references(carry_arg, carry_arg_out)\n\n    args_out: tuple = extract.clear_non_graph_nodes(args)\n\n    # replace the carry from the input args with the carry from the output\n    if self.input_carry_argnum == 'all':\n      args_out = (carry_arg_out,)\n    elif isinstance(self.input_carry_argnum, int):\n      args_out = extract.replace_at(args_out, self.input_carry_argnum, carry_arg_out)\n    else:\n      assert self.input_carry_argnum is None\n      assert carry_arg_out is None\n\n    carry_deque_out = PytreeDeque[list[State | variablelib.Variable]]()\n    graphdefs_out = PytreeDeque[graphlib.GraphDef]()\n    _broadcast_deque_out_tmp = PytreeDeque[\n      list[State | variablelib.Variable]\n    ]()  # discarded\n    pure_args_out: tuple\n    pure_args_out, pure_out = extract.to_tree(\n      (args_out, out),\n      prefix=(self.in_axes, self.out_axes),\n      split_fn=functools.partial(\n        _scan_split_out, carry_deque_out, graphdefs_out, _broadcast_deque_out_tmp\n      ),\n      map_non_graph_nodes=True,\n      ctxtag='scan',\n    )\n    if spmd.PARTITION_NAME in self.transform_metadata:\n      pure_args_out, pure_out = _update_variable_sharding_metadata(\n        (pure_args_out, pure_out),\n        self.transform_metadata,\n        spmd.add_axis,\n      )\n\n    # extract the pure carry from the pure args\n    if self.input_carry_argnum == 'all':\n      pure_carry_arg_out = pure_args_out[0]\n      pure_args_out = ()\n    elif isinstance(self.input_carry_argnum, int):\n      pure_carry_arg_out = pure_args_out[self.input_carry_argnum]\n      pure_args_out = extract.replace_at(pure_args_out, self.input_carry_argnum, None)\n    else:\n      assert self.input_carry_argnum is None\n      pure_carry_arg_out = None\n\n    carry_arg_out = (\n      pure_carry_arg_out,\n      carry_deque_out,\n      broadcast_deque_out,\n      broadcast_arrays_out,\n    )\n    scan_out = (\n      graphdefs_out,\n      pure_args_out,\n      pure_out,\n    )\n    return carry_arg_out, scan_out\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleScanFn:\n  f: tp.Callable[..., tp.Any]\n  graph: bool\n  in_axes: tp.Any\n  out_axes: tp.Any\n  out_is_tuple: bool\n  carry_arg_index: int | None\n  carry_out_index: int | None\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args):\n    updates, snapshot = extract.updates_and_snapshot(args)\n    updates = extract.mask_at(updates, self.carry_arg_index)\n    snapshot = extract.mask_at(snapshot, self.carry_arg_index)\n    if self.carry_arg_index is not None:\n      carry_in = args[self.carry_arg_index]\n    else:\n      carry_in = None\n    if self.graph:\n      args = extract.from_tree2(args)\n\n    out = self.f(*args)\n\n    if self.graph:\n      out = extract.to_tree2(out, prefix=self.out_axes)\n\n    if self.carry_out_index is not None:\n      carry_out = out[self.carry_out_index] if self.out_is_tuple else out\n      extract.check_same_variables(carry_in, carry_out, 'scan')\n\n    def keep_fn(path, prefix, cur, snap):\n      changed = extract.variable_changed(cur, snap)\n      if prefix is None and changed:\n        raise ValueError(\n            f'Broadcast (None axis) Variable at {jax.tree_util.keystr(path)} '\n            'was mutated during scan. Only Carry and scanned Variables can be '\n            'updated.'\n        )\n      return changed\n\n    extract.check_no_aliases('scan', args=updates, out=out)\n    updates = extract.mask_variable_updates(\n      updates, snapshot, prefix=self.in_axes, keep_fn=keep_fn,\n    )\n    if self.out_is_tuple:\n      return (*out, updates)\n    return (out, updates)\n\n\n@tp.overload\ndef scan(\n  *,\n  length: int | None = None,\n  reverse: bool = False,\n  unroll: int | bool = 1,\n  _split_transpose: bool = False,\n  # extended api\n  in_axes: int | None | type[Carry] | tuple[tp.Any, ...] = (Carry, 0),\n  out_axes: tp.Any = (Carry, 0),\n  # nnx specific\n  transform_metadata: tp.Mapping[str, tp.Any] = FrozenDict({}),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tp.Callable[[F], F]:\n  ...\n\n\n@tp.overload\ndef scan(\n  f: F,\n  *,\n  length: int | None = None,\n  reverse: bool = False,\n  unroll: int | bool = 1,\n  _split_transpose: bool = False,\n  # extended api\n  in_axes: int | None | type[Carry] | tuple[tp.Any, ...] = (Carry, 0),\n  out_axes: tp.Any = (Carry, 0),\n  # nnx specific\n  transform_metadata: tp.Mapping[str, tp.Any] = FrozenDict({}),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> F:\n  ...\n\n\ndef scan(\n  f: F | type[Missing] = Missing,\n  *,\n  length: int | None = None,\n  reverse: bool = False,\n  unroll: int | bool = 1,\n  _split_transpose: bool = False,\n  # extended api\n  in_axes: int | None | type[Carry] | tuple[tp.Any, ...] = (Carry, 0),\n  out_axes: tp.Any = (Carry, 0),\n  # nnx specific\n  transform_metadata: tp.Mapping[str, tp.Any] = FrozenDict({}),\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> F | tp.Callable[[F], F]:\n  \"\"\"A Flax NNX transformation of `jax.lax.scan`_.\n\n  Example::\n\n    import jax\n    from flax import nnx\n\n    class Block(nnx.Module):\n      def __init__(self, input_dim, features, *, rngs):\n        self.linear = nnx.Linear(input_dim, features, rngs=rngs)\n        self.dropout = nnx.Dropout(0.1, rngs=rngs)\n\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        x = self.dropout(x)\n        x = jax.nn.relu(x)\n        return x\n\n    class Model(nnx.Module):\n      def __init__(self, num_layers, features, *, rngs):\n        # In this model implementation we create\n        # multiple blocks using vmap\n\n        # As Block contains dropout op, we prefer\n        # to split RNG into num_layers of RNGs\n        # using @nnx.split_rngs decorator.\n        # Next, nnx.vmap creates a vectorized version of Block.\n        # in_axes and out_axes define vectorization axis\n        # of the input splitted rngs and the output Block instance.\n        # Both axes should be 0.\n        @nnx.split_rngs(splits=num_layers)\n        @nnx.vmap(in_axes=(0,), out_axes=0)\n        def create_block(rngs: nnx.Rngs):\n          return Block(features, features, rngs=rngs)\n\n        self.blocks = create_block(rngs)\n        self.num_layers = num_layers\n\n      def __call__(self, x):\n        # Forward pass method implementation\n\n        # We use nnx.scan to apply sequentially the blocks\n        # on the input, for example with num_layers=3\n        # output = block[0](x)\n        # output = block[1](output)\n        # output = block[2](output)\n        #\n        # In `forward` function defined below:\n        # - x represents the loop carry value\n        # - model is the data to scan along the leading axis\n        # nnx.scan args:\n        # - in_axes marks the inputs: x is marked as carry\n        # and the model is to scan along the axis 0\n        # - out_axes marks the output as carry\n        @nnx.scan(in_axes=(nnx.Carry, 0), out_axes=nnx.Carry)\n        def forward(x, model):\n          x = model(x)\n          return x\n\n        return forward(x, self.blocks)\n\n      # Alternatively, we can also decorate `self.__call__` method\n      # @nnx.scan(in_axes=(0, nnx.Carry), out_axes=nnx.Carry)\n      # def __call__(self, x):\n      #   return self.blocks(x)\n\n    model = Model(2, 4, rngs=nnx.Rngs(0))\n    _, params, _ = nnx.split(model, nnx.Param, ...)\n    print(params)  # kernel of shape: (2, 4, 4)\n\n    x = jnp.arange(5 * 4, dtype=\"float32\").reshape((5, 4))\n    y = model(x)\n    print(y.shape)  # shape: (5, 4)\n\n  Args:\n    f: a Python function to be scanned\n    length: optional integer specifying the number of loop iterations\n    reverse: optional boolean specifying whether to run the scan iteration\n      forward (the default) or in reverse\n    unroll: optional positive int or bool specifying, in the underlying\n      operation of the scan primitive, how many scan iterations to unroll\n      within a single iteration of a loop.\n    in_axes: integer, None, :class:`flax.nnx.Carry` or sequence of values specifying\n      the kind of input args. Integer value would specify the axis of corresponding\n      input data to scan along. :class:`flax.nnx.Carry` marks the input data as\n      loop carry value. None marks the input data as auxiliary input.\n    out_axes: integer, None, :class:`flax.nnx.Carry` or sequence of values specifying\n      the kind of output args. See ``in_axes`` for details. Note that If ``in_axes``\n      contains :class:`flax.nnx.Carry` then ``out_axes`` must also contain :class:`flax.nnx.Carry`.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``. When ``False``, using ``StateAxes``\n      is not supported.\n\n  .. _jax.lax.scan: https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.scan.html>\n  \"\"\"\n  if f is Missing:\n    return functools.partial(\n        scan,\n        length=length,\n        reverse=reverse,\n        unroll=unroll,\n        _split_transpose=_split_transpose,\n        in_axes=in_axes,\n        out_axes=out_axes,\n        transform_metadata=transform_metadata,\n        graph=graph,\n        graph_updates=graph_updates,\n    )  # type: ignore[return-value]\n\n  f_unbound, _, was_bound = _resolve_bound_callable(f)\n  if was_bound:\n    _raise_bound_method_error('scan')\n\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if not graph or not graph_updates:\n    return _simple_scan(\n      f, f_unbound, graph=graph,\n      in_axes=in_axes, out_axes=out_axes,\n      length=length, reverse=reverse, unroll=unroll,\n      _split_transpose=_split_transpose,\n    )\n\n  return _graph_updates_scan(\n    f, f_unbound,\n    in_axes=in_axes, out_axes=out_axes,\n    length=length, reverse=reverse, unroll=unroll,\n    _split_transpose=_split_transpose,\n    transform_metadata=transform_metadata,\n  )\n\ndef _simple_scan(\n  f, f_unbound, *,\n  graph, in_axes, out_axes,\n  length, reverse, unroll, _split_transpose,\n):\n  if any(isinstance(x, StateAxes) for x in jax.tree.leaves(in_axes)):\n    raise ValueError(\n      '`in_axes` cannot contain `StateAxes` objects '\n      'when `graph=False`. '\n      + graphlib._tree_mode_suggestion_transform('scan')\n    )\n  if any(isinstance(x, StateAxes) for x in jax.tree.leaves(out_axes)):\n    raise ValueError(\n      '`out_axes` cannot contain `StateAxes` objects '\n      'when `graph=False`. '\n      + graphlib._tree_mode_suggestion_transform('scan')\n    )\n  if graph:\n    extract.check_prefix(in_axes, 'in_axes', 'scan')\n    extract.check_prefix(out_axes, 'out_axes', 'scan')\n\n  out_is_tuple = isinstance(out_axes, tuple)\n  if in_axes is Carry:\n    in_axes = (Carry,)\n  if isinstance(in_axes, tuple):\n    carry_arg_index = next(\n      (i for i, ax in enumerate(in_axes) if ax is Carry), None\n    )\n    updates_out_axes = extract.mask_at(in_axes, carry_arg_index)\n  else:\n    carry_arg_index = None\n    updates_out_axes = in_axes\n\n  if isinstance(out_axes, tuple):\n    carry_out_index = next(\n      (i for i, ax in enumerate(out_axes) if ax is Carry), None\n    )\n  else:\n    carry_out_index = None\n\n  simple_scan_fn = SimpleScanFn(\n    f_unbound, graph=graph,\n    in_axes=in_axes, out_axes=out_axes,\n    out_is_tuple=out_is_tuple,\n    carry_arg_index=carry_arg_index,\n    carry_out_index=carry_out_index,\n  )\n\n  if out_is_tuple:\n    augmented_out_axes = (*out_axes, updates_out_axes)\n  else:\n    augmented_out_axes = (out_axes, updates_out_axes)\n\n  @functools.wraps(f)\n  def simple_scan_wrapper(*args):\n    args = resolve_kwargs(f, args, {})\n    if graph:\n      args = extract.to_tree2(args, prefix=in_axes)\n\n    extract.check_no_aliases('scan', args=args)\n\n    result = pure_jax_fancy_scan(\n      simple_scan_fn,\n      *args,\n      length=length,\n      reverse=reverse,\n      unroll=unroll,\n      _split_transpose=_split_transpose,\n      in_axes=in_axes,\n      out_axes=augmented_out_axes,\n    )\n\n    if out_is_tuple:\n      n = len(out_axes)\n      out = result[:n]\n      updates = result[n]\n    else:\n      out, updates = result\n\n    masked_args = extract.mask_at(args, carry_arg_index)\n    extract.apply_variable_updates(masked_args, updates)\n\n    if carry_arg_index is not None:\n      carry_in = args[carry_arg_index]\n      carry_out = (\n        out[carry_out_index] if out_is_tuple else out\n      )\n      extract.update_carry_variables(carry_in, carry_out)\n      if out_is_tuple:\n        out = extract.replace_at(out, carry_out_index, carry_in)\n      else:\n        out = carry_in\n\n    if graph:\n      out = extract.from_tree2(out)\n\n    return out\n\n  return simple_scan_wrapper\n\n\ndef _graph_updates_scan(\n  f, f_unbound, *,\n  in_axes, out_axes,\n  length, reverse, unroll, _split_transpose,\n  transform_metadata,\n):\n  _check_out_axes(out_axes)\n\n  input_carry_argnum = _get_carry_argnum(in_axes, is_in_axes=True)\n  output_carry_argnum = _get_carry_argnum(out_axes, is_in_axes=False)\n\n  if (input_carry_argnum is None and output_carry_argnum is not None) or (\n    input_carry_argnum is not None and output_carry_argnum is None\n  ):\n    raise ValueError(\n      'If one of in_axes or out_axes has Carry, the other must also have Carry. '\n      f'Got {in_axes=!r} and {out_axes=!r}'\n    )\n\n  scan_fn = ScanFn(\n    f_unbound,\n    input_carry_argnum,\n    output_carry_argnum,\n    in_axes,\n    out_axes,\n    transform_metadata,\n  )\n\n  @functools.wraps(f)\n  @graphlib.update_context('scan')\n  def scan_wrapper(*args, **kwargs):\n    args = resolve_kwargs(f, args, kwargs)\n\n    if in_axes is Carry and len(args) != 1:\n      raise ValueError(\n        f'When in_axes=Carry, the function must take exactly one argument, '\n        f'got {len(args)} arguments.'\n      )\n\n    graphdefs_deque = PytreeDeque()\n    carry_deque = PytreeDeque()\n    broadcast_deque = PytreeDeque()\n    broadcast_arrays = PytreeDeque()\n    pure_args: tuple = extract.to_tree(\n      args,\n      prefix=in_axes,\n      split_fn=functools.partial(\n        _scan_split_in, carry_deque, graphdefs_deque, broadcast_deque, broadcast_arrays\n      ),\n      map_non_graph_nodes=True,\n      ctxtag='scan',\n    )\n    if isinstance(input_carry_argnum, int):\n      pure_carry_arg = pure_args[input_carry_argnum]\n      pure_args = extract.replace_at(pure_args, input_carry_argnum, None)\n    elif input_carry_argnum == 'all':\n      pure_carry_arg = pure_args[0]\n      pure_args = ()\n    else:\n      assert input_carry_argnum is None\n      pure_carry_arg = None\n\n    carry = (pure_carry_arg, carry_deque, broadcast_deque, broadcast_arrays)\n    scan_in = (graphdefs_deque, pure_args)\n\n    carry_out, scan_out = jax.lax.scan(\n      scan_fn,\n      carry,\n      scan_in,\n      length=length,\n      reverse=reverse,\n      unroll=unroll,\n      _split_transpose=_split_transpose,\n    )\n    (\n        pure_carry_arg_out,\n        carry_deque_out,\n        broadcast_deque_out,\n        broadcast_arrays_out,\n    ) = carry_out\n    (\n      graphdefs_out,\n      pure_args_out,\n      pure_out,\n    ) = scan_out\n\n    if input_carry_argnum == 'all':\n      pure_args_out = (pure_carry_arg_out,)\n    elif isinstance(input_carry_argnum, int):\n      pure_args_out = extract.replace_at(pure_args_out, input_carry_argnum, pure_carry_arg_out)\n    else:\n      assert input_carry_argnum is None\n      assert pure_carry_arg_out is None\n\n    args_out, out = extract.from_tree(\n      (pure_args_out, pure_out),\n      prefix=(in_axes, out_axes),\n      merge_fn=functools.partial(\n        _scan_merge_out, carry_deque_out, graphdefs_out, broadcast_deque_out\n      ),\n      is_leaf=lambda x: isinstance(x, (extract.NodeStates, Broadcasted)),\n      map_non_graph_nodes=True,\n      ctxtag='scan',\n      is_inner=False,\n    )\n\n    if input_carry_argnum == 'all':\n      carry_arg = args_out[0]\n    elif isinstance(input_carry_argnum, int):\n      carry_arg = args_out[input_carry_argnum]\n    else:\n      assert input_carry_argnum is None\n      carry_arg = None\n\n    if output_carry_argnum == 'all':\n      out = carry_arg\n    elif isinstance(output_carry_argnum, int):\n      out = extract.replace_at(out, output_carry_argnum, carry_arg)\n    else:\n      assert output_carry_argnum is None\n      assert carry_arg is None\n\n    return out\n\n  return scan_wrapper\n\n\ndef pure_jax_fancy_scan(\n  f,\n  *args,\n  length: int | None = None,\n  reverse: bool = False,\n  unroll: int | bool = 1,\n  _split_transpose: bool = False,\n  in_axes: tp.Any = (Carry, 0),\n  out_axes: tp.Any = (Carry, 0),\n):\n  if in_axes is Carry:\n    in_axes = (Carry,)\n  is_axis_leaf = lambda x: x is None or x is Carry\n\n  if isinstance(in_axes, tuple):\n    for i, ax in enumerate(in_axes):\n      if ax is Carry or ax is None or isinstance(ax, int):\n        continue\n      for leaf in jax.tree.leaves(ax, is_leaf=is_axis_leaf):\n        if leaf is Carry:\n          raise ValueError(\n            'Carry must be a top-level argument, it cannot be nested. '\n            f'Found Carry inside in_axes[{i}]={ax}'\n          )\n\n  if isinstance(out_axes, tuple):\n    for i, ax in enumerate(out_axes):\n      if ax is Carry or ax is None or isinstance(ax, int):\n        continue\n      for path, leaf in jax.tree_util.tree_leaves_with_path(\n        ax, is_leaf=is_axis_leaf,\n      ):\n        if leaf is Carry:\n          raise ValueError(\n            'Carry must be a top-level argument, it cannot be nested. '\n            f'Found Carry at out_axes[{i}]{jax.tree_util.keystr(path)}'\n          )\n\n  in_has_carry = in_axes is Carry or (\n    isinstance(in_axes, tuple) and Carry in in_axes\n  )\n  out_has_carry = out_axes is Carry or (\n    isinstance(out_axes, tuple) and Carry in out_axes\n  )\n  if in_has_carry != out_has_carry:\n    raise ValueError(\n      'If one of in_axes or out_axes has Carry, the other must also '\n      f'have Carry. Got {in_axes=}, {out_axes=}'\n    )\n\n\n  args_flat, args_treedef = jax.tree.flatten(args)\n  _, in_axes_flat = extract.broadcast_prefix2(\n    in_axes, args, is_leaf=is_axis_leaf,\n  )\n\n  carry_indices: list[int] = []\n  broadcast_indices: list[int] = []\n  scan_indices: list[int] = []\n  scan_in_axes: list[int] = []\n\n  carry_leaves: list[tp.Any] = []\n  broadcast_leaves: list[tp.Any] = []\n  scan_leaves: list[tp.Any] = []\n\n  for i, (leaf, ax) in enumerate(zip(args_flat, in_axes_flat, strict=True)):\n    if ax is Carry:\n      carry_indices.append(i)\n      carry_leaves.append(leaf)\n    elif ax is None:\n      broadcast_indices.append(i)\n      broadcast_leaves.append(leaf)\n    elif isinstance(ax, int):\n      scan_indices.append(i)\n      scan_in_axes.append(ax)\n      if ax != 0:\n        leaf = jnp.moveaxis(leaf, ax, 0)\n      scan_leaves.append(leaf)\n    else:\n      raise ValueError(f'Invalid in_axes leaf value: {ax}')\n\n  n_in = len(args_flat)\n  out_info: list[tuple[\n    jax.tree_util.PyTreeDef, list[int], list[int], list[int],\n  ]] = []\n\n  in_broadcast = jax.tree.map(lambda x: x, broadcast_leaves)\n\n  def body_fn(carry_state, scan_x):\n    flat = [None] * n_in\n    for idx, j in enumerate(carry_indices):\n      flat[j] = carry_state[idx]\n    for idx, j in enumerate(broadcast_indices):\n      flat[j] = in_broadcast[idx]\n    if scan_x is not None:\n      for idx, j in enumerate(scan_indices):\n        flat[j] = scan_x[idx]\n\n    reconstructed = args_treedef.unflatten(flat)\n    out = f(*reconstructed)\n\n    out_flat, out_treedef = jax.tree.flatten(out)\n    out_axes_paths, out_axes_flat = extract.broadcast_prefix2(\n      out_axes, out, is_leaf=is_axis_leaf,\n    )\n\n    if not out_info:\n      out_carry_idx = []\n      out_scan_idx = []\n      out_scan_axes = []\n      out_broadcast_idx = []\n      for j, oax in enumerate(out_axes_flat):\n        if oax is Carry:\n          out_carry_idx.append(j)\n        elif oax is None:\n          out_broadcast_idx.append(j)\n        elif isinstance(oax, int):\n          out_scan_idx.append(j)\n          out_scan_axes.append(oax)\n        else:\n          raise ValueError(f'Invalid out_axes leaf value: {oax}')\n      if out_broadcast_idx:\n        broadcast_paths = [\n          jax.tree_util.keystr(out_axes_paths[j]) for j in out_broadcast_idx\n        ]\n        broadcast_str = \"\\n\\n  \".join(broadcast_paths)\n        raise ValueError(\n          'Scan does not support broadcast outputs (None axis). The following '\n          f'output leaves are broadcast:\\n\\n  {broadcast_str}\\n'\n        )\n      out_info.append(\n        (out_treedef, out_carry_idx, out_scan_idx, out_scan_axes),\n      )\n\n    oci = out_info[0][1]\n    osi = out_info[0][2]\n    new_carry = [out_flat[j] for j in oci]\n    new_ys = [out_flat[j] for j in osi]\n\n    return new_carry, new_ys\n\n  final_carry, stacked_ys = jax.lax.scan(\n    body_fn,\n    carry_leaves,\n    scan_leaves if scan_leaves else None,\n    length=length,\n    reverse=reverse,\n    unroll=unroll,\n    _split_transpose=_split_transpose,\n  )\n\n  out_treedef, out_carry_idx, out_scan_idx, out_scan_axes = (\n    out_info[0]\n  )\n  n_out = out_treedef.num_leaves\n  out_flat: list[tp.Any] = [None] * n_out\n  for idx, j in enumerate(out_carry_idx):\n    out_flat[j] = final_carry[idx]\n  for idx, j in enumerate(out_scan_idx):\n    y = stacked_ys[idx]\n    ax = out_scan_axes[idx]\n    if ax != 0:\n      y = jnp.moveaxis(y, 0, ax)\n    out_flat[j] = y\n\n  return out_treedef.unflatten(out_flat)\n\n\n# -------------------------------\n# while_loop\n# -------------------------------\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleWhileLoopBodyFn:\n  f: tp.Callable[..., tp.Any]\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, val):\n    val_variables, _ = extract.updates_and_snapshot(val)\n    if self.graph:\n      val = extract.from_tree2(val)\n    out = self.f(val)\n    if self.graph:\n      out = extract.to_tree2(out)\n    extract.check_same_variables(val_variables, out, 'while_loop')\n    return out\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleWhileLoopCondFn:\n  f: tp.Callable[..., tp.Any]\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f)\n\n  def __call__(self, val):\n    if self.graph:\n      val = extract.from_tree2(val)\n    return self.f(val)\n\n\n@dataclasses.dataclass(eq=False)\nclass WhileLoopCondFn:\n  f: tp.Callable[..., tp.Any]\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f)\n\n  def __call__(self, pure_val):\n    val = extract.from_tree(pure_val)\n    out = self.f(val)\n    return out\n\n\ndef _reconsile_index_mapping(tree_to_fix, example_tree):\n  def f(a, b):\n    if not isinstance(a, extract.NodeStates) or not isinstance(\n      a._graphdef, graphlib.GraphDef\n    ):\n      return a\n    return dataclasses.replace(\n      a, _graphdef=a._graphdef.with_matching_outer_index(b._graphdef)\n    )\n\n  return jax.tree.map(f, tree_to_fix, example_tree,\n                      is_leaf=lambda x: isinstance(x, extract.NodeStates))\n\ndef _add_fake_index_mapping(tree: tp.Any):\n  def per_node_state(node_state: extract.NodeStates | tp.Any):\n    if not isinstance(node_state, extract.NodeStates) or not isinstance(\n      node_state._graphdef, graphlib.GraphDef\n    ):\n      return node_state\n\n    return dataclasses.replace(\n      node_state, _graphdef=node_state._graphdef.with_same_outer_index()\n    )\n\n  return jax.tree.map(per_node_state, tree,\n                      is_leaf=lambda x: isinstance(x, extract.NodeStates))\n\n\ndef _remove_index_mapping(tree: tp.Any):\n  \"\"\"Remove a fake outer_index for the input to match that of the output.\"\"\"\n\n  def per_node_state(node_state: extract.NodeStates | tp.Any):\n    if not isinstance(node_state, extract.NodeStates) or not isinstance(\n      node_state._graphdef, graphlib.GraphDef\n    ):\n      return node_state\n    assert isinstance(node_state._graphdef, graphlib.GraphDef)\n    node_state = dataclasses.replace(\n      node_state, _graphdef=node_state._graphdef.with_no_outer_index()\n    )\n    return node_state\n\n  return jax.tree.map(per_node_state, tree,\n                      is_leaf=lambda x: isinstance(x, extract.NodeStates))\n\n\n@dataclasses.dataclass(eq=False)\nclass WhileLoopBodyFn:\n  f: tp.Callable[..., tp.Any]\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f)\n\n  @graphlib.update_context('while_loop_body')\n  def __call__(self, pure_val):\n    # Removing the dummy index mapping being added outside of body function.\n    pure_val_in = _remove_index_mapping(pure_val)\n\n    val = extract.from_tree(\n      pure_val_in, ctxtag='while_loop_body', is_inner=True\n    )\n    out = self.f(val)\n    pure_out = extract.to_tree(out, ctxtag='while_loop_body')\n\n    try:\n      jax.tree.map(lambda a, b: None, pure_val, pure_out)\n    except ValueError as e:\n      msg = (\n        \"nnx.while_loop requires body function's input and output to \"\n        'have the same reference and pytree structure, but they differ. '\n        'If the mismatch comes from `outer_index` field, you might '\n        'have modified reference structure within the body function, '\n        'which is not allowed.'\n        f'Detail of the mismatch: \\n {str(e)}'\n      )\n      raise ValueError(msg)\n\n    return pure_out\n\n\n@graphlib.update_context('while_loop')\ndef while_loop(cond_fun: tp.Callable[[T], tp.Any],\n               body_fun: tp.Callable[[T], T],\n               init_val: T,\n               *,\n               graph: bool | None = None,\n               graph_updates: bool | None = None) -> T:\n  \"\"\"A Flax NNX transformation of `jax.lax.while_loop <https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.while_loop.html>`_.\n\n  Caution: for the NNX internal reference tracing mechanism to work, you cannot\n  change the variable reference structure of ``init_val`` inside ``body_fun``.\n\n  Example::\n\n    >>> import jax\n    >>> from flax import nnx\n    >>> def fwd_fn(input):\n    ...   module, x, count = input\n    ...   return module, module(x), count - 1.0\n\n    >>> module = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    >>> x = jax.random.normal(jax.random.key(0), (10,))\n    >>> # `module` will be called three times\n    >>> _, y, _ = nnx.while_loop(\n    ...   lambda input: input[-1] > 0, fwd_fn, (module, x, 3.0))\n\n\n  Args:\n    cond_fun: A function for the continue condition of the while loop, taking a\n      single input of type ``T`` and outputting a boolean.\n    body_fun: A function that takes an input of type ``T`` and outputs an ``T``.\n      Note that both data and modules of ``T`` must have the same reference\n      structure between inputs and outputs.\n    init_val: The initial input for ``cond_fun`` and ``body_fun``. Must be of type ``T``.\n    graph: if True, use graph-mode (default). If False, use tree-mode.\n      If None, uses the value of ``nnx_graph_mode`` config.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``.\n\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if not graph or not graph_updates:\n    simple_body_fn = SimpleWhileLoopBodyFn(body_fun, graph=graph)\n    simple_cond_fn = SimpleWhileLoopCondFn(cond_fun, graph=graph)\n\n    if graph:\n      init_val = extract.to_tree2(init_val)\n    val_out = jax.lax.while_loop(simple_cond_fn, simple_body_fn, init_val)\n    val_out = extract.update_carry_variables(init_val, val_out)\n    if graph:\n      val_out = extract.from_tree2(val_out)\n    return val_out\n\n  pure_init_val = extract.to_tree(init_val, ctxtag='while_loop')\n\n  pure_init_val = _add_fake_index_mapping(pure_init_val)\n\n  pure_out = jax.lax.while_loop(\n    WhileLoopCondFn(cond_fun),\n    WhileLoopBodyFn(body_fun),\n    pure_init_val,\n  )\n  out = extract.from_tree(pure_out, ctxtag='while_loop', is_inner=False)\n  return out\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleForiLoopBodyFn:\n  f: tp.Callable[..., tp.Any]\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, i, val):\n    val_variables, _ = extract.updates_and_snapshot(val)\n    if self.graph:\n      val = extract.from_tree2(val)\n    out = self.f(i, val)\n    if self.graph:\n      out = extract.to_tree2(out)\n    extract.check_same_variables(val_variables, out, 'fori_loop')\n    return out\n\n\n@dataclasses.dataclass(eq=False)\nclass ForiLoopBodyFn:\n  f: tp.Callable[..., tp.Any]\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f)\n\n  @graphlib.update_context('fori_loop_body')\n  def __call__(self, i, pure_val_in):\n    val = extract.from_tree(pure_val_in, ctxtag='fori_loop_body', is_inner=True)\n    out = self.f(i, val)\n    pure_out = extract.to_tree(out, ctxtag='fori_loop_body')\n    return pure_out\n\n\n@graphlib.update_context('fori_loop')\ndef fori_loop(lower: int, upper: int,\n              body_fun: tp.Callable[[int, T], T],\n              init_val: T,\n              *,\n              unroll: int | bool | None = None,\n              graph: bool | None = None,\n              graph_updates: bool | None = None) -> T:\n  \"\"\"A Flax NNX transformation of `jax.lax.fori_loop <https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.fori_loop.html>`_.\n\n  Caution: for the NNX internal reference tracing mechanism to work, you cannot\n  change the variable reference structure of `init_val` inside `body_fun`.\n\n  Example::\n\n    >>> import jax\n    >>> from flax import nnx\n\n    >>> def fwd_fn(i, input):\n    ...   m, x = input\n    ...   m.kernel[...] = jnp.identity(10) * i\n    ...   return m, m(x)\n\n    >>> module = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    >>> x = jax.random.normal(jax.random.key(0), (10,))\n    >>> _, y = nnx.fori_loop(2, 4, fwd_fn, (module, x))\n    >>> np.testing.assert_array_equal(y, x * 2 * 3)\n\n\n  Args:\n    lower: An integer representing the loop index lower bound (inclusive).\n    upper: An integer representing the loop index upper bound (exclusive).\n    body_fun: a function that takes an input of type ``T`` and outputs an ``T``.\n      Note that both data and modules of ``T`` must have the same reference\n      structure between inputs and outputs.\n    init_val: the initial input for body_fun. Must be of type ``T``.\n    unroll: An optional integer or boolean that determines how much to unroll\n      the loop. If an integer is provided, it determines how many unrolled\n      loop iterations to run within a single rolled iteration of the loop. If a\n      boolean is provided, it will determine if the loop is competely unrolled\n      (i.e. ``unroll=True``) or left completely unrolled (i.e. ``unroll=False``).\n      This argument is only applicable if the loop bounds are statically known.\n    graph: if True, use graph-mode (default). If False, use tree-mode.\n      If None, uses the value of ``nnx_graph_mode`` config.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``.\n\n  Returns:\n    A loop value from the final iteration, of type ``T``.\n\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if not graph or not graph_updates:\n    simple_body_fn = SimpleForiLoopBodyFn(body_fun, graph=graph)\n\n    if graph:\n      init_val = extract.to_tree2(init_val)\n    val_out = jax.lax.fori_loop(\n      lower, upper,\n      simple_body_fn,\n      init_val,\n      unroll=unroll,\n    )\n    val_out = extract.update_carry_variables(init_val, val_out)\n    if graph:\n      val_out = extract.from_tree2(val_out)\n    return val_out\n\n  pure_init_val = extract.to_tree(init_val, ctxtag='fori_loop')\n  body = ForiLoopBodyFn(body_fun)\n  pure_out = jax.eval_shape(body, lower, pure_init_val)\n  pure_init_val = _reconsile_index_mapping(pure_init_val, pure_out)\n  pure_out = jax.lax.fori_loop(lower, upper,\n                               body, pure_init_val,\n                               unroll=unroll)\n  out = extract.from_tree(pure_out, ctxtag='fori_loop', is_inner=False)\n  return out\n"
  },
  {
    "path": "flax/nnx/transforms/transforms.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# pytype: skip-file\nfrom __future__ import annotations\n\nfrom abc import abstractmethod\nimport dataclasses\nimport functools\nimport inspect\nimport typing as tp\n\nfrom jax._src import checkify as checkify_lib\n\nfrom flax.nnx import (\n  extract,\n  graphlib,\n  variablelib,\n)\nfrom flax.nnx.module import Module\nfrom flax.nnx.proxy_caller import (\n  CallableProxy,\n  DelayedAccessor,\n)\nfrom flax.nnx.transforms import general\nfrom flax.typing import MISSING, Leaf, Missing\nimport jax\nimport jax.core\nimport jax.stages\n\nA = tp.TypeVar('A')\nC = tp.TypeVar('C')\nB = tp.TypeVar('B')\nF = tp.TypeVar('F', bound=tp.Callable[..., tp.Any])\nG = tp.TypeVar('G', bound=tp.Callable[..., tp.Any])\nM = tp.TypeVar('M', bound=Module)\nMA = tp.TypeVar('MA', bound=Module)\nN = tp.TypeVar('N', bound=Module)\nStrInt = tp.TypeVar('StrInt', str, int)\nAxisName = tp.Hashable\nLeaves = list[Leaf]\nIndex = int\n\n\n@tp.overload\ndef resolve_kwargs(\n  fun: tp.Callable[..., tp.Any],\n  args: tuple,\n  kwargs: dict[str, tp.Any],\n) -> tuple: ...\n@tp.overload\ndef resolve_kwargs() -> tp.Callable[[F], F]: ...\ndef resolve_kwargs(\n  fun: tp.Callable[..., tp.Any] | Missing = MISSING,\n  args: tuple | Missing = MISSING,\n  kwargs: dict[str, tp.Any] | Missing = MISSING,\n) -> tuple | tp.Callable[[F], F]:\n  if isinstance(fun, Missing):\n\n    def resolve_kwargs_decorator(f):\n      @functools.wraps(f)\n      def resolve_kwargs_wrapper(*args, **kwargs):\n        args = resolve_kwargs(f, args, kwargs)\n        return f(*args)\n\n      return resolve_kwargs_wrapper\n\n    return resolve_kwargs_decorator  # type: ignore\n\n  if isinstance(args, Missing):\n    raise ValueError('args must be provided')\n  if isinstance(kwargs, Missing):\n    raise ValueError('kwargs must be provided')\n\n  if isinstance(fun, functools.partial):\n    # functools.partial should have an opaque signature.\n    fun = lambda *args, **kwargs: None\n  ba = inspect.signature(fun).bind(*args, **kwargs)\n  ba.apply_defaults()\n  if ba.kwargs:\n    raise TypeError('keyword arguments could not be resolved to positions')\n  else:\n    return ba.args\n\n\n\n# -------------------------------\n# helper utilities for bound methods & indices\n# -------------------------------\n\ndef _resolve_bound_callable(\n  f: tp.Callable[..., tp.Any],\n) -> tuple[tp.Callable[..., tp.Any], tp.Any | None, bool]:\n  \"\"\"Detects and extracts bound methods from NNX Module callables.\n\n  This function unwraps functools.partial layers to reach the underlying\n  callable before checking if it's a bound method of an NNX Module.\n\n  Args:\n    f: A callable that may be a bound method of an NNX Module, potentially\n       wrapped in functools.partial.\n\n  Returns:\n    A tuple of (unbound_fn, bound_self, was_bound) where:\n    - unbound_fn: The unbound function (or original if not bound)\n    - bound_self: The Module instance if f was bound, None otherwise\n    - was_bound: True if f was a bound method, False otherwise\n\n  Note:\n    Preserves functools.partial wrappers around the callable and follows\n    the same detection pattern as _get_unbound_fn in bridge/module.py.\n    Detection occurs before any argnum shifting or index normalization.\n  \"\"\"\n  # Unwrap functools.partial layers to reach the underlying callable.\n  partials: list[tuple[tuple[tp.Any, ...], dict[str, tp.Any] | None]] = []\n  g = f\n  while isinstance(g, functools.partial):  # type: ignore[arg-type]\n    partials.append((g.args or (), g.keywords))  # type: ignore[attr-defined]\n    g = g.func  # type: ignore[attr-defined]\n\n  bound_self = getattr(g, \"__self__\", None)\n  was_bound = bool(inspect.ismethod(g) and isinstance(bound_self, Module))\n  if was_bound:\n    g = g.__func__  # type: ignore[attr-defined]\n\n  # Reapply partials in reverse unwrap order.\n  for args, kwargs in reversed(partials):\n    kwargs = {} if kwargs is None else kwargs\n    g = functools.partial(g, *args, **kwargs)\n\n  return g, (bound_self if was_bound else None), was_bound\n\n\ndef _raise_bound_method_error(transform_name: str):\n  \"\"\"Raises a standardized error for bound method usage with NNX transforms.\n\n  Args:\n    transform_name: Name of the transform (e.g., 'grad', 'jit', 'remat').\n  \"\"\"\n  raise ValueError(\n    f\"nnx.{transform_name} does not support bound methods. \"\n    f\"Use the decorator form @nnx.{transform_name} or call \"\n    f\"nnx.{transform_name}(MyClass.method)(instance, ...) with the unbound method.\"\n  )\n\n\nclass LiftedModule(tp.Generic[M], Module):  # type: ignore[ignored-abstractmethod]\n  @abstractmethod\n  def _call(self, accessor: DelayedAccessor, *args, **kwargs) -> tp.Any:\n    pass\n\n  @property\n  @abstractmethod\n  def _submodule(self) -> M:\n    pass  # type: ignore[bad-return-type] # why pytype?\n\n  def __call__(self, *args, **kwargs) -> tp.Any:\n    return self.call(*args, **kwargs)  # type: ignore\n\n  @property\n  def call(self) -> tp.Any:\n    module = self\n\n    def check_and_call(accessor: DelayedAccessor, *args, **kwargs):\n      return self._call(accessor, *args, **kwargs)\n\n    proxy = CallableProxy(check_and_call)  # type: ignore[arg-type]\n\n    while isinstance(module._submodule, LiftedModule):\n      module = module._submodule\n      proxy = proxy.call\n\n    return proxy  # type: ignore\n\n\n# -------------------------------\n# simple transforms\n# -------------------------------\n@dataclasses.dataclass(frozen=True)\nclass ValueMetadata:\n  var_type: type[variablelib.Variable]\n  value: tp.Any\n  metadata: dict[str, tp.Any]\n\n\ndef _flatten_value_metadata(\n  value_metadata: tp.Union[tp.Any, ValueMetadata],\n):\n  metadata = tuple(sorted(value_metadata.metadata.items()))\n  return (value_metadata.value,), (value_metadata.var_type, metadata)\n\n\ndef _unflatten_value_metadata(aux_data, children):\n  var_type, metadata_items = aux_data\n  metadata = dict(metadata_items)\n  return ValueMetadata(var_type=var_type, value=children[0], metadata=metadata)\n\n\njax.tree_util.register_pytree_node(\n  ValueMetadata,\n  _flatten_value_metadata,\n  _unflatten_value_metadata,\n)\n\n\ndef _to_value_metadata(node):\n  def to_value_metadata(x):\n    if isinstance(x, variablelib.Variable):\n      value = x.get_raw_value()\n      if variablelib.is_array_ref(value):\n        value = value[...]\n      metadata = x.get_metadata()\n      return ValueMetadata(var_type=x.var_type, value=value, metadata=metadata)\n    return x\n\n  return jax.tree.map(\n    to_value_metadata,\n    node,\n    is_leaf=lambda x: isinstance(x, variablelib.Variable),\n  )\n\n\ndef _to_variable(node):\n  # import here to avoid circular imports\n  from flax.nnx.spmd import get_var_pspec\n\n  def to_variable(x):\n    if isinstance(x, ValueMetadata):\n      var = x.var_type._new(x.value, x.metadata)\n\n      global_mesh = jax.sharding.get_abstract_mesh()\n      if global_mesh.axis_sizes == ():\n        global_mesh = None\n      mesh = var.get_metadata(\"mesh\", None) or global_mesh\n      if mesh is not None and (not hasattr(var, 'sharding') or var.sharding is None):\n        pspec = get_var_pspec(var)\n        sharding = jax.sharding.NamedSharding(mesh=mesh, spec=pspec)\n        var.set_value(jax.ShapeDtypeStruct(shape=var.shape, dtype=var.dtype, sharding=sharding))\n      return var\n    return x\n\n  return jax.tree.map(\n    to_variable, node, is_leaf=lambda x: isinstance(x, ValueMetadata)\n  )\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleEvalShapeFn:\n  f: tp.Callable[..., tp.Any]\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args, **kwargs):\n    if self.graph:\n      args, kwargs = extract.from_tree2((args, kwargs))\n    out = self.f(*args, **kwargs)\n    if self.graph:\n      out = extract.to_tree2(out)\n    extract.check_no_aliases('eval_shape', args=args, kwargs=kwargs, out=out)\n    return out\n\n\ndef eval_shape(\n  f: tp.Callable[..., A],\n  *args: tp.Any,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n  **kwargs: tp.Any,\n) -> A:\n  \"\"\"A \\\"lifted\\\" version of `jax.eval_shape <https://jax.readthedocs.io/en/latest/_autosummary/jax.eval_shape.html#jax.eval_shape>`_\n    that can handle `flax.nnx.Module <https://flax.readthedocs.io/en/latest/api_reference/flax.nnx/module.html#flax.nnx.Module>`_\n    / graph nodes as arguments.\n\n  Similar to ``jax.eval_shape``, it computes the shape/dtype of a function `f` without\n    performing any floating point operations (FLOPs) which can be expensive. This can be\n    useful for performing shape inference, for example. Unlike `jax.eval_shape`,\n    `nnx.eval_shape` will automatically compute the expected sharding based on Flax sharding metadata\n    for all Variables not using explicit sharding.\n\n  Args:\n    f: the function to evaluate.\n    *args: positional arguments to ``f``.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references and reference semantics.\n      If ``False``, uses tree-mode which treats Modules as regular JAX\n      pytrees, avoiding the overhead of the graph protocol.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``.\n    **kwargs: keyword arguments to ``f``.\n\"\"\"\n  f_call, _, was_bound = _resolve_bound_callable(f)\n\n  if was_bound:\n    _raise_bound_method_error('eval_shape')\n\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if not graph or not graph_updates:\n    if graph:\n      args, kwargs = extract.to_tree2((args, kwargs))\n    extract.check_no_aliases('eval_shape', args=args, kwargs=kwargs)\n    out = jax.eval_shape(\n      SimpleEvalShapeFn(f_call, graph=graph), *args, **kwargs\n    )\n    if graph:\n      out = extract.from_tree2(out)\n    return out\n\n  args, kwargs = extract.to_tree((args, kwargs))\n\n  @functools.wraps(f)\n  def _eval_shape_fn(*args, **kwargs):\n    args, kwargs = extract.from_tree((args, kwargs))\n    out = f_call(*args, **kwargs)\n    return _to_value_metadata(extract.to_tree(out))\n\n  out = jax.eval_shape(_eval_shape_fn, *args, **kwargs)\n  return extract.from_tree(_to_variable(out))\n\n@dataclasses.dataclass(eq=False)\nclass CheckifyFn:\n  f: tp.Callable[..., tp.Any]\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f)\n\n  def __call__(self, *pure_args, **pure_kwargs):\n    args, kwargs = extract.from_tree(\n      (pure_args, pure_kwargs), ctxtag='checkify', is_inner=True\n    )\n    out = self.f(*args, **kwargs)\n\n    args_out, kwargs_out = extract.clear_non_graph_nodes((args, kwargs))\n    pure_args_out, pure_kwargs_out, pure_out = extract.to_tree(\n      (args, kwargs, out), ctxtag='checkify'\n    )\n    return pure_args_out, pure_kwargs_out, pure_out\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleCheckifyFn:\n  f: tp.Callable[..., tp.Any]\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args):\n    updates, snapshot = extract.updates_and_snapshot(args)\n    if self.graph:\n      args = extract.from_tree2(args)\n    out = self.f(*args)\n    if self.graph:\n      out = extract.to_tree2(out)\n    extract.check_no_aliases('checkify', args=updates, out=out)\n    updates = extract.mask_variable_updates(updates, snapshot)\n    return out, updates\n\ndef checkify(\n  f: tp.Callable[..., checkify_lib.Out],\n  errors: frozenset[type[checkify_lib.JaxException]] = checkify_lib.user_checks,  # type: ignore\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> tp.Callable[..., tuple[checkify_lib.Error, checkify_lib.Out]]:\n  \"\"\"Reference-aware version of `jax.experimental.checkify\n  <https://flax.readthedocs.io/en/latest/nnx_basics.html#the-flax-functional-api>`_.\n\n  Example::\n\n    >>> import jax\n    >>> import jax.numpy as jnp\n    >>> from jax.experimental import checkify\n    >>> import dataclasses\n    >>> from flax import nnx\n    ...\n    >>> class Foo(nnx.Module):\n    ...   def __init__(self, a):\n    ...     self.a = nnx.Param(a)\n    ...\n    >>> @nnx.jit\n    ... def f(m):\n    ...   y = jnp.sin(m.a) # error\n    ...   return m.a + y\n    ...\n    >>> m = Foo(a=jnp.inf)\n    >>> err, out = nnx.checkify(f, errors=checkify.float_checks)(m)\n    >>> # err.throw()\n    >>> print(err)\n    Error(nan generated by primitive: sin.)\n\n  Args:\n    f: the function to checkify.\n    errors: the set of error checks to enable.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references and reference semantics.\n      If ``False``, uses tree-mode which treats Modules as regular JAX\n      pytrees, avoiding the overhead of the graph protocol.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``.\n  \"\"\"\n  f_call, _, was_bound = _resolve_bound_callable(f)\n\n  if was_bound:\n    _raise_bound_method_error('checkify')\n\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if not graph or not graph_updates:\n    checkify_fn = checkify_lib.checkify(\n      SimpleCheckifyFn(f_call, graph=graph), errors,\n    )\n\n    @functools.wraps(f)\n    def simple_checkify_wrapper(*args):\n      if graph:\n        args = extract.to_tree2(args)\n      extract.check_no_aliases('checkify', args=args)\n      error, (out, updates) = checkify_fn(*args)\n      if graph:\n        out = extract.from_tree2(out)\n      extract.apply_variable_updates(args, updates)\n      return error, out\n\n    return simple_checkify_wrapper  # type: ignore\n\n  checkify_fn = checkify_lib.checkify(CheckifyFn(f_call), errors)\n  @functools.wraps(f)\n  @graphlib.update_context('checkify')\n  def checkify_wrapper(*args, **kwargs):\n    pure_args, pure_kwargs = extract.to_tree(\n      (args, kwargs),\n      ctxtag='checkify',\n    )\n    error, (pure_args_out, pure_kwargs_out, pure_out) = checkify_fn(\n      *pure_args, **pure_kwargs\n    )\n\n    args_out, kwargs_out, out = extract.from_tree(\n      (pure_args_out, pure_kwargs_out, pure_out),\n      ctxtag='checkify',\n      is_inner=False,\n    )\n\n    return error, out\n\n  return checkify_wrapper  # type: ignore\n\n\n@dataclasses.dataclass(eq=False)\nclass SimpleCondFn:\n  f: tp.Callable[..., tp.Any]\n  graph: bool\n\n  def __post_init__(self):\n    functools.update_wrapper(self, self.f, updated=())\n\n  @extract.treemap_copy_args\n  def __call__(self, *args):\n    updates, _snapshot = extract.updates_and_snapshot(args)\n    if self.graph:\n      args = extract.from_tree2(args)\n    out = self.f(*args)\n    if self.graph:\n      out = extract.to_tree2(out)\n    extract.check_no_aliases('switch', args=updates, out=out)\n    return out, updates\n\n\ndef cond(\n  pred,\n  true_fun: tp.Callable[..., A],\n  false_fun: tp.Callable[..., A],\n  *operands,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> A:\n  \"\"\"Conditionally apply ``true_fun`` or ``false_fun``.\n\n  Wraps `jax.lax.cond <https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.cond.html>`__\n  to support Flax NNX modules and variables.\n\n  Args:\n    pred: boolean scalar. If True, ``true_fun`` is applied, otherwise\n      ``false_fun``.\n    true_fun: function to apply if ``pred`` is True.\n    false_fun: function to apply if ``pred`` is False.\n    *operands: operands passed to whichever branch is selected.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references and reference semantics.\n      If ``False``, uses tree-mode which treats Modules as regular JAX\n      pytrees, avoiding the overhead of the graph protocol.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``.\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if not graph or not graph_updates:\n    if graph:\n      operands = extract.to_tree2(operands)\n    extract.check_no_aliases('cond', operands=operands)\n    out, updates = jax.lax.cond(\n      pred,\n      SimpleCondFn(true_fun, graph=graph),\n      SimpleCondFn(false_fun, graph=graph),\n      *operands,\n    )\n    if graph:\n      out = extract.from_tree2(out)\n    extract.apply_variable_updates(operands, updates)\n    return out\n\n  @general.split_inputs(ctxtag='cond')\n  def _cond(pred, true_fun, false_fun, *operands):\n    return jax.lax.cond(\n      pred,\n      general.merge_inputs(true_fun, ctxtag='cond'),\n      general.merge_inputs(false_fun, ctxtag='cond'),\n      *operands,\n    )\n\n  return _cond(pred, true_fun, false_fun, *operands)\n\n\ndef switch(\n  index,\n  branches: tp.Sequence[tp.Callable[..., A]],\n  *operands,\n  graph: bool | None = None,\n  graph_updates: bool | None = None,\n) -> A:\n  \"\"\"Select and apply one of ``branches`` based on ``index``.\n\n  Wraps `jax.lax.switch <https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.switch.html>`__\n  to support Flax NNX modules and variables.\n\n  Args:\n    index: integer scalar indicating which branch to apply.\n    branches: sequence of functions to select from.\n    *operands: operands passed to the selected branch.\n    graph: If ``True`` (default), uses graph-mode which supports the full\n      NNX feature set including shared references and reference semantics.\n      If ``False``, uses tree-mode which treats Modules as regular JAX\n      pytrees, avoiding the overhead of the graph protocol.\n    graph_updates: If ``True``, propagates updates on graph structure\n      that happen inside the transform to the input graphs, has no\n      effect when ``graph=False``.\n  \"\"\"\n  if graph is None:\n    graph = graphlib.set_graph_mode.current_value()\n  if graph_updates is None:\n    graph_updates = graphlib.set_graph_updates.current_value()\n  if not graph or not graph_updates:\n    if graph:\n      operands = extract.to_tree2(operands)\n    extract.check_no_aliases('switch', operands=operands)\n    out, updates = jax.lax.switch(\n      index,\n      [SimpleCondFn(f, graph=graph) for f in branches],\n      *operands,\n    )\n    if graph:\n      out = extract.from_tree2(out)\n    extract.apply_variable_updates(operands, updates)\n    return out\n\n  @general.split_inputs(ctxtag='switch')\n  def _switch(index, branches, *operands):\n    return jax.lax.switch(\n      index,\n      [general.merge_inputs(f, ctxtag='switch') for f in branches],\n      *operands,\n    )\n\n  return _switch(index, branches, *operands)\n"
  },
  {
    "path": "flax/nnx/traversals.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Utilities for flattening and unflattening mappings.\n\"\"\"\nfrom __future__ import annotations\n\nfrom collections.abc import Callable, Mapping\nfrom collections.abc import Iterable, Sequence\nfrom typing import Any, overload\n\nfrom flax import struct\n\n\n# the empty node is a struct.dataclass to be compatible with JAX.\n@struct.dataclass\nclass _EmptyNode:\n  pass\n\n\nempty_node = _EmptyNode()\n\n\n# TODO: In Python 3.10, use TypeAlias.\nIsLeafCallable = Callable[[tuple[Any, ...], Mapping[Any, Any]], bool]\n\n\n@overload\ndef flatten_mapping(xs: Mapping[Any, Any],\n                    /,\n                    *,\n                    keep_empty_nodes: bool = False,\n                    is_leaf: None | IsLeafCallable = None,\n                    sep: None = None\n                    ) -> dict[tuple[Any, ...], Any]:\n  ...\n\n@overload\ndef flatten_mapping(xs: Mapping[Any, Any],\n                    /,\n                    *,\n                    keep_empty_nodes: bool = False,\n                    is_leaf: None | IsLeafCallable = None,\n                    sep: str,\n                    ) -> dict[str, Any]:\n  ...\n\ndef flatten_mapping(xs: Mapping[Any, Any],\n                    /,\n                    *,\n                    keep_empty_nodes: bool = False,\n                    is_leaf: None | IsLeafCallable = None,\n                    sep: None | str = None\n                    ) -> dict[Any, Any]:\n  \"\"\"Flatten a nested mapping.\n\n  The nested keys are flattened to a tuple. See ``unflatten_mapping`` on how to\n  restore the nested mapping.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> xs = {'foo': 1, 'bar': {'a': 2, 'b': {}}}\n    >>> flat_xs = nnx.traversals.flatten_mapping(xs)\n    >>> flat_xs\n    {('foo',): 1, ('bar', 'a'): 2}\n\n  Note that empty mappings are ignored and will not be restored by\n  ``unflatten_mapping``.\n\n  Args:\n    xs: a nested mapping\n    keep_empty_nodes: replaces empty mappings with\n      ``traverse_util.empty_node``.\n    is_leaf: an optional function that takes the next nested mapping and nested\n      keys and returns True if the nested mapping is a leaf (i.e., should not be\n      flattened further).\n    sep: if specified, then the keys of the returned mapping will be\n      ``sep``-joined strings (if ``None``, then keys will be tuples).\n  Returns:\n    The flattened mapping.\n  \"\"\"\n  assert isinstance(\n    xs, Mapping\n  ), f'expected Mapping; got {type(xs).__qualname__}'\n\n  def _key(path: tuple[Any, ...]) -> tuple[Any, ...] | str:\n    if sep is None:\n      return path\n    return sep.join(path)\n\n  def _flatten(xs: Any, prefix: tuple[Any, ...]) -> dict[Any, Any]:\n    if not isinstance(xs, Mapping) or (\n      is_leaf and is_leaf(prefix, xs)\n    ):\n      return {_key(prefix): xs}\n    result = {}\n    is_empty = True\n    for key, value in xs.items():\n      is_empty = False\n      path = prefix + (key,)\n      result.update(_flatten(value, path))\n    if keep_empty_nodes and is_empty:\n      if prefix == ():  # when the whole input is empty\n        return {}\n      return {_key(prefix): empty_node}\n    return result\n\n  return _flatten(xs, ())\n\ndef flatten_to_sequence(\n  xs: Mapping[Any, Any],\n  /,\n  *,\n  is_leaf: IsLeafCallable | None = None,\n) -> list[tuple[Any, Any]]:\n  \"\"\"Flatten a nested mapping.\n\n  The nested keys are flattened to a tuple. See ``unflatten_mapping`` on how to\n  restore the nested mapping.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> xs = {'foo': 1, 'bar': {'a': 2, 'b': {}}}\n    >>> flat_xs = nnx.traversals.flatten_to_sequence(xs)\n    >>> flat_xs\n    [(('foo',), 1), (('bar', 'a'), 2)]\n\n  Note that empty mappings are ignored and will not be restored by\n  ``unflatten_mapping``.\n\n  Args:\n    xs: a nested mapping\n    is_leaf: an optional function that takes the next nested mapping and nested\n      keys and returns True if the nested mapping is a leaf (i.e., should not be\n      flattened further).\n\n  Returns:\n    The flattened mapping.\n  \"\"\"\n  assert isinstance(\n    xs, Mapping\n  ), f'expected Mapping; got {type(xs).__qualname__}'\n  result = []\n\n  def _flatten(xs: Any, prefix: tuple[Any, ...]):\n    if not isinstance(xs, Mapping) or (is_leaf and is_leaf(prefix, xs)):\n      result.append((prefix, xs))\n    else:\n      for key, value in xs.items():\n        _flatten(value, (*prefix, key))\n\n  _flatten(xs, ())\n  return result\n\n\n@overload\ndef unflatten_mapping(\n    xs: Sequence[tuple[tuple[Any, ...], Any]], /, *, sep: None = None\n) -> dict[Any, Any]:\n  ...\n\n\n@overload\ndef unflatten_mapping(\n    xs: Mapping[tuple[Any, ...], Any], /, *, sep: None = None\n) -> dict[Any, Any]:\n  ...\n\n\n@overload\ndef unflatten_mapping(xs: Mapping[str, Any], /, *, sep: str) -> dict[Any, Any]:\n  ...\n\n\ndef unflatten_mapping(xs: Any, /, *, sep: str | None = None) -> dict[Any, Any]:\n  \"\"\"Unflatten a mapping.\n\n  See ``flatten_mapping``\n\n  Example::\n\n    >>> from flax import nnx\n    >>> flat_xs = {\n    ...   ('foo',): 1,\n    ...   ('bar', 'a'): 2,\n    ... }\n    >>> xs = nnx.traversals.unflatten_mapping(flat_xs)\n    >>> xs\n    {'foo': 1, 'bar': {'a': 2}}\n\n  Args:\n    xs: a flattened mapping.\n    sep: separator (same as used with ``flatten_mapping()``).\n  Returns:\n    The nested mapping.\n  \"\"\"\n  if isinstance(xs, Mapping):\n    xs = xs.items()\n\n  if not isinstance(xs, Iterable):\n    raise TypeError(\n      f'expected Mapping or Iterable; got {type(xs).__qualname__}'\n    )\n  result: dict[Any, Any] = {}\n  for path, value in xs:\n    if sep is not None:\n      path = path.split(sep)  # type: ignore\n    if value is empty_node:\n      value = {}\n    cursor = result\n    for key in path[:-1]:\n      if key not in cursor:\n        cursor[key] = {}\n      cursor = cursor[key]\n    cursor[path[-1]] = value\n  return result\n"
  },
  {
    "path": "flax/nnx/variablelib.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# pytype: skip-file\nfrom __future__ import annotations\n\nimport dataclasses\nimport functools\nfrom functools import partial\nimport itertools as it\nimport threading\nimport typing as tp\nfrom typing import Any\nimport warnings\n\nfrom flax import config\nfrom flax import errors\nfrom flax.core import spmd as core_spmd\nfrom flax.nnx import reprlib, tracers, visualization\nfrom flax.typing import BaseConfigContext, MISSING, Missing, SizeBytes\nimport jax\nfrom jax._src.state.types import AbstractRef\nimport jax.experimental\nfrom jax.experimental import hijax as hjx\nimport jax.tree_util as jtu\nimport treescope  # type: ignore[import-untyped]\n\nA = tp.TypeVar('A')\nB = tp.TypeVar('B')\nC = tp.TypeVar('C')\nF = tp.TypeVar('F', bound=tp.Callable[..., tp.Any])\nP = tp.TypeVar('P', bound=property)\nV = tp.TypeVar('V', bound='Variable[Any]')\nGetValueHook = tp.Callable[['Variable[A]', A], A]\nSetValueHook = tp.Callable[['Variable[A]', A], A]\nCreateValueHook = tp.Callable[['Variable[A]', A], A]\nAxisName = str\nAxisIndex = int\nAddAxisHook = tp.Callable[[V, AxisIndex, AxisName | None], None]\nRemoveAxisHook = tp.Callable[[V, AxisIndex, AxisName | None], None]\n\n# JAX array refs were renamed a few times between JAX v0.7.0 and v0.8.0.\n# The following ensures we avoid an ImportError or DeprecationWarning.\nif hasattr(jax, 'new_ref') and hasattr(jax, 'Ref'):\n  # JAX v0.7.2 or newer\n  from jax import Ref\nelif hasattr(jax, 'array_ref') and hasattr(jax, 'ArrayRef'):\n  # JAX v0.7.1\n  from jax import ArrayRef as Ref  # type: ignore[import-untyped,no-redef]\nelse:\n  # JAX v0.7.0 or older\n  from jax.experimental import MutableArray as Ref  # type: ignore[no-redef]\n\n\n@dataclasses.dataclass\nclass VariableContext(threading.local):\n  variable_hijax_stack: list[bool] = dataclasses.field(default_factory=list)\n  variable_ref_stack: list[bool] = dataclasses.field(default_factory=list)\n  eager_shard_stack: list[bool] = dataclasses.field(default_factory=list)\n\n\nVARIABLE_CONTEXT = VariableContext()\n\n\nclass use_eager_sharding(BaseConfigContext):\n  \"\"\"Sets whether Variables should use eager sharding by default or not.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> # Use eager sharding by default\n    >>> nnx.use_eager_sharding(True)\n    <...>\n    >>> # Variable will now use eager sharding\n    >>> nnx.using_eager_sharding()\n    True\n\n  It can also be used as a context manager to temporarily\n  change the default behavior for a block of code::\n\n    >>> nnx.use_eager_sharding(False)\n    <...>\n    >>> with nnx.use_eager_sharding(True):\n    ...   nnx.using_eager_sharding()\n    True\n    >>> # it will reset outside\n    >>> v = nnx.Variable(jax.numpy.ones((2, 3)))\n    >>> nnx.using_eager_sharding()\n    False\n\n  Args:\n    value: A boolean indicating if Variables should use eager sharding by default.\n\n  Returns:\n    A context manager that resets the context to the previous value.\n  \"\"\"\n  get_default = classmethod(lambda cls: config.flax_always_shard_variable)\n  get_stack = classmethod(lambda cls: VARIABLE_CONTEXT.eager_shard_stack)\n\n\ndef using_eager_sharding() -> bool:\n  \"\"\"Returns whether Variables are using eager sharding by default.\n\n  Example::\n\n    >>> from flax import nnx\n    >>> nnx.use_eager_sharding(True)\n    <...>\n    >>> nnx.using_eager_sharding()\n    True\n    >>> nnx.use_eager_sharding(False)\n    <...>\n    >>> nnx.using_eager_sharding()\n    False\n\n\n  Returns:\n    A boolean indicating if Variables are using eager sharding by default.\n  \"\"\"\n  return use_eager_sharding.current_value()\n\n\n@dataclasses.dataclass(frozen=True)\nclass VarDefaults(tp.Mapping[str, tp.Any]):\n  hijax: bool\n  ref: bool\n\n  def __getitem__(self, key: str) -> tp.Any:\n    return getattr(self, key)\n\n  def __iter__(self) -> tp.Iterator[str]:\n    return iter(dataclasses.asdict(self))\n\n  def __len__(self) -> int:\n    return len(dataclasses.fields(self))\n\n\n@tp.overload\ndef var_defaults() -> VarDefaults: ...\n\n\n@tp.overload\ndef var_defaults(\n  *, hijax: bool | None = None, ref: bool | None = None\n) -> VarDefaultsContext: ...\n\n\ndef var_defaults(\n  *, hijax: bool | None = None, ref: bool | None = None\n) -> VarDefaultsContext | VarDefaults:\n  if hijax is None and ref is None:\n    return VarDefaults(\n      hijax=VARIABLE_CONTEXT.variable_hijax_stack[-1]\n      if VARIABLE_CONTEXT.variable_hijax_stack\n      else config.flax_hijax_variable,\n      ref=VARIABLE_CONTEXT.variable_ref_stack[-1]\n      if VARIABLE_CONTEXT.variable_ref_stack\n      else False,\n    )\n\n  hijax_prev = None\n  if hijax is not None:\n    if VARIABLE_CONTEXT.variable_hijax_stack:\n      hijax_prev = VARIABLE_CONTEXT.variable_hijax_stack[-1]\n      VARIABLE_CONTEXT.variable_hijax_stack[-1] = hijax\n    else:\n      VARIABLE_CONTEXT.variable_hijax_stack.append(hijax)\n\n  ref_prev = None\n  if ref is not None:\n    if VARIABLE_CONTEXT.variable_ref_stack:\n      ref_prev = VARIABLE_CONTEXT.variable_ref_stack[-1]\n      VARIABLE_CONTEXT.variable_ref_stack[-1] = ref\n    else:\n      VARIABLE_CONTEXT.variable_ref_stack.append(ref)\n\n  return VarDefaultsContext(\n    hijax_prev=hijax_prev,\n    hijax_new=hijax,\n    ref_prev=ref_prev,\n    ref_new=ref,\n  )\n\n\nclass VarDefaultsContext:\n  def __init__(\n    self,\n    *,\n    hijax_prev: bool | None,\n    hijax_new: bool | None,\n    ref_prev: bool | None,\n    ref_new: bool | None,\n  ):\n    self.hijax_prev = hijax_prev\n    self.hijax_new = hijax_new\n    self.ref_prev = ref_prev\n    self.ref_new = ref_new\n\n  def __enter__(self):\n    if self.hijax_new is not None and self.hijax_prev is not None:\n      VARIABLE_CONTEXT.variable_hijax_stack.insert(-1, self.hijax_prev)\n    if self.ref_new is not None and self.ref_prev is not None:\n      VARIABLE_CONTEXT.variable_ref_stack.insert(-1, self.ref_prev)\n\n  def __exit__(self, exc_type, exc_value, traceback):\n    if self.hijax_new is not None:\n      VARIABLE_CONTEXT.variable_hijax_stack.pop()\n    if self.ref_new is not None:\n      VARIABLE_CONTEXT.variable_ref_stack.pop()\n\n  def __call__(self, f: F) -> F:\n    # undo stack change for decorator usage\n    if self.hijax_new is not None:\n      VARIABLE_CONTEXT.variable_hijax_stack.pop()\n      if self.hijax_prev is not None:\n        VARIABLE_CONTEXT.variable_hijax_stack.append(self.hijax_prev)\n\n    if self.ref_new is not None:\n      VARIABLE_CONTEXT.variable_ref_stack.pop()\n      if self.ref_prev is not None:\n        VARIABLE_CONTEXT.variable_ref_stack.append(self.ref_prev)\n\n    @functools.wraps(f)\n    def var_defaults_wrapper(*args, **kwargs):\n      if self.hijax_new is not None:\n        VARIABLE_CONTEXT.variable_hijax_stack.append(self.hijax_new)\n      if self.ref_new is not None:\n        VARIABLE_CONTEXT.variable_ref_stack.append(self.ref_new)\n      try:\n        return f(*args, **kwargs)\n      finally:\n        if self.hijax_new is not None:\n          VARIABLE_CONTEXT.variable_hijax_stack.pop()\n        if self.ref_new is not None:\n          VARIABLE_CONTEXT.variable_ref_stack.pop()\n\n    return var_defaults_wrapper  # type: ignore[return-value]\n\n\ndef is_array_ref(x) -> tp.TypeGuard[Ref]:\n  return isinstance(x, jax.Array | AbstractRef | Ref) and isinstance(\n    jax.typeof(x), AbstractRef | Ref\n  )\n\n\n@dataclasses.dataclass\nclass VariableMetadata(tp.Generic[A]):\n  raw_value: A\n  set_value_hooks: tuple[SetValueHook[A], ...] = ()\n  get_value_hooks: tuple[GetValueHook[A], ...] = ()\n  create_value_hooks: tuple[CreateValueHook[A], ...] = ()\n  add_axis_hooks: tuple[AddAxisHook[Variable[A]], ...] = ()\n  remove_axis_hooks: tuple[RemoveAxisHook[Variable[A]], ...] = ()\n  metadata: tp.Mapping[str, tp.Any] = dataclasses.field(default_factory=dict)\n\n\nPyTreeDef = tp.Any\nLeaf = tp.Any\n\n# ---------------------------------\n# hijax\n# ---------------------------------\n\n\n@dataclasses.dataclass(frozen=True)\nclass VariableQDD:\n  leaf_avals: tuple[hjx.AbstractValue, ...]\n  treedef: PyTreeDef\n  var_type: type[Variable[Any]]\n\n  def to_tangent_qdd(self):\n    leaf_avals = tuple(a.to_tangent_aval() for a in self.leaf_avals)\n    return VariableQDD(leaf_avals, self.treedef, self.var_type)\n\n  def normalize(self):\n    leaf_types = tuple(a.normalize() for a in self.leaf_avals)\n    return VariableQDD(leaf_types, self.treedef, self.var_type)\n\nclass VariableEffect(jax.core.Effect): ...\n\n\nvariable_effect = VariableEffect()\nhjx.control_flow_allowed_effects.add_type(VariableEffect)\n\n\ndef _bind_new_variable(\n    *leaves, treedef, var_type, has_qdd, ref\n) -> HijaxVariable:\n  \"\"\"Binds new_variable_p after instantiating any Zero tangents.\"\"\"\n  leaves = tuple(hjx.instantiate_zeros(leaf) for leaf in leaves)\n  return new_variable_p.bind(\n    *leaves,\n    treedef=treedef,\n    var_type=var_type,\n    has_qdd=has_qdd,\n    ref=ref,\n  )\n\n\ndef _new_hijax_from_variable(variable: Variable) -> HijaxVariable:\n  has_qdd = not variable.ref\n  leaves, treedef = jax.tree.flatten(variable)\n  var_type = type(variable)\n  hijax_var = _bind_new_variable(\n    *leaves,\n    treedef=treedef,\n    var_type=var_type,\n    has_qdd=has_qdd,\n    ref=variable.ref,\n  )\n  return hijax_var\n\n\nclass NewVariable(hjx.HiPrimitive):\n  def is_high(self, *leaves, treedef, var_type, has_qdd, ref) -> bool:\n    return True  # type: ignore\n\n  def impl(self, *leaves, treedef, var_type, has_qdd, ref):\n    return HijaxVariable._new(\n      leaves, treedef, var_type, has_qdd, ref=ref\n    )\n\n  def abstract_eval(self, *leaves, treedef, var_type, has_qdd, ref):\n    aval = AbstractVariable(\n      var_type, treedef, leaves, has_qdd, ref=ref\n    )\n    if has_qdd:\n      qdd = VariableQDD(tuple(leaves), treedef, var_type)\n      aval_qdd = hjx.AvalQDD(aval, qdd)  # type: ignore\n      return aval_qdd, {variable_effect}\n    else:\n      return aval, set()\n\n  def to_lojax(self, *leaves, treedef, var_type, has_qdd, ref):\n    return HijaxVariable._new(leaves, treedef, var_type, has_qdd, ref=ref)\n\n  def jvp(_, primals, tangents, *, treedef, var_type, has_qdd, ref):\n    if has_qdd:\n      raise NotImplementedError(\n        \"jvp not implemented for 'new_variable' with QDD\"\n      )\n    primal_hijax_var = _bind_new_variable(\n      *primals, treedef=treedef, var_type=var_type, has_qdd=has_qdd, ref=ref\n    )\n    tangent_hijax_var = _bind_new_variable(\n      *tangents, treedef=treedef, var_type=var_type, has_qdd=has_qdd, ref=ref\n    )\n    return primal_hijax_var, tangent_hijax_var\n\n  def transpose(\n    _, out_var: HijaxVariable, *input_leaves, treedef, var_type, has_qdd, ref\n  ):\n    if has_qdd:\n      raise NotImplementedError(\n        \"transpose not implemented for 'new_variable' with QDD\"\n      )\n    avals = tuple(\n      map(\n        lambda x: x.aval if hjx.is_undefined_primal(x) else jax.typeof(x),\n        input_leaves,\n      )\n    )\n    leaves_dot = get_variable_p.bind(\n      out_var,\n      treedef=treedef,\n      avals=avals,\n      var_type=var_type,\n      has_qdd=has_qdd,\n    )\n    return leaves_dot\n\n\nnew_variable_p = NewVariable(f'new_variable')\n\n\ndef _set_hijax_state(hijax_var, variable: Variable):\n  leaves, treedef = jax.tree.flatten(variable)\n  set_variable_p.bind(\n    hijax_var, *leaves, treedef=treedef, var_type=type(variable)\n  )\n\n\nclass SetVariable(hjx.HiPrimitive):\n  multiple_results = True\n\n  def is_high(_, *leaf_avals, treedef, var_type) -> bool:\n    return True  # type: ignore\n\n  # TODO: upstream this to Box\n  def impl(_, hijax_var: HijaxVariable, *leaves, treedef, var_type):\n    if not hijax_var.has_qdd:\n      raise errors.ImmutableVariableError(\n        \"Trying to update Variable with 'has_qdd=False'.\"\n      )\n    assert var_type is hijax_var._var_type\n    object.__setattr__(hijax_var, '_leaves', leaves)\n    object.__setattr__(hijax_var, '_treedef', treedef)\n    return []\n\n  def abstract_eval(\n    _, aval_mutable_qdd: hjx.AvalMutableQDD, *leaf_avals, treedef, var_type\n  ):\n    hijax_var: AbstractVariable = aval_mutable_qdd.aval  # type: ignore\n    assert isinstance(hijax_var, AbstractVariable)\n    if not hijax_var.has_qdd:\n      raise errors.ImmutableVariableError(\n        \"Trying to update Variable with 'has_qdd=False'.\"\n      )\n    assert var_type is hijax_var._var_type\n    aval_mutable_qdd.mutable_qdd.update(\n      VariableQDD(leaf_avals, treedef, var_type)\n    )\n    effects = {variable_effect} if hijax_var.has_qdd else set()\n    return [], effects  # TODO better typechecking...\n\n  def to_lojax(_, hijax_var: HijaxVariable, *leaves, treedef, var_type):\n    if not hijax_var.has_qdd:\n      raise errors.ImmutableVariableError(\n        \"Trying to update Variable with 'has_qdd=False'.\"\n      )\n    assert var_type is hijax_var._var_type\n    object.__setattr__(hijax_var, '_leaves', leaves)\n    object.__setattr__(hijax_var, '_treedef', treedef)\n    return []\n\n  def jvp(_, primals, tangents, *, treedef, var_type):\n    variable: Variable\n    variable, *vals = primals\n    variable_dot: Variable\n    variable_dot, *val_dots = tangents\n    if type(variable_dot._raw_value) is hjx.Zero:\n      raise Exception(\n        \"can't differentiate Variable._set operation, \"\n        'did you forget jax.lax.stop_gradient?'\n      )\n    set_variable_p.bind(\n      variable, *vals, treedef=treedef, var_type=type(variable)\n    )\n    set_variable_p.bind(\n      variable_dot, *val_dots, treedef=treedef, var_type=type(variable_dot)\n    )\n    return [], []\n\n  def transpose(_, *args, treedef, var_type):\n    raise NotImplementedError('transpose not implemented for SetHijaxVariable')\n\n\nset_variable_p = SetVariable(f'set_variable')\n\n\ndef _get_hijax_state(hijax_var: HijaxVariable | AbstractVariable) -> Variable:\n  if hijax_var.has_qdd:\n    tys: VariableQDD = jax.experimental.cur_qdd(hijax_var)\n    leaf_vals = get_variable_p.bind(\n      hijax_var,\n      treedef=tys.treedef,\n      avals=tuple(tys.leaf_avals),\n      var_type=hijax_var._var_type,\n      has_qdd=hijax_var.has_qdd,\n    )\n    variable = jax.tree.unflatten(tys.treedef, leaf_vals)\n  else:\n    assert hijax_var._treedef is not None\n    assert hijax_var._leaves is not None\n    if isinstance(hijax_var, (jax.core.Tracer, AbstractVariable)):\n      leaf_avals = hijax_var._leaves\n    else:\n      leaf_avals = tuple(map(jax.typeof, hijax_var._leaves))\n    leaf_vals = get_variable_p.bind(\n      hijax_var,\n      treedef=hijax_var._treedef,\n      avals=leaf_avals,\n      var_type=hijax_var._var_type,\n      has_qdd=hijax_var.has_qdd,\n    )\n    variable = jax.tree.unflatten(hijax_var._treedef, leaf_vals)\n\n  return variable\n\n\nclass GetVariable(hjx.HiPrimitive):\n  multiple_results = True\n\n  def impl(\n    self, hijax_var: HijaxVariable, *, treedef, avals, var_type, has_qdd\n  ):\n    return hijax_var._leaves\n\n  def abstract_eval(self, abstract_var, *, treedef, avals, var_type, has_qdd):\n    if has_qdd:\n      return avals, {variable_effect}\n    else:\n      return avals, set()\n\n  def to_lojax(\n    _, hijax_var: HijaxVariable, *, treedef, avals, var_type, has_qdd\n  ):\n    return hijax_var._leaves\n\n  def jvp(_, primals, tangents, *, treedef, avals, var_type, has_qdd):\n    if has_qdd:\n      raise NotImplementedError(\n        \"jvp not implemented for 'get_variable' with QDD\"\n      )\n    (hijax_var,), (hijax_var_dot,) = primals, tangents\n    return (\n      get_variable_p.bind(\n        hijax_var,\n        treedef=treedef,\n        avals=avals,\n        var_type=var_type,\n        has_qdd=has_qdd,\n      ),\n      get_variable_p.bind(\n        hijax_var_dot,\n        treedef=treedef,\n        avals=tuple(a.to_tangent_aval() for a in avals),\n        var_type=var_type,\n        has_qdd=has_qdd,\n      ),\n    )\n\n  def transpose(_, out, hijax_var, *, treedef, avals, var_type, has_qdd):\n    if has_qdd:\n      raise NotImplementedError(\n        \"transpose not implemented for 'get_variable' with QDD\"\n      )\n    abstract_var: AbstractVariable = (\n      hijax_var.aval\n      if hjx.is_undefined_primal(hijax_var)\n      else jax.typeof(hijax_var)\n    )\n    hijax_var_dot = _bind_new_variable(\n      *out,\n      treedef=abstract_var._treedef,\n      var_type=var_type,\n      has_qdd=has_qdd,\n      ref=abstract_var.ref,\n    )\n    return (hijax_var_dot,)\n\n\nget_variable_p = GetVariable(f'get_variable')\n\n\n# ---------------------------------\n# HijaxVariable\n# ---------------------------------\ndef _variable_has_changed(old: Variable, new: Variable) -> bool:\n  old_structure = jax.tree.structure(old)\n  new_structure = jax.tree.structure(new)\n  if old_structure != new_structure:  # type: ignore[operator]\n    return True\n  old_leaves = jax.tree.leaves(old)\n  new_leaves = jax.tree.leaves(new)\n  return any(o is not n for o, n in zip(old_leaves, new_leaves))\n\n\ndef _as_hijax_property(name: str, *, get: bool, set: bool) -> property:\n  \"\"\"Creates a property that operates on the hijax type.\"\"\"\n\n  def _getter_wrapper(hijax_var):\n    variable = _get_hijax_state(hijax_var)\n    old_state = jax.tree.map(lambda x: x, variable)\n    out = getattr(variable, name)\n    if _variable_has_changed(old_state, variable):\n      _set_hijax_state(hijax_var, variable)\n    return out\n\n  def _setter_wrapper(hijax_var, value):\n    variable = _get_hijax_state(hijax_var)\n    setattr(variable, name, value)\n    _set_hijax_state(hijax_var, variable)\n\n  _hijax_property = property(\n    fget=_getter_wrapper if get else None,\n    fset=_setter_wrapper if set else None,\n  )\n  return _hijax_property  # type: ignore[return]\n\n\ndef _as_aval_property(p: property) -> hjx.aval_property:\n  \"\"\"Wraps a property `p` operate on the aval type.\"\"\"\n  _aval_property = hjx.aval_property(fget=p.fget)\n  return _aval_property  # type: ignore[return]\n\n\ndef _as_hijax_attribute(name: str) -> property:\n  \"\"\"Creates a property that operates on the hijax type.\"\"\"\n\n  def _getter_wrapper(hijax_var):\n    variable = _get_hijax_state(hijax_var)\n    old_state = jax.tree.map(lambda x: x, variable)\n    out = getattr(variable, name)\n    if _variable_has_changed(old_state, variable):\n      _set_hijax_state(hijax_var, variable)\n    return out\n\n  _getter_wrapper.__name__ = name\n  _hijax_property = property(fget=_getter_wrapper)\n\n  return _hijax_property  # type: ignore[return]\n\n\ndef _as_hijax_method(name: str) -> tp.Any:\n  \"\"\"Creates a method that operates on the hijax type.\"\"\"\n\n  def hijax_method_wrapper(hijax_var, *args, **kwargs):\n    variable = _get_hijax_state(hijax_var)\n    old_state = jax.tree.map(lambda x: x, variable)\n    method = getattr(variable, name)\n    out = method(*args, **kwargs)\n    if _variable_has_changed(old_state, variable):\n      _set_hijax_state(hijax_var, variable)\n    return out\n\n  hijax_method_wrapper.__name__ = name\n\n  return hijax_method_wrapper\n\n\ndef _as_tracer_method(name: str):\n  def op(self, hijax_var, *args, **kwargs):\n    variable = _get_hijax_state(hijax_var)\n    old_state = jax.tree.map(lambda x: x, variable)\n    out = getattr(variable, name)(*args, **kwargs)\n    if _variable_has_changed(old_state, variable):\n      _set_hijax_state(hijax_var, variable)\n    return out\n\n  op.__name__ = name\n  return op\n\ndef _not_an_attribute_property(name: str):\n  def _op(self):\n    raise AttributeError(\n      f\"'{type(self).__name__}' object has no attribute '{name}'\"\n    )\n\n  return property(_op)\n\nclass HijaxVariableMeta(type):\n  def __instancecheck__(self, instance):\n    if super().__instancecheck__(instance):\n      return True\n\n    if isinstance(instance, jax.core.Tracer):\n      ty = jax.typeof(instance)\n      return isinstance(ty, AbstractVariable)\n    return False\n\n\nclass HijaxVariable(\n  tp.Generic[A], reprlib.Representable, metaclass=HijaxVariableMeta\n):  # type: ignore\n  __slots__ = ('_treedef', '_leaves', '_var_type', 'has_qdd', '_ref')\n  _treedef: PyTreeDef\n  _leaves: tuple[Leaf, ...]\n  _var_type: type[Variable[tp.Any]]\n  has_qdd: bool\n  _ref: bool\n\n  @classmethod\n  def _new(\n    cls,\n    leaves: tuple[Leaf, ...],\n    treedef: PyTreeDef,\n    var_type: type[Variable[A]],\n    has_qdd: bool,\n    *,\n    ref: bool = False,\n  ):\n    hijax_var = object.__new__(cls)\n    object.__setattr__(hijax_var, '_treedef', treedef)\n    object.__setattr__(hijax_var, '_leaves', leaves)\n    object.__setattr__(hijax_var, '_var_type', var_type)\n    object.__setattr__(hijax_var, 'has_qdd', has_qdd)\n    object.__setattr__(hijax_var, '_ref', ref)\n    return hijax_var\n\n  __init__ = _as_hijax_method('__init__')\n\n  @property\n  def value(self) -> A:\n    raise NotImplementedError(\n      'HijaxVariable.value property is not implemented. For Variable[Array] instances use:\\n\\n'\n      '  variable[...]\\n\\n'\n      'For other Variable types use:\\n\\n'\n      '  variable.get_value()\\n'\n    )\n\n  @value.setter\n  def value(self, new_value: A):\n    raise NotImplementedError(\n      'HijaxVariable.value property is not implemented. For Variable[Array] instances use:\\n\\n'\n      '  variable[...] = new_value\\n\\n'\n      'For other Variable types use:\\n\\n'\n      '  variable.set_value(new_value)\\n'\n    )\n\n  @property\n  def var_type(self) -> type[Variable[A]]:\n    return self._var_type\n\n  _trace_state = _as_hijax_property('_trace_state', get=True, set=False)\n  _can_update = _as_hijax_property('_can_update', get=True, set=False)\n  _check_can_update = _as_hijax_method('_check_can_update')\n  __getattr__ = _as_hijax_method('__getattr__')\n  __setattr__ = _as_hijax_method('__setattr__')\n  __delattr__ = _as_hijax_method('__delattr__')\n  type = _as_hijax_property('type', get=True, set=False)\n  type = _as_hijax_property('type', get=True, set=False)\n  hijax = _as_hijax_property('hijax', get=True, set=False)\n\n  @property\n  def ref(self) -> bool:\n    return self._ref\n\n  get_metadata = _as_hijax_method('get_metadata')\n  set_metadata = _as_hijax_method('set_metadata')\n\n  def copy_from(self, other: Variable[A] | HijaxVariable[A]) -> None:\n    if isinstance(other, HijaxVariable):\n      other = _get_hijax_state(other)\n    variable = _get_hijax_state(self)\n    variable.copy_from(other)  # type: ignore[arg-type]\n    _set_hijax_state(self, variable)\n\n  def update_from_state(self, variable_state: Variable[A] | HijaxVariable[A]):\n    if isinstance(variable_state, HijaxVariable):\n      variable_state = _get_hijax_state(variable_state)\n    variable = _get_hijax_state(self)\n    variable.update_from_state(variable_state)  # type: ignore[arg-type]\n    _set_hijax_state(self, variable)\n\n  get_raw_value = _as_hijax_method('get_raw_value')\n  set_raw_value = _as_hijax_method('set_raw_value')\n  set_value = _as_hijax_method('set_value')\n  get_value = _as_hijax_method('get_value')\n  create_value = _as_hijax_method('create_value')\n  set_raw_value = _as_hijax_method('set_raw_value')\n  add_axis = _as_hijax_method('add_axis')\n  remove_axis = _as_hijax_method('remove_axis')\n  copy = _as_hijax_method('copy')\n  replace = _as_hijax_method('replace')\n  to_state = _as_hijax_method('to_state')\n\n  @classmethod\n  def from_metadata(cls, value: A, metadata: dict[str, tp.Any]):\n    return cls._var_type.from_metadata(value, metadata)  # type: ignore[misc]\n\n  __nnx_repr__ = _as_hijax_method('__nnx_repr__')\n  __treescope_repr__ = _as_hijax_method('__treescope_repr__')\n\n  # --------------------------------------------\n  # proxy methods\n  # --------------------------------------------\n  __jax_array__ = _as_hijax_method('__jax_array__')\n  __getitem__ = _as_hijax_method('__getitem__')\n  __setitem__ = _as_hijax_method('__setitem__')\n  __delitem__ = _as_hijax_method('__delitem__')\n  __call__ = _as_hijax_method('__call__')\n  __len__ = _as_hijax_method('__len__')\n  __iter__ = _as_hijax_method('__iter__')\n  __contains__ = _as_hijax_method('__contains__')\n  __add__ = _as_hijax_method('__add__')\n  __sub__ = _as_hijax_method('__sub__')\n  __mul__ = _as_hijax_method('__mul__')\n  __matmul__ = _as_hijax_method('__matmul__')\n  __truediv__ = _as_hijax_method('__truediv__')\n  __floordiv__ = _as_hijax_method('__floordiv__')\n  __mod__ = _as_hijax_method('__mod__')\n  __divmod__ = _as_hijax_method('__divmod__')\n  __pow__ = _as_hijax_method('__pow__')\n  __lshift__ = _as_hijax_method('__lshift__')\n  __rshift__ = _as_hijax_method('__rshift__')\n  __and__ = _as_hijax_method('__and__')\n  __xor__ = _as_hijax_method('__xor__')\n  __or__ = _as_hijax_method('__or__')\n  __radd__ = _as_hijax_method('__radd__')\n  __rsub__ = _as_hijax_method('__rsub__')\n  __rmul__ = _as_hijax_method('__rmul__')\n  __rmatmul__ = _as_hijax_method('__rmatmul__')\n  __rtruediv__ = _as_hijax_method('__rtruediv__')\n  __rfloordiv__ = _as_hijax_method('__rfloordiv__')\n  __rmod__ = _as_hijax_method('__rmod__')\n  __rdivmod__ = _as_hijax_method('__rdivmod__')\n  __rpow__ = _as_hijax_method('__rpow__')\n  __rlshift__ = _as_hijax_method('__rlshift__')\n  __rrshift__ = _as_hijax_method('__rrshift__')\n  __rand__ = _as_hijax_method('__rand__')\n  __rxor__ = _as_hijax_method('__rxor__')\n  __ror__ = _as_hijax_method('__ror__')\n  __iadd__ = _as_hijax_method('__iadd__')\n  __isub__ = _as_hijax_method('__isub__')\n  __imul__ = _as_hijax_method('__imul__')\n  __imatmul__ = _as_hijax_method('__imatmul__')\n  __itruediv__ = _as_hijax_method('__itruediv__')\n  __ifloordiv__ = _as_hijax_method('__ifloordiv__')\n  __imod__ = _as_hijax_method('__imod__')\n  __ipow__ = _as_hijax_method('__ipow__')\n  __ilshift__ = _as_hijax_method('__ilshift__')\n  __irshift__ = _as_hijax_method('__irshift__')\n  __iand__ = _as_hijax_method('__iand__')\n  __ixor__ = _as_hijax_method('__ixor__')\n  __ior__ = _as_hijax_method('__ior__')\n  __neg__ = _as_hijax_method('__neg__')\n  __pos__ = _as_hijax_method('__pos__')\n  __abs__ = _as_hijax_method('__abs__')\n  __invert__ = _as_hijax_method('__invert__')\n  __complex__ = _as_hijax_method('__complex__')\n  __int__ = _as_hijax_method('__int__')\n  __float__ = _as_hijax_method('__float__')\n  __index__ = _as_hijax_method('__index__')\n  __round__ = _as_hijax_method('__round__')\n  __trunc__ = _as_hijax_method('__trunc__')\n  __floor__ = _as_hijax_method('__floor__')\n  __ceil__ = _as_hijax_method('__ceil__')\n\n  # --------------------------------------------\n  # hijax interface\n  # --------------------------------------------\n\n  def cur_qdd(self):\n    return self.type_state()\n\n  def type_state(self):\n    leaf_avals = tuple(map(jax.typeof, self._leaves))\n    return VariableQDD(leaf_avals, self._treedef, self._var_type)\n\n\ndef _to_abstract_variable(hijax_var: HijaxVariable):\n  if hijax_var.has_qdd:\n    treedef = None\n    leaves = None\n  else:\n    leaves = tuple(map(jax.typeof, hijax_var._leaves))\n    treedef = hijax_var._treedef\n  return AbstractVariable(\n    hijax_var._var_type,\n    treedef,\n    leaves,\n    hijax_var.has_qdd,\n    ref=hijax_var.ref,\n  )\n\n\nhjx.register_hitype(HijaxVariable, _to_abstract_variable)\n\n\n# ---------------------------------\n# AbstractVariable\n# ---------------------------------\nclass AbstractVariable(tp.Generic[A], hjx.MutableHiType):\n  __slots__ = ['_var_type', '_treedef', '_leaves', 'has_qdd', '_ref']\n  _var_type: type[Variable[A]]\n  _treedef: PyTreeDef | None\n  _leaves: tuple[hjx.AbstractValue, ...] | None\n  has_qdd: bool\n  _ref: bool\n\n  @property\n  def ref(self) -> bool:\n    return self._ref\n\n  @property\n  def hijax(self):\n    return True\n\n  _check_can_update = hjx.aval_method(HijaxVariable._check_can_update)\n\n  def __init__(\n    self,\n    var_type: type[Variable[A]],\n    treedef: PyTreeDef | None,\n    leaves: tuple[hjx.AbstractValue, ...] | None,\n    has_qdd: bool,\n    *,\n    ref: bool = False,\n  ):\n    if (treedef is None) ^ (leaves is None):\n      raise ValueError('treedef and leaves must be both provided or both None')\n    object.__setattr__(self, '_treedef', treedef)\n    object.__setattr__(self, '_leaves', leaves)\n    object.__setattr__(self, '_var_type', var_type)\n    object.__setattr__(self, 'has_qdd', has_qdd)\n    object.__setattr__(self, '_ref', ref)\n\n  @property\n  def dtype(self):\n    raise AttributeError\n\n  @property\n  def ndim(self):\n    raise AttributeError\n\n  @property\n  def size(self):\n    raise AttributeError\n\n  @property\n  def shape(self):\n    raise AttributeError\n\n  def __getattr__(self, name: str):\n    # Forward unknown attributes to the value\n    if hasattr(AbstractVariable, name):\n      raise AttributeError(\n        f\"'{type(self).__name__}' object has no attribute '{name}'\"\n      )\n    if name.startswith('_'):\n      raise AttributeError(\n        f\"'{type(self).__name__}' object has no attribute '{name}'\"\n      )\n    return _as_aval_property(_as_hijax_attribute(name))\n\n  # __setattr__ supported via __getattr__\n  # __delattr__ CURRENTLY NOT SUPPORTED\n  type = _as_aval_property(HijaxVariable.type)\n  get_metadata = hjx.aval_method(HijaxVariable.get_metadata)\n  set_metadata = hjx.aval_method(HijaxVariable.set_metadata)\n  copy_from = hjx.aval_method(HijaxVariable.copy_from)\n  update_from_state = hjx.aval_method(HijaxVariable.update_from_state)\n  get_raw_value = hjx.aval_method(HijaxVariable.get_raw_value)\n  set_raw_value = hjx.aval_method(HijaxVariable.set_raw_value)\n  set_value = hjx.aval_method(HijaxVariable.set_value)\n  get_value = hjx.aval_method(HijaxVariable.get_value)\n  create_value = hjx.aval_method(HijaxVariable.create_value)\n  set_raw_value = hjx.aval_method(HijaxVariable.set_raw_value)\n  add_axis = hjx.aval_method(HijaxVariable.add_axis)\n  remove_axis = hjx.aval_method(HijaxVariable.remove_axis)\n  replace = hjx.aval_method(HijaxVariable.replace)\n\n  @hjx.aval_method\n  def from_metadata(self, value, metadata: dict[str, tp.Any]):\n    aval: AbstractVariable = self.aval  # type: ignore\n    variable = aval._var_type.from_metadata(value, metadata)\n    return variable\n\n  copy = hjx.aval_method(HijaxVariable.copy)\n  replace = hjx.aval_method(HijaxVariable.replace)\n  to_state = hjx.aval_method(HijaxVariable.to_state)\n\n  def __str__(self):\n    return f'{self._var_type.__name__}()'\n\n  def __repr__(self):\n    return f'{self._var_type.__name__}()'\n\n  @hjx.aval_method\n  def __treescope_repr__(self, path, subtree_renderer):\n    raise NotImplementedError\n\n  # ---------------------------------\n  # proxy methods\n  # ---------------------------------\n  __jax_array__ = hjx.aval_method(HijaxVariable.__jax_array__)\n  _getitem = _as_tracer_method('__getitem__')\n  _setitem = _as_tracer_method('__setitem__')\n  # __delitem__ CURRENTLY NOT SUPPORTED\n  # __call__ CURRENTLY NOT SUPPORTED\n  _len = _as_tracer_method('__len__')\n  _iter = _as_tracer_method('__iter__')\n  # __contains__ CURRENTLY NOT SUPPORTED\n  _add = _as_tracer_method('__add__')\n  _sub = _as_tracer_method('__sub__')\n  _mul = _as_tracer_method('__mul__')\n  _matmul = _as_tracer_method('__matmul__')\n  _truediv = _as_tracer_method('__truediv__')\n  _floordiv = _as_tracer_method('__floordiv__')\n  _mod = _as_tracer_method('__mod__')\n  _divmod = _as_tracer_method('__divmod__')\n  _pow = _as_tracer_method('__pow__')\n  _lshift = _as_tracer_method('__lshift__')\n  _rshift = _as_tracer_method('__rshift__')\n  _and = _as_tracer_method('__and__')\n  _xor = _as_tracer_method('__xor__')\n  _or = _as_tracer_method('__or__')\n  _radd = _as_tracer_method('__radd__')\n  _rsub = _as_tracer_method('__rsub__')\n  _rmul = _as_tracer_method('__rmul__')\n  _rmatmul = _as_tracer_method('__rmatmul__')\n  _rtruediv = _as_tracer_method('__rtruediv__')\n  _rfloordiv = _as_tracer_method('__rfloordiv__')\n  _rmod = _as_tracer_method('__rmod__')\n  _rdivmod = _as_tracer_method('__rdivmod__')\n  _rpow = _as_tracer_method('__rpow__')\n  _rlshift = _as_tracer_method('__rlshift__')\n  _rrshift = _as_tracer_method('__rrshift__')\n  _rand = _as_tracer_method('__rand__')\n  _rxor = _as_tracer_method('__rxor__')\n  _ror = _as_tracer_method('__ror__')\n  # _iadd CURRENTLY NOT SUPPORTED\n  # _isub CURRENTLY NOT SUPPORTED\n  # _imul CURRENTLY NOT SUPPORTED\n  # _imatmul CURRENTLY NOT SUPPORTED\n  # _itruediv CURRENTLY NOT SUPPORTED\n  # _ifloordiv CURRENTLY NOT SUPPORTED\n  # _imod CURRENTLY NOT SUPPORTED\n  # _ipow CURRENTLY NOT SUPPORTED\n  # _ilshift CURRENTLY NOT SUPPORTED\n  # _irshift CURRENTLY NOT SUPPORTED\n  # _iand CURRENTLY NOT SUPPORTED\n  # _ixor CURRENTLY NOT SUPPORTED\n  # _ior CURRENTLY NOT SUPPORTED\n  _neg = _as_tracer_method('__neg__')\n  _pos = _as_tracer_method('__pos__')\n  _abs = _as_tracer_method('__abs__')\n  _invert = _as_tracer_method('__invert__')\n  _complex = _as_tracer_method('__complex__')\n  _int = _as_tracer_method('__int__')\n  _float = _as_tracer_method('__float__')\n  _index = _as_tracer_method('__index__')\n  _round = _as_tracer_method('__round__')\n  _trunc = _as_tracer_method('__trunc__')\n  _floor = _as_tracer_method('__floor__')\n  _ceil = _as_tracer_method('__ceil__')\n\n  # --------------------------------\n  # hijax interface\n  # --------------------------------\n  cur_qdd = _not_an_attribute_property('cur_qdd')\n\n  def __hash__(self):\n    if self._leaves is not None and self._treedef is not None:\n      return hash(\n        (AbstractVariable, self._var_type, self._treedef, self._leaves)\n      )\n    else:\n      assert self._leaves is None and self._treedef is None\n      return hash((AbstractVariable, self._var_type))\n\n  def __eq__(self, other):\n    return (\n      isinstance(other, AbstractVariable) and self._var_type == other._var_type\n    )\n\n  def str_short(self, short_dtypes=False, **_) -> str:  # type: ignore\n    return f'{self._var_type.__name__}()'\n\n  # mutable interface\n  def lo_ty_qdd(self, variable_state: VariableQDD) -> list:  # type: ignore\n    return [lo_ty for t in variable_state.leaf_avals for lo_ty in t.lo_ty()]\n\n  def new_from_loval(  # type: ignore[override]\n    self, variable_state: VariableQDD, *lo_vals\n  ) -> HijaxVariable:\n    lo_vals_ = iter(lo_vals)\n    hi_vals = [\n      hi_ty.raise_val(*it.islice(lo_vals_, len(hi_ty.lo_ty())))  # type: ignore\n      for hi_ty in variable_state.leaf_avals\n    ]\n    assert next(lo_vals_, None) is None\n    variable: Variable = jax.tree.unflatten(variable_state.treedef, hi_vals)\n    return HijaxVariable._new(\n      hi_vals,\n      variable_state.treedef,\n      self._var_type,\n      has_qdd=self.has_qdd,\n      ref=self.ref,\n    )  # will be mutated\n\n  def read_loval(self, variable_state: VariableQDD, variable) -> list:  # type: ignore\n    leaf_vals, treedef = jax.tree.flatten(_get_hijax_state(variable))\n    assert treedef == variable_state.treedef\n    return [\n      lo_val\n      for hi_ty, hi_val in zip(variable_state.leaf_avals, leaf_vals)\n      for lo_val in hi_ty.lower_val(hi_val)\n    ]  # type: ignore\n\n  def update_from_loval(  # type: ignore[override]\n    self, box_state: VariableQDD, variable, *lo_vals\n  ) -> None:\n    lo_vals_ = iter(lo_vals)\n    hi_vals = [\n      hi_ty.raise_val(*it.islice(lo_vals_, len(hi_ty.lo_ty())))  # type: ignore\n      for hi_ty in box_state.leaf_avals\n    ]\n    assert next(lo_vals_, None) is None\n    _set_hijax_state(variable, jax.tree.unflatten(box_state.treedef, hi_vals))\n\n  def to_tangent_aval(self):\n    return AbstractVariable(\n      self._var_type,\n      self._treedef,\n      self._leaves,\n      self.has_qdd,\n      ref=self.ref,\n    )\n\n# --------------------------------------------\n# Variable\n# --------------------------------------------\n\n\ndef _remap_sharding_metadata(metadata: dict[str, tp.Any]) -> None:\n  if 'sharding' in metadata:\n    warnings.warn(\n      \"'sharding' is deprecated, use 'out_sharding' instead.\",\n      DeprecationWarning,\n      stacklevel=3,\n    )\n    metadata['out_sharding'] = metadata.pop('sharding')\n  if 'sharding_names' in metadata:\n    warnings.warn(\n      \"'sharding_names' is deprecated, use 'out_sharding' instead.\",\n      DeprecationWarning,\n      stacklevel=3,\n    )\n    metadata['out_sharding'] = metadata.pop('sharding_names')\n\n\ndef _variable_operator(name: str) -> tp.Callable[[Variable[A], tp.Any], A]:\n  def variable_operator_method(self, other):\n    value = self.get_value()\n    if isinstance(other, Variable):\n      other = other.get_value()\n    return getattr(value, name)(other)\n\n  variable_operator_method.__name__ = name\n  return variable_operator_method\n\n\ndef _variable_unary_operator(name: str) -> tp.Callable[[Variable[A]], A]:\n  def variable_unary_operator_method(self):\n    value = self.get_value()\n    return getattr(value, name)()\n\n  variable_unary_operator_method.__name__ = name\n  return variable_unary_operator_method\n\n\nclass VariableMeta(type):\n  def __new__(cls, cls_name, bases, attrs):\n    if '__slots__' not in attrs:\n      attrs['__slots__'] = ()\n    return super().__new__(cls, cls_name, bases, attrs)\n\n  def __instancecheck__(self, instance):\n    if super().__instancecheck__(instance):\n      return True\n\n    if isinstance(instance, jax.core.Tracer):\n      ty = jax.typeof(instance)\n      if isinstance(ty, AbstractVariable):\n        return issubclass(ty._var_type, self)\n    if isinstance(instance, HijaxVariable):\n      return issubclass(instance._var_type, self)\n    return False\n\n  if not tp.TYPE_CHECKING:\n\n    def __call__(cls, *args, **kwargs):\n      return cls._variable_meta_call(*args, **kwargs)\n\n  def _variable_meta_call(cls, *args, **kwargs):\n    variable = super().__call__(*args, **kwargs)\n    if variable.hijax:\n      return _new_hijax_from_variable(variable)\n    return variable\n\n\nclass Variable(tp.Generic[A], reprlib.Representable, metaclass=VariableMeta):\n  \"\"\"The base class for all ``Variable`` types. Create custom ``Variable``\n  types by subclassing this class. Numerous NNX graph functions can filter\n  for specific ``Variable`` types, for example, :func:`split`, :func:`state`,\n  :func:`pop`, and :func:`State.filter`.\n\n  Example usage::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n\n    >>> class CustomVariable(nnx.Variable):\n    ...   pass\n\n    >>> class Model(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.linear = nnx.Linear(2, 3, rngs=rngs)\n    ...     self.custom_variable = CustomVariable(jnp.ones((1, 3)))\n    ...   def __call__(self, x):\n    ...     return self.linear(x) + self.custom_variable\n    >>> model = Model(rngs=nnx.Rngs(0))\n\n    >>> linear_variables = nnx.state(model, nnx.Param)\n    >>> jax.tree.map(jnp.shape, linear_variables)\n    State({\n      'linear': {\n        'bias': Param(\n          value=(3,)\n        ),\n        'kernel': Param(\n          value=(2, 3)\n        )\n      }\n    })\n\n    >>> custom_variable = nnx.state(model, CustomVariable)\n    >>> jax.tree.map(jnp.shape, custom_variable)\n    State({\n      'custom_variable': CustomVariable(\n        value=(1, 3)\n      )\n    })\n\n    >>> variables = nnx.state(model)\n    >>> jax.tree.map(jnp.shape, variables)\n    State({\n      'custom_variable': CustomVariable(\n        value=(1, 3)\n      ),\n      'linear': {\n        'bias': Param(\n          value=(3,)\n        ),\n        'kernel': Param(\n          value=(2, 3)\n        )\n      }\n    })\n  \"\"\"\n\n  __slots__ = ('_raw_value', '_trace_state', '_var_metadata')\n  _raw_value: A\n  _trace_state: tracers.TraceState\n  _var_metadata: dict[str, tp.Any]\n  required_metadata = frozenset(\n    ['hijax', 'ref', 'eager_sharding']\n  )\n\n  @property\n  def var_type(self):\n    return type(self)\n\n  @property\n  def hijax(self) -> bool:\n    return self._var_metadata['hijax']\n\n  @property\n  def ref(self) -> bool:\n    return self._var_metadata['ref']\n\n  @property\n  def shape(self: Variable[jax.Array]) -> tuple[int, ...]:\n    return self.get_value().shape\n\n  @property\n  def sharding_names(self):\n    warnings.warn(\n      \"'sharding_names' is deprecated, use 'out_sharding' instead.\",\n      DeprecationWarning,\n      stacklevel=2,\n    )\n    return self.get_metadata('out_sharding', None)\n\n  def __init__(\n    self,\n    value: A | VariableMetadata[A],\n    *,\n    hijax: bool | None = None,\n    ref: bool | None = None,\n    eager_sharding: bool | None = None,\n    **metadata: tp.Any,\n  ):\n    var_t = type(self)\n\n    if isinstance(value, VariableMetadata):\n      aux_metadata = dict(value.metadata)\n      if 'hijax' in aux_metadata:\n        if hijax is not None and hijax != aux_metadata['hijax']:\n          raise ValueError(\n            'Cannot specify hijax both in VariableMetadata and as an '\n            'argument to Variable constructor.'\n          )\n        hijax = aux_metadata.pop('hijax')\n      if 'ref' in aux_metadata:\n        if ref is not None and ref != aux_metadata['ref']:\n          raise ValueError(\n            'Cannot specify ref both in VariableMetadata and as an '\n            'argument to Variable constructor.'\n          )\n        ref = aux_metadata.pop('ref')\n      if 'eager_sharding' in aux_metadata:\n        if (\n          eager_sharding is not None\n          and eager_sharding != aux_metadata['eager_sharding']\n        ):\n          raise ValueError(\n            'Cannot specify eager_sharding both in VariableMetadata and as '\n            'an argument to Variable constructor.'\n          )\n        eager_sharding = aux_metadata['eager_sharding']\n      metadata.update(aux_metadata)\n      value = tp.cast(A, value.raw_value)\n\n    if hijax is None:\n      hijax = var_defaults().hijax\n\n    if ref is None:\n      ref = var_defaults().ref\n\n    if eager_sharding is None:\n      eager_sharding = using_eager_sharding()\n\n    if any(is_array_ref(v) for v in jax.tree.leaves(value)):\n      raise ValueError('Cannot pass a Ref directly into Variable constructor.')\n\n    metadata['hijax'] = hijax\n    metadata['ref'] = ref\n    metadata['eager_sharding'] = eager_sharding\n    object.__setattr__(self, '_trace_state', tracers.TraceState())\n    object.__setattr__(self, '_var_metadata', metadata)\n    object.__setattr__(self, '_raw_value', value)\n\n    if hasattr(var_t, 'on_get_value') and 'on_get_value' not in metadata:\n      metadata['on_get_value'] = var_t.on_get_value\n\n    if hasattr(var_t, 'on_set_value') and 'on_set_value' not in metadata:\n      metadata['on_set_value'] = var_t.on_set_value\n\n    if hasattr(var_t, 'on_create_value') and 'on_create_value' not in metadata:\n      metadata['on_create_value'] = var_t.on_create_value\n\n    if hasattr(var_t, 'on_add_axis') and 'on_add_axis' not in metadata:\n      metadata['on_add_axis'] = var_t.on_add_axis\n\n    if hasattr(var_t, 'on_remove_axis') and 'on_remove_axis' not in metadata:\n      metadata['on_remove_axis'] = var_t.on_remove_axis\n\n    _remap_sharding_metadata(metadata)\n\n    # run create_value hooks\n    if 'on_create_value' in metadata:\n      value = metadata['on_create_value'](self, value)\n\n    object.__setattr__(self, '_raw_value', value)\n    # run create_value hook\n    value = self.create_value(value)  # type: ignore\n    # shard the _value if applicable\n    if eager_sharding and 'out_sharding' in metadata:\n      value = core_spmd.shard_value(\n        value,\n        metadata['out_sharding'],\n        metadata.get('sharding_rules', None),\n        metadata.get('mesh', None),\n      )\n    if ref:\n      value = jax.new_ref(value)  # type: ignore\n    object.__setattr__(self, '_raw_value', value)\n\n  @property\n  def _can_update(self) -> bool:\n    \"\"\"Whether the Variable can be updated in-place in the current trace context.\"\"\"\n    if self.hijax:\n      return True\n    else:\n      return self._trace_state.is_valid()\n\n  def _check_can_update(self):\n    if not self.hijax and not self._trace_state.is_valid():\n      raise errors.TraceContextError(\n        f'Cannot mutate {type(self).__name__} from a different trace level'\n      )\n\n  def __getattr__(self, name: str) -> tp.Any:\n    if name in object.__getattribute__(self, '_var_metadata'):\n      return self._var_metadata[name]\n    return getattr(object.__getattribute__(self, '_raw_value'), name)\n\n  def __setattr__(self, name: str, value: tp.Any):\n    self._check_can_update()\n    try:\n      object.__setattr__(self, name, value)\n    except AttributeError as e:\n      raise AttributeError(\n        f'Cannot set attribute {name}. '\n        f'To set Variable metadata use either:\\n\\n'\n        f'  variable.set_metadata({name}=value)\\n\\nor\\n\\n'\n        f\"  variable.set_metadata('{name}', value)\"\n      ) from e\n\n  def __delattr__(self, name: str):\n    self._check_can_update()\n    try:\n      object.__delattr__(self, name)\n    except AttributeError as e:\n      raise AttributeError(\n        f'Cannot delete attribute {name}. '\n        f'To delete Variable metadata use:\\n\\n'\n        f\"  variable.del_metadata('{name}')\"\n      ) from e\n\n  # NOTE(cgarciae): adding this for backward compatibility with VariableState\n  @property\n  def type(self):\n    \"\"\"The type of the variable.\"\"\"\n\n    return type(self)\n\n  @tp.overload\n  def get_metadata(\n    self, *, exclude_required: bool = False\n  ) -> dict[str, tp.Any]: ...\n\n  @tp.overload\n  def get_metadata(self, name: str, default: tp.Any = MISSING) -> tp.Any: ...\n\n  def get_metadata(\n    self,\n    name: str | None = None,\n    default: tp.Any = MISSING,\n    *,\n    exclude_required: bool | None = None,\n  ) -> tp.Any:\n    \"\"\"Get metadata for the Variable.\n\n    Args:\n      name: The key of the metadata element to get. If not provided, returns\n        the full metadata dictionary.\n      default: The default value to return if the metadata key is not found. If\n        not provided and the key is not found, raises a KeyError.\n    \"\"\"\n    if name is not None and exclude_required is not None:\n      raise TypeError(\n        \"Cannot specify both 'name' and 'exclude_required' arguments.\"\n      )\n    metadata = self._var_metadata.copy()\n    if name is None:\n      if not isinstance(default, Missing):\n        raise TypeError(\n          \"Cannot provide a default value when 'name' is not provided. \"\n          f'Got default={default}'\n        )\n      if exclude_required:\n        for key in self.required_metadata:\n          metadata.pop(key, None)\n      return metadata\n    if name not in metadata and not isinstance(default, Missing):\n      return default\n    return metadata[name]\n\n  @tp.overload\n  def set_metadata(self, metadata: dict[str, tp.Any], /) -> None: ...\n\n  @tp.overload\n  def set_metadata(self, name: str, value: tp.Any, /) -> None: ...\n\n  @tp.overload\n  def set_metadata(self, **metadata: tp.Any) -> None: ...\n\n  def set_metadata(self, *args, **kwargs) -> None:\n    \"\"\"Set metadata for the Variable.\n\n    `set_metadata` can be called in 3 ways:\n\n    1. By passing a dictionary of metadata as the first argument, this will replace\n      the entire Variable's metadata.\n    2. By passing a name and value as the first two arguments, this will set\n      the metadata entry for the given name to the given value.\n    3. By using keyword arguments, this will update the Variable's metadata\n      with the provided key-value pairs.\n    \"\"\"\n    self._check_can_update()\n    if args and kwargs:\n      raise TypeError(\n        'Cannot mix positional and keyword arguments in set_metadata'\n      )\n    if len(args) == 1:\n      metadata = dict(args[0])\n      _remap_sharding_metadata(metadata)\n      if 'hijax' not in metadata:\n        metadata['hijax'] = self.hijax\n      if metadata['hijax'] != self.hijax:\n        raise ValueError(\n          f'Cannot change `hijax` metadata, expected {self.hijax}, '\n          f'got {metadata[\"hijax\"]}'\n        )\n      if 'ref' not in metadata:\n        metadata['ref'] = self.ref\n      if metadata['ref'] != self.ref:\n        raise ValueError(\n          f'Cannot change `ref` metadata, expected {self.ref}, '\n          f'got {metadata[\"ref\"]}'\n        )\n      if 'eager_sharding' not in metadata:\n        metadata['eager_sharding'] = self.eager_sharding\n      if metadata['eager_sharding'] != self.eager_sharding:\n        raise ValueError(\n          f'Cannot change `eager_sharding` metadata, expected '\n          f'{self.eager_sharding}, got {metadata[\"eager_sharding\"]}'\n        )\n      self._var_metadata = metadata\n    elif len(args) == 2:\n      name, value = args\n      if name == 'sharding_names':\n        warnings.warn(\n          \"'sharding_names' is deprecated, use 'out_sharding' instead.\",\n          DeprecationWarning,\n          stacklevel=2,\n        )\n        name = 'out_sharding'\n      elif name == 'sharding':\n        warnings.warn(\n          \"'sharding' is deprecated, use 'out_sharding' instead.\",\n          DeprecationWarning,\n          stacklevel=2,\n        )\n        name = 'out_sharding'\n      if name == 'hijax' and value != self.hijax:\n        raise ValueError(\n          f'Cannot change `hijax` metadata, expected {self.hijax}, got {value}'\n        )\n      if name == 'ref' and value != self.ref:\n        raise ValueError(\n          f'Cannot change `ref` metadata, expected {self.ref}, got {value}'\n        )\n      self._var_metadata[name] = value\n    elif kwargs:\n      _remap_sharding_metadata(kwargs)\n      if 'hijax' in kwargs and kwargs['hijax'] != self.hijax:\n        raise ValueError(\n          f'Cannot change `hijax` metadata, expected {self.hijax}, '\n          f'got {kwargs[\"hijax\"]}'\n        )\n      if 'ref' in kwargs and kwargs['ref'] != self.ref:\n        raise ValueError(\n          f'Cannot change `ref` metadata, expected {self.ref}, '\n          f'got {kwargs[\"ref\"]}'\n        )\n      self._var_metadata.update(kwargs)\n    else:\n      raise TypeError(\n        f'set_metadata takes either 1 or 2 arguments, or at least 1 keyword argument, '\n        f'got args={args}, kwargs={kwargs}'\n      )\n\n  def has_metadata(self, name: str) -> bool:\n    \"\"\"Check if the Variable has a metadata entry for the given name.\n\n    Args:\n      name: The key of the metadata element to check.\n    Returns:\n      True if the metadata entry exists, False otherwise.\n    \"\"\"\n    return name in self._var_metadata\n\n  def del_metadata(self, name: str) -> None:\n    \"\"\"Delete a metadata entry for the Variable.\n\n    Args:\n      name: The key of the metadata element to delete.\n    \"\"\"\n    self._check_can_update()\n    if name in ('hijax', 'ref'):\n      raise ValueError(f'Cannot delete `{name}` metadata')\n    del self._var_metadata[name]\n\n  def copy_from(self, other: Variable[A]) -> None:\n    if type(self) is not type(other):\n      raise ValueError(\n        f'Cannot copy from incompatible container, '\n        f'expected {type(self).__name__}, got {type(other).__name__}'\n      )\n    if self is other:\n      return\n    self._raw_value = other._raw_value\n    self._var_metadata.clear()\n    self._var_metadata.update(other.get_metadata())\n\n  def update_from_state(self, variable_state: Variable[A]):\n    self._raw_value = variable_state._raw_value\n\n    if self._var_metadata != variable_state._var_metadata:\n      metadata = variable_state.get_metadata()\n      metadata['hijax'] = self.hijax\n      metadata['ref'] = self.ref\n      self._var_metadata = metadata\n\n  @tp.final\n  def get_raw_value(self) -> A:\n    return self._raw_value\n\n  # @tp.final\n  def set_raw_value(self, value: A, *, _unsafe_bypass_check: bool = False):\n    if not _unsafe_bypass_check:\n      self._check_can_update()\n    self._raw_value = value\n\n  @property\n  def raw_value(self) -> A:\n    warnings.warn(\n      \"'.raw_value' access is now deprecated. Use:\\n\\n\"\n      '  variable.get_raw_value()\\n',\n      DeprecationWarning,\n      stacklevel=2,\n    )\n    return self.get_raw_value()\n\n  @raw_value.setter\n  def raw_value(self, value: A):\n    warnings.warn(\n      \"'.raw_value' setter is now deprecated. Use:\\n\\n\"\n      '  variable.set_raw_value(value)\\n',\n      DeprecationWarning,\n      stacklevel=2,\n    )\n    self.set_raw_value(value)\n\n  @property\n  def value(self) -> A:\n    warnings.warn(\n      \"'.value' access is now deprecated. For Variable[Array] instances use:\\n\\n\"\n      '  variable[...]\\n\\n'\n      'For other Variable types use:\\n\\n'\n      '  variable.get_value()\\n',\n      DeprecationWarning,\n      stacklevel=2,\n    )\n    return self.get_value()\n\n  @value.setter\n  def value(self, value: A):\n    warnings.warn(\n      \"'.value' setter is now deprecated. For Variable[Array] instances use:\\n\\n\"\n      '  variable[...] = value\\n\\n'\n      'For other Variable types use:\\n\\n'\n      '  variable.set_value(value)\\n',\n      DeprecationWarning,\n      stacklevel=2,\n    )\n    self.set_value(value)\n\n  def create_value(self, value: A):\n    return value\n\n  def get_value(self, *, index: tp.Any = MISSING) -> A:\n    value = jax.tree.map(lambda x: x, self._raw_value)  # make a copy\n    if not isinstance(index, Missing):\n      if is_array_ref(value):\n        value = value[index]\n      elif isinstance(value, jax.Array) and index is ...:\n        pass  # skip trivial access\n      else:\n        value = value[index]\n    elif is_array_ref(value):\n      value = value[...]\n    if 'on_get_value' in self._var_metadata:\n      value = self._var_metadata['on_get_value'](self, value)\n    return value  # type: ignore\n\n  def set_value(self, value: A, *, index: tp.Any = MISSING):\n    value = jax.tree.map(lambda x: x, value)  # make a copy\n    if 'on_set_value' in self._var_metadata:\n      value = self._var_metadata['on_set_value'](self, value)\n    # update _raw_value\n    if is_array_ref(self._raw_value):\n      if isinstance(index, Missing):\n        self._raw_value[...] = value\n      else:\n        self._raw_value[index] = value\n    elif isinstance(self._raw_value, jax.Array) and (\n      not isinstance(index, Missing)\n    ):\n      # check if its a full replace to av\n      if (\n        index == ...\n        and isinstance(value, jax.Array)\n        and value.shape == self._raw_value[index].shape\n        and value.dtype == self._raw_value.dtype\n        and (\n          getattr(value, 'sharding', None)\n          == getattr(self._raw_value, 'sharding', None)\n        )\n      ):\n        self._raw_value = value\n      else:\n        self._raw_value = self._raw_value.at[index].set(value)  # type: ignore\n    else:\n      if isinstance(index, Missing):\n        self._raw_value = value\n      else:\n        self._raw_value[index] = value  # type: ignore\n\n  def add_axis(self, axis_index: AxisIndex, axis_name: AxisName | None):\n    if 'on_add_axis' in self._var_metadata:\n      self._var_metadata['on_add_axis'](self, axis_index, axis_name)\n\n  def remove_axis(self, axis_index: AxisIndex, axis_name: AxisName | None):\n    if 'on_remove_axis' in self._var_metadata:\n      self._var_metadata['on_remove_axis'](self, axis_index, axis_name)\n\n  @tp.overload\n  def copy(self, value: B, **kwargs) -> Variable[B]: ...\n\n  @tp.overload\n  def copy(self, **kwargs) -> Variable[A]: ...\n\n  def copy(\n    self,\n    value: tp.Any = MISSING,\n    *,\n    _copy_ref: bool = True,\n    **updates,\n  ) -> Variable[tp.Any]:\n    assert 'raw_value' not in updates\n\n    new_metadata = self.get_metadata() | updates\n\n    if not isinstance(value, Missing):\n      pass\n    elif 'value' in updates:\n      value = updates.pop('value')\n    else:\n      value = self.get_raw_value()\n    if _copy_ref and is_array_ref(value):\n      value = value[...]\n\n    if _copy_ref and new_metadata['ref']:\n      value = jax.new_ref(value)\n      new_metadata['ref'] = True\n\n    value = jax.tree.map(lambda x: x, value)  # make a copy\n    obj = self.from_metadata(value, new_metadata)\n    return obj\n\n  @classmethod\n  def _new(\n    cls,\n    value: A,\n    metadata: dict[str, tp.Any],\n  ) -> Variable[A]:\n    obj = object.__new__(cls)\n    # skip __setattr__ for trace_state initialization\n    object.__setattr__(obj, '_trace_state', tracers.TraceState())\n    object.__setattr__(obj, '_var_metadata', metadata)\n    object.__setattr__(obj, '_raw_value', value)\n    return obj\n\n  @classmethod\n  def from_metadata(\n    cls,\n    value: A,\n    attributes: dict[str, tp.Any],\n  ) -> Variable[A]:\n    variable = cls._new(value, dict(attributes))\n    if attributes['hijax']:\n      variable = _new_hijax_from_variable(variable)  # type: ignore[assignment]\n    return variable  # type: ignore[return-value]\n\n  replace = copy\n  to_state = copy\n\n  def __nnx_repr__(self):\n    stats = SizeBytes.from_any(self._raw_value)\n    if stats:\n      comment = f' # {stats}'\n    else:\n      comment = ''\n\n    yield reprlib.Object(type=type(self).__name__, comment=comment)\n    yield reprlib.Attr('value', self.get_value())\n    for name, value in self._var_metadata.items():\n      if name == 'hijax' and value == config.flax_hijax_variable:\n        continue\n      if name == 'ref' and not value:\n        continue\n      if name == 'eager_sharding' and value == config.flax_always_shard_variable:\n        continue\n      yield reprlib.Attr(name, value)\n\n  def __treescope_repr__(self, path, subtree_renderer):\n    size_bytes = SizeBytes.from_any(self.get_value())\n    if size_bytes:\n      stats_repr = f' # {size_bytes}'\n      first_line_annotation = treescope.rendering_parts.comment_color(\n        treescope.rendering_parts.text(f'{stats_repr}')\n      )\n    else:\n      first_line_annotation = None\n\n    metadata = {\n      name: value\n      for name, value in self._var_metadata.items()\n      if not (name == 'hijax' and value == config.flax_hijax_variable)\n      and not (name == 'ref' and not value)\n      and not (name == 'eager_sharding' and value == config.flax_always_shard_variable)\n    }\n    children = {'value': self.get_value(), **metadata}\n    return visualization.render_object_constructor(\n      object_type=type(self),\n      attributes=children,\n      path=path,\n      subtree_renderer=subtree_renderer,\n      first_line_annotation=first_line_annotation,\n    )\n\n  # hooks API\n  if tp.TYPE_CHECKING:\n\n    def on_get_value(self, value: A) -> A: ...\n\n    def on_set_value(self, value: A) -> A: ...\n\n    def on_create_value(self, value: A) -> A: ...\n\n    def on_add_axis(\n      self: V, axis_index: AxisIndex, axis_name: AxisName | None\n    ) -> V: ...\n\n    def on_remove_axis(\n      self: V, axis_index: AxisIndex, axis_name: AxisName | None\n    ) -> V: ...\n\n  def __jax_array__(self):\n    return self.get_value()\n\n  # pickle support\n  def __getstate__(self):\n    return {\n      '_raw_value': self._raw_value,\n      '_trace_state': self._trace_state,\n      '_var_metadata': self._var_metadata,\n    }\n\n  def __setstate__(self, state):\n    # skip __setattr__ for trace_state initialization\n    object.__setattr__(self, '_trace_state', state['_trace_state'])\n    object.__setattr__(self, '_var_metadata', state['_var_metadata'])\n    object.__setattr__(self, '_raw_value', state['_raw_value'])\n\n  # --------------------------------------------\n  # proxy methods\n  # --------------------------------------------\n  @tp.overload\n  def __getitem__(self: Variable[jax.Array], key) -> jax.Array:\n    ...\n\n  @tp.overload\n  def __getitem__(self: Variable[dict[tp.Any, B]], key) -> B:\n    ...\n\n  @tp.overload\n  def __getitem__(self: Variable[list[B]], key: int) -> B:\n    ...\n\n  @tp.overload\n  def __getitem__(self: Variable[tuple[B, ...]], key: int) -> B:\n    ...\n\n  @tp.overload\n  def __getitem__(self, key) -> tp.Any:\n    ...\n\n  def __getitem__(self, key):\n    return self.get_value(index=key)\n\n  def __setitem__(self, key, value) -> None:\n    self.set_value(value, index=key)\n\n  def __delitem__(self, key) -> None:\n    value = self.get_value()\n    del value[key]  # type: ignore\n    self.set_value(value)  # type: ignore\n\n  def __call__(self, *args, **kwargs) -> tp.Any:\n    return self.get_value()(*args, **kwargs)  # type: ignore\n\n  def __len__(self) -> int:\n    return len(self.get_value())  # type: ignore\n\n  def __iter__(self) -> tp.Iterator:\n    return iter(self.get_value())  # type: ignore\n\n  def __contains__(self, item) -> bool:\n    return item in self.get_value()  # type: ignore\n\n  __add__ = _variable_operator('__add__')\n  __sub__ = _variable_operator('__sub__')\n  __mul__ = _variable_operator('__mul__')\n  __matmul__ = _variable_operator('__matmul__')\n  __truediv__ = _variable_operator('__truediv__')\n  __floordiv__ = _variable_operator('__floordiv__')\n  __mod__ = _variable_operator('__mod__')\n  __pow__ = _variable_operator('__pow__')\n  __lshift__ = _variable_operator('__lshift__')\n  __rshift__ = _variable_operator('__rshift__')\n  __and__ = _variable_operator('__and__')\n  __xor__ = _variable_operator('__xor__')\n  __or__ = _variable_operator('__or__')\n  __radd__ = _variable_operator('__radd__')\n  __rsub__ = _variable_operator('__rsub__')\n  __rmul__ = _variable_operator('__rmul__')\n  __rmatmul__ = _variable_operator('__rmatmul__')\n  __rtruediv__ = _variable_operator('__rtruediv__')\n  __rfloordiv__ = _variable_operator('__rfloordiv__')\n  __rmod__ = _variable_operator('__rmod__')\n  __rpow__ = _variable_operator('__rpow__')\n  __rlshift__ = _variable_operator('__rlshift__')\n  __rrshift__ = _variable_operator('__rrshift__')\n  __rand__ = _variable_operator('__rand__')\n  __rxor__ = _variable_operator('__rxor__')\n  __ror__ = _variable_operator('__ror__')\n\n  def __eq__(self, other) -> bool:\n    if isinstance(other, Variable):\n      other = other.get_value()\n    return self.get_value().__eq__(other)  # type: ignore\n\n  def __iadd__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable[...] += x` instead.'\n    )\n\n  def __isub__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable[...] -= x` instead.'\n    )\n\n  def __imul__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable[...] *= x` instead.'\n    )\n\n  def __imatmul__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable.value @= x` instead.'\n    )\n\n  def __itruediv__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable.value /= x` instead.'\n    )\n\n  def __ifloordiv__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable.value //= x`` instead.'\n    )\n\n  def __imod__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable.value %= x` instead.'\n    )\n\n  def __ipow__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable.value **= x`` instead.'\n    )\n\n  def __ilshift__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable.value <<= x`` instead.'\n    )\n\n  def __irshift__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable.value >>= x`` instead.'\n    )\n\n  def __iand__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable.value &= x` instead.'\n    )\n\n  def __ixor__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable.value ^= x` instead.'\n    )\n\n  def __ior__(self: V, other) -> V:\n    raise NotImplementedError(\n      'In-place operations are no longer supported for Variable.\\n'\n      'Use `variable.value |= x` instead.'\n    )\n\n  __neg__ = _variable_unary_operator('__neg__')\n  __pos__ = _variable_unary_operator('__pos__')\n  __abs__ = _variable_unary_operator('__abs__')\n  __invert__ = _variable_unary_operator('__invert__')\n  __complex__ = _variable_unary_operator('__complex__')\n  __int__ = _variable_unary_operator('__int__')\n  __float__ = _variable_unary_operator('__float__')\n  __index__ = _variable_unary_operator('__index__')\n  __trunc__ = _variable_unary_operator('__trunc__')\n  __floor__ = _variable_unary_operator('__floor__')\n  __ceil__ = _variable_unary_operator('__ceil__')\n\n  def __round__(self, ndigits: int = 0) -> A:\n    return self.get_value().__round__(ndigits)  # type: ignore\n\n  # --------------------------------------------\n\n  def __init_subclass__(cls) -> None:\n    if '__slots__' not in vars(cls):\n      cls.__slots__ = ()  # type: ignore[assignment]\n    super().__init_subclass__()\n    jax.tree_util.register_pytree_with_keys(\n      cls,\n      flatten_with_keys=_variable_flatten_with_keys,\n      unflatten_func=partial(_variable_unflatten, cls),  # type: ignore\n      flatten_func=_variable_flatten,\n    )\n\n\ndef _variable_flatten_with_keys(x: Variable[tp.Any]):\n  metadata = tuple(sorted(x._var_metadata.items()))\n  node = (jtu.GetAttrKey('value'), x._raw_value)\n  return (node,), metadata\n\n\ndef _variable_flatten(x: Variable[tp.Any]):\n  metadata = tuple(sorted(x._var_metadata.items()))\n  return (x._raw_value,), metadata\n\n\ndef _variable_unflatten(\n  cls: type[Variable[tp.Any]],\n  static: tuple[tuple[str, tp.Any], ...],\n  children: tuple[tp.Any],\n):\n  return cls._new(children[0], dict(static))\n\n\njax.tree_util.register_pytree_with_keys(\n  Variable,\n  flatten_with_keys=_variable_flatten_with_keys,\n  unflatten_func=partial(_variable_unflatten, Variable),  # type: ignore\n  flatten_func=_variable_flatten,\n)\n\nVariableState = Variable\n\n\nclass Param(Variable[A]):\n  \"\"\"The canonical learnable parameter. All learnable parameters\n  in NNX layer modules will have the ``Param`` :class:`Variable`\n  type::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n\n    >>> layer = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    >>> jax.tree.map(jnp.shape, nnx.state(layer))\n    State({\n      'bias': Param(\n        value=(3,)\n      ),\n      'kernel': Param(\n        value=(2, 3)\n      )\n    })\n  \"\"\"\n\n  pass\n\n\nclass BatchStat(Variable[A]):\n  \"\"\"The mean and variance batch statistics stored in\n  the :class:`BatchNorm` layer. Note, these are not the\n  learnable scale and bias parameters, but rather the\n  running average statistics that are typically used\n  during post-training inference::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n\n    >>> layer = nnx.BatchNorm(3, rngs=nnx.Rngs(0))\n    >>> jax.tree.map(jnp.shape, nnx.state(layer))\n    State({\n      'bias': Param(\n        value=(3,)\n      ),\n      'mean': BatchStat(\n        value=(3,)\n      ),\n      'scale': Param(\n        value=(3,)\n      ),\n      'var': BatchStat(\n        value=(3,)\n      )\n    })\n  \"\"\"\n\n  pass\n\n\nclass Cache(Variable[A]):\n  \"\"\"Autoregressive cache in :class:`MultiHeadAttention`::\n\n  >>> from flax import nnx\n  >>> import jax, jax.numpy as jnp\n\n  >>> layer = nnx.MultiHeadAttention(\n  ...   num_heads=2,\n  ...   in_features=3,\n  ...   qkv_features=6,\n  ...   out_features=6,\n  ...   decode=True,\n  ...   rngs=nnx.Rngs(0),\n  ... )\n\n  >>> layer.init_cache((1, 3))\n  >>> jax.tree.map(jnp.shape, nnx.state(layer, nnx.Cache))\n  State({\n    'cache_index': Cache(\n      value=()\n    ),\n    'cached_key': Cache(\n      value=(1, 2, 3)\n    ),\n    'cached_value': Cache(\n      value=(1, 2, 3)\n    )\n  })\n  \"\"\"\n\n  pass\n\n\nclass Intermediate(Variable[A]):\n  \"\"\":class:`Variable` type that is typically used for\n  :func:`Module.sow`. Use :func:`nnx.capture` to retrieve\n  the sowed values::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n\n    >>> class Model(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.linear1 = nnx.Linear(2, 3, rngs=rngs)\n    ...     self.linear2 = nnx.Linear(3, 4, rngs=rngs)\n    ...   def __call__(self, x):\n    ...     x = self.linear1(x)\n    ...     self.sow(nnx.Intermediate, 'i', x)\n    ...     x = self.linear2(x)\n    ...     return x\n    >>> model = Model(rngs=nnx.Rngs(0))\n\n    >>> x = jnp.ones((1, 2))\n    >>> y, intms = nnx.capture(model, nnx.Intermediate)(x)\n    >>> jax.tree.map(jnp.shape, intms)\n    State({\n      'i': Intermediate(\n        value=((1, 3),)\n      )\n    })\n  \"\"\"\n\n  pass\n\n\nclass Perturbation(Intermediate[A]):\n  \"\"\":class:`Variable` type that is typically used for\n  :func:`Module.perturb`. Use :func:`nnx.capture` to retrieve\n  the perturbation values::\n\n    >>> from flax import nnx\n    >>> import jax, jax.numpy as jnp\n\n    >>> class Model(nnx.Module):\n    ...   def __init__(self, rngs):\n    ...     self.linear1 = nnx.Linear(2, 3, rngs=rngs)\n    ...     self.linear2 = nnx.Linear(3, 4, rngs=rngs)\n    ...   def __call__(self, x):\n    ...     x = self.linear1(x)\n    ...     x = self.perturb('i', x)\n    ...     x = self.linear2(x)\n    ...     return x\n    >>> model = Model(rngs=nnx.Rngs(0))\n\n    >>> x = jnp.ones((1, 2))\n    >>> y, perturbations = nnx.capture(model, nnx.Perturbation)(x)\n    >>> jax.tree.map(jnp.shape, perturbations)\n    State({\n      'i': Perturbation(\n        value=(1, 3)\n      )\n    })\n  \"\"\"\n\n  pass\n\n\ndef with_metadata(\n  initializer: F,\n  set_value_hooks: tp.Union[SetValueHook[A], tp.Sequence[SetValueHook[A]]] = (),\n  get_value_hooks: tp.Union[SetValueHook[A], tp.Sequence[SetValueHook[A]]] = (),\n  create_value_hooks: tp.Union[\n    CreateValueHook[A], tp.Sequence[CreateValueHook[A]]\n  ] = (),\n  add_axis_hooks: tp.Union[\n    AddAxisHook[Variable[A]], tp.Sequence[AddAxisHook[Variable[A]]]\n  ] = (),\n  remove_axis_hooks: tp.Union[\n    RemoveAxisHook[Variable[A]],\n    tp.Sequence[RemoveAxisHook[Variable[A]]],\n  ] = (),\n  **metadata: tp.Any,\n) -> F:\n  if set_value_hooks:\n    if callable(set_value_hooks):\n      set_value_hooks = (set_value_hooks,)\n    else:\n      set_value_hooks = tuple(set_value_hooks)\n  else:\n    set_value_hooks = ()\n\n  if get_value_hooks:\n    if callable(get_value_hooks):\n      get_value_hooks = (get_value_hooks,)\n    else:\n      get_value_hooks = tuple(get_value_hooks)\n  else:\n    get_value_hooks = ()\n\n  if create_value_hooks:\n    if callable(create_value_hooks):\n      create_value_hooks = (create_value_hooks,)\n    else:\n      create_value_hooks = tuple(create_value_hooks)\n  else:\n    create_value_hooks = ()\n\n  if add_axis_hooks:\n    if callable(add_axis_hooks):\n      add_axis_hooks = (add_axis_hooks,)\n    else:\n      add_axis_hooks = tuple(add_axis_hooks)\n  else:\n    add_axis_hooks = ()\n\n  if remove_axis_hooks:\n    if callable(remove_axis_hooks):\n      remove_axis_hooks = (remove_axis_hooks,)\n    else:\n      remove_axis_hooks = tuple(remove_axis_hooks)\n  else:\n    remove_axis_hooks = ()\n\n  @functools.wraps(initializer)\n  def wrapper(*args):\n    return VariableMetadata(\n      initializer(*args),\n      set_value_hooks=set_value_hooks,\n      get_value_hooks=get_value_hooks,\n      create_value_hooks=create_value_hooks,\n      add_axis_hooks=add_axis_hooks,\n      remove_axis_hooks=remove_axis_hooks,\n      metadata=metadata,\n    )\n\n  return wrapper  # type: ignore\n\n\n###################################################\n### Variable type/class <-> string name mapping ###\n###################################################\n# Assumption: the mapping is 1-1 and unique.\n\nVariableTypeCache: dict[str, tp.Type[Variable[tp.Any]]] = {}\n\n\ndef variable_type_from_name(\n  name: str,\n  /,\n  *,\n  base: type[Variable[tp.Any]] = Variable,\n  allow_register: bool = False,\n) -> tp.Type[Variable[tp.Any]]:\n  \"\"\"Given a Linen-style collection name, get or create its NNX Variable class.\"\"\"\n  if name not in VariableTypeCache:\n    if not allow_register:\n      raise ValueError(\n        f'Name {name} is not registered in the registry. '\n        'To register a new name, use register_variable_name() '\n        'or set allow_register=True.'\n      )\n    VariableTypeCache[name] = type(name, (base,), {})\n  return VariableTypeCache[name]\n\n\ndef variable_name_from_type(\n  typ: tp.Type[Variable[tp.Any]], /, *, allow_register: bool = False\n) -> str:\n  \"\"\"Given an NNX Variable type, get its Linen-style collection name.\n\n  Should output the exact inversed result of `variable_type_from_name()`.\"\"\"\n  for name, t in VariableTypeCache.items():\n    if typ == t:\n      return name\n\n  if not allow_register:\n    raise ValueError(\n      f'Type {typ} is not registered in the registry. '\n      'To register a new type, use register_variable_name() '\n      'or set allow_register=True.'\n    )\n  name = typ.__name__\n  if name in VariableTypeCache:\n    raise ValueError(\n      'Name {name} is already registered in the registry as {VariableTypeCache[name]}. '\n      'It cannot be linked with this type {typ}.'\n    )\n  register_variable_name(name, typ)\n  return name\n\n\n@tp.overload\ndef register_variable_name(\n  name: str,\n  typ: type[Variable[tp.Any]],\n  *,\n  overwrite: bool = False,\n) -> type[Variable[tp.Any]]: ...\n\n\n@tp.overload\ndef register_variable_name(\n  name: str,\n  *,\n  overwrite: bool = False,\n) -> tp.Callable[[type[Variable[tp.Any]]], type[Variable[tp.Any]]]: ...\n\n\ndef register_variable_name(\n  name: str,\n  typ: type[Variable[A]] | Missing = MISSING,\n  *,\n  overwrite=False,\n) -> type[Variable[A]] | tp.Callable[[type[Variable[A]]], type[Variable[A]]]:\n  \"\"\"Register a pair of Linen collection name and its NNX type.\"\"\"\n  if isinstance(typ, Missing):\n    return partial(register_variable_name, name, overwrite=overwrite)\n  typ = tp.cast(type[Variable[A]], typ)\n  if not overwrite and name in VariableTypeCache:\n    raise ValueError(\n      f'Name {name} already mapped to type {VariableTypeCache[name]}. '\n      'To overwrite, call register_variable_name() with `overwrite=True`.'\n    )\n  VariableTypeCache[name] = typ\n  return typ\n\n\n# add known variable type names\nregister_variable_name('params', Param)\nregister_variable_name('batch_stats', BatchStat)\nregister_variable_name('cache', Cache)\nregister_variable_name('intermediates', Intermediate)\nregister_variable_name('perturbations', Perturbation)\n"
  },
  {
    "path": "flax/nnx/visualization.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing as tp\n\nimport treescope  # type: ignore[import-untyped]\nfrom treescope import rendering_parts, renderers\n\ntry:\n  from IPython import get_ipython\n\n  in_ipython = get_ipython() is not None\nexcept ImportError:\n  in_ipython = False\n\n\ndef display(*args):\n  \"\"\"Display the given objects using the Treescope pretty-printer.\n\n  If treescope is not installed or the code is not running in IPython,\n  ``display`` will print the objects instead.\n  \"\"\"\n  if not in_ipython:\n    for x in args:\n      print(x)\n    return\n\n  for x in args:\n    treescope.display(x, ignore_exceptions=True, autovisualize=True)\n\n\ndef render_object_constructor(\n  object_type: type[tp.Any],\n  attributes: tp.Mapping[str, tp.Any],\n  path: str | None,\n  subtree_renderer: renderers.TreescopeSubtreeRenderer,\n  roundtrippable: bool = False,\n  color: str | None = None,\n  first_line_annotation: rendering_parts.RenderableTreePart | None = None,\n) -> rendering_parts.Rendering:\n  \"\"\"Renders an object in \"constructor format\", similar to a dataclass.\n\n  This produces a rendering like `Foo(bar=1, baz=2)`, where Foo identifies the\n  type of the object, and bar and baz are the names of the attributes of the\n  object. It is a *requirement* that these are the actual attributes of the\n  object, which can be accessed via `obj.bar` or similar; otherwise, the\n  path renderings will break.\n\n  This can be used from within a `__treescope_repr__` implementation via ::\n\n    def __treescope_repr__(self, path, subtree_renderer):\n      return repr_lib.render_object_constructor(\n          object_type=type(self),\n          attributes=<dict of attributes here>,\n          path=path,\n          subtree_renderer=subtree_renderer,\n      )\n\n  Args:\n    object_type: The type of the object.\n    attributes: The attributes of the object, which will be rendered as keyword\n      arguments to the constructor.\n    path: The path to the object. When `render_object_constructor` is called\n      from `__treescope_repr__`, this should come from the `path` argument to\n      `__treescope_repr__`.\n    subtree_renderer: The renderer to use to render subtrees. When\n      `render_object_constructor` is called from `__treescope_repr__`, this\n      should come from the `subtree_renderer` argument to `__treescope_repr__`.\n    roundtrippable: Whether evaluating the rendering as Python code will produce\n      an object that is equal to the original object. This implies that the\n      keyword arguments are actually the keyword arguments to the constructor,\n      and not some other attributes of the object.\n    color: The background color to use for the object rendering. If None, does\n      not use a background color. A utility for assigning a random color based\n      on a string key is given in `treescope.formatting_util`.\n    first_line_annotation: An annotation for the first line of the node when it\n      is expanded.\n\n  Returns:\n    A rendering of the object, suitable for returning from `__treescope_repr__`.\n  \"\"\"\n  if roundtrippable:\n    constructor = rendering_parts.siblings(\n      rendering_parts.maybe_qualified_type_name(object_type), '('\n    )\n    closing_suffix = rendering_parts.text(')')\n  else:\n    constructor = rendering_parts.siblings(\n      rendering_parts.roundtrip_condition(roundtrip=rendering_parts.text('<')),\n      rendering_parts.maybe_qualified_type_name(object_type),\n      '(',\n    )\n    closing_suffix = rendering_parts.siblings(\n      ')',\n      rendering_parts.roundtrip_condition(roundtrip=rendering_parts.text('>')),\n    )\n\n  children = []\n  for i, (name, value) in enumerate(attributes.items()):\n    child_path = None if path is None else f'{path}.{name}'\n\n    if i < len(attributes) - 1:\n      # Not the last child. Always show a comma, and add a space when\n      # collapsed.\n      comma_after = rendering_parts.siblings(\n        ',',\n        rendering_parts.fold_condition(collapsed=rendering_parts.text(' ')),\n      )\n    else:\n      # Last child: only show the comma when the node is expanded.\n      comma_after = rendering_parts.fold_condition(\n        expanded=rendering_parts.text(',')\n      )\n\n    child_line = rendering_parts.build_full_line_with_annotations(\n      rendering_parts.siblings_with_annotations(\n        f'{name}=',\n        subtree_renderer(value, path=child_path),\n      ),\n      comma_after,\n    )\n    children.append(child_line)\n\n  return rendering_parts.build_foldable_tree_node_from_children(\n    prefix=constructor,\n    children=children,\n    suffix=closing_suffix,\n    path=path,\n    background_color=color,\n    first_line_annotation=first_line_annotation,\n  )"
  },
  {
    "path": "flax/oss/ .git-blame-ignore-revs",
    "content": "# .git-blame-ignore-revs\n#\n# These commits will be ignored by the github blame view.\n# The git blame CLI can ignore them as well by doing:\n# git blame --ignore-revs-file .git-blame-ignore-revs <filepath>\n# or via global config:\n# git config --global blame.ignoreRevsFile .git-blame-ignore-revs\n# see the blame.markIgnoredLines and blame.markUnblamableLines options.\n#\n# remove all trailing whitespaces\n442df07ca1a90f04c685cdae9f8e488bbffc2f83\n"
  },
  {
    "path": "flax/py.typed",
    "content": "# Marker file for PEP 561. The package uses inline types.\n"
  },
  {
    "path": "flax/serialization.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Serialization utilities for Jax.\n\nAll Flax classes that carry state (e.g., Optimizer) can be turned into a\nstate dict of numpy arrays for easy serialization.\n\"\"\"\nimport enum\nimport threading\nfrom contextlib import contextmanager\nfrom typing import Any\n\nimport jax\nimport msgpack\nimport numpy as np\n\n_STATE_DICT_REGISTRY: dict[Any, Any] = {}\n\n\nclass _ErrorContext(threading.local):\n  \"\"\"Context for deserialization error messages.\"\"\"\n\n  def __init__(self):\n    self.path = []\n\n\n_error_context = _ErrorContext()\n\n\n@contextmanager\ndef _record_path(name):\n  try:\n    _error_context.path.append(name)\n    yield\n  finally:\n    _error_context.path.pop()\n\n\ndef current_path():\n  \"\"\"Current state_dict path during deserialization for error messages.\"\"\"\n  return '/'.join(_error_context.path)\n\n\nclass _NamedTuple:\n  \"\"\"Fake type marker for namedtuple for registry.\"\"\"\n\n  pass\n\n\ndef _is_namedtuple(x):\n  \"\"\"Duck typing test for namedtuple factory-generated objects.\"\"\"\n  return isinstance(x, tuple) and hasattr(x, '_fields')\n\n\ndef from_state_dict(target, state: dict[str, Any], name: str = '.'):\n  \"\"\"Restores the state of the given target using a state dict.\n\n  This function takes the current target as an argument. This\n  lets us know the exact structure of the target,\n  as well as lets us add assertions that shapes and dtypes don't change.\n\n  In practice, none of the leaf values in ``target`` are actually\n  used. Only the tree structure, shapes and dtypes.\n\n  Args:\n    target: the object of which the state should be restored.\n    state: a dictionary generated by ``to_state_dict`` with the desired new\n           state for ``target``.\n    name: name of branch taken, used to improve deserialization error messages.\n  Returns:\n    A copy of the object with the restored state.\n  \"\"\"\n  if _is_namedtuple(target):\n    ty = _NamedTuple\n  else:\n    ty = type(target)\n  if ty not in _STATE_DICT_REGISTRY:\n    return state\n  ty_from_state_dict = _STATE_DICT_REGISTRY[ty][1]\n  with _record_path(name):\n    return ty_from_state_dict(target, state)\n\n\ndef to_state_dict(target) -> dict[str, Any]:\n  \"\"\"Returns a dictionary with the state of the given target.\"\"\"\n  if _is_namedtuple(target):\n    ty = _NamedTuple\n  else:\n    ty = type(target)\n  if ty not in _STATE_DICT_REGISTRY:\n    return target\n\n  ty_to_state_dict = _STATE_DICT_REGISTRY[ty][0]\n  state_dict = ty_to_state_dict(target)\n  if isinstance(state_dict, dict):\n    for key in state_dict.keys():\n      assert isinstance(key, str), 'A state dict must only have string keys.'\n  return state_dict\n\n\ndef is_serializable(target):\n  if not isinstance(target, type):\n    target = type(target)\n  return target in _STATE_DICT_REGISTRY\n\n\ndef register_serialization_state(\n  ty, ty_to_state_dict, ty_from_state_dict, override=False\n):\n  \"\"\"Register a type for serialization.\n\n  Args:\n    ty: the type to be registered\n    ty_to_state_dict: a function that takes an instance of ty and\n      returns its state as a dictionary.\n    ty_from_state_dict: a function that takes an instance of ty and\n      a state dict, and returns a copy of the instance with the restored state.\n    override: override a previously registered serialization handler\n      (default: False).\n  \"\"\"\n  if ty in _STATE_DICT_REGISTRY and not override:\n    raise ValueError(\n      f'a serialization handler for \"{ty.__name__}\" is already registered'\n    )\n  _STATE_DICT_REGISTRY[ty] = (ty_to_state_dict, ty_from_state_dict)\n\n\ndef _list_state_dict(xs: list[Any]) -> dict[str, Any]:\n  return {str(i): to_state_dict(x) for i, x in enumerate(xs)}\n\n\ndef _restore_list(xs, state_dict: dict[str, Any]) -> list[Any]:\n  if len(state_dict) != len(xs):\n    raise ValueError(\n      'The size of the list and the state dict do not match,'\n      f' got {len(xs)} and {len(state_dict)} '\n      f'at path {current_path()}'\n    )\n  ys = []\n  for i in range(len(state_dict)):\n    y = from_state_dict(xs[i], state_dict[str(i)], name=str(i))\n    ys.append(y)\n  return ys\n\n\ndef _dict_state_dict(xs: dict[str, Any]) -> dict[str, Any]:\n  str_keys = {str(k) for k in xs.keys()}\n  if len(str_keys) != len(xs):\n    raise ValueError(\n      'Dict keys do not have a unique string representation: '\n      f'{str_keys} vs given: {xs}'\n    )\n  return {str(key): to_state_dict(value) for key, value in xs.items()}\n\n\ndef _restore_dict(xs, states: dict[str, Any]) -> dict[str, Any]:\n  diff = set(map(str, xs.keys())).difference(states.keys())\n  if diff:\n    raise ValueError(\n      'The target dict keys and state dict keys do not match, target dict'\n      f' contains keys {diff} which are not present in state dict at path'\n      f' {current_path()}'\n    )\n\n  return {\n    key: from_state_dict(value, states[str(key)], name=str(key))\n    for key, value in xs.items()\n  }\n\n\ndef _namedtuple_state_dict(nt) -> dict[str, Any]:\n  return {key: to_state_dict(getattr(nt, key)) for key in nt._fields}\n\n\ndef _restore_namedtuple(xs, state_dict: dict[str, Any]):\n  \"\"\"Rebuild namedtuple from serialized dict.\"\"\"\n  if set(state_dict.keys()) == {'name', 'fields', 'values'}:\n    # TODO(jheek): remove backward compatible named tuple restoration early 2022\n    state_dict = {\n      state_dict['fields'][str(i)]: state_dict['values'][str(i)]\n      for i in range(len(state_dict['fields']))\n    }\n\n  sd_keys = set(state_dict.keys())\n  nt_keys = set(xs._fields)\n\n  if sd_keys != nt_keys:\n    raise ValueError(\n      'The field names of the state dict and the named tuple do not match,'\n      f' got {sd_keys} and {nt_keys} at path {current_path()}'\n    )\n  fields = {\n    k: from_state_dict(getattr(xs, k), v, name=k) for k, v in state_dict.items()\n  }\n  return type(xs)(**fields)\n\n\nregister_serialization_state(dict, _dict_state_dict, _restore_dict)\nregister_serialization_state(list, _list_state_dict, _restore_list)\nregister_serialization_state(\n  tuple,\n  _list_state_dict,\n  lambda xs, state_dict: tuple(_restore_list(list(xs), state_dict)),\n)\nregister_serialization_state(\n  _NamedTuple, _namedtuple_state_dict, _restore_namedtuple\n)\n\nregister_serialization_state(\n  jax.tree_util.Partial,\n  lambda x: (\n    {\n      'args': to_state_dict(x.args),\n      'keywords': to_state_dict(x.keywords),\n    }\n  ),\n  lambda x, sd: jax.tree_util.Partial(\n    x.func,\n    *from_state_dict(x.args, sd['args']),\n    **from_state_dict(x.keywords, sd['keywords']),\n  ),\n)\n\n# On-the-wire / disk serialization format\n\n# We encode state-dicts via msgpack, using its custom type extension.\n# https://github.com/msgpack/msgpack/blob/master/spec.md\n#\n# - ndarrays and DeviceArrays are serialized to nested msgpack-encoded string\n#   of (shape-tuple, dtype-name (e.g. 'float32'), row-major array-bytes).\n#   Note: only simple ndarray types are supported, no objects or fields.\n#\n# - native complex scalars are converted to nested msgpack-encoded tuples\n#   (real, imag).\n\n\ndef _ndarray_to_bytes(arr) -> bytes:\n  \"\"\"Save ndarray to simple msgpack encoding.\"\"\"\n  if isinstance(arr, jax.Array):\n    arr = np.array(arr)\n  if arr.dtype.hasobject or arr.dtype.isalignedstruct:\n    raise ValueError(\n      'Object and structured dtypes not supported '\n      'for serialization of ndarrays.'\n    )\n  tpl = (arr.shape, arr.dtype.name, arr.tobytes('C'))\n  return msgpack.packb(tpl, use_bin_type=True)\n\n\ndef _dtype_from_name(name: str):\n  \"\"\"Handle JAX bfloat16 dtype correctly.\"\"\"\n  if name == b'bfloat16':\n    return jax.numpy.bfloat16\n  else:\n    return np.dtype(name)\n\n\ndef _ndarray_from_bytes(data: bytes) -> np.ndarray:\n  \"\"\"Load ndarray from simple msgpack encoding.\"\"\"\n  shape, dtype_name, buffer = msgpack.unpackb(data, raw=True)\n  return np.frombuffer(\n    buffer, dtype=_dtype_from_name(dtype_name), count=-1, offset=0\n  ).reshape(shape, order='C')\n\n\nclass _MsgpackExtType(enum.IntEnum):\n  \"\"\"Messagepack custom type ids.\"\"\"\n\n  ndarray = 1\n  native_complex = 2\n  npscalar = 3\n\n\ndef _msgpack_ext_pack(x):\n  \"\"\"Messagepack encoders for custom types.\"\"\"\n  # TODO(flax-dev): Array here only work when they are fully addressable.\n  # If they are not fully addressable, use the GDA path for checkpointing.\n  if isinstance(x, (np.ndarray, jax.Array)):\n    return msgpack.ExtType(_MsgpackExtType.ndarray, _ndarray_to_bytes(x))\n  if isinstance(x, np.generic):\n    # pack scalar as ndarray\n    return msgpack.ExtType(\n      _MsgpackExtType.npscalar, _ndarray_to_bytes(np.asarray(x))\n    )\n  elif isinstance(x, complex):\n    return msgpack.ExtType(\n      _MsgpackExtType.native_complex, msgpack.packb((x.real, x.imag))\n    )\n  return x\n\n\ndef _msgpack_ext_unpack(code, data):\n  \"\"\"Messagepack decoders for custom types.\"\"\"\n  if code == _MsgpackExtType.ndarray:\n    return _ndarray_from_bytes(data)\n  elif code == _MsgpackExtType.native_complex:\n    complex_tuple = msgpack.unpackb(data)\n    return complex(complex_tuple[0], complex_tuple[1])\n  elif code == _MsgpackExtType.npscalar:\n    ar = _ndarray_from_bytes(data)\n    return ar[()]  # unpack ndarray to scalar\n  return msgpack.ExtType(code, data)\n\n\n# Chunking array leaves\n\n# msgpack has a hard limit of 2**31 - 1 bytes per object leaf.  To circumvent\n# this limit for giant arrays (e.g. embedding tables), we traverse the tree\n# and break up arrays near the limit into flattened array chunks.\n\n# True limit is 2**31 - 1, but leave a margin for encoding padding.\nMAX_CHUNK_SIZE = 2**30\n\n\ndef _np_convert_in_place(d):\n  \"\"\"Convert any jax devicearray leaves to numpy arrays in place.\"\"\"\n  if isinstance(d, dict):\n    for k, v in d.items():\n      if isinstance(v, jax.Array):\n        d[k] = np.array(v)\n      elif isinstance(v, dict):\n        _np_convert_in_place(v)\n  elif isinstance(d, jax.Array):\n    return np.array(d)\n  return d\n\n\n_tuple_to_dict = lambda tpl: {str(x): y for x, y in enumerate(tpl)}\n_dict_to_tuple = lambda dct: tuple(dct[str(i)] for i in range(len(dct)))\n\n\ndef _chunk(arr) -> dict[str, Any]:\n  \"\"\"Convert array to a canonical dictionary of chunked arrays.\"\"\"\n  chunksize = max(1, int(MAX_CHUNK_SIZE / arr.dtype.itemsize))\n  data = {'__msgpack_chunked_array__': True, 'shape': _tuple_to_dict(arr.shape)}\n  flatarr = arr.reshape(-1)\n  chunks = [\n    flatarr[i : i + chunksize] for i in range(0, flatarr.size, chunksize)\n  ]\n  data['chunks'] = _tuple_to_dict(chunks)\n  return data\n\n\ndef _unchunk(data: dict[str, Any]):\n  \"\"\"Convert canonical dictionary of chunked arrays back into array.\"\"\"\n  assert '__msgpack_chunked_array__' in data\n  shape = _dict_to_tuple(data['shape'])\n  flatarr = np.concatenate(_dict_to_tuple(data['chunks']))\n  return flatarr.reshape(shape)\n\n\ndef _chunk_array_leaves_in_place(d):\n  \"\"\"Convert oversized array leaves to safe chunked form in place.\"\"\"\n  if isinstance(d, dict):\n    for k, v in d.items():\n      if isinstance(v, np.ndarray):\n        if v.size * v.dtype.itemsize > MAX_CHUNK_SIZE:\n          d[k] = _chunk(v)\n      elif isinstance(v, dict):\n        _chunk_array_leaves_in_place(v)\n  elif isinstance(d, np.ndarray):\n    if d.size * d.dtype.itemsize > MAX_CHUNK_SIZE:\n      return _chunk(d)\n  return d\n\n\ndef _unchunk_array_leaves_in_place(d):\n  \"\"\"Convert chunked array leaves back into array leaves, in place.\"\"\"\n  if isinstance(d, dict):\n    if '__msgpack_chunked_array__' in d:\n      return _unchunk(d)\n    else:\n      for k, v in d.items():\n        if isinstance(v, dict) and '__msgpack_chunked_array__' in v:\n          d[k] = _unchunk(v)\n        elif isinstance(v, dict):\n          _unchunk_array_leaves_in_place(v)\n  return d\n\n\n# User-facing API calls:\n\n\ndef msgpack_serialize(pytree, in_place: bool = False) -> bytes:\n  \"\"\"Save data structure to bytes in msgpack format.\n\n  Low-level function that only supports python trees with array leaves,\n  for custom objects use ``to_bytes``.  It splits arrays above MAX_CHUNK_SIZE into\n  multiple chunks.\n\n  Args:\n    pytree: python tree of dict, list, tuple with python primitives\n      and array leaves.\n    in_place: boolean specifying if pytree should be modified in place.\n\n  Returns:\n    msgpack-encoded bytes of pytree.\n  \"\"\"\n  if not in_place:\n    pytree = jax.tree_util.tree_map(lambda x: x, pytree)\n  pytree = _np_convert_in_place(pytree)\n  pytree = _chunk_array_leaves_in_place(pytree)\n  return msgpack.packb(pytree, default=_msgpack_ext_pack, strict_types=True)\n\n\ndef msgpack_restore(encoded_pytree: bytes):\n  \"\"\"Restore data structure from bytes in msgpack format.\n\n  Low-level function that only supports python trees with array leaves,\n  for custom objects use ``from_bytes``.\n\n  Args:\n    encoded_pytree: msgpack-encoded bytes of python tree.\n\n  Returns:\n    Python tree of dict, list, tuple with python primitive\n    and array leaves.\n  \"\"\"\n  state_dict = msgpack.unpackb(\n    encoded_pytree, ext_hook=_msgpack_ext_unpack, raw=False\n  )\n  return _unchunk_array_leaves_in_place(state_dict)\n\n\ndef from_bytes(target, encoded_bytes: bytes):\n  \"\"\"Restore optimizer or other object from msgpack-serialized state-dict.\n\n  Args:\n    target: template object with state-dict registrations that matches\n      the structure being deserialized from ``encoded_bytes``.\n    encoded_bytes: msgpack serialized object structurally isomorphic to\n      ``target``.  Typically a flax model or optimizer.\n\n  Returns:\n    A new object structurally isomorphic to ``target`` containing the updated\n    leaf data from saved data.\n  \"\"\"\n  state_dict = msgpack_restore(encoded_bytes)\n  return from_state_dict(target, state_dict)\n\n\ndef to_bytes(target) -> bytes:\n  \"\"\"Save optimizer or other object as msgpack-serialized state-dict.\n\n  Args:\n    target: template object with state-dict registrations to be\n      serialized to msgpack format.  Typically a flax model or optimizer.\n\n  Returns:\n    Bytes of msgpack-encoded state-dict of ``target`` object.\n  \"\"\"\n  state_dict = to_state_dict(target)\n  return msgpack_serialize(state_dict, in_place=True)\n"
  },
  {
    "path": "flax/struct.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Utilities for defining custom classes that can be used with jax transformations.\"\"\"\n\nfrom collections.abc import Callable\nimport dataclasses\nimport functools\nfrom typing import TypeVar, overload\n\nimport jax\nfrom typing_extensions import (\n  dataclass_transform,  # pytype: disable=not-supported-yet\n)\n\nfrom . import serialization\n\n_T = TypeVar('_T')\n\n\ndef field(pytree_node=True, *, metadata=None, **kwargs):\n  return dataclasses.field(metadata=(metadata or {}) | {'pytree_node': pytree_node},\n                           **kwargs)\n\n\n@dataclass_transform(field_specifiers=(field,))  # type: ignore[literal-required]\n@overload\ndef dataclass(clz: _T, **kwargs) -> _T:\n  ...\n\n\n@dataclass_transform(field_specifiers=(field,))  # type: ignore[literal-required]\n@overload\ndef dataclass(**kwargs) -> Callable[[_T], _T]:\n  ...\n\n\n@dataclass_transform(field_specifiers=(field,))  # type: ignore[literal-required]\ndef dataclass(\n    clz: _T | None = None,\n    **kwargs,\n) -> _T | Callable[[_T], _T]:\n  \"\"\"Create a class which can be passed to functional transformations.\n\n  .. note::\n    Inherit from ``PyTreeNode`` instead to avoid type checking issues when\n    using PyType.\n\n  Jax transformations such as ``jax.jit`` and ``jax.grad`` require objects that are\n  immutable and can be mapped over using the ``jax.tree_util`` methods.\n  The ``dataclass`` decorator makes it easy to define custom classes that can be\n  passed safely to Jax. Define JAX data as normal attribute fields, and use\n  ``pytree_node=False`` to define static metadata.\n\n  See example::\n\n    >>> from flax import struct\n    >>> import jax\n    >>> from typing import Any, Callable\n\n    >>> @struct.dataclass\n    ... class Model:\n    ...   params: Any\n    ...   # use pytree_node=False to indicate an attribute should not be touched\n    ...   # by Jax transformations.\n    ...   apply_fn: Callable = struct.field(pytree_node=False)\n\n    ...   def __apply__(self, *args):\n    ...     return self.apply_fn(*args)\n\n    >>> params = {}\n    >>> params_b = {}\n    >>> apply_fn = lambda v, x: x\n    >>> model = Model(params, apply_fn)\n\n    >>> # model.params = params_b  # Model is immutable. This will raise an error.\n    >>> model_b = model.replace(params=params_b)  # Use the replace method instead.\n\n    >>> # This class can now be used safely in Jax to compute gradients w.r.t. the\n    >>> # parameters.\n    >>> model = Model(params, apply_fn)\n    >>> loss_fn = lambda model: 3.\n    >>> model_grad = jax.grad(loss_fn)(model)\n\n  Note that dataclasses have an auto-generated ``__init__`` where\n  the arguments of the constructor and the attributes of the created\n  instance match 1:1. If you desire a \"smart constructor\", for example to\n  optionally derive some of the attributes from others,\n  make an additional static or class method. Consider the following example::\n\n    >>> @struct.dataclass\n    ... class DirectionAndScaleKernel:\n    ...   direction: jax.Array\n    ...   scale: jax.Array\n\n    ...   @classmethod\n    ...   def create(cls, kernel):\n    ...     scale = jax.numpy.linalg.norm(kernel, axis=0, keepdims=True)\n    ...     direction = direction / scale\n    ...     return cls(direction, scale)\n\n  Args:\n    clz: the class that will be transformed by the decorator.\n    **kwargs: arguments to pass to the dataclass constructor.\n\n  Returns:\n    The new class.\n  \"\"\"\n  # Support passing arguments to the decorator (e.g. @dataclass(kw_only=True))\n  if clz is None:\n    return functools.partial(dataclass, **kwargs)  # type: ignore[bad-return-type]\n\n  # check if already a flax dataclass\n  if '_flax_dataclass' in clz.__dict__:\n    return clz\n\n  if 'frozen' not in kwargs.keys():\n    kwargs['frozen'] = True\n  data_clz = dataclasses.dataclass(**kwargs)(clz)  # type: ignore\n  meta_fields = []\n  data_fields = []\n  for field_info in dataclasses.fields(data_clz):\n    is_pytree_node = field_info.metadata.get('pytree_node', True)\n    if is_pytree_node:\n      data_fields.append(field_info.name)\n    else:\n      meta_fields.append(field_info.name)\n\n  def replace(self, **updates):\n    \"\"\"Returns a new object replacing the specified fields with new values.\"\"\"\n    return dataclasses.replace(self, **updates)\n\n  data_clz.replace = replace\n\n  jax.tree_util.register_dataclass(data_clz, data_fields, meta_fields)\n\n  def to_state_dict(x):\n    state_dict = {\n      name: serialization.to_state_dict(getattr(x, name))\n      for name in data_fields\n    }\n    return state_dict\n\n  def from_state_dict(x, state):\n    \"\"\"Restore the state of a data class.\"\"\"\n    state = state.copy()  # copy the state so we can pop the restored fields.\n    updates = {}\n    for name in data_fields:\n      if name not in state:\n        raise ValueError(\n          f'Missing field {name} in state dict while restoring'\n          f' an instance of {clz.__name__},'\n          f' at path {serialization.current_path()}'\n        )\n      value = getattr(x, name)\n      value_state = state.pop(name)\n      updates[name] = serialization.from_state_dict(\n        value, value_state, name=name\n      )\n    if state:\n      names = ','.join(state.keys())\n      raise ValueError(\n        f'Unknown field(s) \"{names}\" in state dict while'\n        f' restoring an instance of {clz.__name__}'\n        f' at path {serialization.current_path()}'\n      )\n    return x.replace(**updates)\n\n  serialization.register_serialization_state(\n    data_clz, to_state_dict, from_state_dict\n  )\n\n  # add a _flax_dataclass flag to distinguish from regular dataclasses\n  data_clz._flax_dataclass = True  # type: ignore[attr-defined]\n\n  return data_clz  # type: ignore\n\n\nTNode = TypeVar('TNode', bound='PyTreeNode')\n\n\n@dataclass_transform(field_specifiers=(field,))  # type: ignore[literal-required]\nclass PyTreeNode:\n  \"\"\"Base class for dataclasses that should act like a JAX pytree node.\n\n  See ``flax.struct.dataclass`` for the ``jax.tree_util`` behavior.\n  This base class additionally avoids type checking errors when using PyType.\n\n  Example::\n\n    >>> from flax import struct\n    >>> import jax\n    >>> from typing import Any, Callable\n\n    >>> class Model(struct.PyTreeNode):\n    ...   params: Any\n    ...   # use pytree_node=False to indicate an attribute should not be touched\n    ...   # by Jax transformations.\n    ...   apply_fn: Callable = struct.field(pytree_node=False)\n\n    ...   def __apply__(self, *args):\n    ...     return self.apply_fn(*args)\n\n    >>> params = {}\n    >>> params_b = {}\n    >>> apply_fn = lambda v, x: x\n    >>> model = Model(params, apply_fn)\n\n    >>> # model.params = params_b  # Model is immutable. This will raise an error.\n    >>> model_b = model.replace(params=params_b)  # Use the replace method instead.\n\n    >>> # This class can now be used safely in Jax to compute gradients w.r.t. the\n    >>> # parameters.\n    >>> model = Model(params, apply_fn)\n    >>> loss_fn = lambda model: 3.\n    >>> model_grad = jax.grad(loss_fn)(model)\n  \"\"\"\n\n  def __init_subclass__(cls, **kwargs):\n    super().__init_subclass__()\n    dataclass(cls, **kwargs)  # pytype: disable=wrong-arg-types\n\n  def __init__(self, *args, **kwargs):\n    # stub for pytype\n    raise NotImplementedError\n\n  def replace(self: TNode, **overrides) -> TNode:\n    # stub for pytype\n    raise NotImplementedError\n"
  },
  {
    "path": "flax/testing/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\n\"\"\"Flax testing utilities.\"\"\"\n\nfrom .benchmark import Benchmark\n"
  },
  {
    "path": "flax/testing/benchmark.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Benchmark class for Flax regression and integration testing.\n\nThis file defines utility functions for collecting model training results\nfrom TensorBoard summaries, and for reporting benchmarks in a JSON format\nfor pickup by continuous integration / monitoring frameworks.\n\nWhen the `benchmark_output_dir` is provided, the benchmark results are\nsaved in this directory in the JSON format with a single file per\nbenchmark.\n\"\"\"\n\nimport functools\nimport inspect\nimport itertools\nimport json\nimport os\nimport tempfile\n\nfrom absl import flags, logging\nfrom absl.testing import absltest\nfrom tensorboard.backend.event_processing import (\n  directory_watcher,\n  event_file_loader,\n  io_wrapper,\n)\nfrom tensorboard.summary import v1 as summary_lib\nfrom tensorboard.util import tensor_util\n\nfrom flax import io\n\n_BENCHMARK_OUTPUT_DIR = flags.DEFINE_string(\n  'benchmark_output_dir', default=None, help='Benchmark output directory.'\n)\n\n_SCALAR_PLUGIN_NAME = (\n  summary_lib.scalar_pb('', 0).value[0].metadata.plugin_data.plugin_name\n)\n\n\ndef _make_events_generator(path):\n  \"\"\"Makes a generator yielding TensorBoard events from files in `path`.\"\"\"\n  return directory_watcher.DirectoryWatcher(\n    path, event_file_loader.EventFileLoader, io_wrapper.IsSummaryEventsFile\n  ).Load()\n\n\ndef _is_scalar_value(value):\n  if value.HasField('metadata') and value.metadata.HasField('plugin_data'):\n    plugin_data = value.metadata.plugin_data\n    return plugin_data.plugin_name == _SCALAR_PLUGIN_NAME\n\n  return False\n\n\ndef _process_event(event):\n  \"\"\"Parse TensorBoard scalars into a (tag, wall_time, step, scalar) tuple.\"\"\"\n  for value in event.summary.value:\n    if not _is_scalar_value(value):\n      continue\n\n    if value.HasField('tensor'):\n      yield (\n        value.tag,\n        event.wall_time,\n        event.step,\n        tensor_util.make_ndarray(value.tensor).item(),\n      )\n\n\ndef _get_tensorboard_scalars(path):\n  \"\"\"Read and parse scalar TensorBoard summaries.\n\n  Args:\n    path: str. Path containing TensorBoard event files.\n\n  Returns:\n    Dictionary mapping summary tags (str) to lists of\n    (wall_time, step, scalar) tuples.\n  \"\"\"\n  gen = _make_events_generator(path)\n  data = filter(lambda x: x.HasField('summary'), gen)\n  data = itertools.chain.from_iterable(map(_process_event, data))\n\n  data_by_key = {}\n  for tag, wall_time, step, value in data:\n    if not tag in data_by_key:\n      data_by_key[tag] = []\n    data_by_key[tag].append((wall_time, step, value))\n  return data_by_key\n\n\nclass Benchmark(absltest.TestCase):\n  \"\"\"Benchmark class for Flax examples.\n\n  This class overrides the behaviour of `self.assert*` methods to be\n  deferred instead of failing immediately. This allows for using absltest\n  assert methods for checking benchmark target metrics. This is also\n  necessary for correctly reporting benchmark results and determining its\n  success.\n  \"\"\"\n\n  def __init__(self, *args, **kwargs):\n    \"\"\"Wrap test methods in a try-except decorator to delay exceptions.\"\"\"\n    super().__init__(*args, **kwargs)\n    for func_name in dir(self):\n      if func_name.startswith('assert'):\n        func = getattr(self, func_name)\n        patched_func = functools.partial(self._collect_assert_wrapper, fn=func)\n        setattr(self, func_name, patched_func)\n\n    self._benchmark_output_dir = _BENCHMARK_OUTPUT_DIR.value\n    # Create target directory if defined.\n    if self._benchmark_output_dir and not io.exists(self._benchmark_output_dir):\n      io.makedirs(self._benchmark_output_dir)\n\n  # pylint: disable=invalid-name\n  def _collect_assert_wrapper(self, *args, fn=None, **kwargs):\n    \"\"\"Wrapper around assert methods that caputres and collects failures.\"\"\"\n    try:\n      return fn(*args, **kwargs)\n    except self.failureException as ex:\n      self._outstanding_fails.append(ex)\n\n  def setUp(self):\n    \"\"\"Setup ran before each test.\"\"\"\n    super().setUp()\n    self._reported_name = None\n    self._reported_wall_time = None\n    self._reported_metrics = {}\n    self._reported_extras = {}\n    self._outstanding_fails = []\n\n  def tearDown(self):\n    \"\"\"Tear down after each test.\"\"\"\n    super().tearDown()\n    self._report_benchmark_results()\n    for message in self._outstanding_fails:\n      raise self.failureException(message)\n\n  def get_tmp_model_dir(self):\n    \"\"\"Returns an unique temporary directory for storing model data.\n\n    Returns path by appending Classname.testname to `benchmark_output_dir` flag\n    if defined else uses a temporary directory. This helps to export summary\n    files to tensorboard as multiple separate runs for each test method.\n    \"\"\"\n    if self._benchmark_output_dir:\n      model_dir = self._benchmark_output_dir\n    else:\n      model_dir = tempfile.mkdtemp()\n    model_dir_path = os.path.join(\n      model_dir, self._reported_name or self._get_test_name()\n    )\n    # Create directories if they don't exist.\n    if not io.exists(model_dir_path):\n      io.makedirs(model_dir_path)\n    return model_dir_path\n\n  def has_outstanding_fails(self):\n    \"\"\"Determine whether the benchmark failed, but the error is deferred.\"\"\"\n    return len(self._outstanding_fails) > 0\n\n  def read_summaries(self, path):\n    \"\"\"Read TensorBoard summaries.\"\"\"\n    return _get_tensorboard_scalars(path)\n\n  def report_wall_time(self, wall_time: float):\n    \"\"\"Report wall time for the benchmark.\"\"\"\n    self._update_reported_name()\n    self._reported_wall_time = wall_time\n\n  def report_metrics(self, metrics: dict[str, float]):\n    \"\"\"Report metrics for the benchmark.\"\"\"\n    self._update_reported_name()\n    self._reported_metrics.update(metrics)\n\n  def report_metric(self, name: str, value: float):\n    \"\"\"Report a single metric for the benchmark.\"\"\"\n    self.report_metrics({name: value})\n\n  def report_extras(self, extras: dict[str, str]):\n    \"\"\"Report extras for the benchmark.\"\"\"\n    self._update_reported_name()\n    self._reported_extras.update(extras)\n\n  def report_extra(self, name: str, value: str):\n    \"\"\"Report a single extra for the benchmark.\"\"\"\n    self.report_extras({name: value})\n\n  def _get_test_name(self, prefix='test_'):\n    \"\"\"Returns full name of test class and method calling report_benchmark.\n\n    The name is based on the *outermost* Benchmark class in the class stack.\n    Based on tensorflow/python/platform/benchmark.py\n\n    Args:\n      prefix: str. Prefix that the caller method must have.\n\n    Returns:\n      Resolved test name as `ClassName.test_name`.\n    \"\"\"\n    # Find the caller method (outermost Benchmark class).\n    stack = inspect.stack()\n    calling_class, name = None, None\n    for frame_info in stack[::-1]:\n      f_locals = frame_info.frame.f_locals\n      f_self = f_locals.get('self', None)\n      if isinstance(f_self, Benchmark):\n        name = frame_info.function\n        if name.startswith(prefix):\n          calling_class = f_self\n          break\n    if calling_class is None:\n      raise ValueError('Unable to determine the calling Benchmark class.')\n\n    # Prefix the name with the class name.\n    class_name = type(calling_class).__name__\n    name = f'{class_name}.{name}'\n    return name\n\n  def _update_reported_name(self):\n    \"\"\"Record / update test name for the benchmark.\"\"\"\n    self._reported_name = self._reported_name or self._get_test_name()\n\n  def _report_benchmark_results(self):\n    \"\"\"Produce benchmark results report.\n\n    Results are reported as a JSON string with the following schema:\n    ```\n    {\n      \"name\": <class.testMethod>\n      \"succeeded\": true / false\n      \"wall_time\": float (containing wall-time for the benchmark)\n      \"metrics\": {\n        \"string\" -> float map of other performance metrics\n      }\n      \"extras\": {\n        \"string\" -> \"string\" map containing anything else of interest\n      }\n    }\n    ```\n    \"\"\"\n    name = self._reported_name\n    if not name:\n      raise ValueError(\n        'Unable to determine test name for reporting '\n        'benchmark results. Make sure you are using '\n        '`self.report_*` methods.'\n      )\n\n    succeeded = not self.has_outstanding_fails()\n    results = {\n      'name': name,\n      'succeeded': succeeded,\n      'metrics': self._reported_metrics,\n      'extras': self._reported_extras,\n    }\n    if self._reported_wall_time is not None:\n      results['wall_time'] = self._reported_wall_time\n    if not succeeded:\n      msg = '\\n'.join([str(fail) for fail in self._outstanding_fails])\n      results['extras']['failed_assertions'] = msg\n\n    results_str = json.dumps(results)\n    logging.info(results_str)\n\n    # Maybe save results as a file for pickup by CI / monitoring frameworks.\n    if self._benchmark_output_dir:\n      filename = os.path.join(self._benchmark_output_dir, name + '.json')\n      with io.GFile(filename, 'w') as fout:\n        fout.write(results_str)\n"
  },
  {
    "path": "flax/traceback_util.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Flax specific traceback_util functions.\"\"\"\n\nfrom jax._src import traceback_util as jax_traceback_util\nfrom jax.extend import source_info_util\n\nfrom flax import config\n\n# pylint: disable=protected-access\n\n# Globals:\n# Whether to filter flax frames from traceback.\n_flax_filter_tracebacks = config.flax_filter_frames\n# Flax specific set of paths to exclude from tracebacks.\n_flax_exclusions = set()\n\n\n# re-import JAX symbol for convenience.\napi_boundary = jax_traceback_util.api_boundary\n\n\ndef register_exclusion(path):\n  \"\"\"Marks a Flax source file for exclusion.\"\"\"\n  global _flax_exclusions, _flax_filter_tracebacks\n  # Record flax exclusions so we can dynamically add and remove them.\n  _flax_exclusions.add(path)\n  if _flax_filter_tracebacks:\n    jax_traceback_util.register_exclusion(path)\n    source_info_util.register_exclusion(path)\n\ndef hide_flax_in_tracebacks():\n  \"\"\"Hides Flax internal stack frames in tracebacks.\"\"\"\n  global _flax_exclusions, _flax_filter_tracebacks\n  _flax_filter_tracebacks = True\n  for exclusion in _flax_exclusions:\n    if exclusion not in jax_traceback_util._exclude_paths:\n      jax_traceback_util._exclude_paths.append(exclusion)\n\n\ndef show_flax_in_tracebacks():\n  \"\"\"Shows Flax internal stack frames in tracebacks.\"\"\"\n  global _flax_exclusions, _flax_filter_tracebacks\n  _flax_filter_tracebacks = False\n  for exclusion in _flax_exclusions:\n    if exclusion in jax_traceback_util._exclude_paths:\n      jax_traceback_util._exclude_paths.remove(exclusion)\n"
  },
  {
    "path": "flax/training/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Flax training utilities.\"\"\"\n"
  },
  {
    "path": "flax/training/checkpoints.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Checkpointing helper functions.\n\nHandles saving and restoring optimizer checkpoints based on step-number or\nother numerical metric in filename.  Cleans up older / worse-performing\ncheckpoint files.\n\"\"\"\n\nimport functools\nimport os\nimport pathlib\nimport re\nimport time\nimport warnings\nfrom concurrent.futures import thread\nfrom typing import (\n  Any,\n)\nfrom collections.abc import Callable, Iterable\n\nimport jax\nimport orbax.checkpoint as ocp\nfrom absl import logging\nfrom jax import monitoring, process_index\nfrom jax import tree_util as jtu\nfrom jax.experimental.array_serialization.serialization import (\n  GlobalAsyncCheckpointManager,\n  get_tensorstore_spec,\n)\nfrom jax.experimental.multihost_utils import sync_global_devices\n\nfrom flax import config, core, errors, io, serialization, traverse_util\nfrom flax.training import orbax_utils\n\n\n_READ_CHECKPOINT_EVENT: str = '/jax/checkpoint/read/durations_sec'\n_WRITE_CHECKPOINT_EVENT: str = '/jax/checkpoint/write/durations_sec'\n\n\n# Single-group reg-exps for int or float numerical substrings.\n# captures sign:\nSIGNED_FLOAT_RE = re.compile(r'([-+]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][-+]?\\d+)?)')\n# does not capture sign:\nUNSIGNED_FLOAT_RE = re.compile(\n  r'[-+]?((?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][-+]?\\d+)?)'\n)\n# Module name followed by number.\nMODULE_NUM_RE = re.compile(r'(.*)_\\d+$')\n# Alternative schemes handled by `gfile`, e.g. on Google Cloud Storage (GCS).\nSCHEME_RE = re.compile('^(?P<scheme>[a-z][a-z0-9.+-]+://)?(?P<path>.*)', re.I)\n\n# Multiprocess arrays (GlobalDeviceArray, or JAX array with multiprocess\n# sharding) is across processes and will be stored in directories with this\n# postfix, separated from the non-distributed data (e.g. the larger pytree)\nMP_ARRAY_POSTFIX = '_gda'\n# Occurrences of multiprocess arrays in the target pytree will be\n# replaced by this string placeholder.\nMP_ARRAY_PH = '//GDAPlaceholder:'\n\n# Add a copy-success file to a distributed array directory to indicate the\n# array save is complete.\n# We need this for GCS because GCS's directory move is not atomic.\nCOMMIT_SUCCESS_FILE = 'commit_success.txt'\n\n# Orbax main checkpoint file name.\nORBAX_CKPT_FILENAME = 'checkpoint'\nORBAX_MANIFEST_OCDBT = 'manifest.ocdbt'\nORBAX_METADATA_FILENAME = '_METADATA'\n\nPyTree = Any\n\n# TODO(flax-dev): Remove this once flax is using the latest jax release\n# containing jax.Array attribute.\nMultiprocessArrayType = Any\n\n\ndef _is_multiprocess_array(value: Any) -> bool:\n  \"\"\"Use GlobalAsyncCheckpointManager to save the array if it's only partially available on this host.\"\"\"\n  if isinstance(value, jax.Array):\n    return not value.is_fully_addressable\n  return False\n\n\ndef _checkpoint_path(\n  ckpt_dir: str, step: int | float | str, prefix: str = 'checkpoint_'\n) -> str:\n  return os.path.join(ckpt_dir, f'{prefix}{step}')\n\n\ndef _checkpoint_path_step(path: str) -> float | None:\n  \"\"\"Returns the step number of a checkpoint path.\"\"\"\n  for s in SIGNED_FLOAT_RE.split(path)[::-1]:\n    if SIGNED_FLOAT_RE.match(s):\n      return float(s)\n  return None\n\n\ndef _allowempty_listdir(path: str):\n  try:\n    return io.listdir(path)\n  except io.NotFoundError:\n    return []\n\n\ndef _safe_remove(path: str):\n  \"\"\"Identify whether a path is a dir or list and choose the correct remove method.\"\"\"\n  if io.isdir(path):\n    io.rmtree(path)\n  else:\n    io.remove(path)\n\n\ndef _is_orbax_checkpoint(path: str) -> bool:\n  return (\n      io.exists(os.path.join(path, ORBAX_CKPT_FILENAME))\n      or io.exists(os.path.join(path, ORBAX_METADATA_FILENAME))\n      or io.exists(os.path.join(path, ORBAX_MANIFEST_OCDBT))\n  )\n\n\nclass AsyncManager:\n  \"\"\"A simple object to track async checkpointing.\n\n  How to use: create an instance and pass to save_checkpoint() calls:\n    am = AsyncManager()\n    save_checkpoint(..., async_manager=am)\n  \"\"\"\n\n  def __init__(self, max_workers: int = 1):\n    self.executor = thread.ThreadPoolExecutor(max_workers=max_workers)\n    self.save_future = None\n\n  def wait_previous_save(self):\n    \"\"\"Block until the previous save finishes, to keep files' consistency.\"\"\"\n    if self.save_future and not self.save_future.done():\n      logging.warning(\n        'The previous async save_checkpoint has not finished yet. Waiting '\n        'for it to complete before the next save.'\n      )\n      self.save_future.result()\n\n  def save_async(self, task: Callable[[], Any]):\n    \"\"\"Run a task async.\n\n    The future will be tracked as self.save_future.\n\n    Args:\n      task: The callable to be executed asynchronously.\n    \"\"\"\n    self.wait_previous_save()\n    self.save_future = self.executor.submit(task)  # type: ignore\n\n\ndef _split_mp_arrays(\n  target: dict[str, Any]\n) -> tuple[dict[str, Any], list[tuple[MultiprocessArrayType, str]]]:\n  \"\"\"Split out the multiprocess arrays from the target pytree to save.\"\"\"\n  # When target is a single leaf instead of a pytree dict.\n  if not isinstance(target, (core.FrozenDict, dict)):\n    if _is_multiprocess_array(target):\n      return MP_ARRAY_PH, [(target, '')]\n    return target, []\n  # Traverse the target and handle distributed arrays.\n  flattened = traverse_util.flatten_dict(target, keep_empty_nodes=True)\n  mpa_targets = []\n  for key, value in flattened.items():\n    if _is_multiprocess_array(value):\n      subpath = '/'.join(key)\n      mpa_targets.append((value, subpath))\n      flattened[key] = MP_ARRAY_PH + subpath\n  target = traverse_util.unflatten_dict(flattened)\n  return target, mpa_targets\n\n\ndef _make_mpa_dirs(\n  mpa_targets: list[tuple[MultiprocessArrayType, str]], tmp_path: str\n):\n  # Temporary array path is not used in GCS.\n  if tmp_path.startswith('gs://'):\n    return\n  mpa_tmp_path = tmp_path + MP_ARRAY_POSTFIX\n  # Clean up the previous MPA dir, in case some leftover from last preemption\n  # lingers.\n  if io.exists(mpa_tmp_path):\n    logging.info('Removing outdated MPA temporary files at %s', mpa_tmp_path)\n    io.rmtree(mpa_tmp_path)\n  _, mpa_subpaths = zip(*mpa_targets)\n  for subpath in mpa_subpaths:\n    io.makedirs(os.path.join(mpa_tmp_path, subpath))\n\n\ndef _save_mpas(\n  gda_manager,\n  mpa_targets: list[tuple[MultiprocessArrayType, str]],\n  tmp_path: str,\n  final_path: str,\n  base_path: str,\n  keep: int,\n  overwrite: bool,\n  keep_every_n_steps: int | None,\n  ckpt_start_time: float,\n  async_manager: AsyncManager | None = None,\n):\n  \"\"\"Save the multiprocess arrays given the paths.\"\"\"\n  mpa_list, mpa_subpaths = zip(*mpa_targets)\n  mpa_tmp_path, mpa_final_path = (\n    tmp_path + MP_ARRAY_POSTFIX,\n    final_path + MP_ARRAY_POSTFIX,\n  )\n  write_commit_success = False\n  # If the checkpoint directory is a GCS directory, then keep the final\n  # checkpoint directory as the temporary checkpoint directory. This is because\n  # renames are not atomic on GCS. When restoring check for the existence of a\n  # success file.\n  # TODO: figure out a way to unit-test the behavior.\n  if tmp_path.startswith('gs://'):\n    mpa_tmp_path = mpa_final_path\n    write_commit_success = True\n  mpa_paths = [os.path.join(mpa_tmp_path, x) for x in mpa_subpaths]\n  ts_specs = [get_tensorstore_spec(x) for x in mpa_paths]\n  gda_manager.serialize(\n    list(mpa_list),\n    ts_specs,\n    on_commit_callback=functools.partial(\n      _save_commit,\n      tmp_path,\n      final_path,\n      base_path,\n      keep,\n      overwrite,\n      keep_every_n_steps,\n      ckpt_start_time,\n      has_mpa=True,\n      write_commit_success=write_commit_success,\n      async_manager=async_manager,\n    ),\n  )\n\n\ndef _restore_mpas(\n  state_dict,\n  target: Any | None,\n  ckpt_path: str,\n  step: int | float | None,\n  gda_manager: GlobalAsyncCheckpointManager | None,\n  allow_partial: bool = False,\n):\n  \"\"\"Restore the multiprocess arrays given the target structure and type.\"\"\"\n\n  def _check_mpa_errors():\n    if not gda_manager:\n      raise errors.MPACheckpointingRequiredError(ckpt_path, step)\n    if not target and not allow_partial:\n      raise errors.MPARestoreTargetRequiredError(ckpt_path, step)\n\n  def _safe_deserialize(\n    target_mpas: list[tuple[tuple[Any, ...], MultiprocessArrayType, str]],\n    gda_manager: Any,\n  ) -> list[MultiprocessArrayType]:\n    gda_manager.wait_until_finished()\n\n    # Check if reading from GCS and the array dir is potentially corrupted.\n    if ckpt_path.startswith('gs://') and not io.exists(\n      os.path.join(ckpt_path + MP_ARRAY_POSTFIX, COMMIT_SUCCESS_FILE)\n    ):\n      raise errors.MPARestoreDataCorruptedError(step, ckpt_path)\n\n    # Check if the given target array types are valid.\n    shardings = []\n    for _, arr, path in target_mpas:\n      if isinstance(arr, jax.Array):\n        shardings.append(arr.sharding)\n\n    # Restore the arrays.\n    ts_specs = [get_tensorstore_spec(path) for _, _, path in target_mpas]\n    return gda_manager.deserialize(shardings, ts_specs)\n\n  # When target is a single leaf instead of a pytree dict.\n  if not isinstance(state_dict, (core.FrozenDict, dict)):\n    if (\n      _is_multiprocess_array(target)\n      and isinstance(state_dict, str)\n      and state_dict.startswith(MP_ARRAY_PH)\n    ):\n      _check_mpa_errors()\n      return _safe_deserialize(\n        [((), target, ckpt_path + MP_ARRAY_POSTFIX)], gda_manager\n      )[0]\n    return state_dict\n\n  # Go through the restored checkpoint pytree for all MPAs\n  flattened = traverse_util.flatten_dict(state_dict, keep_empty_nodes=True)\n  target_flattened = {}\n  if target:\n    target_flattened = traverse_util.flatten_dict(\n      serialization.to_state_dict(target), keep_empty_nodes=True\n    )\n  # A list of (state_dict_key, target_array, array_file_path) for every array\n  # to be restored\n  target_mpas = []\n  for key, value in flattened.items():\n    if isinstance(value, str) and value.startswith(MP_ARRAY_PH):\n      _check_mpa_errors()\n      if (\n        not target\n        or (key not in target_flattened)\n        or (not _is_multiprocess_array(target_flattened[key]))\n      ):\n        if allow_partial:\n          logging.warning(\n            'Multiprocess array %s could not be restored because a valid'\n            ' array is not found in target at the corresponding location.'\n            ' Proceed to restore other arrays because'\n            ' allow_partial_restoration=True',\n            key,\n          )\n        else:\n          raise errors.MPARestoreTargetRequiredError(ckpt_path, step, key)\n      else:\n        mpa_path = os.path.join(\n          ckpt_path + MP_ARRAY_POSTFIX, value[len(MP_ARRAY_PH) :]\n        )\n        target_mpas.append((key, target_flattened[key], mpa_path))\n\n  # If any MPA needs to be restored, call deserialize\n  if target_mpas:\n    mpa_list = _safe_deserialize(target_mpas, gda_manager)\n    for mpa, (key, _, _) in zip(mpa_list, target_mpas):\n      flattened[key] = mpa\n    state_dict = traverse_util.unflatten_dict(flattened)\n  return state_dict\n\n\ndef natural_sort(file_list: Iterable[str], signed: bool = True) -> list[str]:\n  \"\"\"Natural sort for filenames with numerical substrings.\n\n  Args:\n    file_list: list of paths to sort containing numerical substrings.\n    signed: bool: if leading '-' (or '+') signs should be included in numerical\n      substrings as a sign or treated as a separator.\n\n  Returns:\n    List of filenames sorted 'naturally', not lexicographically: any\n    integer substrings are used to subsort numerically. e.g.\n    file_1, file_10, file_2  -->  file_1, file_2, file_10\n    file_0.1, file_-0.2, file_2.0  -->  file_-0.2, file_0.1, file_2.0\n  \"\"\"\n  float_re = SIGNED_FLOAT_RE if signed else UNSIGNED_FLOAT_RE\n\n  def maybe_num(s):\n    if float_re.match(s):\n      return float(s)\n    else:\n      return s\n\n  def split_keys(s):\n    return [maybe_num(c) for c in float_re.split(s)]\n\n  return sorted(file_list, key=split_keys)\n\n\ndef safe_normpath(path: str) -> str:\n  \"\"\"Normalizes path safely to get around `io.glob()` limitations.\"\"\"\n  match = SCHEME_RE.match(path)\n  assert match is not None\n  d = match.groupdict()\n  return (d['scheme'] or '') + os.path.normpath(d['path'])\n\n\ndef _remove_invalid_ckpts(\n  ckpt_path: str,\n  base_path: str,\n  keep: int,\n  overwrite: bool,\n  keep_every_n_steps: int | None,\n  has_mpa: bool,\n) -> None:\n  \"\"\"Clean up the checkpoint space according to `overwrite`, `keep`, and `keep_every_n_steps` parameters.\"\"\"\n  dir_path, prefix = os.path.split(base_path)\n  checkpoint_files: list[Any] = [\n    pathlib.PurePath(c) for c in _allowempty_listdir(dir_path)\n  ]\n  checkpoint_files = [\n    os.path.join(dir_path, c)\n    for c in checkpoint_files\n    if c.match(f'{prefix}*') and not c.match(f'*{MP_ARRAY_POSTFIX}')\n  ]\n  checkpoint_files = natural_sort(checkpoint_files)\n\n  # Remove newer checkpoints\n  if overwrite and ckpt_path in checkpoint_files:\n    ind = checkpoint_files.index(ckpt_path) + 1\n    newer_ckpts = checkpoint_files[ind:]\n    checkpoint_files = checkpoint_files[:ind]\n    for path in newer_ckpts:\n      logging.info('Removing checkpoint at %s', path)\n      if has_mpa:\n        # MPA might be removed already but the main ckpt is still there. This\n        # can happen if the job is previously preempted after deleting the MPA\n        # checkpoint folder and before deleting the main checkpoint.\n        if io.exists(path + MP_ARRAY_POSTFIX):\n          io.rmtree(path + MP_ARRAY_POSTFIX)\n      _safe_remove(path)\n\n  # Remove old checkpoint files.\n  last_kept = -float('inf')\n  if len(checkpoint_files) > keep:\n    old_ckpts = checkpoint_files[:-keep]\n    # Note: old_ckpts is sorted from oldest to newest.\n    for path in old_ckpts:\n      if keep_every_n_steps:\n        step_number = _checkpoint_path_step(path)\n        if step_number and (step_number - last_kept) >= keep_every_n_steps:\n          logging.debug(\n            'Not deleting %s, because last_kept=%f and keeping '\n            'every %d steps.',\n            path,\n            last_kept,\n            keep_every_n_steps,\n          )\n          last_kept = step_number\n          continue\n      logging.info('Removing checkpoint at %s', path)\n      if has_mpa:\n        # MPA might be removed already but the main ckpt is still there.\n        if io.exists(path + MP_ARRAY_POSTFIX):\n          io.rmtree(path + MP_ARRAY_POSTFIX)\n      _safe_remove(path)\n\n\ndef _save_commit(\n  ckpt_tmp_path: str,\n  ckpt_path: str,\n  base_path: str,\n  keep: int,\n  overwrite: bool,\n  keep_every_n_steps: int | None,\n  ckpt_start_time: float,\n  has_mpa: bool,\n  write_commit_success: bool,\n  async_manager: AsyncManager | None = None,\n) -> None:\n  \"\"\"Commit changes after saving checkpoints to disk.\n\n  This function does the following, sequentially:\n    1. Make sure all ckpt writing finishes, and rename them from temp path to\n    the final path.\n    2. Remove newer checkpoints (files that ordered larger than this save) if\n    `overwrite=True`.\n    3. Remove old checkpoint files based on `keep` and `keep_every_n_steps`.\n    4. Record program duration saved by this checkpoint.\n  \"\"\"\n  mpa_ckpt_tmp_path, mpa_ckpt_path = (\n    ckpt_tmp_path + MP_ARRAY_POSTFIX,\n    ckpt_path + MP_ARRAY_POSTFIX,\n  )\n  # Rename the multiprocess array path once serialization and writing finished.\n  if has_mpa:\n    if write_commit_success:\n      commit_success_path = os.path.join(mpa_ckpt_path, COMMIT_SUCCESS_FILE)\n      with io.GFile(commit_success_path, 'w') as f:\n        f.write(f'Checkpoint commit was successful to {mpa_ckpt_path}')\n    else:\n      # Commits are a two stage process (renaming the array folder and renaming\n      # the main ckpt file in sequential order). We always try to overwrite\n      # here because the array ckpt might be already renamed in a previously\n      # interrupted commit. NOTE: io.rename does not support overwriting\n      # directories via `rename` so we manually overwrite it.\n      if io.exists(mpa_ckpt_path):\n        logging.info('Removing outdated checkpoint at %s', mpa_ckpt_path)\n        io.rmtree(mpa_ckpt_path)\n      io.rename(mpa_ckpt_tmp_path, mpa_ckpt_path)\n  # Commit the main checkpoint file after arrays (if any) are committed\n  if async_manager:\n    async_manager.wait_previous_save()\n  io.rename(ckpt_tmp_path, ckpt_path, overwrite=overwrite)\n  logging.info('Saved checkpoint at %s', ckpt_path)\n\n  # Remove newer and older invalid checkpoints.\n  _remove_invalid_ckpts(\n    ckpt_path, base_path, keep, overwrite, keep_every_n_steps, has_mpa\n  )\n  # Record checkpoint-related metrics.\n  ocp.utils.record_saved_duration(ckpt_start_time)\n  if async_manager:\n    jax.monitoring.record_event_duration_secs(\n      '/jax/checkpoint/write/async/total_duration_secs',\n      time.time() - ckpt_start_time,\n    )\n\n\ndef _check_overwrite_error(\n  ckpt_tmp_path: str, ckpt_path: str, base_path: str, step: int\n):\n  \"\"\"Throw error if a ckpt file of this step or higher already exists.\"\"\"\n  dir_path, prefix = os.path.split(base_path)\n  checkpoint_files: list[Any] = [\n    pathlib.PurePath(c) for c in _allowempty_listdir(dir_path)\n  ]\n  checkpoint_files = [\n    os.path.join(dir_path, c)\n    for c in checkpoint_files\n    if c.match(f'{prefix}*') and not c.match(f'*{MP_ARRAY_POSTFIX}')\n  ]\n  if ckpt_path in checkpoint_files:\n    raise errors.InvalidCheckpointError(ckpt_path, step)\n  checkpoint_files.append(ckpt_path)\n\n  checkpoint_files = natural_sort(checkpoint_files)\n  # Handle the case if the job was preempted after the temporary checkpoint\n  # was written, but before it was renamed to the final checkpoint name\n  if checkpoint_files[-1] == ckpt_tmp_path:\n    checkpoint_files.pop()\n  if ckpt_path != checkpoint_files[-1]:\n    raise errors.InvalidCheckpointError(ckpt_path, step)\n\n\ndef _save_main_ckpt_file(\n  target: bytes,\n  has_mpa: bool,\n  paths: tuple[str, str],\n  base_path: str,\n  step: int,\n  keep: int,\n  overwrite: bool,\n  keep_every_n_steps: int | None,\n  ckpt_start_time: float,\n):\n  \"\"\"Save the main checkpoint file via file system.\"\"\"\n  ckpt_tmp_path, ckpt_path = paths\n  io.makedirs(os.path.dirname(ckpt_path))\n\n  with io.GFile(ckpt_tmp_path, 'wb') as fp:\n    fp.write(target)\n\n  # Postpone the commitment of checkpoint to after MPA writes are done.\n  if not has_mpa:\n    _save_commit(\n      ckpt_tmp_path,\n      ckpt_path,\n      base_path,\n      keep,\n      overwrite,\n      keep_every_n_steps,\n      ckpt_start_time,\n      has_mpa=False,\n      write_commit_success=False,\n    )\n\n\ndef _get_checkpoint_paths(\n  ckpt_dir: str | os.PathLike,\n  step: int | float,\n  prefix: str = 'checkpoint_',\n) -> tuple[str, str, str]:\n  \"\"\"Generate the checkpoint paths used in this save operation.\"\"\"\n  ckpt_dir = os.fspath(ckpt_dir)  # Pathlib -> str\n  logging.info('Saving checkpoint at step: %s', step)\n  # normalize path because io.glob() can modify path './', '//' ...\n  ckpt_dir = safe_normpath(ckpt_dir)\n  ckpt_tmp_path = _checkpoint_path(ckpt_dir, 'tmp', prefix)\n  ckpt_path = _checkpoint_path(ckpt_dir, step, prefix)\n  base_path = os.path.join(ckpt_dir, prefix)\n  return ckpt_path, ckpt_tmp_path, base_path\n\n\ndef save_checkpoint(\n  ckpt_dir: str | os.PathLike,\n  target: PyTree,\n  step: int | float,\n  prefix: str = 'checkpoint_',\n  keep: int = 1,\n  overwrite: bool = False,\n  keep_every_n_steps: int | None = None,\n  async_manager: AsyncManager | None = None,\n  orbax_checkpointer: ocp.Checkpointer | None = None,\n) -> str:\n  \"\"\"Save a checkpoint of the model. Suitable for single-host.\n\n  In this method, every JAX process saves the checkpoint on its own. Do not\n  use it if you have multiple processes and you intend for them to save data\n  to a common directory (e.g., a GCloud bucket). To save multi-process\n  checkpoints to a shared storage or to save ``GlobalDeviceArray``s, use\n  ``save_checkpoint_multiprocess()`` instead.\n\n  Pre-emption safe by writing to temporary before a final rename and cleanup\n  of past files. However, if async_manager is used, the final\n  commit will happen inside an async callback, which can be explicitly waited\n  by calling ``async_manager.wait_previous_save()``.\n\n  Example usage::\n\n    >>> from flax.training import checkpoints\n    >>> import jax.numpy as jnp\n    >>> import tempfile\n\n    >>> with tempfile.TemporaryDirectory() as dir_path:\n    ...   test_object = {\n    ...     'a': jnp.array([1, 2, 3], jnp.int32),\n    ...     'b': jnp.array([1, 1, 1], jnp.int32),\n    ...   }\n    ...   file_path = checkpoints.save_checkpoint(\n    ...     dir_path, target=test_object, step=0, prefix='test_', keep=1\n    ...   )\n    ...   restored_object = checkpoints.restore_checkpoint(\n    ...     file_path, target=None\n    ...   )\n    >>> restored_object\n    {'a': Array([1, 2, 3], dtype=int32), 'b': Array([1, 1, 1], dtype=int32)}\n\n  Args:\n    ckpt_dir: str or pathlib-like path to store checkpoint files in.\n    target: serializable flax object, usually a flax optimizer.\n    step: int or float: training step number or other metric number.\n    prefix: str: checkpoint file name prefix.\n    keep: number of past checkpoint files to keep.\n    overwrite: overwrite existing checkpoint files if a checkpoint at the\n      current or a later step already exits (default: False).\n    keep_every_n_steps: if defined, keep every checkpoints every n steps (in\n      addition to keeping the last 'keep' checkpoints).\n    async_manager: if defined, the save will run without blocking the main\n      thread. Only works for single host. Note that an ongoing save will still\n      block subsequent saves, to make sure overwrite/keep logic works correctly.\n    orbax_checkpointer: if defined, the save will be done by ocp. In the future,\n      all Flax checkpointing features will be migrated to Orbax, and starting to\n      use an ``orbax_checkpointer`` is recommended. Please check out the\n      checkpointing guide\n      (https://flax.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html#save-checkpoints)\n      for how to use Orbax checkpointers.\n\n  Returns:\n    Filename of saved checkpoint.\n  \"\"\"\n  jax.monitoring.record_event('/jax/flax/checkpoint/save')\n  start_time = time.time()\n  # Make sure all saves are finished before the logic of checking and removing\n  # outdated checkpoints happens.\n  if async_manager:\n    async_manager.wait_previous_save()\n\n  ckpt_path, ckpt_tmp_path, base_path = _get_checkpoint_paths(\n    ckpt_dir, step, prefix\n  )\n\n  if config.flax_use_orbax_checkpointing or orbax_checkpointer:\n    logging.info(\n      'Using Orbax as backend to save Flax checkpoints. For potential'\n      ' troubleshooting see:'\n      ' https://flax.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html#orbax-as-backend-troubleshooting'\n    )\n    if jax.process_count() > 1:\n      logging.warning(\n        'Multiple JAX processes detected when calling single-process'\n        ' `save_checkpoint`. Your devices will HANG if this function is only'\n        ' called on process 0! Troubleshoot at:'\n        ' https://flax.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html#if-your-devices-hang-when-writing-checkpoints'\n      )\n\n    # Make sure any previous work is done before making file changes.\n    if orbax_checkpointer and isinstance(\n      orbax_checkpointer, ocp.AsyncCheckpointer\n    ):\n      orbax_checkpointer.wait_until_finished()\n    # If no checkpointer provided, save synchronously with default setting.\n    if not orbax_checkpointer:\n      orbax_checkpointer = ocp.Checkpointer(\n        ocp.PyTreeCheckpointHandler()\n      )\n    # Check singular target.\n    if jtu.treedef_is_leaf(jtu.tree_structure(target)) and not isinstance(\n      orbax_checkpointer._handler,\n      ocp.ArrayCheckpointHandler,  # pylint: disable=protected-access\n    ):\n      raise ValueError(\n        'Orbax backend only accept pytree as save target. To save singular'\n        ' objects like numbers or Numpy arrays, checkout'\n        ' https://flax.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html#if-you-don-t-save-pytrees'\n      )\n\n    orbax_checkpointer.save(\n      ckpt_path, target, force=overwrite\n    )\n    # Do a process check here in case people call this for multihost.\n    if process_index() == 0:\n      _remove_invalid_ckpts(\n        ckpt_path, base_path, keep, overwrite, keep_every_n_steps, True\n      )\n    end_time = time.time()\n    monitoring.record_event_duration_secs(\n      _WRITE_CHECKPOINT_EVENT, end_time - start_time\n    )\n    return ckpt_path\n\n  warnings.warn(\n    (\n      'Flax Checkpointing will soon be deprecated in favor of Orbax'\n      ' (https://github.com/google/orbax). Please refer to the Checkpoint'\n      ' Upgrade Guide'\n      ' (https://flax.readthedocs.io/en/latest/guides/converting_and_upgrading/orbax_upgrade_guide.html)'\n      ' to self-migrate your code to ocp.'\n    ),\n    DeprecationWarning,\n  )\n  if not overwrite:\n    _check_overwrite_error(ckpt_tmp_path, ckpt_path, base_path, step)  # type: ignore\n\n  target = serialization.to_bytes(target)\n\n  # Save the files via I/O sync or async.\n  def save_main_ckpt_task():\n    jax.monitoring.record_event('/jax/flax/checkpoint/save_main_ckpt_task')\n    return _save_main_ckpt_file(\n      target,\n      False,\n      (ckpt_tmp_path, ckpt_path),\n      base_path,\n      step,\n      keep,\n      overwrite,\n      keep_every_n_steps,\n      start_time,\n    )\n\n  if async_manager:\n    async_manager.save_async(save_main_ckpt_task)\n  else:\n    save_main_ckpt_task()\n  end_time = time.time()\n  monitoring.record_event_duration_secs(\n    _WRITE_CHECKPOINT_EVENT, end_time - start_time\n  )\n  return ckpt_path\n\n\ndef save_checkpoint_multiprocess(\n  ckpt_dir: str | os.PathLike,\n  target: PyTree,\n  step: int | float,\n  prefix: str = 'checkpoint_',\n  keep: int = 1,\n  overwrite: bool = False,\n  keep_every_n_steps: int | None = None,\n  async_manager: AsyncManager | None = None,\n  gda_manager: GlobalAsyncCheckpointManager | None = None,\n  orbax_checkpointer: ocp.Checkpointer | None = None,\n) -> str:\n  \"\"\"Save a checkpoint of the model in multi-process environment.\n\n  Use this method to save ``GlobalDeviceArray``s, or to save data to a\n  common directory. Only process 0 will save the main checkpoint file and\n  remove old checkpoint files.\n\n  Pre-emption safe by writing to temporary before a final rename and cleanup\n  of past files. However, if async_manager or gda_manager is used, the final\n  commit will happen inside an async callback, which can be explicitly waited\n  by calling ``async_manager.wait_previous_save()`` or\n  ``gda_manager.wait_until_finished()``.\n\n  Args:\n    ckpt_dir: str or pathlib-like path to store checkpoint files in.\n    target: serializable flax object, usually a flax optimizer.\n    step: int or float: training step number or other metric number.\n    prefix: str: checkpoint file name prefix.\n    keep: number of past checkpoint files to keep.\n    overwrite: overwrite existing checkpoint files if a checkpoint at the\n      current or a later step already exits (default: False).\n    keep_every_n_steps: if defined, keep every checkpoints every n steps (in\n      addition to keeping the last 'keep' checkpoints).\n    async_manager: if defined, the save will run without blocking the main\n      thread. Only works for single host. Note that an ongoing save will still\n      block subsequent saves, to make sure overwrite/keep logic works correctly.\n    gda_manager: required if target contains a JAX GlobalDeviceArray. Will save\n      the GDAs to a separate subdirectory with postfix \"_gda\" asynchronously.\n      Same as async_manager, this will block subsequent saves.\n    orbax_checkpointer: if defined, the save will be done by Orbax. In the\n      future, all Flax checkpointing features will be migrated to Orbax, and\n      starting to use an ``orbax_checkpointer`` is recommended. Please check out\n      the checkpointing guide\n      (https://flax.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html#save-checkpoints)\n      for how to use Orbax checkpointers.\n\n  Returns:\n    Filename of saved checkpoint.\n  \"\"\"\n  jax.monitoring.record_event('/jax/flax/checkpoint/save')\n  start_time = time.time()\n  # Make sure all saves are finished before the logic of checking and removing\n  # outdated checkpoints happens.\n  sync_global_devices('Flax:Checkpoint:StartSave')\n  if async_manager:\n    async_manager.wait_previous_save()\n  if gda_manager:\n    gda_manager.wait_until_finished()\n    sync_global_devices('Flax:Checkpoint:WaitLastSaveDone')\n\n  ckpt_path, ckpt_tmp_path, base_path = _get_checkpoint_paths(\n    ckpt_dir, step, prefix\n  )\n\n  if config.flax_use_orbax_checkpointing or orbax_checkpointer:\n    logging.info(\n      'Using Orbax as backend to save Flax checkpoints. For potential'\n      ' troubleshooting see:'\n      ' https://flax.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html#orbax-as-backend-troubleshooting'\n    )\n    # Make sure any previous work is done before making file changes.\n    if orbax_checkpointer and isinstance(\n      orbax_checkpointer, ocp.AsyncCheckpointer\n    ):\n      orbax_checkpointer.wait_until_finished()\n\n    # If no checkpointer provided, save synchronously with default setting.\n    if not orbax_checkpointer:\n      orbax_checkpointer = ocp.Checkpointer(\n        ocp.PyTreeCheckpointHandler()\n      )\n    # Check singular target.\n    if jtu.treedef_is_leaf(jtu.tree_structure(target)) and not isinstance(\n      orbax_checkpointer._handler,\n      ocp.ArrayCheckpointHandler,  # pylint: disable=protected-access\n    ):\n      raise ValueError(\n        'Orbax backend only accept pytree as save target. To save singular'\n        ' objects like numbers or Numpy arrays, checkout'\n        ' https://flax.readthedocs.io/en/latest/guides/training_techniques/use_checkpointing.html#if-you-don-t-save-pytrees'\n      )\n\n    if process_index() == 0:\n      _remove_invalid_ckpts(\n        ckpt_path, base_path, keep, overwrite, keep_every_n_steps, True\n      )\n    orbax_checkpointer.save(\n      ckpt_path, target, force=overwrite\n    )\n    end_time = time.time()\n    monitoring.record_event_duration_secs(\n      _WRITE_CHECKPOINT_EVENT, end_time - start_time\n    )\n    return ckpt_path\n\n  warnings.warn(\n    (\n      'Flax Checkpointing will soon be deprecated in favor of Orbax'\n      ' (https://github.com/google/orbax). Please refer to the Checkpoint'\n      ' Upgrade Guide'\n      ' (https://flax.readthedocs.io/en/latest/guides/converting_and_upgrading/orbax_upgrade_guide.html)'\n      ' to self-migrate your code to ocp.'\n    ),\n    DeprecationWarning,\n  )\n\n  target = serialization.to_state_dict(target)\n  target, mpa_targets = _split_mp_arrays(target)\n  target = serialization.msgpack_serialize(target)\n  has_mpa = bool(mpa_targets)\n\n  if not overwrite:\n    _check_overwrite_error(ckpt_tmp_path, ckpt_path, base_path, step)  # type: ignore\n    sync_global_devices('Flax:Checkpoint:CheckOverwriteBeforeSave')\n\n  # Save the files via I/O sync or async.\n  def save_main_ckpt_task():\n    jax.monitoring.record_event('/jax/flax/checkpoint/save_main_ckpt_task')\n    return _save_main_ckpt_file(\n      target,\n      has_mpa,\n      (ckpt_tmp_path, ckpt_path),\n      base_path,\n      step,\n      keep,\n      overwrite,\n      keep_every_n_steps,\n      start_time,\n    )\n\n  # Write the main checkpoint file only via process 0, to avoid race condition.\n  if process_index() == 0:\n    if async_manager:\n      async_manager.save_async(save_main_ckpt_task)\n    else:\n      save_main_ckpt_task()\n\n  if has_mpa:\n    if not gda_manager:\n      raise errors.MPACheckpointingRequiredError(ckpt_path, step)\n    # Creating the directory containing GDAs explicitly. This should happen only\n    # on process 0 and before any worker starts to write GDA data.\n    if process_index() == 0:\n      _make_mpa_dirs(mpa_targets, ckpt_tmp_path)\n    sync_global_devices('Flax:Checkpoint:AfterCreateMPADir')\n    _save_mpas(\n      gda_manager,\n      mpa_targets,\n      ckpt_tmp_path,\n      ckpt_path,\n      base_path,\n      keep,\n      overwrite,\n      keep_every_n_steps,\n      start_time,\n      async_manager,\n    )\n\n  end_time = time.time()\n  monitoring.record_event_duration_secs(\n    _WRITE_CHECKPOINT_EVENT, end_time - start_time\n  )\n  return ckpt_path\n\n\ndef _all_checkpoints(\n  ckpt_dir: str | os.PathLike, prefix: str = 'checkpoint_'\n) -> list[str]:\n  \"\"\"Retrieve all checkpoint paths in directory.\n\n  Args:\n    ckpt_dir: str: directory of checkpoints to restore from.\n    prefix: str: name prefix of checkpoint files.\n\n  Returns:\n    Sorted list of checkpoint paths or empty list if no checkpoints were found.\n  \"\"\"\n  ckpt_dir = os.fspath(ckpt_dir)  # Pathlib -> str\n  checkpoint_files: list[Any] = [\n    pathlib.PurePath(c) for c in _allowempty_listdir(ckpt_dir)\n  ]\n  checkpoint_files = [\n    os.path.join(ckpt_dir, c)\n    for c in checkpoint_files\n    if c.match(f'{prefix}*')\n    and not c.match(f'{prefix}tmp')\n    and not c.match(f'*{MP_ARRAY_POSTFIX}')\n    and not c.match(f'*{ocp.utils.TMP_DIR_SUFFIX}*')\n  ]\n  checkpoint_files = natural_sort(checkpoint_files)\n  if checkpoint_files:\n    return checkpoint_files\n  else:\n    return []\n\n\ndef latest_checkpoint(\n  ckpt_dir: str | os.PathLike, prefix: str = 'checkpoint_'\n) -> str | None:\n  \"\"\"Retrieve the path of the latest checkpoint in a directory.\n\n  Args:\n    ckpt_dir: str: directory of checkpoints to restore from.\n    prefix: str: name prefix of checkpoint files.\n\n  Returns:\n    The latest checkpoint path or None if no checkpoints were found.\n  \"\"\"\n  checkpoint_files = _all_checkpoints(ckpt_dir, prefix)\n  if checkpoint_files:\n    return checkpoint_files[-1]\n  else:\n    return None\n\n\ndef available_steps(\n  ckpt_dir: str | os.PathLike,\n  prefix: str = 'checkpoint_',\n  step_type: type = int,\n) -> list[int | float]:\n  \"\"\"Return step numbers of available checkpoints in a directory.\n\n\n  Args:\n    ckpt_dir: str: directory of checkpoints to restore from.\n    prefix: str: name prefix of checkpoint files.\n    step_type: type: type for steps, int (default) or float.\n\n  Returns:\n    Sorted list of available steps or empty list if no checkpoints were found.\n  \"\"\"\n  checkpoint_files = _all_checkpoints(ckpt_dir, prefix)\n\n  checkpoint_steps = []\n\n  for file in checkpoint_files:\n    prefix_idx = file.rfind(prefix)\n    checkpoint_steps += [step_type(file[prefix_idx + len(prefix) :])]\n\n  return checkpoint_steps\n\n\ndef restore_checkpoint(\n  ckpt_dir: str | os.PathLike,\n  target: Any | None,\n  step: int | float | None = None,\n  prefix: str = 'checkpoint_',\n  parallel: bool = True,\n  gda_manager: GlobalAsyncCheckpointManager | None = None,\n  allow_partial_mpa_restoration: bool = False,\n  orbax_checkpointer: ocp.Checkpointer | None = None,\n  orbax_transforms: dict | None = None,\n) -> PyTree:\n  \"\"\"Restore last/best checkpoint from checkpoints in path.\n\n  Sorts the checkpoint files naturally, returning the highest-valued\n  file, e.g.:\n\n  *  ``ckpt_1, ckpt_2, ckpt_3 --> ckpt_3``\n\n  *  ``ckpt_0.01, ckpt_0.1, ckpt_0.001 --> ckpt_0.1``\n\n  *  ``ckpt_-1.0, ckpt_1.0, ckpt_1e5 --> ckpt_1e5``\n\n  Example usage::\n\n    >>> from flax.training import checkpoints\n    >>> import jax.numpy as jnp\n    >>> import tempfile\n    ...\n    >>> with tempfile.TemporaryDirectory() as dir_path:\n    ...   test_object = {\n    ...     'a': jnp.array([1, 2, 3], jnp.int32),\n    ...     'b': jnp.array([1, 1, 1], jnp.int32),\n    ...   }\n    ...   file_path = checkpoints.save_checkpoint(\n    ...     dir_path, target=test_object, step=0, prefix='test_', keep=1\n    ...   )\n    ...   restored_object = checkpoints.restore_checkpoint(\n    ...     file_path, target=None\n    ...   )\n    >>> restored_object\n    {'a': Array([1, 2, 3], dtype=int32), 'b': Array([1, 1, 1], dtype=int32)}\n\n  Args:\n    ckpt_dir: str: checkpoint file or directory of checkpoints to restore from.\n    target: matching object to rebuild via deserialized state-dict. If None, the\n      deserialized state-dict is returned as-is.\n    step: int or float: step number to load or None to load latest. If\n      specified, ckpt_dir must be a directory.\n    prefix: str: name prefix of checkpoint files.\n    parallel: bool: whether to load seekable checkpoints in parallel, for speed.\n    gda_manager: required if checkpoint contains a multiprocess array\n      (GlobalDeviceArray or jax Array from pjit). Will read the arrays from the\n      separate subdirectory with postfix \"_gda\".\n    allow_partial_mpa_restoration: If true, the given ``target`` doesn't have to\n      contain all valid multiprocess arrays. As a result, the restored Pytree\n      may have some MPAs not restored correctly. Use this if you cannot provide\n      a fully valid ``target`` and don't need all the MPAs in the checkpoint to\n      be restored.\n    orbax_checkpointer: the ``ocp.Checkpointer`` that handles the underlying\n      restore, if the given checkpoint is saved with ocp.\n    orbax_transforms: the Orbax transformations that will be passed into\n      ``orbax_checkpointer.restore()`` call.\n\n  Returns:\n    Restored ``target`` updated from checkpoint file, or if no step specified\n    and\n    no checkpoint files present, returns the passed-in ``target`` unchanged.\n    If a file path is specified and is not found, the passed-in ``target`` will\n    be\n    returned. This is to match the behavior of the case where a directory path\n    is specified but the directory has not yet been created.\n  \"\"\"\n  jax.monitoring.record_event('/jax/flax/checkpoint/restore')\n  start_time = time.time()\n  # Make sure any previous work is done before checking files.\n  if orbax_checkpointer and isinstance(\n    orbax_checkpointer, ocp.AsyncCheckpointer\n  ):\n    orbax_checkpointer.wait_until_finished()\n\n  ckpt_dir = os.fspath(ckpt_dir)  # Pathlib -> str\n  ckpt_dir = safe_normpath(ckpt_dir)\n  if step is not None:\n    ckpt_path = _checkpoint_path(ckpt_dir, step, prefix)\n    if not io.exists(ckpt_path):\n      raise ValueError(f'Matching checkpoint not found: {ckpt_path}')\n  else:\n    if not io.exists(ckpt_dir):\n      logging.info('Found no checkpoint directory at %s', ckpt_dir)\n      return target\n    if io.isdir(ckpt_dir):\n      # This means the given dir is an orbax checkpoint.\n      if _is_orbax_checkpoint(ckpt_dir):\n        ckpt_path = ckpt_dir\n      else:\n        ckpt_path = latest_checkpoint(ckpt_dir, prefix)  # type: ignore\n        if not ckpt_path:\n          logging.info(\n            'Found no checkpoint files in %s with prefix %s', ckpt_dir, prefix\n          )\n          return target\n    else:\n      ckpt_path = ckpt_dir\n\n  # Restore the checkpoint with Orbax if needed.\n  is_orbax = _is_orbax_checkpoint(ckpt_path)\n  ckpt_type = 'orbax' if is_orbax else 'legacy Flax'\n  logging.info(f'Restoring {ckpt_type} checkpoint from {ckpt_path}')\n  if is_orbax:\n    if not orbax_checkpointer:\n      orbax_checkpointer = ocp.Checkpointer(\n        ocp.PyTreeCheckpointHandler()\n      )\n\n    restore_kwargs = {}\n    if target is not None:\n      restore_kwargs['restore_args'] = orbax_utils.restore_args_from_target(\n        target\n      )\n      if isinstance(orbax_checkpointer._handler, ocp.PyTreeCheckpointHandler):  # pylint: disable=protected-access\n        restore_kwargs[\n          'transforms'\n        ] = orbax_utils.maybe_construct_transformations(\n          target, orbax_transforms\n        )\n    restored = orbax_checkpointer.restore(\n      ckpt_path, item=target, **restore_kwargs\n    )\n    restored = serialization.to_state_dict(restored)\n    if target is not None:\n      restored = serialization.from_state_dict(target, restored)\n    end_time = time.time()\n    monitoring.record_event_duration_secs(\n      _READ_CHECKPOINT_EVENT, end_time - start_time\n    )\n    return restored\n\n  # Legacy Flax checkpoint restoration.\n  ckpt_size = io.getsize(ckpt_path)\n  with io.GFile(ckpt_path, 'rb') as fp:\n    if parallel and fp.seekable():\n      buf_size = 128 << 20  # 128M buffer.\n      num_bufs = ckpt_size / buf_size\n      logging.debug('num_bufs: %d', num_bufs)\n      checkpoint_contents = bytearray(ckpt_size)\n\n      def read_chunk(i):\n        # NOTE: We have to re-open the file to read each chunk, otherwise the\n        # parallelism has no effect. But we could reuse the file pointers\n        # within each thread.\n        with io.GFile(ckpt_path, 'rb') as f:\n          f.seek(i * buf_size)\n          buf = f.read(buf_size)\n          if buf:\n            checkpoint_contents[i * buf_size : i * buf_size + len(buf)] = buf\n          return len(buf) / buf_size\n\n      pool_size = 32\n      pool = thread.ThreadPoolExecutor(pool_size)\n      results = pool.map(read_chunk, range(int(num_bufs) + 1))\n      pool.shutdown(wait=False)\n      logging.debug(f'results: {list(results)}')\n    else:\n      checkpoint_contents = fp.read()\n\n  state_dict = serialization.msgpack_restore(checkpoint_contents)\n  state_dict = _restore_mpas(\n    state_dict,\n    target,\n    ckpt_path,\n    step,\n    gda_manager,\n    allow_partial_mpa_restoration,\n  )\n\n  if target is None:\n    restored_checkpoint = state_dict\n  else:\n    restored_checkpoint = serialization.from_state_dict(target, state_dict)\n\n  end_time = time.time()\n  monitoring.record_event_duration_secs(\n    _READ_CHECKPOINT_EVENT, end_time - start_time\n  )\n\n  return restored_checkpoint\n\n\ndef convert_pre_linen(params: PyTree) -> PyTree:\n  \"\"\"Converts a pre-Linen parameter pytree.\n\n  In pre-Linen API submodules were numbered incrementally, independent of the\n  submodule class. With Linen this behavior has changed to keep separate\n  submodule counts per module class.\n\n  Consider the following module::\n\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = nn.Conv(1, 1)(x)\n        x = nn.Dense(1)(x)\n        return x\n\n  In pre-Linen the resulting params would have had the structure:\n\n    ``{'Conv_0': { ... }, 'Dense_1': { ... } }``\n\n  With Linen the resulting params would instead have had the structure:\n\n    ``{'Conv_0': { ... }, 'Dense_0': { ... } }``\n\n  To convert from pre-Linen format to Linen simply call::\n\n    params = convert_pre_linen(pre_linen_params)\n\n  Note that you can also use this utility to convert pre-Linen collections\n  because they're following the same module naming. Note though that collections\n  were \"flat\" in pre-Linen and first need to be unflattened before they can be\n  used with this function::\n\n    batch_stats = convert_pre_linen(flax.traverse_util.unflatten_dict({\n        tuple(k.split('/')[1:]): v\n        for k, v in pre_linen_model_state.as_dict().items()\n    }))\n\n  Then Linen variables can be defined from these converted collections::\n\n    variables = {'params': params, 'batch_stats': batch_stats}\n\n  Args:\n    params: Parameter pytree in pre-Linen format. If the pytree is already in\n      Linen format, then the returned pytree is unchanged (i.e. this function\n      can safely be called on any loaded checkpoint for use with Linen).\n\n  Returns:\n    Parameter pytree with Linen submodule naming.\n  \"\"\"\n  if not isinstance(params, (dict, core.FrozenDict)):\n    return params\n  params_renamed = {}\n  counts: dict[Any, Any] = {}\n  names = natural_sort(params.keys())\n  for name in names:\n    value = params[name]\n    match = MODULE_NUM_RE.match(name)\n    if match:\n      module = match.group(1)\n      num = counts.get(module, 0)\n      name = f'{module}_{num}'\n      counts[module] = num + 1\n    params_renamed[name] = convert_pre_linen(value)\n\n  if isinstance(params, core.FrozenDict):\n    params_renamed = core.freeze(params_renamed)  # type: ignore\n  return params_renamed\n"
  },
  {
    "path": "flax/training/common_utils.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Common utility functions used in data-parallel Flax examples.\n\nThis module is a historical grab-bag of utility functions primarily concerned\nwith helping write pmap-based data-parallel training loops.\n\"\"\"\n\nimport jax\nimport jax.numpy as jnp\nfrom jax import lax\n\n\ndef shard(xs):\n  \"\"\"Helper for pmap to shard a pytree of arrays by local_device_count.\n\n  Args:\n    xs: a pytree of arrays.\n  Returns:\n    A matching pytree with arrays' leading dimensions sharded by the\n    local device count.\n  \"\"\"\n  local_device_count = jax.local_device_count()\n  return jax.tree_util.tree_map(\n    lambda x: x.reshape((local_device_count, -1) + x.shape[1:]), xs\n  )\n\n\ndef shard_prng_key(prng_key):\n  \"\"\"Helper to shard (aka split) a PRNGKey for use with pmap'd functions.\n\n  PRNG keys can be used at train time to drive stochastic modules\n  e.g. Dropout. We would like a different PRNG key for each local\n  device so that we end up with different random numbers on each one,\n  hence we split our PRNG key.\n\n  Args:\n    prng_key: JAX PRNGKey\n  Returns:\n    A new array of PRNGKeys with leading dimension equal to local device count.\n  \"\"\"\n  return jax.random.split(prng_key, num=jax.local_device_count())\n\n\ndef stack_forest(forest):\n  \"\"\"Helper function to stack the leaves of a sequence of pytrees.\n\n  Args:\n    forest: a sequence of pytrees (e.g tuple or list) of matching structure\n      whose leaves are arrays with individually matching shapes.\n  Returns:\n    A single pytree of the same structure whose leaves are individually\n      stacked arrays.\n  \"\"\"\n  stack_args = lambda *args: jnp.stack(args)\n  return jax.tree_util.tree_map(stack_args, *forest)\n\n\ndef get_metrics(device_metrics):\n  \"\"\"Helper utility for pmap, gathering replicated timeseries metric data.\n\n  Args:\n   device_metrics: replicated, device-resident pytree of metric data,\n     whose leaves are presumed to be a sequence of arrays recorded over time.\n  Returns:\n   A pytree of unreplicated, host-resident, stacked-over-time arrays useful for\n   computing host-local statistics and logging.\n  \"\"\"\n  # We select the first element of x in order to get a single copy of a\n  # device-replicated metric.\n  device_metrics = jax.tree_util.tree_map(\n      lambda x: x.addressable_shards[0].data.squeeze(0), device_metrics\n  )\n  metrics_np = jax.device_get(device_metrics)\n  return stack_forest(metrics_np)\n\n\ndef onehot(labels, num_classes, on_value=1.0, off_value=0.0):\n  \"\"\"Create a dense one-hot version of an indexed array.\n\n  NB: consider using the more standard ``jax.nn.one_hot`` instead.\n\n  Args:\n    labels: an n-dim JAX array whose last dimension contains integer indices.\n    num_classes: the maximum possible index.\n    on_value: the \"on\" value for the one-hot array, defaults to 1.0.\n    off_value: the \"off\" value for the one-hot array, defaults to 0.0.\n  Returns:\n    A (n+1)-dim array whose last dimension contains one-hot vectors of length\n    num_classes.\n  \"\"\"\n  x = labels[..., None] == jnp.arange(num_classes).reshape(\n    (1,) * labels.ndim + (-1,)\n  )\n  x = lax.select(x, jnp.full(x.shape, on_value), jnp.full(x.shape, off_value))\n  return x.astype(jnp.float32)\n"
  },
  {
    "path": "flax/training/dynamic_scale.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Dynamic loss scaling for mixed precision gradients.\"\"\"\n\nimport functools\nfrom typing import Any, NamedTuple\nfrom collections.abc import Callable, Sequence\n\nimport jax\nimport jax.numpy as jnp\nfrom jax import lax\n\nfrom flax import struct\nfrom flax.typing import Array\n\n\nclass DynamicScaleResult(NamedTuple):\n  dynamic_scale: 'DynamicScale'\n  finite: Array\n  aux: Any\n  grad: Any\n\n\nclass DynamicScale(struct.PyTreeNode):\n  \"\"\"Dynamic loss scaling for mixed precision gradients.\n\n  For many models gradient computations in float16 will result in numerical\n  issues because small/large gradients being flushed to zero/infinity.\n  Dynamic loss scaling is an algorithm that aims to find the largest scalar\n  multiple for which the gradient does not overflow. This way the risk of\n  underflow is minimized.\n\n  the `value_and_grad` method mimicks `jax.value_and_grad`. Beside the loss\n  and gradients it also ouputs and updated `DynamicScale` instance with the\n  current loss scale factor. This method also returns a boolean value indicating\n  whether the gradients are finite.\n\n  Example::\n\n    from flax.training.dynamic_scale import DynamicScale\n\n    def loss_fn(p):\n      return jnp.asarray(p, jnp.float16) ** 2\n    p = jnp.array(1., jnp.float32)\n\n    dyn_scale = DynamicScale(growth_interval=10)\n    compute_grad = jax.jit(lambda ds, p: ds.value_and_grad(loss_fn)(p))\n    for _ in range(100):\n      dyn_scale, is_fin, loss, grad = compute_grad(dyn_scale, p)\n      p += jnp.where(is_fin, 0.01 * grad, 0.)\n      print(loss)\n\n  Jax currently cannot execute conditionals efficiently on GPUs therefore we\n  selectively ignore the gradient update using `jax.numpy.where` in case of\n  non-finite gradients.\n\n  Attributes:\n    growth_factor: how much to grow the scalar after a period of finite\n      gradients (default: 2.).\n    backoff_factor: how much to shrink the scalar after a non-finite gradient\n      (default: 0.5).\n    growth_interval: after how many steps of finite gradients the scale should\n      be increased (default: 2000).\n    fin_steps: indicates how many gradient steps in a row have been finite.\n    scale: the current scale by which the loss is multiplied.\n    minimum_scale: the minimum value that the scale can take (default: the\n      smallest positive number representable in floating point).\n  \"\"\"\n\n  growth_factor: float = struct.field(pytree_node=False, default=2.0)\n  backoff_factor: float = struct.field(pytree_node=False, default=0.5)\n  growth_interval: int = struct.field(pytree_node=False, default=2000)\n  fin_steps: int = 0\n  scale: float = 65536.0\n  minimum_scale: float | None = struct.field(\n    pytree_node=False, default=jnp.finfo(jnp.float32).tiny\n  )\n\n  def value_and_grad(\n    self,\n    fun: Callable[..., Any],\n    argnums: int | Sequence[int] = 0,\n    has_aux: bool = False,\n    axis_name: str | None = None,\n  ) -> Callable[..., DynamicScaleResult]:\n    \"\"\"Wrapper around `jax.value_and_grad`.\n\n    Args:\n      fun: Function to be differentiated. Its arguments at positions specified\n        by ``argnums`` should be arrays, scalars, or standard Python containers.\n        It should return a scalar (which includes arrays with shape ``()`` but\n        not arrays with shape ``(1,)`` etc.)\n      argnums: Optional, integer or sequence of integers. Specifies which\n        positional argument(s) to differentiate with respect to (default 0).\n      has_aux: Optional, bool. Indicates whether ``fun`` returns a pair where\n        the first element is considered the output of the mathematical function\n        to be differentiated and the second element is auxiliary data. Default\n        False.\n      axis_name: If an axis is given the gradients will be averaged across\n        replicas (default: None). Note, this is only used for pmap and shard\n        map. For SPMD jit, you do not need to manually synchronize. Just make\n        sure that the axes are correctly annotated and XLA:SPMD will insert the\n        necessary collectives.\n\n    Returns:\n      A function that takes the same arguments as `fun` and\n      returns a DynamicScaleResult\n    \"\"\"\n\n    @functools.wraps(fun)\n    def loss_wrapper(*args):\n      aux = fun(*args)\n      if has_aux:\n        return (self.scale * aux[0], aux[1])\n      else:\n        return self.scale * aux\n\n    grad_fn = jax.value_and_grad(loss_wrapper, argnums, has_aux)\n\n    def grad_fn_wrapper(*args):\n      aux, grad = grad_fn(*args)\n      aux = (aux[0] / self.scale, aux[1]) if has_aux else aux / self.scale\n\n      grad = jax.tree_util.tree_map(\n        lambda g: jnp.asarray(g, jnp.float32) / self.scale, grad\n      )\n      if axis_name is not None:\n        grad = lax.pmean(grad, axis_name)\n\n      finite = jnp.array(True)\n      for g in jax.tree_util.tree_leaves(grad):\n        finite &= jnp.all(lax.is_finite(g))\n\n      grow = self.fin_steps == self.growth_interval\n      fin_scale = jnp.where(\n        grow & finite,\n        jnp.minimum(\n          self.scale * self.growth_factor, jnp.finfo(jnp.float32).max\n        ),\n        self.scale,\n      )\n      inf_scale = self.scale * self.backoff_factor\n      if self.minimum_scale is not None:\n        inf_scale = jnp.maximum(inf_scale, self.minimum_scale)\n      new_scale = jnp.where(finite, fin_scale, inf_scale)\n      new_fin_steps = jnp.where(grow | (~finite), 0, self.fin_steps + 1)\n\n      new_self = self.replace(fin_steps=new_fin_steps, scale=new_scale)\n      return DynamicScaleResult(new_self, finite, aux, grad)\n\n    return grad_fn_wrapper\n"
  },
  {
    "path": "flax/training/early_stopping.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Early stopping.\"\"\"\n\nimport math\n\nfrom flax import struct\n\n\nclass EarlyStopping(struct.PyTreeNode):\n  \"\"\"Early stopping to avoid overfitting during training.\n\n  The following example stops training early if the difference between losses\n  recorded in the current epoch and previous epoch is less than 1e-3\n  consecutively for 2 times::\n\n    >>> from flax.training.early_stopping import EarlyStopping\n\n    >>> def train_epoch(optimizer, train_ds, batch_size, epoch, input_rng):\n    ...   ...\n    ...   loss = [4, 3, 3, 3, 2, 2, 2, 2, 1, 1][epoch]\n    ...   return None, {'loss': loss}\n\n    >>> early_stop = EarlyStopping(min_delta=1e-3, patience=2)\n    >>> optimizer = None\n    >>> for epoch in range(10):\n    ...   optimizer, train_metrics = train_epoch(\n    ...       optimizer=optimizer, train_ds=None, batch_size=None, epoch=epoch, input_rng=None)\n    ...   early_stop = early_stop.update(train_metrics['loss'])\n    ...   if early_stop.should_stop:\n    ...     print(f'Met early stopping criteria, breaking at epoch {epoch}')\n    ...     break\n    Met early stopping criteria, breaking at epoch 7\n\n  Attributes:\n    min_delta: Minimum delta between updates to be considered an\n        improvement.\n    patience: Number of steps of no improvement before stopping.\n    best_metric: Current best metric value.\n    patience_count: Number of steps since last improving update.\n    should_stop: Whether the training loop should stop to avoid\n        overfitting.\n    has_improved: Whether the metric has improved greater or\n      equal to the min_delta in the last ``.update`` call.\n  \"\"\"\n\n  min_delta: float = 0\n  patience: int = 0\n  best_metric: float = float('inf')\n  patience_count: int = 0\n  should_stop: bool = False\n  has_improved: bool = False\n\n  def reset(self):\n    return self.replace(\n      best_metric=float('inf'),\n      patience_count=0,\n      should_stop=False,\n      has_improved=False,\n    )\n\n  def update(self, metric):\n    \"\"\"Update the state based on metric.\n\n    Returns:\n      The updated EarlyStopping class. The ``.has_improved`` attribute is True\n      when there was an improvement greater than ``min_delta`` from the previous\n      ``best_metric``.\n    \"\"\"\n\n    if (\n      math.isinf(self.best_metric) or self.best_metric - metric > self.min_delta\n    ):\n      return self.replace(\n        best_metric=metric, patience_count=0, has_improved=True\n      )\n    else:\n      should_stop = self.patience_count >= self.patience or self.should_stop\n      return self.replace(\n        patience_count=self.patience_count + 1,\n        should_stop=should_stop,\n        has_improved=False,\n      )\n"
  },
  {
    "path": "flax/training/lr_schedule.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Learning rate schedules used in FLAX image classification examples.\n\nNote that with `FLIP #1009`_ learning rate schedules in ``flax.training`` are\n**effectively deprecated** in favor of Optax_ schedules. Please refer to\n`Optimizer Schedules`_ for more information.\n\n.. _FLIP #1009: https://github.com/google/flax/blob/main/docs/flip/1009-optimizer-api.md\n.. _Optax: https://github.com/deepmind/optax\n.. _Optimizer Schedules: https://optax.readthedocs.io/en/latest/api.html#optimizer-schedules\n\"\"\"\n\nimport jax.numpy as jnp\nimport numpy as np\nfrom absl import logging\n\n\ndef _piecewise_constant(boundaries, values, t):\n  index = jnp.sum(boundaries < t)\n  return jnp.take(values, index)\n\n\ndef create_constant_learning_rate_schedule(\n  base_learning_rate, steps_per_epoch, warmup_length=0.0\n):\n  \"\"\"Create a constant learning rate schedule with optional warmup.\n\n  Note that with `FLIP #1009`_ learning rate schedules in ``flax.training`` are\n  **effectively deprecated** in favor of Optax_ schedules. Please refer to\n  `Optimizer Schedules`_ for more information.\n\n  .. _FLIP #1009: https://github.com/google/flax/blob/main/docs/flip/1009-optimizer-api.md\n  .. _Optax: https://github.com/deepmind/optax\n  .. _Optimizer Schedules: https://optax.readthedocs.io/en/latest/api.html#optimizer-schedules\n\n  Holds the learning rate constant. This function also offers a learing rate\n  warmup as per https://arxiv.org/abs/1706.02677, for the purpose of training\n  with large mini-batches.\n\n  Args:\n    base_learning_rate: the base learning rate\n    steps_per_epoch: the number of iterations per epoch\n    warmup_length: if > 0, the learning rate will be modulated by a warmup\n      factor that will linearly ramp-up from 0 to 1 over the first\n      ``warmup_length`` epochs\n\n  Returns:\n    Function ``f(step) -> lr`` that computes the learning rate for a given step.\n  \"\"\"\n  logging.warning(\n    'Learning rate schedules in ``flax.training`` are effectively deprecated '\n    'in favor of Optax schedules. Please refer to '\n    'https://optax.readthedocs.io/en/latest/api.html#optimizer-schedules'\n    ' for alternatives.'\n  )\n\n  def learning_rate_fn(step):\n    lr = base_learning_rate\n    if warmup_length > 0.0:\n      lr = lr * jnp.minimum(1.0, step / float(warmup_length) / steps_per_epoch)\n    return lr\n\n  return learning_rate_fn\n\n\ndef create_stepped_learning_rate_schedule(\n  base_learning_rate, steps_per_epoch, lr_sched_steps, warmup_length=0.0\n):\n  \"\"\"Create a stepped learning rate schedule with optional warmup.\n\n  Note that with `FLIP #1009`_ learning rate schedules in ``flax.training`` are\n  **effectively deprecated** in favor of Optax_ schedules. Please refer to\n  `Optimizer Schedules`_ for more information.\n\n  .. _FLIP #1009: https://github.com/google/flax/blob/main/docs/flip/1009-optimizer-api.md\n  .. _Optax: https://github.com/deepmind/optax\n  .. _Optimizer Schedules: https://optax.readthedocs.io/en/latest/api.html#optimizer-schedules\n\n  A stepped learning rate schedule decreases the learning rate\n  by specified amounts at specified epochs. The steps are given as\n  the ``lr_sched_steps`` parameter. A common ImageNet schedule decays the\n  learning rate by a factor of 0.1 at epochs 30, 60 and 80. This would be\n  specified as::\n\n    [\n      [30, 0.1],\n      [60, 0.01],\n      [80, 0.001]\n    ]\n\n  This function also offers a learing rate warmup as per\n  https://arxiv.org/abs/1706.02677, for the purpose of training with large\n  mini-batches.\n\n  Args:\n    base_learning_rate: the base learning rate\n    steps_per_epoch: the number of iterations per epoch\n    lr_sched_steps: the schedule as a list of steps, each of which is\n      a ``[epoch, lr_factor]`` pair; the step occurs at epoch ``epoch`` and\n      sets the learning rate to ``base_learning_rage * lr_factor``\n    warmup_length: if > 0, the learning rate will be modulated by a warmup\n      factor that will linearly ramp-up from 0 to 1 over the first\n      ``warmup_length`` epochs\n\n  Returns:\n    Function ``f(step) -> lr`` that computes the learning rate for a given step.\n  \"\"\"\n  logging.warning(\n    'Learning rate schedules in ``flax.training`` are effectively deprecated '\n    'in favor of Optax schedules. Please refer to '\n    'https://optax.readthedocs.io/en/latest/api.html#optimizer-schedules'\n    ' for alternatives.'\n  )\n  boundaries = [step[0] for step in lr_sched_steps]\n  decays = [step[1] for step in lr_sched_steps]\n  boundaries = np.array(boundaries) * steps_per_epoch\n  boundaries = np.round(boundaries).astype(int)\n  values = np.array([1.0] + decays) * base_learning_rate\n\n  def learning_rate_fn(step):\n    lr = _piecewise_constant(boundaries, values, step)\n    if warmup_length > 0.0:\n      lr = lr * jnp.minimum(1.0, step / float(warmup_length) / steps_per_epoch)\n    return lr\n\n  return learning_rate_fn\n\n\ndef create_cosine_learning_rate_schedule(\n  base_learning_rate, steps_per_epoch, halfcos_epochs, warmup_length=0.0\n):\n  \"\"\"Create a cosine learning rate schedule with optional warmup.\n\n  Note that with `FLIP #1009`_ learning rate schedules in ``flax.training`` are\n  **effectively deprecated** in favor of Optax_ schedules. Please refer to\n  `Optimizer Schedules`_ for more information.\n\n  .. _FLIP #1009: https://github.com/google/flax/blob/main/docs/flip/1009-optimizer-api.md\n  .. _Optax: https://github.com/deepmind/optax\n  .. _Optimizer Schedules: https://optax.readthedocs.io/en/latest/api.html#optimizer-schedules\n\n  A cosine learning rate schedule modules the learning rate with\n  half a cosine wave, gradually scaling it to 0 at the end of training.\n\n  This function also offers a learing rate warmup as per\n  https://arxiv.org/abs/1706.02677, for the purpose of training with large\n  mini-batches.\n\n  Args:\n    base_learning_rate: the base learning rate\n    steps_per_epoch: the number of iterations per epoch\n    halfcos_epochs: the number of epochs to complete half a cosine wave;\n      normally the number of epochs used for training\n    warmup_length: if > 0, the learning rate will be modulated by a warmup\n      factor that will linearly ramp-up from 0 to 1 over the first\n      ``warmup_length`` epochs\n\n  Returns:\n    Function ``f(step) -> lr`` that computes the learning rate for a given step.\n  \"\"\"\n  logging.warning(\n    'Learning rate schedules in ``flax.training`` are effectively deprecated '\n    'in favor of Optax schedules. Please refer to '\n    'https://optax.readthedocs.io/en/latest/api.html#optimizer-schedules'\n    ' for alternatives.'\n  )\n  halfwavelength_steps = halfcos_epochs * steps_per_epoch\n\n  def learning_rate_fn(step):\n    scale_factor = jnp.cos(step * jnp.pi / halfwavelength_steps) * 0.5 + 0.5\n    lr = base_learning_rate * scale_factor\n    if warmup_length > 0.0:\n      lr = lr * jnp.minimum(1.0, step / float(warmup_length) / steps_per_epoch)\n    return lr\n\n  return learning_rate_fn\n"
  },
  {
    "path": "flax/training/orbax_utils.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Utils for Orbax Checkpointing, available even after Flax Checkpointing is deprecated.\"\"\"\n\nimport warnings\nfrom typing import Any\n\nimport jax\nimport numpy as np\nimport orbax.checkpoint as ocp\nfrom jax.sharding import Mesh\n\nPyTree = Any\n\n\ndef is_multi_device_array(value: Any) -> bool:\n  \"\"\"Instruct Orbax to save this array with Tensorstore instead of msgpack.\"\"\"\n  if isinstance(value, jax.Array):\n    return not value.is_fully_replicated\n  return False\n\n\ndef save_args_from_target(target: Any) -> Any:\n  return jax.tree_util.tree_map(lambda _: ocp.SaveArgs(), target)\n\n\ndef maybe_construct_transformations(\n  target: Any, transforms: Any | None\n) -> Any:\n  if transforms is not None:\n    return transforms\n  flat_transforms = {}\n  flat_target = ocp.utils.to_flat_dict(target, sep='/', keep_empty_nodes=True)\n  for k, v in flat_target.items():\n    if v is None:\n      flat_transforms[k] = ocp.Transform(use_fallback=True)\n  return flat_transforms\n\n\ndef restore_args_from_target(target: Any, mesh: Mesh | None = None) -> Any:\n  \"\"\"Creates Orbax `restore_args` given a target Pytree.\n\n  Args:\n    target: The Pytree that has the same structure as the checkpoint. The arrays\n      restored from checkpoint will have the same `sharding` as the target\n      Pytree's corresponding arrays.\n    mesh: DEPRECATED ARG. Please simply use your mesh to create the arrays\n      in your `target`, no need to pass it here.\n\n  Returns:\n    A Pytree of Orbax `RestoreArgs` or `ArrayRestoreArgs`\n  \"\"\"\n\n  def find_sharding(x):\n    if hasattr(x, 'sharding'):\n      return x.sharding\n    return None\n\n  # Simpler case: no JAX arrays\n  if not any(\n      jax.tree_util.tree_flatten(jax.tree_util.tree_map(find_sharding, target))[\n          0\n      ]\n  ):\n    return jax.tree_util.tree_map(\n        lambda x: ocp.RestoreArgs(restore_type=np.ndarray), target\n    )\n\n  # JAX arrays: find sharding from the given target and create RestoreArgs\n  sharding_tree = jax.tree_util.tree_map(find_sharding, target)\n  if mesh is not None:\n    warnings.warn(\n        (\n            'restore_args_from_target(): `mesh` arg is deprecated. Simply'\n            ' calling the function with target pytree should suffice.'\n        ),\n        DeprecationWarning,\n    )\n    def substitute_embedding(s):\n      return jax.sharding.NamedSharding(mesh, s.spec)\n    sharding_tree = jax.tree_util.tree_map(substitute_embedding, sharding_tree)\n  restore_args = ocp.checkpoint_utils.construct_restore_args(\n      target, sharding_tree, set_global_shape=False\n  )\n  return restore_args\n"
  },
  {
    "path": "flax/training/prefetch_iterator.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Utility for constructing an iterator which prefetches data asynchronously.\"\"\"\n\nimport threading\nimport warnings\n\n\nclass PrefetchIterator:\n  \"\"\"Wraps an iterator to provide async prefetching.\n\n  DEPRECATION WARNING:\n  TensorFlow datasets no longer require manual prefetching.\n\n  Previously this class was used to make data loading using TensorFlow datasets\n  more efficient. Now TF data handles prefetching with NumPy iterators\n  correctly.\n\n  Example::\n\n    tf_iter = dataset.as_numpy_iterator()  # only loads data while calling next\n    tf_iter = PrefetchIterator(tf_iter)  # prefetches data in the background\n\n  \"\"\"\n\n  def __init__(self, data_iter, buffer_size=1):\n    \"\"\"Construct a PrefetchIterator.\n\n    Args:\n      data_iter: the Iterator that should be prefetched.\n      buffer_size: how many items to prefetch (default: 1).\n    \"\"\"\n    warnings.warn(\n      'PrefetchIterator is deprecated. Use the standard `tf.data`'\n      ' prefetch method instead',\n      DeprecationWarning,\n    )\n\n    self._data_iter = data_iter\n    self.buffer_size = buffer_size\n    self._cond = threading.Condition()\n    self._buffer = []\n    self._active = True\n    self._thread = threading.Thread(target=self._prefetch_loop, daemon=True)\n    self._thread.start()\n    self._error = None\n\n  def __iter__(self):\n    return self\n\n  def __next__(self):\n    with self._cond:\n      self._cond.wait_for(lambda: self._buffer or not self._active)\n      if self._buffer:\n        item = self._buffer.pop(0)\n        self._cond.notify_all()\n        return item\n      if self._error:\n        raise self._error  # pylint: disable=raising-bad-type\n      assert not self._active\n      raise StopIteration()\n\n  def close(self):\n    with self._cond:\n      self._active = False\n      self._cond.notify_all()\n\n  def _prefetch_loop(self):\n    \"\"\"Prefetch loop that prefetches a tf dataset.\"\"\"\n\n    def _predicate():\n      return len(self._buffer) < self.buffer_size or not self._active\n\n    while True:\n      try:\n        item = next(self._data_iter)\n        with self._cond:\n          self._buffer.append(item)\n          self._cond.notify_all()\n          self._cond.wait_for(_predicate)\n          if not self._active:\n            return\n      except Exception as e:  # pylint: disable=broad-except\n        with self._cond:\n          self._error = e\n          self._active = False\n          self._cond.notify_all()\n          return\n"
  },
  {
    "path": "flax/training/train_state.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom typing import Any\nfrom collections.abc import Callable\n\nimport optax\n\nimport jax\nfrom flax import core, struct\nfrom flax.linen.fp8_ops import OVERWRITE_WITH_GRADIENT\n\n\nclass TrainState(struct.PyTreeNode):\n  \"\"\"Simple train state for the common case with a single Optax optimizer.\n\n  Example usage::\n\n    >>> import flax.linen as nn\n    >>> from flax.training.train_state import TrainState\n    >>> import jax, jax.numpy as jnp\n    >>> import optax\n\n    >>> x = jnp.ones((1, 2))\n    >>> y = jnp.ones((1, 2))\n    >>> model = nn.Dense(2)\n    >>> variables = model.init(jax.random.key(0), x)\n    >>> tx = optax.adam(1e-3)\n\n    >>> state = TrainState.create(\n    ...     apply_fn=model.apply,\n    ...     params=variables['params'],\n    ...     tx=tx)\n\n    >>> def loss_fn(params, x, y):\n    ...   predictions = state.apply_fn({'params': params}, x)\n    ...   loss = optax.l2_loss(predictions=predictions, targets=y).mean()\n    ...   return loss\n    >>> loss_fn(state.params, x, y)\n    Array(1.8136346, dtype=float32)\n\n    >>> grads = jax.grad(loss_fn)(state.params, x, y)\n    >>> state = state.apply_gradients(grads=grads)\n    >>> loss_fn(state.params, x, y)\n    Array(1.8079796, dtype=float32)\n\n  Note that you can easily extend this dataclass by subclassing it for storing\n  additional data (e.g. additional variable collections).\n\n  For more exotic usecases (e.g. multiple optimizers) it's probably best to\n  fork the class and modify it.\n\n  Args:\n    step: Counter starts at 0 and is incremented by every call to\n      ``.apply_gradients()``.\n    apply_fn: Usually set to ``model.apply()``. Kept in this dataclass for\n      convenience to have a shorter params list for the ``train_step()`` function\n      in your training loop.\n    params: The parameters to be updated by ``tx`` and used by ``apply_fn``.\n    tx: An Optax gradient transformation.\n    opt_state: The state for ``tx``.\n  \"\"\"\n\n  step: int | jax.Array\n  apply_fn: Callable = struct.field(pytree_node=False)\n  params: core.FrozenDict[str, Any] = struct.field(pytree_node=True)\n  tx: optax.GradientTransformation = struct.field(pytree_node=False)\n  opt_state: optax.OptState = struct.field(pytree_node=True)\n\n  def apply_gradients(self, *, grads, **kwargs):\n    \"\"\"Updates ``step``, ``params``, ``opt_state`` and ``**kwargs`` in return value.\n\n    Note that internally this function calls ``.tx.update()`` followed by a call\n    to ``optax.apply_updates()`` to update ``params`` and ``opt_state``.\n\n    Args:\n      grads: Gradients that have the same pytree structure as ``.params``.\n      **kwargs: Additional dataclass attributes that should be ``.replace()``-ed.\n\n    Returns:\n      An updated instance of ``self`` with ``step`` incremented by one, ``params``\n      and ``opt_state`` updated by applying ``grads``, and additional attributes\n      replaced as specified by ``kwargs``.\n    \"\"\"\n    if OVERWRITE_WITH_GRADIENT in grads:\n      grads_with_opt = grads['params']\n      params_with_opt = self.params['params']\n    else:\n      grads_with_opt = grads\n      params_with_opt = self.params\n\n    updates, new_opt_state = self.tx.update(\n      grads_with_opt, self.opt_state, params_with_opt\n    )\n    new_params_with_opt = optax.apply_updates(params_with_opt, updates)\n\n    # As implied by the OWG name, the gradients are used directly to update the\n    # parameters.\n    if OVERWRITE_WITH_GRADIENT in grads:\n      new_params = {\n        'params': new_params_with_opt,\n        OVERWRITE_WITH_GRADIENT: grads[OVERWRITE_WITH_GRADIENT],\n      }\n    else:\n      new_params = new_params_with_opt\n    return self.replace(\n      step=self.step + 1,\n      params=new_params,\n      opt_state=new_opt_state,\n      **kwargs,\n    )\n\n  @classmethod\n  def create(cls, *, apply_fn, params, tx, **kwargs):\n    \"\"\"Creates a new instance with ``step=0`` and initialized ``opt_state``.\"\"\"\n    # We exclude OWG params when present because they do not need opt states.\n    params_with_opt = (\n      params['params'] if OVERWRITE_WITH_GRADIENT in params else params\n    )\n    opt_state = tx.init(params_with_opt)\n    return cls(\n      step=0,\n      apply_fn=apply_fn,\n      params=params,\n      tx=tx,\n      opt_state=opt_state,\n      **kwargs,\n    )\n"
  },
  {
    "path": "flax/traverse_util.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"A utility for traversing immutable datastructures.\n\nA Traversal can be used to iterate and update complex data structures.\nTraversals take in an object and return a subset of its contents.\nFor example, a Traversal could select an attribute of an object::\n\n  >>> from flax import traverse_util\n  >>> import dataclasses\n\n  >>> @dataclasses.dataclass\n  ... class Foo:\n  ...   foo: int = 0\n  ...   bar: int = 0\n  ...\n  >>> x = Foo(foo=1)\n  >>> iterator = traverse_util.TraverseAttr('foo').iterate(x)\n  >>> list(iterator)\n  [1]\n\nMore complex traversals can be constructed using composition.\nIt is often useful to start from the identity traversal and use a method chain\nto construct the intended Traversal::\n\n  >>> data = [{'foo': 1, 'bar': 2}, {'foo': 3, 'bar': 4}]\n  >>> traversal = traverse_util.t_identity.each()['foo']\n  >>> iterator = traversal.iterate(data)\n  >>> list(iterator)\n  [1, 3]\n\nTraversals can also be used to make changes using the ``update`` method::\n\n  >>> data = {'foo': Foo(bar=2)}\n  >>> traversal = traverse_util.t_identity['foo'].bar\n  >>> data = traversal.update(lambda x: x + x, data)\n  >>> data\n  {'foo': Foo(foo=0, bar=4)}\n\nTraversals never mutate the original data. Therefore, an update essentially\nreturns a copy of the data including the provided updates.\n\"\"\"\n\nimport abc\nimport copy\nimport dataclasses\nimport warnings\nfrom typing import Any\nfrom collections.abc import Callable\n\nimport jax\n\nimport flax\nfrom flax.core.scope import VariableDict\nfrom flax.typing import PathParts\n\nfrom . import struct\n\n\n# the empty node is a struct.dataclass to be compatible with JAX.\n@struct.dataclass\nclass _EmptyNode:\n  pass\n\n\nempty_node = _EmptyNode()\n\n\ndef _flatten(xs, prefix, keep_empty_nodes, is_leaf, sep):\n  def _key(path):\n    if sep is None:\n      return path\n    return sep.join(path)\n\n  if not isinstance(xs, (flax.core.FrozenDict, dict)) or (\n      is_leaf and is_leaf(prefix, xs)\n  ):\n    return {_key(prefix): xs}\n  result = {}\n  is_empty = True\n  for key, value in xs.items():\n    is_empty = False\n    path = prefix + (key,)\n    result.update(_flatten(value, path, keep_empty_nodes, is_leaf, sep))\n  if keep_empty_nodes and is_empty:\n    if prefix == ():  # when the whole input is empty\n      return {}\n    return {_key(prefix): empty_node}\n  return result\n\n\ndef flatten_dict(xs, keep_empty_nodes=False, is_leaf=None, sep=None):\n  \"\"\"Flatten a nested dictionary.\n\n  The nested keys are flattened to a tuple.\n  See ``unflatten_dict`` on how to restore the\n  nested dictionary structure.\n\n  Example::\n\n    >>> from flax.traverse_util import flatten_dict\n\n    >>> xs = {'foo': 1, 'bar': {'a': 2, 'b': {}}}\n    >>> flat_xs = flatten_dict(xs)\n    >>> flat_xs\n    {('foo',): 1, ('bar', 'a'): 2}\n\n  Note that empty dictionaries are ignored and\n  will not be restored by ``unflatten_dict``.\n\n  Args:\n    xs: a nested dictionary\n    keep_empty_nodes: replaces empty dictionaries\n      with ``traverse_util.empty_node``.\n    is_leaf: an optional function that takes the\n      next nested dictionary and nested keys and\n      returns True if the nested dictionary is a\n      leaf (i.e., should not be flattened further).\n    sep: if specified, then the keys of the returned\n      dictionary will be ``sep``-joined strings (if\n      ``None``, then keys will be tuples).\n  Returns:\n    The flattened dictionary.\n  \"\"\"\n  assert isinstance(\n    xs, (flax.core.FrozenDict, dict)\n  ), f'expected (frozen)dict; got {type(xs)}'\n\n  return _flatten(xs, (), keep_empty_nodes, is_leaf, sep)\n\n\ndef unflatten_dict(xs, sep=None):\n  \"\"\"Unflatten a dictionary.\n\n  See ``flatten_dict``\n\n  Example::\n\n    >>> flat_xs = {\n    ...   ('foo',): 1,\n    ...   ('bar', 'a'): 2,\n    ... }\n    >>> xs = unflatten_dict(flat_xs)\n    >>> xs\n    {'foo': 1, 'bar': {'a': 2}}\n\n  Args:\n    xs: a flattened dictionary\n    sep: separator (same as used with ``flatten_dict()``).\n  Returns:\n    The nested dictionary.\n  \"\"\"\n  assert isinstance(xs, dict), f'input is not a dict; it is a {type(xs)}'\n  result = {}\n  for path, value in xs.items():\n    if sep is not None:\n      path = path.split(sep)\n    if value is empty_node:\n      value = {}\n    cursor = result\n    for key in path[:-1]:\n      if key not in cursor:\n        cursor[key] = {}\n      cursor = cursor[key]\n    cursor[path[-1]] = value\n  return result\n\n\ndef path_aware_map(\n  f: Callable[[PathParts, Any], Any], nested_dict: VariableDict\n) -> VariableDict:\n  \"\"\"A map function that operates over nested dictionary structures while taking\n  the path to each leaf into account.\n\n  Example::\n\n    >>> import jax.numpy as jnp\n    >>> from flax import traverse_util\n\n    >>> params = {'a': {'x': 10, 'y': 3}, 'b': {'x': 20}}\n    >>> f = lambda path, x: x + 5 if 'x' in path else -x\n    >>> traverse_util.path_aware_map(f, params)\n    {'a': {'x': 15, 'y': -3}, 'b': {'x': 25}}\n\n  Args:\n    f: A callable that takes in ``(path, value)`` arguments and maps them\n      to a new value. Here ``path`` is a tuple of strings.\n    nested_dict: A nested dictionary structure.\n\n  Returns:\n    A new nested dictionary structure with the mapped values.\n  \"\"\"\n  flat = flatten_dict(nested_dict, keep_empty_nodes=True)\n  return unflatten_dict(\n    {k: f(k, v) if v is not empty_node else v for k, v in flat.items()}\n  )\n\n\nclass Traversal(abc.ABC):\n  \"\"\"Base class for all traversals.\"\"\"\n\n  def __new__(cls, *args, **kwargs):\n    # Must override __new__ instead of __init__ since this is an ABC\n    warnings.warn(\n      '`flax.traverse_util.Traversal` will be deprecated. If you are using '\n      'it for `flax.optim`, use `optax` instead. Refer to the update guide '\n      'https://flax.readthedocs.io/en/latest/guides/converting_and_upgrading/optax_update_guide.html '\n      'for detailed instructions.',\n      DeprecationWarning,\n    )\n    return super().__new__(cls)\n\n  @abc.abstractmethod\n  def update(self, fn, inputs):\n    \"\"\"Update the focused items.\n\n    Args:\n      fn: the callback function that maps each traversed item\n        to its updated value.\n      inputs: the object that should be traversed.\n    Returns:\n      A new object with the updated values.\n    \"\"\"\n    pass\n\n  @abc.abstractmethod\n  def iterate(self, inputs):\n    \"\"\"Iterate over the values selected by this ``Traversal``.\n\n    Args:\n      inputs: the object that should be traversed.\n    Returns:\n      An iterator over the traversed values.\n    \"\"\"\n    pass\n\n  def set(self, values, inputs):\n    \"\"\"Overrides the values selected by the ``Traversal``.\n\n    Args:\n      values: a list containing the new values.\n      inputs: the object that should be traversed.\n    Returns:\n      A new object with the updated values.\n    \"\"\"\n\n    def update_fn(_):\n      if not values:\n        raise ValueError('Not enough values provided')\n      return values.pop(0)\n\n    y = self.update(update_fn, inputs)\n    if values:\n      raise ValueError('Too many values provided')\n    return y\n\n  def compose(self, other):\n    \"\"\"Compose two traversals.\"\"\"\n    return TraverseCompose(self, other)\n\n  def merge(self, *traversals):\n    \"\"\"Compose an arbitrary number of traversals and merge the results.\"\"\"\n    return self.compose(TraverseMerge(*traversals))\n\n  def each(self):\n    \"\"\"Traverse each item in the selected containers.\"\"\"\n    return self.compose(TraverseEach())\n\n  def tree(self):\n    \"\"\"Traverse each item in a pytree.\"\"\"\n    return self.compose(TraverseTree())\n\n  def filter(self, fn):\n    \"\"\"Filter the selected values.\"\"\"\n    return self.compose(TraverseFilter(fn))\n\n  def __getattr__(self, attr):\n    return self.compose(TraverseAttr(attr))\n\n  def __getitem__(self, key):\n    return self.compose(TraverseItem(key))\n\n\nclass TraverseId(Traversal):\n  \"\"\"The identity Traversal.\"\"\"\n\n  def update(self, fn, inputs):\n    return fn(inputs)\n\n  def iterate(self, inputs):\n    yield inputs\n\n\nwith warnings.catch_warnings():\n  warnings.simplefilter('ignore', DeprecationWarning)\n  t_identity = TraverseId()\n\n\nclass TraverseMerge(Traversal):\n  \"\"\"Merges the selection from a set of traversals.\"\"\"\n\n  def __init__(self, *traversals):\n    self._traversals = traversals\n\n  def update(self, fn, inputs):\n    for traversal in self._traversals:\n      inputs = traversal.update(fn, inputs)\n    return inputs\n\n  def iterate(self, inputs):\n    for traversal in self._traversals:\n      yield from traversal.iterate(inputs)\n\n\nclass TraverseCompose(Traversal):\n  \"\"\"Compose two traversals.\"\"\"\n\n  def __init__(self, x, y):\n    self._x = x\n    self._y = y\n\n  def update(self, fn, inputs):\n    def update_fn(x):\n      return self._y.update(fn, x)\n\n    return self._x.update(update_fn, inputs)\n\n  def iterate(self, inputs):\n    for x in self._x.iterate(inputs):\n      yield from self._y.iterate(x)\n\n\nclass TraverseFilter(Traversal):\n  \"\"\"Filter selected values based on a predicate.\"\"\"\n\n  def __init__(self, fn):\n    self._fn = fn\n\n  def update(self, fn, inputs):\n    if self._fn(inputs):\n      return fn(inputs)\n    else:\n      return inputs\n\n  def iterate(self, inputs):\n    if self._fn(inputs):\n      yield inputs\n\n\ndef _is_namedtuple(t):\n  return issubclass(t, tuple) and hasattr(t, '_fields')\n\n\nclass TraverseAttr(Traversal):\n  \"\"\"Traverse the attribute of an object.\"\"\"\n\n  def __init__(self, attr):\n    self._attr = attr\n\n  def update(self, fn, inputs):\n    value = fn(getattr(inputs, self._attr))\n    if _is_namedtuple(type(inputs)):\n      return inputs._replace(**{self._attr: value})\n    elif dataclasses.is_dataclass(inputs):\n      return dataclasses.replace(inputs, **{self._attr: value})\n    else:\n      inputs = copy.copy(inputs)\n      setattr(inputs, self._attr, value)\n      return inputs\n\n  def iterate(self, inputs):\n    yield getattr(inputs, self._attr)\n\n\nclass TraverseItem(Traversal):\n  \"\"\"Traverse the item of an object.\"\"\"\n\n  def __init__(self, key):\n    self._key = key\n\n  def update(self, fn, inputs):\n    if isinstance(inputs, tuple):\n      ty = type(inputs)\n      if isinstance(self._key, slice):\n        sl = self._key\n      else:\n        sl = slice(self._key, self._key + 1)\n      indices = set(range(*sl.indices(len(inputs))))\n\n      args = [\n        fn(inputs[i]) if i in indices else inputs[i] for i in range(len(inputs))\n      ]\n      if _is_namedtuple(ty):\n        return ty(*args)\n      else:\n        return ty(args)\n    else:\n      xs = copy.copy(inputs)\n      xs[self._key] = fn(xs[self._key])\n      return xs\n\n  def iterate(self, inputs):\n    if isinstance(self._key, slice):\n      yield from inputs[self._key]\n    else:\n      yield inputs[self._key]\n\n\nclass TraverseEach(Traversal):\n  \"\"\"Traverse each item of a container.\"\"\"\n\n  def update(self, fn, inputs):\n    ty = type(inputs)\n    if ty is dict:\n      return {key: fn(val) for key, val in inputs.items()}\n    if ty not in {list, tuple}:\n      raise ValueError('Only the entries of a list or tuple can be traversed.')\n    return ty(fn(x) for x in inputs)\n\n  def iterate(self, inputs):\n    if isinstance(inputs, dict):\n      yield from inputs.values()\n    else:\n      yield from inputs\n\n\nclass TraverseTree(Traversal):\n  \"\"\"Traverse every item in a pytree.\"\"\"\n\n  def update(self, fn, inputs):\n    return jax.tree_util.tree_map(fn, inputs)\n\n  def iterate(self, inputs):\n    yield from jax.tree_util.tree_leaves(inputs)\n\n\ndef _get_params_dict(inputs):\n  if isinstance(inputs, (dict, flax.core.FrozenDict)):\n    return flax.core.unfreeze(inputs)\n  else:\n    raise ValueError(\n      'Can only traverse a flax Model instance or a nested dict, not '\n      f'{type(inputs)}'\n    )\n\n\ndef _sorted_items(x):\n  \"\"\"Returns items of a dict ordered by keys.\"\"\"\n  return sorted(x.items(), key=lambda x: x[0])\n\n\nclass ModelParamTraversal(Traversal):\n  \"\"\"Select model parameters using a name filter.\n\n  This traversal operates on a nested dictionary of parameters and selects a\n  subset based on the ``filter_fn`` argument.\n\n  See :class:`flax.optim.MultiOptimizer` for an example of how to use\n  :class:`ModelParamTraversal` to update subsets of the parameter tree with a\n  specific optimizer.\n  \"\"\"\n\n  def __init__(self, filter_fn):\n    \"\"\"Constructor a new ModelParamTraversal.\n\n    Args:\n      filter_fn: a function that takes a parameter's full name and its value and\n        returns whether this parameter should be selected or not. The name of a\n        parameter is determined by the module hierarchy and the parameter name\n        (for example: '/module/sub_module/parameter_name').\n    \"\"\"\n    self._filter_fn = filter_fn\n\n  def iterate(self, inputs):\n    params = _get_params_dict(inputs)\n    flat_dict = flatten_dict(params)\n    for key, value in _sorted_items(flat_dict):\n      path = '/' + '/'.join(key)\n      if self._filter_fn(path, value):\n        yield value\n\n  def update(self, fn, inputs):\n    params = _get_params_dict(inputs)\n    flat_dict = flatten_dict(params, keep_empty_nodes=True)\n    new_dict = {}\n    for key, value in _sorted_items(flat_dict):\n      # empty_node is not an actual leave. It's just a stub for empty nodes\n      # in the nested dict.\n      if value is not empty_node:\n        path = '/' + '/'.join(key)\n        if self._filter_fn(path, value):\n          value = fn(value)\n      new_dict[key] = value\n    new_params = unflatten_dict(new_dict)\n    if isinstance(inputs, flax.core.FrozenDict):\n      return flax.core.FrozenDict(new_params)\n    else:\n      return new_params\n"
  },
  {
    "path": "flax/typing.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom __future__ import annotations\n\nimport abc\nfrom collections import deque\nimport functools\nfrom functools import partial\nfrom typing import (\n  Any,\n  Generic,\n  Optional,\n  Protocol,\n  TypeGuard,\n  TypeVar,\n  Union,\n)\nfrom collections.abc import Iterator\nfrom collections.abc import Callable, Hashable, Mapping, Sequence\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom flax.core import FrozenDict\n\nimport dataclasses\nimport jax.tree_util as jtu\n\n\n# General\n\nArray = Union[jax.Array, Any]\nPRNGKey = jax.Array\nRNGSequences = dict[str, PRNGKey]\nDtype = Union[jax.typing.DTypeLike, Any]\nShape = Sequence[int]\nK = TypeVar('K')\n\nclass Key(Hashable, Protocol):\n  def __lt__(self: K, value: K, /) -> bool:\n    ...\n\ndef is_key_like(x: Any) -> TypeGuard[Key]:\n  return hasattr(x, '__hash__') and hasattr(x, '__lt__')\n\nPath = str\nPathParts = tuple[Key, ...]\n\nLeaf = Any\n\n\n# Linear\n\nPrecisionLike = Union[\n  None,\n  str,\n  jax.lax.Precision,\n  tuple[str, str],\n  tuple[jax.lax.Precision, jax.lax.Precision],\n]\nDotGeneralT = Callable[..., Array]\nConvGeneralDilatedT = Callable[..., Array]\nEinsumT = Callable[..., Array]\n\nPaddingLike = Union[str, int, Sequence[Union[int, tuple[int, int]]]]\nLaxPadding = Union[str, Sequence[tuple[int, int]]]\n\n\n# Initializers\n\nInitializer = Union[jax.nn.initializers.Initializer, Callable[..., Any]]\n\n\n# Collections\n\nCollection = Mapping[str, Any]\nMutableCollection = dict[str, Any]\n\n\n# Dicts\n\nVariableDict = Mapping[str, Collection]\nFrozenVariableDict = FrozenDict[str, Collection]\nMutableVariableDict = dict[str, MutableCollection]\n\nPRNGFoldable = Union[int, str]\n\n\n# Axes\n\nT = TypeVar('T')\n\n@dataclasses.dataclass(frozen=True)\nclass In(Generic[T]):\n  \"\"\"Specifies a variable collection should only be lifted as input.\"\"\"\n\n  axis: T\n\n@dataclasses.dataclass(frozen=True)\nclass Out(Generic[T]):\n  \"\"\"Specifies a variable collection should only be lifted as output.\"\"\"\n\n  axis: T\n\nAxis = Optional[int]\nInOutAxis = Union[Axis, In[Axis], Out[Axis]]\n\nScanAxis = int\nInOutScanAxis = Union[ScanAxis, In[ScanAxis], Out[ScanAxis]]\n\nAxes = Union[int, Sequence[int]]\n\n\n# SPMD\n\nLogicalNames = tuple[Union[str, None], ...]\nAxisName = str | tuple[str, ...] | None\n\n# Maps each logical axis  to physical mesh, can be either None (replicated),\n# one physical axis or a tuple of physical axes.\nLogicalRules = Sequence[tuple[str, AxisName]]\nArrayPytree = Any  # pylint: disable=invalid-name\nLogicalPartitionSpec = Any  # pylint: disable=invalid-name\nLogicalPartitionSpecPytree = Any  # pylint: disable=invalid-name\nPartitionSpecPytree = Any  # pylint: disable=invalid-name\n\nSharding = tuple[AxisName, ...]\n\nA = TypeVar('A')\nHA = TypeVar('HA', bound=Hashable)\nHB = TypeVar('HB')\n\n\nclass PytreeDeque(deque[A]):\n  pass\n\n\ndef _pytree_deque_flatten(xs: PytreeDeque, *, with_path: bool):\n  if with_path:\n    nodes = tuple((jtu.SequenceKey(i), x) for i, x in enumerate(xs))\n    return nodes, ()\n  else:\n    return xs, ()\n\n\ndef _pytree_deque_unflatten(_, nodes):\n  return PytreeDeque(nodes)\n\n\njtu.register_pytree_with_keys(\n  PytreeDeque,\n  partial(_pytree_deque_flatten, with_path=True),\n  _pytree_deque_unflatten,\n  flatten_func=partial(_pytree_deque_flatten, with_path=False),\n)\n\nclass Missing:\n  pass\n\n\nMISSING = Missing()\n\n\ndef _bytes_repr(num_bytes):\n  count, units = (\n    (f'{num_bytes / 1e9:,.1f}', 'GB')\n    if num_bytes > 1e9\n    else (f'{num_bytes / 1e6:,.1f}', 'MB')\n    if num_bytes > 1e6\n    else (f'{num_bytes / 1e3:,.1f}', 'KB')\n    if num_bytes > 1e3\n    else (f'{num_bytes:,}', 'B')\n  )\n\n  return f'{count} {units}'\n\n\nclass ShapeDtype(Protocol):\n  shape: Shape\n  dtype: Dtype\n\n\ndef has_shape_dtype(x: Any) -> TypeGuard[ShapeDtype]:\n  return hasattr(x, 'shape') and hasattr(x, 'dtype')\n\n\n@dataclasses.dataclass(frozen=True, slots=True)\nclass SizeBytes:  # type: ignore[misc]\n  size: int\n  bytes: int\n\n  @classmethod\n  def from_array(cls, x: ShapeDtype):\n    size = int(np.prod(x.shape))\n    dtype: jnp.dtype\n    if isinstance(x.dtype, str):\n      dtype = jnp.dtype(x.dtype)\n    else:\n      dtype = x.dtype  # type: ignore\n    bytes = size * dtype.itemsize  # type: ignore\n    return cls(size, bytes)\n\n  def __add__(self, other: SizeBytes):\n    return type(self)(self.size + other.size, self.bytes + other.bytes)\n\n  def __bool__(self) -> bool:\n    return bool(self.size)\n\n  def __repr__(self) -> str:\n    bytes_repr = _bytes_repr(self.bytes)\n    return f'{self.size:,} ({bytes_repr})'\n\n  @classmethod\n  def from_any(cls, x):\n    leaves = jax.tree.leaves(x)\n    size_bytes = cls(0, 0)\n    for leaf in leaves:\n      if has_shape_dtype(leaf):\n        size_bytes += cls.from_array(leaf)\n\n    return size_bytes\n\n\nTupleArg = TypeVar('TupleArg', bound=tuple)\n\n\nclass PromoteDtypeFn(Protocol):\n  def __call__(\n    self, args: TupleArg, /, *, dtype: Any = None, inexact: bool = True\n  ) -> TupleArg: ...\n\n\nclass HashableMapping(Mapping[HA, HB], Hashable):\n  _mapping: dict[HA, HB] | Mapping[HA, HB]\n\n  def __init__(self, mapping: Mapping[HA, HB], copy: bool = True):\n    self._mapping = dict(mapping) if copy else mapping\n\n  def __contains__(self, key: object) -> bool:\n    return key in self._mapping\n\n  def __getitem__(self, key: HA) -> HB:\n    return self._mapping[key]\n\n  def __iter__(self) -> Iterator[HA]:\n    return iter(self._mapping)\n\n  def __len__(self) -> int:\n    return len(self._mapping)\n\n  def __hash__(self) -> int:\n    # use type-aware sorting to support int keys\n    def _pytree__key_sort_fn(item: tuple[Any, Any]) -> tuple[int, Any]:\n      key, _ = item\n      if isinstance(key, int):\n        return (0, key)\n      elif isinstance(key, str):\n        return (1, key)\n      else:\n        raise ValueError(f'Unsupported key type: {type(key)!r}')\n\n    return hash(tuple(sorted(self._mapping.items(), key=_pytree__key_sort_fn)))\n\n  def __eq__(self, other: Any) -> bool:\n    return (\n      isinstance(other, HashableMapping) and self._mapping == other._mapping\n    )\n\n  def __repr__(self) -> str:\n    return repr(self._mapping)\n\n  def update(self, other: Mapping[HA, HB]) -> HashableMapping[HA, HB]:\n    \"\"\"Updates the mapping with another mapping.\"\"\"\n    mapping = dict(self._mapping)\n    mapping.update(other)\n    return HashableMapping(mapping, copy=False)\n\n\nF = TypeVar('F', bound=Callable[..., Any])\n\n\nclass BaseConfigContext(abc.ABC):\n  @classmethod\n  @abc.abstractmethod\n  def get_default(cls):\n    ...\n\n  @classmethod\n  @abc.abstractmethod\n  def get_stack(cls) -> list:\n    ...\n\n  def __init__(self, value, /):\n    stack = self.get_stack()\n    if stack:\n      self.prev_value = stack[-1]\n      stack[-1] = value\n    else:\n      self.prev_value = None\n      stack.append(value)\n    self.new_value = value\n\n  @classmethod\n  def current_value(cls):\n    stack = cls.get_stack()\n    if stack:\n      return stack[-1]\n    return cls.get_default()\n\n  def __enter__(self):\n    if self.prev_value is not None:\n      self.get_stack().insert(-1, self.prev_value)\n\n  def __exit__(self, exc_type, exc_value, traceback):\n    self.get_stack().pop()\n\n  def __call__(self, f: F) -> F:\n    self.get_stack().pop()\n    if self.prev_value is not None:\n      self.get_stack().append(self.prev_value)\n\n    @functools.wraps(f)\n    def wrapper(*args, **kwargs):\n      self.get_stack().append(self.new_value)\n      try:\n        return f(*args, **kwargs)\n      finally:\n        self.get_stack().pop()\n\n    return wrapper  # type: ignore[return-value]\n"
  },
  {
    "path": "flax/version.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Current Flax version at head on Github.\"\"\"\n__version__ = '0.12.6'\n"
  },
  {
    "path": "flaxlib_src/.gitignore",
    "content": "/target\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n.pytest_cache/\n*.py[cod]\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\n.venv/\nenv/\nbin/\nbuild/\ndevelop-eggs/\ndist/\neggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\ninclude/\nman/\nvenv/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\npip-selfcheck.json\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.cache\nnosetests.xml\ncoverage.xml\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n\n# Rope\n.ropeproject\n\n# Django stuff:\n*.log\n*.pot\n\n.DS_Store\n\n# Sphinx documentation\ndocs/_build/\n\n# PyCharm\n.idea/\n\n# VSCode\n.vscode/\n\n# Pyenv\n.python-version\n\n# cibuildwheel\n/wheelhouse"
  },
  {
    "path": "flaxlib_src/CMakeLists.txt",
    "content": "# Set the minimum CMake version and policies for highest tested version\ncmake_minimum_required(VERSION 3.15...3.27)\n\n# Set up the project and ensure there is a working C++ compiler\nproject(flaxlib LANGUAGES CXX)\n\n# Warn if the user invokes CMake directly\nif (NOT SKBUILD)\n  message(WARNING \"\\\n  This CMake file is meant to be executed using 'scikit-build-core'.\n  Running it directly will almost certainly not produce the desired\n  result. If you are a user trying to install this package, use the\n  command below, which will install all necessary build dependencies,\n  compile the package in an isolated environment, and then install it.\n  =====================================================================\n   $ pip install .\n  =====================================================================\n  If you are a software developer, and this is your own package, then\n  it is usually much more efficient to install the build dependencies\n  in your environment once and use the following command that avoids\n  a costly creation of a new virtual environment at every compilation:\n  =====================================================================\n   $ pip install nanobind scikit-build-core[pyproject]\n   $ pip install --no-build-isolation -ve .\n  =====================================================================\n  You may optionally add -Ceditable.rebuild=true to auto-rebuild when\n  the package is imported. Otherwise, you need to rerun the above\n  after editing C++ files.\")\nendif()\n\n# Try to import all Python components potentially needed by nanobind\nfind_package(Python 3.8\n  REQUIRED COMPONENTS Interpreter Development.Module\n  OPTIONAL_COMPONENTS Development.SABIModule)\n\n# Import nanobind through CMake's find_package mechanism\nfind_package(nanobind CONFIG REQUIRED)\n\n# We are now ready to compile the actual extension module\nnanobind_add_module(\n  # Name of the extension\n  flaxlib_cpp\n\n  # Target the stable ABI for Python 3.12+, which reduces\n  # the number of binary wheels that must be built. This\n  # does nothing on older Python versions\n  STABLE_ABI\n\n  # Source code goes here\n  src/lib.cc\n)\n\n# Install directive for scikit-build-core\ninstall(TARGETS flaxlib_cpp LIBRARY DESTINATION flaxlib)"
  },
  {
    "path": "flaxlib_src/Cargo.toml",
    "content": "[package]\nname = \"flaxlib\"\nversion = \"0.0.1-a1\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n[lib]\nname = \"flaxlib\"\ncrate-type = [\"cdylib\"]\n\n[dependencies]\npyo3 = \"0.21.2\"\n"
  },
  {
    "path": "flaxlib_src/LICENSE",
    "content": "                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "flaxlib_src/README.md",
    "content": "# flaxlib\n\n## Build flaxlib from source\n\nInstall necessary dependencies to build the C++ based package.\n\n```shell\npip install meson-python ninja build\n```\n\nClone the Flax repository, navigate to the flaxlib source directory.\n\n```shell\ngit clone git@github.com:google/flax.git\ncd flax/flaxlib_src\n```\n\nConfigure the build.\n\n```shell\nmkdir -p subprojects\nmeson wrap install robin-map\nmeson wrap install nanobind\nmeson setup builddir\n```\n\nCompile the code. You'll need to run this repeatedly if you modify the source\ncode. Note that the actual wheel name will differ depending on your system.\n\n```shell\nmeson compile -C builddir\npython -m build . -w\npip install dist/flaxlib-0.0.1-cp311-cp311-macosx_14_0_arm64.whl --force-reinstall\n```\n"
  },
  {
    "path": "flaxlib_src/pyproject.toml",
    "content": "[build-system]\nrequires = [\"scikit-build-core >=0.4.3\", \"nanobind >=1.3.2\"]\nbuild-backend = \"scikit_build_core.build\"\n\n[project]\nname = \"flaxlib\"\nversion = \"0.0.1\"\nrequires-python = \">=3.10\"\nclassifiers = [\n    \"Programming Language :: C++\",\n    \"Programming Language :: Python :: Implementation :: CPython\",\n    \"Programming Language :: Python :: Implementation :: PyPy\",\n]\n\n[project.optional-dependencies]\ntests = [\n    \"pytest\",\n]\n\n[tool.scikit-build]\n# Protect the configuration against future changes in scikit-build-core\nminimum-version = \"0.4\"\n\n# Setuptools-style build caching in a local directory\nbuild-dir = \"build/{wheel_tag}\"\n\n# Build stable ABI wheels for CPython 3.12+\nwheel.py-api = \"cp312\""
  },
  {
    "path": "flaxlib_src/src/flaxlib/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom .flaxlib_cpp import RefMap as RefMap\nfrom .flaxlib_cpp import IndexMap as IndexMap\nfrom .flaxlib_cpp import NodeDef as NodeDef\nfrom .flaxlib_cpp import VariableDef as VariableDef\nfrom .flaxlib_cpp import NodeRef as NodeRef\n"
  },
  {
    "path": "flaxlib_src/src/flaxlib/flaxlib_cpp.pyi",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing as tp\n\nRefMap = tp.MutableMapping[tp.Any, int]\nIndexMap = dict[int, tp.Any]\n\nclass NodeDef:\n  type: type\n  index: int | None\n  outer_index: int | None\n  num_attributes: int\n  metadata: tp.Any\n\n  def with_no_outer_index(self) -> NodeDef: ...\n  def with_same_outer_index(self) -> NodeDef: ...\n  def __eq__(self, other: tp.Any) -> bool: ...\n  def __hash__(self) -> int: ...\n  def __getstate__(\n    self,\n  ) -> tuple[tp.Any, tp.Any, tp.Any, tp.Any, tp.Any]: ...\n  @staticmethod\n  def __setstate__(\n    nodedef: NodeDef, state: tuple[tp.Any, tp.Any, tp.Any, tp.Any, tp.Any]\n  ) -> None: ...\n\nclass VariableDef:\n  type: type\n  index: int\n  outer_index: int | None\n  metadata: tp.Any\n\n  def with_no_outer_index(self) -> VariableDef: ...\n  def with_same_outer_index(self) -> VariableDef: ...\n  def __eq__(self, other: tp.Any) -> bool: ...\n  def __hash__(self) -> int: ...\n  def __getstate__(\n    self,\n  ) -> tuple[tp.Any, int, tp.Any, tp.Any]: ...\n  @staticmethod\n  def __setstate__(\n    variabledef: 'VariableDef', state: tuple[tp.Any, int, tp.Any, tp.Any]\n  ) -> None: ...\n\nclass NodeRef:\n  index: int\n\n  def __eq__(self, other: tp.Any) -> bool: ...\n  def __hash__(self) -> int: ...\n  def __getstate__(self) -> tuple[int]: ...\n  @staticmethod\n  def __setstate__(noderef: NodeRef, state: tuple[int]) -> None: ...\n\n"
  },
  {
    "path": "flaxlib_src/src/lib.cc",
    "content": "// Copyright 2024 The Flax Authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#include <nanobind/nanobind.h>\n#include <nanobind/stl/string.h>\n#include <nanobind/stl/vector.h>\n#include <nanobind/stl/map.h>\n#include <nanobind/stl/unordered_map.h>\n#include <nanobind/stl/bind_map.h>\n#include <nanobind/stl/optional.h>\n#include <map>\n#include <vector>\n#include <unordered_map>\n#include <string>\n#include <Python.h>\n\nnamespace nb = nanobind;\nusing namespace nb::literals;\n\n// -----------------------------------\n// helper functions\n// -----------------------------------\nintptr_t nb_id(const nb::object &obj)\n{\n  // Get the object ID\n  return reinterpret_cast<intptr_t>(obj.ptr());\n}\n\nnb::tuple vector_to_tuple(const std::vector<nb::object> &vec)\n{\n\n  if (vec.empty())\n  {\n    return nb::tuple();\n  }\n  else\n  {\n    return nb::tuple(nb::cast(vec));\n  }\n}\n\n// 1. Hash function for nb::object\nstruct NbObjectHash\n{\n  std::size_t operator()(const nb::object &obj) const\n  {\n    return nb::hash(obj);\n  }\n};\n\n// 2. Equality function for nb::object (Important!)\nstruct NbObjectEqual\n{\n  bool operator()(const nb::object &a, const nb::object &b) const\n  {\n    return a.equal(b);\n  }\n};\n\nnamespace flaxlib\n{\n  //---------------------------------------------------------------\n  // NNXContext\n  //---------------------------------------------------------------\n\n  struct PythonContext\n  {\n    nb::object nnx;\n    nb::object graph;\n    nb::object jax;\n    nb::object np;\n    nb::object jax_Array;\n    nb::object np_ndarray;\n    nb::type_object GraphNodeImpl;\n    nb::type_object PytreeNodeImpl;\n    nb::type_object Object;\n    nb::type_object Variable;\n    nb::object get_node_impl;\n\n    PythonContext()\n    {\n      nnx = nb::module_::import_(\"flax.nnx\");\n      graph = nb::module_::import_(\"flax.nnx.graph\");\n      jax = nb::module_::import_(\"jax\");\n      np = nb::module_::import_(\"numpy\");\n      jax_Array = jax.attr(\"Array\");\n      np_ndarray = np.attr(\"ndarray\");\n      GraphNodeImpl = graph.attr(\"GraphNodeImpl\");\n      PytreeNodeImpl = graph.attr(\"PytreeNodeImpl\");\n      Object = nnx.attr(\"Object\");\n      Variable = graph.attr(\"Variable\");\n      get_node_impl = graph.attr(\"get_node_impl\");\n    }\n\n    ~PythonContext()\n    {\n      graph.release();\n      jax.release();\n      np.release();\n      jax_Array.release();\n      np_ndarray.release();\n      GraphNodeImpl.release();\n      PytreeNodeImpl.release();\n      Variable.release();\n      get_node_impl.release();\n    }\n  };\n\n  static std::optional<PythonContext> _python_context;\n\n  PythonContext &get_python_context()\n  {\n    if (!_python_context)\n    {\n      _python_context.emplace();\n    }\n    return *_python_context;\n  }\n\n  //---------------------------------------------------------------\n  // IndexMap\n  //---------------------------------------------------------------\n\n  struct IndexMap : public std::unordered_map<int, nb::object>\n  {\n  };\n\n  //---------------------------------------------------------------\n  // RefMap\n  //---------------------------------------------------------------\n\n  struct RefMapKeysIterator\n  {\n    std::unordered_map<intptr_t, std::tuple<nb::object, int>>::iterator it;\n    std::unordered_map<intptr_t, std::tuple<nb::object, int>>::iterator end;\n\n    RefMapKeysIterator(std::unordered_map<intptr_t, std::tuple<nb::object, int>>::iterator it, std::unordered_map<intptr_t, std::tuple<nb::object, int>>::iterator end)\n        : it(it), end(end) {}\n\n    nb::object __next__()\n    {\n      if (it == end)\n      {\n        throw nb::stop_iteration();\n      }\n      auto elem = it->second;\n      ++it;\n      return std::get<0>(elem);\n    }\n  };\n\n  struct RefMapItemsIterator\n  {\n    std::unordered_map<intptr_t, std::tuple<nb::object, int>>::iterator it;\n    std::unordered_map<intptr_t, std::tuple<nb::object, int>>::iterator end;\n\n    RefMapItemsIterator(std::unordered_map<intptr_t, std::tuple<nb::object, int>>::iterator it, std::unordered_map<intptr_t, std::tuple<nb::object, int>>::iterator end)\n        : it(it), end(end) {}\n\n    RefMapItemsIterator __iter__()\n    {\n      return *this;\n    }\n\n    nb::tuple __next__()\n    {\n      if (it == end)\n      {\n        throw nb::stop_iteration();\n      }\n      auto elem = it->second;\n      ++it;\n      return nb::make_tuple(std::get<0>(elem), std::get<1>(elem));\n    }\n  };\n\n  struct RefMap\n  {\n    std::unordered_map<intptr_t, std::tuple<nb::object, int>> mapping;\n\n    RefMap() {}\n\n    RefMap(const nb::object &iterable) : RefMap()\n    {\n      for (auto item : iterable)\n      {\n        nb::object obj = item[0];\n        auto value = nb::cast<int>(item[1]);\n        mapping[nb_id(obj)] = {obj, value};\n      }\n    }\n\n    void update(const RefMap &other)\n    {\n      for (const auto &[key_id, value_tuple] : other.mapping)\n      {\n        mapping[key_id] = value_tuple;\n      }\n    }\n\n    int __getitem__(const nb::object &key)\n    {\n      return std::get<1>(mapping[nb_id(key)]);\n    }\n\n    void __setitem__(const nb::object &key, int value)\n    {\n      mapping[nb_id(key)] = std::make_tuple(key, value);\n    }\n\n    int __len__() const\n    {\n      return mapping.size();\n    }\n\n    bool __contains__(const nb::object &key) const\n    {\n      return mapping.find(nb_id(key)) != mapping.end();\n    }\n\n    RefMapKeysIterator __iter__()\n    {\n      return RefMapKeysIterator(mapping.begin(), mapping.end());\n    };\n\n    RefMapItemsIterator items()\n    {\n      return RefMapItemsIterator(mapping.begin(), mapping.end());\n    }\n\n    std::optional<int> get(const nb::object &key, std::optional<int> default_value = std::nullopt)\n    {\n      auto it = mapping.find(nb_id(key));\n      if (it != mapping.end())\n      {\n        return std::get<1>(it->second);\n      }\n      return default_value;\n    }\n  };\n\n  static IndexMap indexmap_from_refmap(const RefMap &refmap)\n  {\n    IndexMap indexmap;\n    for (const auto &[_, value_index] : refmap.mapping)\n    {\n      nb::object value = std::get<0>(value_index);\n      int index = std::get<1>(value_index);\n      indexmap[index] = value;\n    }\n    return indexmap;\n  };\n\n  static RefMap refmap_from_indexmap(const IndexMap &indexmap)\n  {\n    RefMap refmap;\n    for (const auto &[index, value] : indexmap)\n    {\n      refmap.mapping[nb_id(value)] = std::make_tuple(value, index);\n    }\n    return refmap;\n  };\n\n  //---------------------------------------------------------------\n  // NodeDef\n  //---------------------------------------------------------------\n\n  struct NodeDef\n  {\n    nb::object type;\n    std::optional<int> index;\n    std::optional<int> outer_index;\n    int num_attributes;\n    nb::object metadata;\n\n    NodeDef(nb::object type, std::optional<int> index, std::optional<int> outer_index, int num_attributes, nb::object metadata)\n        : type(type), index(index), outer_index(outer_index), num_attributes(num_attributes), metadata(metadata) {}\n\n    NodeDef with_no_outer_index() const\n    {\n      return NodeDef(type, index, std::nullopt, num_attributes, metadata);\n    }\n\n    NodeDef with_same_outer_index() const\n    {\n      return NodeDef(type, index, index, num_attributes, metadata);\n    }\n\n    bool __eq__(const nb::object &other_obj) const\n    {\n      if (!nb::isinstance<NodeDef>(other_obj))\n      {\n        return false;\n      }\n      NodeDef other = nb::cast<NodeDef>(other_obj);\n      return type.equal(other.type) && index == other.index && outer_index == other.outer_index && num_attributes == other.num_attributes && metadata.equal(other.metadata);\n    }\n\n    int __hash__() const\n    {\n      // return nb::hash(type) ^ nb::hash(nb::cast(index)) ^ nb::hash(nb::cast(outer_index)) ^ nb::hash(nb::cast(num_attributes)) ^ nb::hash(metadata);\n      return nb::hash(nb::make_tuple(type, index, outer_index, num_attributes, metadata));\n    }\n\n    nb::tuple __getstate__() const\n    {\n      return nb::make_tuple(type, index, outer_index, num_attributes, metadata);\n    }\n\n    static void __setstate__(NodeDef &nodedef, nb::tuple &t)\n    {\n      new (&nodedef) NodeDef(t[0], nb::cast<std::optional<int>>(t[1]), nb::cast<std::optional<int>>(t[2]), nb::cast<int>(t[3]), t[4]);\n    }\n  };\n\n  //---------------------------------------------------------------\n  // VariableDef\n  //---------------------------------------------------------------\n\n  struct VariableDef\n  {\n    nb::object type;\n    int index;\n    std::optional<int> outer_index;\n    nb::object metadata;\n\n    VariableDef(nb::object type, int index, std::optional<int> outer_index, nb::object metadata)\n        : type(type), index(index), outer_index(outer_index), metadata(metadata) {}\n\n    VariableDef with_no_outer_index() const\n    {\n      return VariableDef(type, index, std::nullopt, metadata);\n    }\n\n    VariableDef with_same_outer_index() const\n    {\n      return VariableDef(type, index, index, metadata);\n    }\n\n    bool __eq__(const nb::object &other_obj) const\n    {\n      if (!nb::isinstance<VariableDef>(other_obj))\n      {\n        return false;\n      }\n      VariableDef other = nb::cast<VariableDef>(other_obj);\n      return type.equal(other.type) && index == other.index && outer_index == other.outer_index && metadata.equal(other.metadata);\n    }\n\n    int __hash__() const\n    {\n      // return nb::hash(type) ^ nb::hash(nb::cast(index)) ^ nb::hash(nb::cast(outer_index)) ^ nb::hash(metadata);\n      return nb::hash(nb::make_tuple(type, index, outer_index, metadata));\n    }\n\n    nb::tuple __getstate__() const\n    {\n      return nb::make_tuple(type, index, outer_index, metadata);\n    }\n\n    static void __setstate__(VariableDef &variabledef, nb::tuple &t)\n    {\n      new (&variabledef) VariableDef(t[0], nb::cast<int>(t[1]), nb::cast<std::optional<int>>(t[2]), t[3]);\n    }\n  };\n\n  //---------------------------------------------------------------\n  // NodeRef\n  //---------------------------------------------------------------\n\n  struct NodeRef\n  {\n    int index;\n\n    NodeRef(int index)\n        : index(index) {}\n\n    bool __eq__(const nb::object &other_obj) const\n    {\n      if (!nb::isinstance<NodeRef>(other_obj))\n      {\n        return false;\n      }\n      NodeRef other = nb::cast<NodeRef>(other_obj);\n      return index == other.index;\n    }\n\n    int __hash__() const\n    {\n      return nb::hash(nb::cast(index));\n    }\n\n    nb::tuple __getstate__() const\n    {\n      return nb::make_tuple(index);\n    }\n\n    static void __setstate__(NodeRef &noderef, nb::tuple &t)\n    {\n      new (&noderef) NodeRef(nb::cast<int>(t[0]));\n    }\n  };\n\n  NB_MODULE(flaxlib_cpp, m)\n  {\n    nb::bind_map<IndexMap>(m, \"IndexMap\")\n        .def_static(\"from_refmap\", &indexmap_from_refmap);\n    nb::class_<flaxlib::RefMapKeysIterator>(m, \"RefMapKeysIterator\")\n        .def(\"__next__\", &flaxlib::RefMapKeysIterator::__next__);\n\n    nb::class_<flaxlib::RefMapItemsIterator>(m, \"RefMapItemsIterator\")\n        .def(\"__iter__\", &flaxlib::RefMapItemsIterator::__iter__)\n        .def(\"__next__\", &flaxlib::RefMapItemsIterator::__next__);\n\n    nb::class_<flaxlib::RefMap>(m, \"RefMap\")\n        .def(nb::init<>())\n        .def(nb::init<nb::object>(), nb::arg(\"iterable\"))\n        .def(\"update\", &flaxlib::RefMap::update)\n        .def_static(\"from_indexmap\", &refmap_from_indexmap)\n        .def(\"__getitem__\", &flaxlib::RefMap::__getitem__, nb::arg(\"key\").none())\n        .def(\"__setitem__\", &flaxlib::RefMap::__setitem__, nb::arg(\"key\").none(), nb::arg(\"value\"))\n        .def(\"__len__\", &flaxlib::RefMap::__len__)\n        .def(\"__contains__\", &flaxlib::RefMap::__contains__, nb::arg(\"key\").none())\n        .def(\"__iter__\", &flaxlib::RefMap::__iter__)\n        .def(\"items\", &flaxlib::RefMap::items)\n        .def(\"get\", &flaxlib::RefMap::get, nb::arg(\"key\").none(), nb::arg(\"default_value\").none());\n\n    nb::class_<flaxlib::NodeDef>(m, \"NodeDef\")\n        .def(nb::init<nb::object, std::optional<int>, std::optional<int>, int, nb::object>(),\n             nb::arg(\"type\"), nb::arg(\"index\").none(), nb::arg(\"outer_index\").none(), nb::arg(\"num_attributes\"), nb::arg(\"metadata\").none())\n        .def_prop_ro(\"type\", [](const flaxlib::NodeDef &n)\n                     { return n.type; })\n        .def_prop_ro(\"index\", [](const flaxlib::NodeDef &n)\n                     { return n.index; })\n        .def_prop_ro(\"outer_index\", [](const flaxlib::NodeDef &n)\n                     { return n.outer_index; })\n        .def_prop_ro(\"num_attributes\", [](const flaxlib::NodeDef &n)\n                     { return n.num_attributes; })\n        .def_prop_ro(\"metadata\", [](const flaxlib::NodeDef &n)\n                     { return n.metadata; })\n        .def(\"with_no_outer_index\", &flaxlib::NodeDef::with_no_outer_index)\n        .def(\"with_same_outer_index\", &flaxlib::NodeDef::with_same_outer_index)\n        .def(\"__eq__\", &flaxlib::NodeDef::__eq__, nb::arg().none())\n        .def(\"__hash__\", &flaxlib::NodeDef::__hash__)\n        .def(\"__getstate__\", &flaxlib::NodeDef::__getstate__)\n        .def(\"__setstate__\", &flaxlib::NodeDef::__setstate__);\n\n    nb::class_<flaxlib::VariableDef>(m, \"VariableDef\")\n        .def(nb::init<nb::object, int, std::optional<int>, nb::object>(),\n             nb::arg(\"type\"), nb::arg(\"index\"), nb::arg(\"outer_index\").none(), nb::arg(\"metadata\").none())\n        .def_prop_ro(\"type\", [](const flaxlib::VariableDef &n)\n                     { return n.type; })\n        .def_prop_ro(\"index\", [](const flaxlib::VariableDef &n)\n                     { return n.index; })\n        .def_prop_ro(\"outer_index\", [](const flaxlib::VariableDef &n)\n                     { return n.outer_index; })\n        .def_prop_ro(\"metadata\", [](const flaxlib::VariableDef &n)\n                     { return n.metadata; })\n        .def(\"with_no_outer_index\", &flaxlib::VariableDef::with_no_outer_index)\n        .def(\"with_same_outer_index\", &flaxlib::VariableDef::with_same_outer_index)\n        .def(\"__eq__\", &flaxlib::VariableDef::__eq__, nb::arg().none())\n        .def(\"__hash__\", &flaxlib::VariableDef::__hash__)\n        .def(\"__getstate__\", &flaxlib::VariableDef::__getstate__)\n        .def(\"__setstate__\", &flaxlib::VariableDef::__setstate__);\n\n    nb::class_<flaxlib::NodeRef>(m, \"NodeRef\")\n        .def(nb::init<int>(),\n             nb::arg(\"index\"))\n        .def_prop_ro(\"index\", [](const flaxlib::NodeRef &n)\n                     { return n.index; })\n        .def(\"__eq__\", &flaxlib::NodeRef::__eq__, nb::arg().none())\n        .def(\"__hash__\", &flaxlib::NodeRef::__hash__)\n        .def(\"__getstate__\", &flaxlib::NodeRef::__getstate__)\n        .def(\"__setstate__\", &flaxlib::NodeRef::__setstate__);\n  }\n} // namespace flaxlib"
  },
  {
    "path": "nnx.py",
    "content": "# Copyright 2026 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Standalone nnx module shim.\n\nThis module allows importing nnx directly as a standalone module:\n\n    import nnx\n\ninstead of:\n\n    from flax import nnx\n\nBoth imports refer to the exact same module, ensuring that\n`nnx.Module` and `flax.nnx.Module` are the same class in memory.\n\"\"\"\n\nfrom flax.nnx import *\nfrom flax.version import __version__ as __version__\nfrom flax import nnx as _nnx\n\n# Re-export the module's metadata\n__all__: list[str] = _nnx.__all__ if hasattr(_nnx, '__all__') else []"
  },
  {
    "path": "pylintrc",
    "content": "# This Pylint rcfile contains a best-effort configuration to uphold the\n# best-practices and style described in the Google Python style guide:\n#   https://google.github.io/styleguide/pyguide.html\n#\n# Its canonical open-source location is:\n#   https://google.github.io/styleguide/pylintrc\n\n[MASTER]\n\n# Add files or directories to the blacklist. They should be base names, not\n# paths.\nignore=third_party\n\n# Add files or directories matching the regex patterns to the blacklist. The\n# regex matches against base names, not paths.\nignore-patterns=\n\n# Pickle collected data for later comparisons.\npersistent=no\n\n# List of plugins (as comma separated values of python modules names) to load,\n# usually to register additional checkers.\nload-plugins=\n\n# Use multiple processes to speed up Pylint.\njobs=4\n\n# Allow loading of arbitrary C extensions. Extensions are imported into the\n# active Python interpreter and may run arbitrary code.\nunsafe-load-any-extension=no\n\n# A comma-separated list of package or module names from where C extensions may\n# be loaded. Extensions are loading into the active Python interpreter and may\n# run arbitrary code\nextension-pkg-whitelist=\n\n\n[MESSAGES CONTROL]\n\n# Only show warnings with the listed confidence levels. Leave empty to show\n# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED\nconfidence=\n\n# Enable the message, report, category or checker with the given id(s). You can\n# either give multiple identifier separated by comma (,) or put this option\n# multiple time (only on the command line, not in the configuration file where\n# it should appear only once). See also the \"--disable\" option for examples.\n#enable=\n\n# Disable the message, report, category or checker with the given id(s). You\n# can either give multiple identifiers separated by comma (,) or put this\n# option multiple times (only on the command line, not in the configuration\n# file where it should appear only once).You can also use \"--disable=all\" to\n# disable everything first and then reenable specific checks. For example, if\n# you want to run only the similarities checker, you can use \"--disable=all\n# --enable=similarities\". If you want to run only the classes checker, but have\n# no Warning level messages displayed, use\"--disable=all --enable=classes\n# --disable=W\"\ndisable=apply-builtin,\n        backtick,\n        bad-option-value,\n        buffer-builtin,\n        c-extension-no-member,\n        cmp-builtin,\n        cmp-method,\n        coerce-builtin,\n        coerce-method,\n        delslice-method,\n        div-method,\n        duplicate-code,\n        eq-without-hash,\n        execfile-builtin,\n        file-builtin,\n        filter-builtin-not-iterating,\n        fixme,\n        getslice-method,\n        global-statement,\n        hex-method,\n        idiv-method,\n        implicit-str-concat-in-sequence,\n        import-error,\n        import-self,\n        import-star-module-level,\n        input-builtin,\n        intern-builtin,\n        invalid-str-codec,\n        locally-disabled,\n        long-builtin,\n        long-suffix,\n        map-builtin-not-iterating,\n        metaclass-assignment,\n        next-method-called,\n        next-method-defined,\n        no-absolute-import,\n        no-else-break,\n        no-else-continue,\n        no-else-raise,\n        no-else-return,\n        no-member,\n        no-self-use,\n        nonzero-method,\n        oct-method,\n        old-division,\n        old-ne-operator,\n        old-octal-literal,\n        old-raise-syntax,\n        parameter-unpacking,\n        print-statement,\n        raising-string,\n        range-builtin-not-iterating,\n        raw_input-builtin,\n        rdiv-method,\n        reduce-builtin,\n        relative-import,\n        reload-builtin,\n        round-builtin,\n        setslice-method,\n        signature-differs,\n        standarderror-builtin,\n        suppressed-message,\n        sys-max-int,\n        too-few-public-methods,\n        too-many-ancestors,\n        too-many-arguments,\n        too-many-boolean-expressions,\n        too-many-branches,\n        too-many-instance-attributes,\n        too-many-locals,\n        too-many-public-methods,\n        too-many-return-statements,\n        too-many-statements,\n        trailing-newlines,\n        unichr-builtin,\n        unicode-builtin,\n        unpacking-in-except,\n        useless-else-on-loop,\n        useless-suppression,\n        using-cmp-argument,\n        xrange-builtin,\n        zip-builtin-not-iterating,\n\n\n[REPORTS]\n\n# Set the output format. Available formats are text, parseable, colorized, msvs\n# (visual studio) and html. You can also give a reporter class, eg\n# mypackage.mymodule.MyReporterClass.\noutput-format=text\n\n# Put messages in a separate file for each module / package specified on the\n# command line instead of printing them on stdout. Reports (if any) will be\n# written in a file name \"pylint_global.[txt|html]\". This option is deprecated\n# and it will be removed in Pylint 2.0.\nfiles-output=no\n\n# Tells whether to display a full report or only the messages\nreports=no\n\n# Python expression which should return a note less than 10 (10 is the highest\n# note). You have access to the variables errors warning, statement which\n# respectively contain the number of errors / warnings messages and the total\n# number of statements analyzed. This is used by the global evaluation report\n# (RP0004).\nevaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)\n\n# Template used to display messages. This is a python new-style format string\n# used to format the message information. See doc for all details\n#msg-template=\n\n\n[BASIC]\n\n# Good variable names which should always be accepted, separated by a comma\ngood-names=main,_\n\n# Bad variable names which should always be refused, separated by a comma\nbad-names=\n\n# Colon-delimited sets of names that determine each other's naming style when\n# the name regexes allow several styles.\nname-group=\n\n# Include a hint for the correct naming format with invalid-name\ninclude-naming-hint=no\n\n# List of decorators that produce properties, such as abc.abstractproperty. Add\n# to this list to register other decorators that produce valid properties.\nproperty-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl\n\n# Regular expression matching correct function names\nfunction-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$\n\n# Regular expression matching correct variable names\nvariable-rgx=^[a-z][a-z0-9_]*$\n\n# Regular expression matching correct constant names\nconst-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$\n\n# Regular expression matching correct attribute names\nattr-rgx=^_{0,2}[a-z][a-z0-9_]*$\n\n# Regular expression matching correct argument names\nargument-rgx=^[a-z][a-z0-9_]*$\n\n# Regular expression matching correct class attribute names\nclass-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$\n\n# Regular expression matching correct inline iteration names\ninlinevar-rgx=^[a-z][a-z0-9_]*$\n\n# Regular expression matching correct class names\nclass-rgx=^_?[A-Z][a-zA-Z0-9]*$\n\n# Regular expression matching correct module names\nmodule-rgx=^(_?[a-z][a-z0-9_]*|__init__)$\n\n# Regular expression matching correct method names\nmethod-rgx=(?x)^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$\n\n# Regular expression which should only match function or class names that do\n# not require a docstring.\nno-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$\n\n# Minimum line length for functions/classes that require docstrings, shorter\n# ones are exempt.\ndocstring-min-length=10\n\n\n[TYPECHECK]\n\n# List of decorators that produce context managers, such as\n# contextlib.contextmanager. Add to this list to register other decorators that\n# produce valid context managers.\ncontextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager\n\n# Tells whether missing members accessed in mixin class should be ignored. A\n# mixin class is detected if its name ends with \"mixin\" (case insensitive).\nignore-mixin-members=yes\n\n# List of module names for which member attributes should not be checked\n# (useful for modules/projects where namespaces are manipulated during runtime\n# and thus existing member attributes cannot be deduced by static analysis. It\n# supports qualified module names, as well as Unix pattern matching.\nignored-modules=\n\n# List of class names for which member attributes should not be checked (useful\n# for classes with dynamically set attributes). This supports the use of\n# qualified names.\nignored-classes=optparse.Values,thread._local,_thread._local\n\n# List of members which are set dynamically and missed by pylint inference\n# system, and so shouldn't trigger E1101 when accessed. Python regular\n# expressions are accepted.\ngenerated-members=\n\n\n[FORMAT]\n\n# Maximum number of characters on a single line.\nmax-line-length=80\n\n# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt\n# lines made too long by directives to pytype.\n\n# Regexp for a line that is allowed to be longer than the limit.\nignore-long-lines=(?x)(\n  ^\\s*(\\#\\ )?<?https?://\\S+>?$|\n  ^\\s*(from\\s+\\S+\\s+)?import\\s+.+$)\n\n# Allow the body of an if to be on the same line as the test if there is no\n# else.\nsingle-line-if-stmt=yes\n\n# List of optional constructs for which whitespace checking is disabled. `dict-\n# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\\n222: 2}.\n# `trailing-comma` allows a space between comma and closing bracket: (a, ).\n# `empty-line` allows space-only lines.\nno-space-check=\n\n# Maximum number of lines in a module\nmax-module-lines=99999\n\n# String used as indentation unit.  The internal Google style guide mandates 2\n# spaces.  Google's externaly-published style guide says 4, consistent with\n# PEP 8.  Here, we use 2 spaces, for conformity with many open-sourced Google\n# projects (like TensorFlow).\nindent-string='  '\n\n# Number of spaces of indent required inside a hanging  or continued line.\nindent-after-paren=4\n\n# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.\nexpected-line-ending-format=\n\n\n[MISCELLANEOUS]\n\n# List of note tags to take in consideration, separated by a comma.\nnotes=TODO\n\n\n[VARIABLES]\n\n# Tells whether we should check for unused import in __init__ files.\ninit-import=no\n\n# A regular expression matching the name of dummy variables (i.e. expectedly\n# not used).\ndummy-variables-rgx=^\\*{0,2}(_$|unused_|dummy_)\n\n# List of additional names supposed to be defined in builtins. Remember that\n# you should avoid to define new builtins when possible.\nadditional-builtins=\n\n# List of strings which can identify a callback function by name. A callback\n# name must start or end with one of those strings.\ncallbacks=cb_,_cb\n\n# List of qualified module names which can have objects that can redefine\n# builtins.\nredefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools\n\n\n[LOGGING]\n\n# Logging modules to check that the string format arguments are in logging\n# function parameter format\nlogging-modules=logging,absl.logging\n\n\n[SIMILARITIES]\n\n# Minimum lines number of a similarity.\nmin-similarity-lines=4\n\n# Ignore comments when computing similarities.\nignore-comments=yes\n\n# Ignore docstrings when computing similarities.\nignore-docstrings=yes\n\n# Ignore imports when computing similarities.\nignore-imports=no\n\n\n[SPELLING]\n\n# Spelling dictionary name. Available dictionaries: none. To make it working\n# install python-enchant package.\nspelling-dict=\n\n# List of comma separated words that should not be checked.\nspelling-ignore-words=\n\n# A path to a file that contains private dictionary; one word per line.\nspelling-private-dict-file=\n\n# Tells whether to store unknown words to indicated private dictionary in\n# --spelling-private-dict-file option instead of raising a message.\nspelling-store-unknown-words=no\n\n\n[IMPORTS]\n\n# Deprecated modules which should not be used, separated by a comma\ndeprecated-modules=regsub,\n                   TERMIOS,\n                   Bastion,\n                   rexec,\n                   sets\n\n# Create a graph of every (i.e. internal and external) dependencies in the\n# given file (report RP0402 must not be disabled)\nimport-graph=\n\n# Create a graph of external dependencies in the given file (report RP0402 must\n# not be disabled)\next-import-graph=\n\n# Create a graph of internal dependencies in the given file (report RP0402 must\n# not be disabled)\nint-import-graph=\n\n# Force import order to recognize a module as part of the standard\n# compatibility libraries.\nknown-standard-library=\n\n# Force import order to recognize a module as part of a third party library.\nknown-third-party=enchant, absl\n\n# Analyse import fallback blocks. This can be used to support both Python 2 and\n# 3 compatible code, which means that the block might have code that exists\n# only in one or another interpreter, leading to false positives when analysed.\nanalyse-fallback-blocks=no\n\n\n[CLASSES]\n\n# List of method names used to declare (i.e. assign) instance attributes.\ndefining-attr-methods=__init__,\n                      __new__,\n                      setUp\n\n# List of member names, which should be excluded from the protected access\n# warning.\nexclude-protected=_asdict,\n                  _fields,\n                  _replace,\n                  _source,\n                  _make\n\n# List of valid names for the first argument in a class method.\nvalid-classmethod-first-arg=cls,\n                            class_\n\n# List of valid names for the first argument in a metaclass class method.\nvalid-metaclass-classmethod-first-arg=mcs\n\n\n[EXCEPTIONS]\n\n# Exceptions that will emit a warning when being caught. Defaults to\n# \"Exception\"\novergeneral-exceptions=StandardError,\n                       Exception,\n                       BaseException\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools\", \"setuptools-scm\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"flax\"\nrequires-python = \">=3.11\"\ndescription = \"Flax: A neural network library for JAX designed for flexibility\"\nkeywords = []\nauthors = [\n    {name = \"Flax team\", email = \"flax-dev@google.com\"},\n]\ndependencies = [\n    \"numpy>=1.23.2\",\n    # keep in sync with jax-version in .github/workflows/build.yml\n    \"jax>=0.8.1\",\n    \"msgpack\",\n    \"optax\",\n    \"orbax-checkpoint\",\n    \"tensorstore\",\n    \"rich>=11.1\",\n    \"typing_extensions>=4.2\",\n    \"PyYAML>=5.4.1\",\n    \"treescope>=0.1.7\",\n    \"orbax-export>=0.0.8\",\n]\nclassifiers = [\n    \"Development Status :: 3 - Alpha\",\n    \"Intended Audience :: Developers\",\n    \"Intended Audience :: Science/Research\",\n    \"License :: OSI Approved :: Apache Software License\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Topic :: Scientific/Engineering :: Artificial Intelligence\",\n]\ndynamic = [\"version\", \"readme\"]\n\n[project.optional-dependencies]\ntesting = [\n    \"clu\",\n    \"clu<=0.0.9; python_version<'3.10'\",\n    \"einops\",\n    \"gymnasium[atari]; python_version<'3.14'\",\n    \"jaxlib\",\n    \"jaxtyping\",\n    \"jraph>=0.0.6dev0\",\n    \"ml-collections\",\n    \"mypy\",\n    \"opencv-python\",\n    # Set protobuf version for python 3.13+ to prevent error in\n    # examples/mnist/train_test.py::TrainTest::test_train_and_evaluate\n    # Failed to construct dataset \"mnist\", builder_kwargs \"{}\": Value out of range: 11594722\n    \"protobuf<6; python_version>='3.13'\",\n    \"pytest\",\n    \"pytest-cov\",\n    \"pytest-custom_exit_code\",\n    \"pytest-xdist\",\n    \"pytype\",\n    # WMT/LM1B examples\n    \"sentencepiece==0.2.0\",  # Segfault bug in 0.2.1\n    \"tensorflow_text>=2.11.0; platform_system!='Darwin' and python_version < '3.13'\",\n    \"tensorflow_datasets\",\n    \"tensorflow>=2.12.0; python_version<'3.13'\", # to fix Numpy np.bool8 deprecation error\n    \"tensorflow>=2.20.0; python_version>='3.13' and python_version<'3.14'\",\n    # Temporary fix for https://github.com/google/flax/issues/5143\n    \"keras<3.13\",\n    \"torch\",\n    \"treescope>=0.1.1; python_version>='3.10'\",\n    \"cloudpickle>=3.0.0\",\n    \"ale-py>=0.10.2; python_version<'3.14'\",\n]\ndocs = [\n    \"sphinx==6.2.1\",\n    \"sphinx-book-theme\",\n    \"Pygments>=2.6.1\",\n    \"ipykernel\",\n    \"tqdm==4.67.1\",\n    \"myst_nb\",\n    \"nbstripout\",\n    \"recommonmark\",\n    \"ipython_genutils\",\n    \"sphinx-design\",\n    \"jupytext==1.13.8\",\n    \"dm-haiku>=0.0.14\",\n    \"docutils\",\n    # The next packages are for notebooks.\n    \"matplotlib\",\n    \"scikit-learn\",\n    # The next packages are used in testcode blocks.\n    \"ml_collections\",\n    # notebooks\n    \"einops\",\n    \"kagglehub>=0.3.3\",\n    \"ipywidgets>=8.1.5\",\n]\ndev = [\n    \"nanobind>=2.5.0\",\n    \"pre-commit>=3.8.0\",\n    \"scikit-build-core[pyproject]>=0.11.0\",\n]\n\n[project.urls]\nhomepage = \"https://github.com/google/flax\"\n\n[tool.setuptools.dynamic]\nreadme = {file = [\"README.md\"], content-type = \"text/markdown\"}\nversion = {attr = \"flax.version.__version__\"}\n\n[tool.setuptools.packages.find]\ninclude = [\"flax*\"]\n\n[tool.setuptools.package-data]\nflax = [\"*py.typed\"]\n\n[tool.yapf]\nbased_on_style = \"yapf\"\n\n[tool.pytype]\n# TODO(levskaya): figure out why we get pyi-error from flax's root __init__.py\n# could be a pytype bug.\ndisable = \"pyi-error\"\n\n[tool.mypy]\nshow_error_codes = true\nno_implicit_optional = true\ndisable_error_code = \"attr-defined\"\n\n[[tool.mypy.overrides]]\nmodule = [\n    \"tensorflow.*\",\n    \"tensorboard.*\",\n    \"absl.*\",\n    \"jax.*\",\n    \"rich.*\",\n    \"flax.*\",\n    \"jaxlib.cuda.*\",\n    \"jaxlib.cpu.*\",\n    \"msgpack\",\n    \"numpy.*\",\n    \"optax.*\",\n    \"orbax.*\",\n    \"opt_einsum.*\",\n    \"scipy.*\",\n    \"libtpu.*\",\n    \"jaxlib.mlir.*\",\n    \"yaml\",\n]\nignore_missing_imports = true\ndisable_error_code = \"annotation-unchecked\"\n# exclude nnx examples\n[[tool.mypy.overrides]]\nmodule = \"flax.nnx.examples.*\"\nignore_errors = true\n\n[tool.pytest.ini_options]\nfilterwarnings = [\n    # By default error out on any warnings.\n    \"error\",\n    # Jax warning when no gpu/tpu found.\n\t\"ignore:No GPU/TPU found, falling back to CPU.*:UserWarning\",\n    # traverse_util.Traversal will be removed soon.\n    \"ignore:`flax.traverse_util.Traversal` will be deprecated.*:DeprecationWarning\",\n    # Deprecated legacy checkpoint - just want to keep the tests running for a while\n    \"ignore:Flax Checkpointing will soon be deprecated in favor of Orbax.*:DeprecationWarning\",\n    # DeprecationWarning: The inputs_kv arg will be deprecated soon. Use inputs_k and inputs_v instead.\n    \"ignore:.*The inputs_kv arg will be deprecated soon. Use inputs_k and inputs_v instead.*:DeprecationWarning\",\n    # DeprecationWarning: the function signature of MultiHeadDotProductAttention's `__call__` method has changed\n    \"ignore:.*the function signature of MultiHeadDotProductAttention's `__call__` method has changed.*:DeprecationWarning\",\n    # DeprecationWarning: ml_dtypes.float8_e4m3b11 is deprecated.\n    \"ignore:.*ml_dtypes.float8_e4m3b11 is deprecated.*:DeprecationWarning\",\n    # pytest-cov uses a deprecated feature of pytest-xdist. (2023-11-06)\n    \"ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning\",\n    # DeprecationWarning: jax.random.KeyArray is deprecated.\n    \"ignore:.*jax.random.KeyArray is deprecated.*:DeprecationWarning\",\n    # DeprecationWarning: jax.core.Shape is deprecated.\n    \"ignore:.*jax.core.Shape is deprecated.*:DeprecationWarning\",\n    # DeprecationWarning: pkg_resources is deprecated as an API.\n    \"ignore:.*pkg_resources is deprecated as an API.*:DeprecationWarning\",\n    # DeprecationWarning: Deprecated call to `pkg_resources.declare_namespace('google')`.\n    \"ignore:.*Deprecated call to.*pkg_resources.declare_namespace.*:DeprecationWarning\",\n    # jax.xla_computation is deprecated but TF still uses it.\n    \"ignore:.*jax.xla_computation is deprecated.*:DeprecationWarning\",\n    # Orbax warnings inside deprecated `flax.training` package.\n    \"ignore:.*Couldn't find sharding info under RestoreArgs.*:UserWarning\",\n    # RuntimeWarning: invalid value encountered in cast\n    \"ignore:.*invalid value encountered in cast.*:RuntimeWarning\",\n    # RuntimeWarning: divide by zero encountered in equal/not_equal\n    \"ignore:.*divide by zero encountered in.*:RuntimeWarning\",\n    # DeprecationWarning: numpy.core is deprecated\n    \"ignore:.*numpy.core is deprecated.*:DeprecationWarning\",\n    # DeprecationWarning: shape requires ndarray or scalar arguments\n    \"ignore:.*shape requires ndarray or scalar arguments.*:DeprecationWarning\",\n    # UserWarning: Sharding info not provided when restoring\n    \"ignore:.*Sharding info not provided when restoring.*:UserWarning\",\n    # UserWarning: pkg_resources is deprecated as an API.\n    \"ignore:.*pkg_resources is deprecated as an API.*:UserWarning\",\n    # DeprecationWarning: 'Data' is deprecated, please replace:\n    \"ignore:.*[Data|Static]' is deprecated, please replace.*:DeprecationWarning\",\n    # DeprecationWarning: Implicit conversion of an array to a dtype is deprecated; rather than dtype=arr use dtype=arr.dtype.\n    \"ignore:.*Implicit conversion of an array to a dtype is deprecated; rather than dtype=arr use dtype=arr.dtype.*:DeprecationWarning\",\n    # DeprecationWarning: Setting `jax_pmap_shmap_merge` is deprecated in JAX v0.9.0 and will be removed in JAX v0.10.0\n    \"ignore:.*Setting `jax_pmap_shmap_merge` is deprecated in JAX.*:DeprecationWarning\",\n]\n\n[tool.coverage.report]\nexclude_lines = [\n    \"@abc.abstractmethod\",\n    \"raise NotImplementedError\",\n]\n\n[tool.pyink]\npyink-indentation = 2\npyink-use-majority-quotes = true\nline-length = 80\npreview = true\n\n[tool.ruff]\n# Exclude a variety of commonly ignored directories.\nexclude = [\n  \"__init__.py\",\n  \"activation.py\",\n  \"partitioning.py\",\n  \"flax/core/variables.py\",\n  \"examples/\",\n]\n\nline-length = 80\nindent-width = 2\n\n[tool.ruff.lint]\n# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`)  codes by default.\nselect = [\"F401\"]\nignore = []\n# Allow fix for all enabled rules (when `--fix`) is provided.\n# Full list of rules: https://docs.astral.sh/ruff/rules/\nfixable = [\"ALL\"]\nunfixable = []\n\n[tool.ruff.format]\nindent-style = \"space\"\nquote-style = \"single\"\n\n\n[tool.uv]\n# Ignore uv.lock and always upgrade the package to the latest\nupgrade-package = [\"jax\", \"jaxlib\", \"orbax-checkpoint\"]\n"
  },
  {
    "path": "tests/checkpoints_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.training.checkpoints.\"\"\"\n\nimport copy\nimport os\nimport pathlib\nfrom typing import Any\n\nimport jax\nimport numpy as np\nimport orbax.checkpoint as orbax\nfrom absl.testing import absltest, parameterized\nfrom jax import numpy as jnp\n\nfrom flax import config, core, errors, io, struct\nfrom flax import linen as nn\nfrom flax.training import checkpoints\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nPyTree = Any\n\n\ndef check_eq(xs, ys):\n  return jax.tree_util.tree_all(\n    jax.tree_util.tree_map(np.testing.assert_allclose, xs, ys)\n  )\n\n\ndef shuffle(l):\n  \"\"\"Functional shuffle.\"\"\"\n  l = copy.copy(l)\n  np.random.shuffle(l)\n  return l\n\n\nclass Inner(nn.Module):\n  \"\"\"Inner class based on nn.\"\"\"\n\n  @nn.compact\n  def __call__(self, x):\n    x = nn.Conv(10, (2, 2))(x)\n    x = nn.normalization.BatchNorm(True)(x)\n    return x\n\n\nclass Model(nn.Module):\n  \"\"\"Simple model based on nn.\"\"\"\n\n  @nn.compact\n  def __call__(self, inputs):\n    x = nn.Conv(10, (2, 2))(inputs)\n    x = Inner()(x)\n    x = x.reshape([x.shape[0], -1])\n    x = nn.normalization.BatchNorm(True)(x)\n    x = nn.Dense(10)(x)\n    x = nn.log_softmax(x)\n    return x\n\n\n@struct.dataclass\nclass CustomDC:\n  foo: Any\n  bar: Any\n\n\nclass CheckpointsTest(parameterized.TestCase):\n  def setUp(self):\n    super().setUp()\n    config.update('flax_use_orbax_checkpointing', False)  # default value\n\n  def test_naturalsort(self):\n    np.random.seed(0)\n    tests = [\n      ['file_1', 'file_2', 'file_10', 'file_11', 'file_21'],\n      ['file_0.001', 'file_0.01', 'file_0.1', 'file_1'],\n      ['file_-3.0', 'file_-2', 'file_-1', 'file_0.0'],\n      ['file_1e1', 'file_1.0e2', 'file_1e3', 'file_1.0e4'],\n      ['file_1', 'file_2', 'file_9', 'file_1.0e1', 'file_11'],\n    ]\n    for test in tests:\n      self.assertEqual(test, checkpoints.natural_sort(shuffle(test)))\n\n  def test_safe_normpath(self):\n    tests = ['./a/b/c', '/a//b/c', '/a/../b/c', 'a/b/./c', 'gs://a//b/c']\n    expected = ['a/b/c', '/a/b/c', '/b/c', 'a/b/c', 'gs://a/b/c']\n    for test, expect in zip(tests, expected):\n      self.assertEqual(expect, checkpoints.safe_normpath(test))\n\n  @parameterized.parameters({'use_orbax': True}, {'use_orbax': False})\n  def test_save_restore_checkpoints(self, use_orbax):\n    config.update('flax_use_orbax_checkpointing', use_orbax)\n    tmp_dir = pathlib.Path(self.create_tempdir().full_path)\n    test_object0 = {\n      'a': np.array([0, 0, 0], np.int32),\n      'b': np.array([0, 0, 0], np.int32),\n    }\n    test_object1 = {\n      'a': np.array([1, 2, 3], np.int32),\n      'b': np.array([1, 1, 1], np.int32),\n    }\n    test_object2 = {\n      'a': np.array([4, 5, 6], np.int32),\n      'b': np.array([2, 2, 2], np.int32),\n    }\n    new_object = checkpoints.restore_checkpoint(\n      tmp_dir, test_object0, prefix='test_'\n    )\n    check_eq(new_object, test_object0)\n    checkpoints.save_checkpoint(\n      tmp_dir, test_object1, 0, prefix='test_', keep=1\n    )\n    self.assertIn('test_0', os.listdir(tmp_dir))\n    new_object = checkpoints.restore_checkpoint(\n      tmp_dir, test_object0, prefix='test_'\n    )\n    check_eq(new_object, test_object1)\n    checkpoints.save_checkpoint(\n      tmp_dir, test_object1, 1, prefix='test_', keep=1\n    )\n    checkpoints.save_checkpoint(\n      tmp_dir, test_object2, 2, prefix='test_', keep=1\n    )\n    new_object = checkpoints.restore_checkpoint(\n      tmp_dir, test_object0, prefix='test_'\n    )\n    check_eq(new_object, test_object2)\n    checkpoints.save_checkpoint(\n      tmp_dir, test_object2, 3, prefix='test_', keep=2\n    )\n    checkpoints.save_checkpoint(\n      tmp_dir, test_object1, 4, prefix='test_', keep=2\n    )\n    new_object = checkpoints.restore_checkpoint(\n      tmp_dir, test_object0, prefix='test_'\n    )\n    check_eq(new_object, test_object1)\n    new_object = checkpoints.restore_checkpoint(\n      tmp_dir, test_object0, step=3, prefix='test_'\n    )\n    check_eq(new_object, test_object2)\n\n    # Restore with a specific checkpoint path, not the directory path.\n    new_object = checkpoints.restore_checkpoint(\n      os.path.join(tmp_dir, 'test_3'), test_object0\n    )\n    check_eq(new_object, test_object2)\n    # If a specific path is specified, but it does not exist, the same behavior\n    # as when a directory is empty should apply: the target is returned\n    # unchanged.\n    new_object = checkpoints.restore_checkpoint(\n      os.path.join(tmp_dir, 'test_not_there'), test_object0\n    )\n    check_eq(new_object, test_object0)\n    with self.assertRaises(ValueError):\n      checkpoints.restore_checkpoint(\n        tmp_dir, test_object0, step=5, prefix='test_'\n      )\n\n  @parameterized.parameters({'use_orbax': True}, {'use_orbax': False})\n  def test_overwrite_checkpoints(self, use_orbax):\n    config.update('flax_use_orbax_checkpointing', use_orbax)\n    overwrite_error = ValueError if use_orbax else errors.InvalidCheckpointError\n    tmp_dir = self.create_tempdir().full_path\n    test_object0 = {'a': np.array([0, 0, 0], np.int32)}\n    test_object = {'a': np.array([1, 2, 3], np.int32)}\n\n    checkpoints.save_checkpoint(tmp_dir, test_object0, 0, keep=1)\n    with self.assertRaises(overwrite_error):\n      checkpoints.save_checkpoint(tmp_dir, test_object, 0, keep=1)\n    checkpoints.save_checkpoint(tmp_dir, test_object, 0, keep=1, overwrite=True)\n    new_object = checkpoints.restore_checkpoint(tmp_dir, test_object0)\n    check_eq(new_object, test_object)\n\n    non_norm_dir_path = tmp_dir + '//'\n    checkpoints.save_checkpoint(non_norm_dir_path, test_object, 4, keep=1)\n    new_object = checkpoints.restore_checkpoint(non_norm_dir_path, test_object0)\n    check_eq(new_object, test_object)\n\n  @parameterized.parameters(\n    {'use_orbax': True, 'keep_every_n_steps': None},\n    {'use_orbax': False, 'keep_every_n_steps': 7},\n  )\n  def test_keep(self, use_orbax, keep_every_n_steps):\n    config.update('flax_use_orbax_checkpointing', use_orbax)\n    tmp_dir = self.create_tempdir().full_path\n    test_object = {'a': np.array([1, 2, 3], np.int32)}\n    steps_start = 17\n    steps_end = 37\n    keep = 3\n    increment = 5\n\n    for step in range(steps_start, steps_end, increment):\n      checkpoints.save_checkpoint(\n        tmp_dir,\n        test_object,\n        step=step,\n        keep=keep,\n        keep_every_n_steps=keep_every_n_steps,\n      )\n\n    last_checkpoint = -float('inf')\n    for step in range(steps_start, steps_end, increment):\n      if ((steps_end - step) / increment <= keep) or (\n        keep_every_n_steps and (step - last_checkpoint) >= keep_every_n_steps\n      ):\n        restored = checkpoints.restore_checkpoint(\n          tmp_dir, target=None, step=step\n        )\n        check_eq(restored, test_object)\n        last_checkpoint = step\n      else:\n        with self.assertRaises(ValueError):\n          checkpoints.restore_checkpoint(tmp_dir, target=None, step=step)\n\n  @parameterized.parameters({'use_orbax': True}, {'use_orbax': False})\n  def test_save_restore_checkpoints_w_float_steps(self, use_orbax):\n    config.update('flax_use_orbax_checkpointing', use_orbax)\n    tmp_dir = self.create_tempdir().full_path\n    test_object0 = {\n      'a': np.array([0, 0, 0], np.int32),\n      'b': np.array([0, 0, 0], np.int32),\n    }\n    test_object1 = {\n      'a': np.array([1, 2, 3], np.int32),\n      'b': np.array([1, 1, 1], np.int32),\n    }\n    test_object2 = {\n      'a': np.array([4, 5, 6], np.int32),\n      'b': np.array([2, 2, 2], np.int32),\n    }\n    checkpoints.save_checkpoint(\n      tmp_dir, test_object1, 0.0, prefix='test_', keep=1\n    )\n    self.assertIn('test_0.0', os.listdir(tmp_dir))\n    new_object = checkpoints.restore_checkpoint(\n      tmp_dir, test_object0, prefix='test_'\n    )\n    check_eq(new_object, test_object1)\n    checkpoints.save_checkpoint(\n      tmp_dir, test_object1, 2.0, prefix='test_', keep=1\n    )\n    checkpoints.save_checkpoint(\n      tmp_dir, test_object2, 3.0, prefix='test_', keep=2\n    )\n    self.assertIn('test_3.0', os.listdir(tmp_dir))\n    self.assertIn('test_2.0', os.listdir(tmp_dir))\n    check_eq(new_object, test_object1)\n\n  @parameterized.parameters({'use_orbax': True}, {'use_orbax': False})\n  def test_save_restore_checkpoints_target_none(self, use_orbax):\n    config.update('flax_use_orbax_checkpointing', use_orbax)\n    tmp_dir = self.create_tempdir().full_path\n    test_object0 = {\n      'a': np.array([0, 0, 0], np.int32),\n      'b': np.array([0, 0, 0], np.int32),\n    }\n    # Target pytree is a dictionary, so it's equal to a restored state_dict.\n    checkpoints.save_checkpoint(tmp_dir, test_object0, 0)\n    new_object = checkpoints.restore_checkpoint(tmp_dir, target=None)\n    check_eq(new_object, test_object0)\n    # Target pytree it's a tuple, check the expected state_dict is recovered.\n    test_object1 = (\n      np.array([0, 0, 0], np.int32),\n      np.array([1, 1, 1], np.int32),\n    )\n    checkpoints.save_checkpoint(tmp_dir, test_object1, 1)\n    new_object = checkpoints.restore_checkpoint(tmp_dir, target=None)\n    expected_new_object = {str(k): v for k, v in enumerate(test_object1)}\n    check_eq(new_object, expected_new_object)\n\n  @parameterized.parameters({'use_orbax': True}, {'use_orbax': False})\n  def test_save_restore_checkpoints_target_singular(self, use_orbax):\n    config.update('flax_use_orbax_checkpointing', use_orbax)\n    tmp_dir = self.create_tempdir().full_path\n    test_object0 = np.array([0, 0, 0], np.int32)\n    test_object1 = np.array([1, 1, 1], np.int32)\n    # Orbax backend returns error if target is singular. Orbax user need to use\n    # ArrayCheckpointHandler instead.\n    if use_orbax:\n      with self.assertRaises(ValueError):\n        checkpoints.save_checkpoint(tmp_dir, test_object1, 0)\n    else:\n      checkpoints.save_checkpoint(tmp_dir, test_object1, 0)\n      new_object = checkpoints.restore_checkpoint(tmp_dir, target=None)\n      check_eq(new_object, test_object1)\n      checkpoints.save_checkpoint(tmp_dir, test_object0, 1)\n      new_object = checkpoints.restore_checkpoint(tmp_dir, target=test_object1)\n      check_eq(new_object, test_object0)\n\n  @parameterized.parameters({'use_orbax': True}, {'use_orbax': False})\n  def test_save_restore_checkpoints_target_empty(self, use_orbax):\n    config.update('flax_use_orbax_checkpointing', use_orbax)\n    tmp_dir = self.create_tempdir().full_path\n    test_object0 = {}\n    test_object1 = []\n    # Orbax returns ValueError if the target is empty, but legacy Flax doesn't.\n    if use_orbax:\n      with self.assertRaises(ValueError):\n        checkpoints.save_checkpoint(tmp_dir, test_object1, 0)\n    else:\n      checkpoints.save_checkpoint(tmp_dir, test_object1, 0)\n      new_object = checkpoints.restore_checkpoint(tmp_dir, target=None)\n      check_eq(new_object, test_object0)\n      checkpoints.save_checkpoint(tmp_dir, test_object0, 1)\n      new_object = checkpoints.restore_checkpoint(tmp_dir, target=test_object1)\n      check_eq(new_object, test_object1)\n\n  def test_async_save_checkpoints(self):\n    tmp_dir = pathlib.Path(self.create_tempdir().full_path)\n    test_object0 = {\n      'a': np.array([0, 0, 0], np.int32),\n      'b': np.array([0, 0, 0], np.int32),\n    }\n    test_object1 = {\n      'a': np.random.normal(size=(1000, 1000)),\n      'b': np.random.normal(size=(1000, 1000)),\n    }\n    test_object2 = {\n      'a': np.random.normal(size=(1000, 1000)),\n      'b': np.random.normal(size=(1000, 1000)),\n    }\n    test_object3 = {\n      'a': np.random.normal(size=(1000, 1000)),\n      'b': np.random.normal(size=(1000, 1000)),\n    }\n    am = checkpoints.AsyncManager()\n    checkpoints.save_checkpoint(\n      tmp_dir, test_object1, 0, prefix='test_', keep=1, async_manager=am\n    )\n    # Hard-wait the write to be done, then check its content.\n    am.save_future.result()\n    self.assertIn('test_0', os.listdir(tmp_dir))\n    new_object = checkpoints.restore_checkpoint(\n      tmp_dir, test_object1, prefix='test_'\n    )\n    check_eq(new_object, test_object1)\n    # Check two consecutive saves happen in the right order.\n    checkpoints.save_checkpoint(\n      tmp_dir, test_object2, 1, prefix='test_', keep=1, async_manager=am\n    )\n    checkpoints.save_checkpoint(\n      tmp_dir, test_object3, 2, prefix='test_', keep=1, async_manager=am\n    )\n    am.save_future.result()\n    self.assertIn('test_2', os.listdir(tmp_dir))\n    new_object = checkpoints.restore_checkpoint(\n      tmp_dir, test_object1, prefix='test_'\n    )\n    check_eq(new_object, test_object3)\n\n  def test_last_checkpoint(self):\n    tmp_dir = pathlib.Path(self.create_tempdir().full_path)\n    with io.GFile(os.path.join(tmp_dir, 'test_tmp'), 'w') as f:\n      f.write('test_tmp')\n    io.makedirs(os.path.join(tmp_dir, 'test_tmp_gda'))\n    self.assertEqual(checkpoints.latest_checkpoint(tmp_dir, 'test_'), None)\n\n    with io.GFile(os.path.join(tmp_dir, 'test_0'), 'w') as f:\n      f.write('test_0')\n    io.makedirs(os.path.join(tmp_dir, 'test_0_gda'))\n    self.assertEqual(\n      checkpoints.latest_checkpoint(tmp_dir, 'test_'),\n      os.path.join(tmp_dir, 'test_0'),\n    )\n\n    with io.GFile(os.path.join(tmp_dir, 'test_10'), 'w') as f:\n      f.write('test_10')\n    self.assertEqual(\n      checkpoints.latest_checkpoint(tmp_dir, 'test_'),\n      os.path.join(tmp_dir, 'test_10'),\n    )\n    self.assertEqual(checkpoints.latest_checkpoint(tmp_dir, 'ckpt_'), None)\n\n    path = f'orbaxtest_{orbax.utils.TMP_DIR_SUFFIX}_10'\n    with io.GFile(os.path.join(tmp_dir, path), 'w') as f:\n      f.write('orbaxtest_10')\n    self.assertIsNone(checkpoints.latest_checkpoint(tmp_dir, 'orbaxtest_'))\n\n  @parameterized.parameters(\n    {'step_type': int, 'steps': [1, 5, 112]},\n    {'step_type': float, 'steps': [1.0, 4.5, 5.6]},\n  )\n  def test_available_steps(self, step_type, steps):\n    tmp_dir = pathlib.Path(self.create_tempdir().full_path)\n    with io.GFile(os.path.join(tmp_dir, 'test_tmp'), 'w') as f:\n      f.write('test_tmp')\n    io.makedirs(os.path.join(tmp_dir, 'test_tmp_gda'))\n\n    for step in steps:\n      with io.GFile(os.path.join(tmp_dir, 'test_' + str(step)), 'w') as f:\n        f.write('test_' + str(step))\n      io.makedirs(os.path.join(tmp_dir, 'test_' + str(step) + '_gda'))\n\n    self.assertEqual(\n      checkpoints.available_steps(tmp_dir, 'test_', step_type=step_type),\n      steps,\n    )\n\n  @parameterized.parameters({'use_orbax': True}, {'use_orbax': False})\n  def test_complex_pytree(self, use_orbax):\n    config.update('flax_use_orbax_checkpointing', use_orbax)\n    tmp_dir = self.create_tempdir().full_path\n    to_save = [\n      CustomDC(foo=12, bar=core.freeze({'x': jnp.array((1, 4))})),\n      np.array((2, 3)),\n    ]\n    target = [\n      CustomDC(foo=0, bar=core.freeze({'x': jnp.array((0, 0))})),\n      np.array((0, 0)),\n    ]\n    checkpoints.save_checkpoint(tmp_dir, to_save, 0)\n    restored = checkpoints.restore_checkpoint(tmp_dir, target=target)\n    check_eq(restored, to_save)\n\n  # restore_checkpoint can automatically restore either orbax or legacy files.\n  def test_auto_restore(self):\n    tmp_dir = self.create_tempdir().full_path\n    to_save = [CustomDC(foo=12, bar={'x': jnp.array((1, 4))}), np.array((2, 3))]\n    target = [CustomDC(foo=0, bar={'x': jnp.array((0, 0))}), np.array((0, 0))]\n    # Store an orbax ckpt\n    config.update('flax_use_orbax_checkpointing', True)\n    checkpoints.save_checkpoint(tmp_dir, to_save, 0, prefix='test_')\n    # And a legacy ckpt\n    config.update('flax_use_orbax_checkpointing', False)\n    checkpoints.save_checkpoint(tmp_dir, to_save, 1, prefix='test_', keep=2)\n\n    # Both gets restored with same API.\n    restored = checkpoints.restore_checkpoint(\n      os.path.join(tmp_dir, 'test_0'), target=target\n    )\n    check_eq(restored, to_save)\n    restored = checkpoints.restore_checkpoint(\n      os.path.join(tmp_dir, 'test_1'), target=target\n    )\n    check_eq(restored, to_save)\n\n  @parameterized.parameters({'use_orbax': True}, {'use_orbax': False})\n  def test_smaller_target(self, use_orbax):\n    config.update('flax_use_orbax_checkpointing', use_orbax)\n    tmp_dir = self.create_tempdir().full_path\n    to_save = {'a': jnp.ones((16, 256, 1024))}\n    target = {'a': jnp.zeros((2, 3))}\n\n    checkpoints.save_checkpoint(tmp_dir, to_save, 0, keep=1)\n    new_object = checkpoints.restore_checkpoint(tmp_dir, target)\n    check_eq(new_object, to_save)\n\n  def test_convert_pre_linen(self):\n    params = checkpoints.convert_pre_linen(\n      {\n        'mod_0': {\n          'submod1_0': {},\n          'submod2_1': {},\n          'submod1_2': {},\n        },\n        'mod2_2': {'submod2_2_0': {}},\n        'mod2_11': {'submod2_11_0': {}},\n        'mod2_1': {'submod2_1_0': {}},\n      }\n    )\n    self.assertDictEqual(\n      core.unfreeze(params),\n      {\n        'mod_0': {\n          'submod1_0': {},\n          'submod1_1': {},\n          'submod2_0': {},\n        },\n        'mod2_0': {'submod2_1_0': {}},\n        'mod2_1': {'submod2_2_0': {}},\n        'mod2_2': {'submod2_11_0': {}},\n      },\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/colab_tpu_jax_version.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# JAX/jaxlib should be both 0.3.25\\n\",\n    \"# because newer JAX versions are *not* supported on TPU runtimes\\n\",\n    \"# Flax should be included in a ƒresh kernel.\\n\",\n    \"!pip freeze | egrep 'jax|flax'\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# should show 8 TPU devices\\n\",\n    \"import jax, jax.tools.colab_tpu\\n\",\n    \"jax.tools.colab_tpu.setup_tpu()\\n\",\n    \"jax.devices()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# sometimes it's necessary to install additional packages; but we need to keep\\n\",\n    \"# JAX/jaxlib versions pinned to what is supported by the TPU runtime...\\n\",\n    \"!pip install jax==0.3.25 jaxlib==0.3.25 flax\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# in case JAX version has changed after the '!pip install`, below command should\\n\",\n    \"# show the offending packages\\n\",\n    \"!pip install -qq pipdeptree\\n\",\n    \"!pipdeptree -w silence -r -p jax\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# it's possible to get dependency tree without installing packages, but this\\n\",\n    \"# usually takes some 2-3 minutes...\\n\",\n    \"!pip install -qq pipgrip\\n\",\n    \"!pipgrip --tree flax==0.6.4\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"accelerator\": \"TPU\",\n  \"gpuClass\": \"standard\",\n  \"language_info\": {\n   \"name\": \"python\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "tests/configurations_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom unittest import mock\n\nfrom absl.testing import absltest\n\nfrom flax.configurations import bool_flag, config\n\n\nclass MyTestCase(absltest.TestCase):\n  def setUp(self):\n    super().setUp()\n    self.enter_context(mock.patch.object(config, '_values', {}))\n    self._flag = bool_flag('flax_test', default=False, help='Just a test flag.')\n\n  def test_duplicate_flag(self):\n    with self.assertRaisesRegex(RuntimeError, 'already defined'):\n      bool_flag(self._flag.name, default=False, help='Another test flag.')\n\n  def test_default(self):\n    self.assertFalse(self._flag.value)\n    self.assertFalse(config.flax_test)\n\n  def test_typed_update(self):\n    config.update(self._flag, True)\n    self.assertTrue(self._flag.value)\n    self.assertTrue(config.flax_test)\n\n  def test_untyped_update(self):\n    config.update(self._flag.name, True)\n    self.assertTrue(self._flag.value)\n    self.assertTrue(config.flax_test)\n\n  def test_update_unknown_flag(self):\n    with self.assertRaisesRegex(LookupError, 'Unrecognized config option'):\n      config.update('unknown', True)\n\n  def test_temp_flip(self):\n    self.assertFalse(self._flag.value)\n    with config.temp_flip_flag('test', True):\n      self.assertTrue(self._flag.value)\n    self.assertFalse(self._flag.value)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/core_frozen_dict_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax\nfrom absl.testing import absltest, parameterized\n\nfrom flax.core import FrozenDict, copy, freeze, pop, pretty_repr, unfreeze\n\n\nclass FrozenDictTest(parameterized.TestCase):\n  def test_frozen_dict_copies(self):\n    xs = {'a': 1, 'b': {'c': 2}}\n    frozen = freeze(xs)\n    xs['a'] += 1\n    xs['b']['c'] += 1\n    self.assertEqual(unfreeze(frozen), {'a': 1, 'b': {'c': 2}})\n\n  def test_frozen_dict_maps(self):\n    xs = {'a': 1, 'b': {'c': 2}}\n    frozen = FrozenDict(xs)\n    frozen2 = jax.tree_util.tree_map(lambda x: x + x, frozen)\n    self.assertEqual(unfreeze(frozen2), {'a': 2, 'b': {'c': 4}})\n\n  def test_frozen_dict_pop(self):\n    xs = {'a': 1, 'b': {'c': 2}}\n    b, a = FrozenDict(xs).pop('a')\n    self.assertEqual(a, 1)\n    self.assertEqual(unfreeze(b), {'b': {'c': 2}})\n\n  def test_frozen_dict_partially_maps(self):\n    x = jax.tree_util.tree_map(\n      lambda a, b: (a, b), freeze({'a': 2}), freeze({'a': {'b': 1}})\n    )\n    self.assertEqual(unfreeze(x), {'a': (2, {'b': 1})})\n\n  def test_frozen_dict_hash(self):\n    xs = {'a': 1, 'b': {'c': 2}}\n    ys = {'a': 1, 'b': {'c': 3}}\n    self.assertNotEqual(hash(freeze(xs)), hash(freeze(ys)))\n\n  def test_frozen_items(self):\n    xs = {'a': 1, 'b': {'c': 2}}\n    items = list(freeze(xs).items())\n\n    self.assertEqual(items, [('a', 1), ('b', freeze(xs['b']))])\n\n  def test_frozen_dict_repr(self):\n    expected = \"\"\"FrozenDict({\n    a: 1,\n    b: {\n        c: 2,\n        d: {},\n    },\n})\"\"\"\n\n    xs = FrozenDict({'a': 1, 'b': {'c': 2, 'd': {}}})\n    self.assertEqual(repr(xs), expected)\n    self.assertEqual(repr(FrozenDict()), 'FrozenDict({})')\n\n  def test_frozen_dict_reduce(self):\n    before = FrozenDict(a=FrozenDict(b=1, c=2))\n    cl, data = before.__reduce__()\n    after = cl(*data)\n    self.assertIsNot(before, after)\n    self.assertEqual(before, after)\n    self.assertEqual(after, {'a': {'b': 1, 'c': 2}})\n\n  def test_frozen_dict_copy_reserved_name(self):\n    result = FrozenDict({'a': 1}).copy({'cls': 2})\n    self.assertEqual(result, {'a': 1, 'cls': 2})\n\n  @parameterized.parameters(\n    {\n      'x': {'a': 1, 'b': {'c': 2}},\n      'key': 'b',\n      'actual_new_x': {'a': 1},\n      'actual_value': {'c': 2},\n    },\n    {\n      'x': FrozenDict({'a': 1, 'b': {'c': 2}}),\n      'key': 'b',\n      'actual_new_x': FrozenDict({'a': 1}),\n      'actual_value': FrozenDict({'c': 2}),\n    },\n  )\n  def test_utility_pop(self, x, key, actual_new_x, actual_value):\n    new_x, value = pop(x, key)\n    self.assertTrue(\n      new_x == actual_new_x and isinstance(new_x, type(actual_new_x))\n    )\n    self.assertTrue(\n      value == actual_value and isinstance(value, type(actual_value))\n    )\n\n  @parameterized.parameters(\n    {\n      'x': {'a': 1, 'b': {'c': 2}},\n      'add_or_replace': {'b': {'c': -1, 'd': 3}},\n      'actual_new_x': {'a': 1, 'b': {'c': -1, 'd': 3}},\n    },\n    {\n      'x': FrozenDict({'a': 1, 'b': {'c': 2}}),\n      'add_or_replace': FrozenDict({'b': {'c': -1, 'd': 3}}),\n      'actual_new_x': FrozenDict({'a': 1, 'b': {'c': -1, 'd': 3}}),\n    },\n  )\n  def test_utility_copy(self, x, add_or_replace, actual_new_x):\n    new_x = copy(x, add_or_replace=add_or_replace)\n    self.assertTrue(\n      new_x == actual_new_x and isinstance(new_x, type(actual_new_x))\n    )\n\n  @parameterized.parameters(\n    {\n      'x': {'a': 1, 'b': {'c': 2}},\n    },\n    {\n      'x': FrozenDict({'a': 1, 'b': {'c': 2}}),\n    },\n  )\n  def test_utility_copy_singlearg(self, x):\n    new_x = copy(x)\n    self.assertTrue(new_x == x and isinstance(new_x, type(x)))\n\n  @parameterized.parameters(\n    {\n      'x': {'a': 1, 'b': {'c': 2}},\n      'pretty_str': '{\\n    a: 1,\\n    b: {\\n        c: 2,\\n    },\\n}',\n    },\n    {\n      'x': FrozenDict({'a': 1, 'b': {'c': 2}}),\n      'pretty_str': (\n        'FrozenDict({\\n    a: 1,\\n    b: {\\n        c: 2,\\n    },\\n})'\n      ),\n    },\n    {\n      'x': 345,\n      'pretty_str': '345',\n    },\n  )\n  def test_utility_pretty_repr(self, x, pretty_str):\n    self.assertEqual(pretty_repr(x), pretty_str)\n\n  def test_flatten(self):\n    frozen = freeze({'c': 1, 'b': {'a': 2}})\n    flat_leaves, tdef = jax.tree_util.tree_flatten(frozen)\n    self.assertEqual(flat_leaves, [2, 1])\n    self.assertEqual(\n      jax.tree_util.tree_unflatten(tdef, flat_leaves),\n      frozen,\n    )\n    flat_path_leaves, tdef = jax.tree_util.tree_flatten_with_path(frozen)\n    self.assertEqual(\n      flat_path_leaves,\n      [\n        ((jax.tree_util.DictKey('b'), jax.tree_util.DictKey('a')), 2),\n        ((jax.tree_util.DictKey('c'),), 1),\n      ],\n    )\n    self.assertEqual(\n      jax.tree_util.tree_unflatten(tdef, [l for _, l in flat_path_leaves]),\n      frozen,\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/core_lift_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax\nimport numpy as np\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\n\nfrom flax import errors\nfrom flax.core import FrozenDict, apply, copy, init, lift, nn\n\n\nclass LiftTest(absltest.TestCase):\n  def test_aliasing(self):\n    def f(scope):\n      a = scope.push('a')\n\n      def g(scopes, _):\n        scope, a = scopes\n        self.assertEqual(a.parent, scope)\n\n      lift.vmap(g, variable_axes={}, split_rngs={})((scope, a), jnp.ones((1,)))\n\n    init(f)(random.key(0))\n\n  def test_undefined_param(self):\n    def f(scope):\n      dense = lift.vmap(\n        nn.dense,\n        in_axes=(0, None),\n        out_axes=0,\n        variable_axes={'params': 0},\n        split_rngs={'params': True},\n      )\n      dense(scope.push('dense'), np.ones((3, 2)), 2)\n\n    msg = r'Could not find parameter named \"kernel\" in scope \"/vmap\\(dense\\)\".'\n    with self.assertRaisesRegex(errors.ScopeParamNotFoundError, msg):\n      apply(f)({'params': {'dense': {'abc': np.ones((3, 3))}}})\n\n  def test_jit_cache(self):\n    compiles = 0\n\n    @lift.jit\n    def f(scope, _module_hash, x):\n      nonlocal compiles\n      compiles += 1\n      if scope.is_mutable_collection(\n        'intermediates'\n      ) and not scope.is_mutable_collection('params'):\n        scope.put_variable('intermediates', 'x', x + 1)\n      return nn.dense(scope, x, 1)\n\n    x = np.ones((3, 2))\n    module_hash = 1\n    _, params = init(f)(random.key(0), module_hash, x)\n    init(f)(random.key(0), module_hash, x)\n    self.assertEqual(compiles, 1)\n    apply(f)(params, module_hash, x)\n    self.assertEqual(compiles, 2)  # apply should cause a compile\n    apply(f)(params, module_hash, x)\n    self.assertEqual(compiles, 2)  # applying again should not\n    # edge case where only the implicit return of the jitted functions changes.\n    # this should not use the previously cached apply.\n    _, state = apply(f, mutable='intermediates')(params, module_hash, x)\n    self.assertEqual(compiles, 3)  # applying again should not\n    self.assertEqual(state['intermediates']['x'].sum(), 3 * 2 * 2)\n\n  def test_vjp(self):\n    def g(scope, x, y):\n      p = scope.param('test', nn.initializers.constant(0.5), ())\n      scope.variable('state', 'counter', lambda: 0)\n      return p * x * y\n\n    def f(scope, x, y):\n      z, bwd = lift.vjp(g, scope, x, y)\n      return bwd(jnp.ones(y.shape))\n\n    x = jnp.array([1.0, 2.0, 3.0])\n    y = jnp.array([4.0, 5.0, 6.0])\n    _, params = init(f)(random.key(0), x, y)\n    params_grad, x_grad, y_grad = apply(f)(params, x, y)\n    self.assertEqual(\n      params_grad,\n      {\n        'params': FrozenDict({'test': 32.0}),\n      },\n    )\n    np.testing.assert_allclose(x_grad, [2.0, 2.5, 3.0])\n    np.testing.assert_allclose(y_grad, [0.5, 1.0, 1.5])\n\n  def test_jvp(self):\n    def g(scope, x):\n      p = scope.param('test', nn.initializers.zeros_init(), ())\n      scope.variable('state', 'counter', lambda: 0)\n      return p * x\n\n    def f(scope, x):\n      vars_t = jax.tree_util.tree_map(\n        jnp.ones_like, scope.variables().get('params', {})\n      )\n      _, out_t = lift.jvp(\n        g, scope, (x,), (jnp.zeros_like(x),), {'params': vars_t}\n      )\n      return out_t\n\n    x = jnp.ones((3,))\n    _, params = init(f)(random.key(0), x)\n    y_t = apply(f)(params, x)\n    np.testing.assert_allclose(y_t, jnp.ones_like(x))\n\n  def test_while_loop(self):\n\n    def f(scope, x):\n      key_zero = random.key(0)\n      key_zero = jnp.broadcast_to(key_zero, (2, *key_zero.shape))\n      scope.param('inc', lambda _: 1)\n      scope.put_variable('state', 'acc', 0)\n      scope.put_variable('state', 'rng_params', key_zero)\n      scope.put_variable('state', 'rng_loop', jax.random.clone(key_zero))\n\n      def cond_fn(scope, c):\n        acc = scope.get_variable('state', 'acc')\n        return acc < x\n\n      def body_fn(scope, c):\n        i = scope.get_variable('state', 'acc')\n        p_rng = scope.make_rng('params')\n        l_rng = scope.make_rng('loop')\n        scope.put_variable(\n          'state',\n          'rng_params',\n          scope.get_variable('state', 'rng_params').at[i].set(p_rng),\n        )\n        scope.put_variable(\n          'state',\n          'rng_loop',\n          scope.get_variable('state', 'rng_loop').at[i].set(l_rng),\n        )\n        inc = scope.get_variable('params', 'inc')\n        scope.put_variable('state', 'acc', i + inc)\n        return c + 2\n\n      return lift.while_loop(\n        cond_fn,\n        body_fn,\n        scope,\n        0,\n        carry_variables='state',\n        split_rngs={'params': False, 'loop': True},\n      )\n\n    x = 2\n    c, vars = apply(f, mutable=True)(\n      {}, x, rngs={'params': random.key(1), 'loop': random.key(2)}\n    )\n    self.assertEqual(vars['state']['acc'], x)\n    self.assertEqual(c, 2 * x)\n    self.assertEqual(\n      vars['state']['rng_params'][0], vars['state']['rng_params'][1]\n    )\n    with jax.debug_key_reuse(False):\n      self.assertNotEqual(\n        vars['state']['rng_loop'][0],\n        vars['state']['rng_loop'][1],\n      )\n\n  def test_cond(self):\n    def f(scope, x, pred):\n      scope.variable('state', 'true_count', lambda: 0)\n      scope.variable('state', 'false_count', lambda: 0)\n\n      def true_fn(scope, x):\n        scope.variable('state', 'true_count').value += 1\n        return scope.child(nn.dense)(x, 2)\n\n      def false_fn(scope, x):\n        scope.variable('state', 'false_count').value += 1\n        return -scope.child(nn.dense)(x, 2)\n\n      return lift.cond(pred, true_fn, false_fn, scope, x)\n\n    x = jnp.ones((1, 3))\n    y1, vars = init(f)(random.key(0), x, True)\n    self.assertEqual(vars['state'], {'true_count': 1, 'false_count': 0})\n    y2, vars = apply(f, mutable='state')(vars, x, False)\n    self.assertEqual(vars['state'], {'true_count': 1, 'false_count': 1})\n    np.testing.assert_allclose(y1, -y2)\n\n  def test_switch(self):\n    def f(scope, x, index):\n      scope.variable('state', 'a_count', lambda: 0)\n      scope.variable('state', 'b_count', lambda: 0)\n      scope.variable('state', 'c_count', lambda: 0)\n\n      def a_fn(scope, x):\n        scope.variable('state', 'a_count').value += 1\n        return scope.child(nn.dense)(x, 2)\n\n      def b_fn(scope, x):\n        scope.variable('state', 'b_count').value += 1\n        return -scope.child(nn.dense)(x, 2)\n\n      def c_fn(scope, x):\n        scope.variable('state', 'c_count').value += 1\n        return scope.child(nn.dense)(x, 2)\n\n      return lift.switch(index, [a_fn, b_fn, c_fn], scope, x)\n\n    x = jnp.ones((1, 3))\n    y1, vars = init(f)(random.key(0), x, 0)\n    self.assertEqual(vars['state'], {'a_count': 1, 'b_count': 0, 'c_count': 0})\n    y2, updates = apply(f, mutable='state')(vars, x, 1)\n    vars = copy(vars, updates)\n    self.assertEqual(vars['state'], {'a_count': 1, 'b_count': 1, 'c_count': 0})\n    np.testing.assert_allclose(y1, -y2)\n    y3, updates = apply(f, mutable='state')(vars, x, 2)\n    vars = copy(vars, updates)\n    self.assertEqual(vars['state'], {'a_count': 1, 'b_count': 1, 'c_count': 1})\n    np.testing.assert_allclose(y1, y3)\n\n  def test_subscope_var_aliasing(self):\n    def test(scope, x):\n      subscope = scope.push(name='a')\n      subscope.put_variable('state', 'x', 0.0)\n      _ = lift.while_loop(\n        lambda scope, x: False,\n        lambda scope, x: x,\n        scope,\n        jnp.array(0, jnp.int32),\n        carry_variables=['state'],\n      )\n      subscope.put_variable('state', 'x', 1.0)\n      val0 = scope.variables()['state']['a']['x']\n      val1 = subscope.variables()['state']['x']\n      self.assertEqual(val0, val1)\n      return x\n\n    init(test)(random.key(0), 1.0)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/core_meta_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random, sharding\nfrom jax.experimental import mesh_utils\n\nfrom flax import errors\nfrom flax.core import init, lift, meta, nn\n\n\nclass MetaTest(absltest.TestCase):\n  def test_boxed_param(self):\n    def f(scope, xs):\n      def g(scope, x):\n        kernel_init = meta.with_partitioning(\n          nn.initializers.ones_init(), ('in', 'out')\n        )\n        kernel = scope.param('kernel', kernel_init, (x.shape[-1], 2))\n        kernel_box = scope.get_variable('params', 'kernel')\n        self.assertIsInstance(kernel_box, meta.Partitioned)\n        self.assertEqual(kernel_box.names, ('in', 'out'))\n        return x @ kernel\n\n      lift.vmap(\n        g,\n        in_axes=0,\n        variable_axes={'params': 0},\n        split_rngs={'params': True},\n        metadata_params={meta.PARTITION_NAME: 'batch'},\n      )(scope, xs)\n\n    _, variables = init(f)(random.key(0), jnp.zeros((8, 3)))\n    self.assertEqual(\n      variables['params']['kernel'].names, ('batch', 'in', 'out')\n    )\n\n  def test_boxed_variable(self):\n    def f(scope, xs):\n      def g(scope, x):\n        kernel_init = meta.with_partitioning(\n          nn.initializers.ones_init(), ('in', 'out')\n        )\n        kernel = scope.variable(\n          'params',\n          'kernel',\n          kernel_init,\n          scope.make_rng('params'),\n          (x.shape[-1], 2),\n        )\n        kernel.value += 1.0\n        self.assertEqual(kernel.value.sum(), kernel.value.size * 2)\n        kernel_box = scope.get_variable('params', 'kernel')\n        self.assertIsInstance(kernel_box, meta.Partitioned)\n        self.assertEqual(kernel_box.names, ('in', 'out'))\n        return x @ kernel.value\n\n      lift.vmap(\n        g,\n        in_axes=0,\n        variable_axes={'params': 0},\n        split_rngs={'params': True},\n        metadata_params={meta.PARTITION_NAME: 'batch'},\n      )(scope, xs)\n\n    _, variables = init(f)(random.key(0), jnp.zeros((8, 3)))\n    self.assertEqual(\n      variables['params']['kernel'].names, ('batch', 'in', 'out')\n    )\n\n  def test_partition_axis_unspecified(self):\n    def f(scope, xs):\n      def g(scope, x):\n        kernel_init = meta.with_partitioning(\n          nn.initializers.ones_init(), ('in', 'out')\n        )\n        scope.param('kernel', kernel_init, (3, 2))\n        return x\n\n      with self.assertRaises(errors.PartitioningUnspecifiedError):\n        lift.vmap(\n          g,\n          in_axes=0,\n          variable_axes={'params': 0},\n          split_rngs={'params': True},\n          metadata_params={},\n        )(scope, xs)\n\n    init(f)(random.key(0), jnp.zeros((8, 3)))\n\n  def test_unbox(self):\n    xs = {\n      'kernel': meta.Partitioned(jnp.zeros((3, 2)), ('in', 'out')),\n      'complex': meta.Partitioned(\n        {'K': jnp.zeros((3, 2)), 'b': jnp.zeros((3,))}, ('data',)\n      ),\n    }\n    unboxed = meta.unbox(xs)\n    unboxed_shapes = jax.tree_util.tree_map(jnp.shape, unboxed)\n    self.assertEqual(\n      unboxed_shapes,\n      {\n        'kernel': (3, 2),\n        'complex': {\n          'K': (3, 2),\n          'b': (3,),\n        },\n      },\n    )\n\n  def test_scan_over_layers(self):\n    def f(scope, x):\n      def body(scope, x):\n        kernel_init = meta.with_partitioning(\n          nn.initializers.ones_init(), ('in', 'out')\n        )\n        y = nn.dense(scope, x, 3, kernel_init=kernel_init)\n        return y, ()\n\n      c, _ = lift.scan(\n        body,\n        variable_axes={'params': 0},\n        split_rngs={'params': True},\n        length=8,\n        metadata_params={meta.PARTITION_NAME: 'layers'},\n      )(scope, x)\n      return c\n\n    _, variables = init(f)(random.key(0), jnp.zeros((8, 3)))\n    boxed_shapes = jax.tree_util.tree_map(jnp.shape, variables['params'])\n    self.assertEqual(\n      boxed_shapes,\n      {\n        'kernel': meta.Partitioned((8, 3, 3), ('layers', 'in', 'out')),\n        'bias': (8, 3),\n      },\n    )\n\n  def test_get_partition_spec(self):\n    xs = {\n      'kernel': meta.Partitioned(jnp.zeros((8, 3, 3)), ('layers', 'in', 'out')),\n      'bias': jnp.zeros((8, 3)),\n      'step': jnp.array(100),\n    }\n    ps = meta.get_partition_spec(xs)\n    self.assertEqual(\n      ps,\n      {\n        'kernel': jax.sharding.PartitionSpec('layers', 'in', 'out'),\n        'bias': jax.sharding.PartitionSpec(),\n        'step': jax.sharding.PartitionSpec(),\n      },\n    )\n\n  def test_get_sharding(self):\n    devices = mesh_utils.create_device_mesh((jax.local_device_count(), 1))\n    mesh = sharding.Mesh(devices, ('in', 'out'))\n    xs = {\n      'kernel': meta.Partitioned(jnp.zeros((8, 3)), ('in', 'out')),\n      'bias': jnp.zeros((8, 3)),\n      'step': jnp.array(100),\n    }\n    ps = meta.get_sharding(xs, mesh)\n    self.assertEqual(\n      ps,\n      {\n        'kernel': jax.sharding.NamedSharding(\n          mesh, jax.sharding.PartitionSpec('in', 'out')\n        ),\n        'bias': jax.sharding.NamedSharding(mesh, jax.sharding.PartitionSpec()),\n        'step': jax.sharding.NamedSharding(mesh, jax.sharding.PartitionSpec()),\n      },\n    )\n\n  def test_boxed_param_with_mesh(self):\n    devices = mesh_utils.create_device_mesh((jax.local_device_count(), 1))\n    mesh = sharding.Mesh(devices, ('in', 'out'))\n\n    def f(scope, x):\n      kernel_init = meta.with_partitioning(\n        nn.initializers.ones_init(), ('in', 'out'), mesh=mesh\n      )\n      kernel = scope.param('kernel', kernel_init, (x.shape[-1], 2))\n      kernel_box = scope.get_variable('params', 'kernel')\n      self.assertIsInstance(kernel_box, meta.Partitioned)\n      self.assertEqual(kernel_box.names, ('in', 'out'))\n      return x @ kernel\n\n    @jax.jit\n    def create_state():\n      y, variables = init(f)(random.key(0), jnp.zeros((8, 4)))\n      spec = meta.get_partition_spec(variables)\n      shardings = jax.tree_util.tree_map(\n          lambda s: sharding.NamedSharding(mesh, s), spec\n      )\n      variables = jax.lax.with_sharding_constraint(variables, shardings)\n      return variables\n\n    variables = create_state()\n    self.assertEqual(variables['params']['kernel'].names, ('in', 'out'))\n    self.assertIs(variables['params']['kernel'].mesh, mesh)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/core_scope_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax\nimport numpy as np\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\n\nfrom flax import errors\nfrom flax import config\nfrom flax.core import Scope, apply, freeze, init, lazy_init, nn, scope\nfrom flax.core.scope import LazyRng\n\n\nclass ScopeTest(absltest.TestCase):\n  def test_rng(self):\n    def f(scope):\n      self.assertTrue(scope.has_rng('params'))\n      self.assertFalse(scope.has_rng('dropout'))\n      rng = scope.make_rng('params')\n      self.assertTrue(\n        np.all(rng == LazyRng.create(random.key(0), 1).as_jax_rng())\n      )\n\n    init(f)(random.key(0))\n\n  def test_in_filter(self):\n    filter_true = lambda x, y: self.assertTrue(scope.in_filter(x, y))\n    filter_false = lambda x, y: self.assertFalse(scope.in_filter(x, y))\n\n    filter_true(True, 'any_string1')\n    filter_false(False, 'any_string2')\n    filter_true('exact_match', 'exact_match')\n    filter_false('no_match1', 'no_match2')\n    filter_true(['one', 'two'], 'one')\n    filter_false(['one', 'two'], 'three')\n    filter_false([], 'one')\n    filter_false([], None)\n\n  def test_union_filter(self):\n    def union_check(a, b, ans):\n      self.assertEqual(scope.union_filters(a, b), ans)\n      self.assertEqual(scope.union_filters(b, a), ans)\n\n    union_check(['a', 'b'], ['b', 'c'], {'a', 'b', 'c'})\n    union_check(True, False, True)\n    union_check(False, False, set())\n    union_check(True, True, True)\n    union_check(\n      scope.DenyList(['a', 'b']),\n      scope.DenyList(['b', 'c']),\n      scope.DenyList({'b'}),\n    )\n    union_check(\n      scope.DenyList(['a', 'b']), ['b', 'c'], scope.DenyList({'a'})\n    )\n\n  def test_intersect_filter(self):\n    def intersect_check(a, b, ans):\n      self.assertEqual(scope.intersect_filters(a, b), ans)\n      self.assertEqual(scope.intersect_filters(b, a), ans)\n\n    intersect_check(['a', 'b'], ['b', 'c'], {'b'})\n    intersect_check(True, False, False)\n    intersect_check(False, False, set())\n    intersect_check(True, True, True)\n    intersect_check(\n      scope.DenyList(['a', 'b']),\n      scope.DenyList(['b', 'c']),\n      scope.DenyList({'a', 'b', 'c'}),\n    )\n    intersect_check(scope.DenyList(['a', 'b']), ['b', 'c'], {'c'})\n\n  def test_subtract_filter(self):\n    def subtract_check(a, b, ans):\n      self.assertEqual(scope.subtract_filters(a, b), ans)\n\n    subtract_check(['a', 'b'], ['b', 'c'], {'a'})\n    subtract_check(True, False, scope.DenyList(False))\n    subtract_check(False, False, set())\n    subtract_check(True, True, False)\n    subtract_check(True, 'a', scope.DenyList('a'))\n    subtract_check(\n      scope.DenyList(['a', 'b']), scope.DenyList(['b', 'c']), {'c'}\n    )\n    subtract_check(\n      scope.DenyList(['a', 'b']),\n      ['b', 'c'],\n      scope.DenyList({'a', 'b', 'c'}),\n    )\n\n  def test_group_collections(self):\n    params = {'dense1': {'x': [10, 20]}}\n    batch_stats = {'dense1': {'ema': 5}}\n    xs = {'params': params, 'batch_stats': batch_stats}\n\n    # Retrieve all keys only once.\n    group = scope.group_collections(xs, ['params', 'params'])\n    self.assertEqual(group, ({'params': params}, {}))\n\n    # Ignore non-existing keys.\n    self.assertEqual(scope.group_collections(xs, ['vars']), ({},))\n\n    # False gets nothing and True retrieves all keys once.\n    self.assertEqual(\n      scope.group_collections(xs, [False, True, True]), ({}, xs, {})\n    )\n\n  def test_inconsistent_param_shapes(self):\n    def f(scope):\n      scope.param('test', nn.initializers.ones_init(), (4,))\n\n    msg = (\n        r'For parameter \"test\" in \"/\", the given initializer is expected to'\n        r' generate shape \\(4,\\), but the existing parameter it received has'\n        r' shape \\(2,\\).'\n    )\n    with self.assertRaisesRegex(errors.ScopeParamShapeError, msg):\n      apply(f)(freeze({'params': {'test': np.ones((2,))}}))\n\n  def test_apply_variables_bad_pytree(self):\n    def f(scope):\n      scope.param('kernel', nn.initializers.ones_init(), (4,))\n\n    params = freeze(\n      {\n        'params': {\n          'kernel': np.ones((4,)),\n        },\n      }\n    )\n    apply(f)(params)  # Valid.\n    msg = 'but got a dict with an extra params layer'\n    with self.assertRaisesRegex(\n      errors.ApplyScopeInvalidVariablesStructureError, msg\n    ):\n      apply(f)({'params': params})\n\n  def test_mutate_undefined_collection(self):\n    def f(scope):\n      scope.put_variable('state', 'test', 123)\n\n    msg = (\n      r'Cannot update variable \"test\" in \"/\" because collection \"state\" is'\n      r' immutable.'\n    )\n    with self.assertRaisesRegex(errors.ModifyScopeVariableError, msg):\n      init(f, mutable='params')(random.key(0))\n\n  def test_undefined_param(self):\n    def f(scope):\n      nn.dense(scope.push('dense'), np.ones((1, 2)), 2)\n\n    msg = r'Could not find parameter named \"kernel\" in scope \"/dense\".'\n    with self.assertRaisesRegex(errors.ScopeParamNotFoundError, msg):\n      apply(f)({'params': {'abc': 1}})\n\n  def test_variable_is_mutable(self):\n    def f(scope, should_be_mutable):\n      test = scope.variable('state', 'test', lambda: 1)\n      self.assertEqual(test.is_mutable(), should_be_mutable)\n\n    _, variables = apply(f, mutable='state')({}, True)\n    apply(f, mutable=False)(variables, False)\n\n  def test_rngs_check_w_frozen_dict(self):\n    def f(scope, x):\n      return x\n\n    _ = apply(f)({}, np.array([0.0]), rngs=freeze({'a': random.key(0)}))\n\n  def test_rng_check_w_old_and_new_keys(self):\n    # random.key always returns a new-style typed PRNG key.\n    key = random.key(0)\n    self.assertTrue(scope._is_valid_rng(key))\n    self.assertFalse(scope._is_valid_rng(random.split(key)))\n\n    # random.PRNGKey returns an old-style uint32 key by default.\n    old_key = random.PRNGKey(0)\n    self.assertTrue(scope._is_valid_rng(old_key))\n    self.assertFalse(scope._is_valid_rng(random.split(old_key)))\n\n    # Also explicitly test raw key data, because the jax_enable_custom_prng\n    # flag can make PRNGKey return new-style keys.\n    raw_key = random.key_data(key)\n    self.assertTrue(scope._is_valid_rng(raw_key))\n    self.assertFalse(scope._is_valid_rng(random.split(raw_key)))\n\n  def test_rng_check_w_lazy_rng(self):\n    key = random.key(0)\n    self.assertTrue(scope._is_valid_rng(scope.LazyRng.create(key, 1)))\n\n  def test_jax_leak_detector(self):\n    with jax.check_tracer_leaks(True):\n\n      def f(scope):\n        def g(scope):\n          pass\n\n        scope.child(g)()\n\n      jax.jit(init(f))(random.key(0))\n\n  def test_rng_counter_reuse(self):\n    root = Scope({}, {'dropout': random.key(0)})\n\n    def f(scope):\n      return scope.make_rng('dropout')\n\n    a = root.child(f)()\n    root = root.rewound()\n    b = root.child(f)()\n    self.assertFalse(jnp.allclose(a, b))\n\n  def test_empty_col_error(self):\n    root = Scope({})\n    with self.assertRaises(errors.ScopeCollectionNotFound):\n      root.param('test', nn.initializers.zeros_init(), ())\n    root = Scope({'params': {}})\n    with self.assertRaises(errors.ScopeCollectionNotFound):\n      root.param('test', nn.initializers.zeros_init(), ())\n\n    root = Scope({'params': {'abc': 1}})\n    with self.assertRaises(errors.ScopeCollectionNotFound):\n      root.variable('state', 'test', jnp.zeros, ())\n    root = Scope({'state': {}})\n    with self.assertRaises(errors.ScopeCollectionNotFound):\n      root.variable('state', 'test', jnp.zeros, ())\n\n  def test_variable_no_init(self):\n    root = Scope({}, mutable='state')\n    with self.assertRaises(errors.ScopeCollectionNotFound):\n      root.variable('state', 'test')\n    root = Scope({'state': {'abc': 1}}, mutable='state')\n    abc = root.variable('state', 'abc')\n    self.assertEqual(abc.value, 1)\n    with self.assertRaises(errors.ScopeVariableNotFoundError):\n      root.variable('state', 'test')\n\n  def test_variable_alias(self):\n    scope = Scope({}, mutable='state')\n    subscope = scope.push(name='a')\n    subscope.put_variable('state', 'x', 0.0)\n    scope.put_variable('state', 'a', {'x': jnp.array(1.0, jnp.float32)})\n    self.assertEqual(\n      scope.variables()['state']['a']['x'], subscope.variables()['state']['x']\n    )\n\n  def test_lazy_init(self):\n    def f(scope, x):\n      k = scope.param(\n        'kernel', nn.initializers.lecun_normal(), (x.shape[-1], x.shape[-1])\n      )\n      return x @ k\n\n    init_fn = lazy_init(f)\n    # provide a massive input message which would OOM if any compute ops were actually executed\n    variables = init_fn(\n      random.key(0),\n      jax.ShapeDtypeStruct((1024 * 1024 * 1024, 128), jnp.float32),\n    )\n    self.assertEqual(variables['params']['kernel'].shape, (128, 128))\n\n  def test_lazy_init_fails_on_data_dependence(self):\n    def f(scope, x):\n      # kernel is initialized with x so params are now dependent on the input\n      k = scope.param('kernel', lambda _: x)\n      return x * k\n\n    init_fn = lazy_init(f)\n    with self.assertRaises(errors.LazyInitError):\n      init_fn(random.key(0), jax.ShapeDtypeStruct((8, 4), jnp.float32))\n\n  @config.temp_flip_flag('fix_rng_separator', True)\n  def test_fold_in_static_seperator(self):\n    x = LazyRng(random.key(0), ('ab', 'c'))\n    y = LazyRng(random.key(0), ('a', 'bc'))\n    self.assertFalse(np.all(x.as_jax_rng() == y.as_jax_rng()))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/design/core_attention_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom functools import partial\nfrom collections.abc import Callable, Sequence\n\nimport jax\nfrom absl.testing import absltest\nfrom jax import lax, random\nfrom jax import numpy as jnp\n\nfrom flax.core import Array, Scope, init, lift, nn, unfreeze\n\n\ndef softmax_attn(scope: Scope, weights: Array):\n  del scope\n  norm_dims = tuple(range(weights.ndim // 2, weights.ndim))\n  log_norms = jax.scipy.special.logsumexp(\n    weights, axis=norm_dims, keepdims=True\n  )\n  return jnp.exp(weights - log_norms)\n\n\ndef with_dropout(fn, rate: float, deterministic: bool = False):\n  def attn_fn(scope: Scope, weights: Array):\n    attn_weights = fn(scope, weights)\n    return nn.dropout(\n      scope, attn_weights, deterministic=deterministic, rate=rate\n    )\n\n  return attn_fn\n\n\ndef _dot_product_attention(\n  scope: Scope,\n  query: Array,\n  key: Array,\n  value: Array,\n  bias: Array | None = None,\n  attn_fn: Callable = softmax_attn,\n  dtype=jnp.float32,\n):\n  assert key.ndim == query.ndim\n  assert key.ndim == value.ndim\n\n  n = query.ndim\n  attn_weights = lax.dot_general(query, key, (((n - 1,), (n - 1,)), ((), ())))\n  if bias is not None:\n    attn_weights += bias\n  attn_weights = attn_fn(scope, attn_weights)\n  attn_weights = attn_weights.astype(dtype)\n\n  contract_dims = (\n    tuple(range(n - 1, attn_weights.ndim)),\n    tuple(range(0, n - 1)),\n  )\n  y = lax.dot_general(attn_weights, value, (contract_dims, ((), ())))\n  return y\n\n\ndef dot_product_attention(\n  scope: Scope,\n  inputs_q: Array,\n  inputs_kv: Array,\n  bias: Array | None = None,\n  qkv_features: int | None = None,\n  out_features: int | None = None,\n  attn_fn: Callable = softmax_attn,\n  dtype=jnp.float32,\n):\n  if qkv_features is None:\n    qkv_features = inputs_q.shape[-1]\n  if out_features is None:\n    out_features = inputs_q.shape[-1]\n  dense = partial(nn.dense, features=qkv_features, bias=False, dtype=dtype)\n\n  query = scope.child(dense, 'query')(inputs_q)\n  key = scope.child(dense, 'key')(inputs_kv)\n  value = scope.child(dense, 'value')(inputs_kv)\n\n  y = _dot_product_attention(\n    scope, query, key, value, bias=bias, attn_fn=attn_fn, dtype=dtype\n  )\n\n  return scope.child(nn.dense, 'out')(y, features=out_features, dtype=dtype)\n\n\ndef multi_head_dot_product_attention(\n  scope: Scope,\n  inputs_q: Array,\n  inputs_kv: Array,\n  bias: Array | None = None,\n  qkv_features: int | None = None,\n  out_features: int | None = None,\n  attn_fn: Callable = softmax_attn,\n  batch_axes: Sequence[int] = (0,),\n  num_heads: int = 1,\n  dtype=jnp.float32,\n  broadcast_dropout=False,\n):\n  if qkv_features is None:\n    qkv_features = inputs_q.shape[-1]\n  if out_features is None:\n    out_features = inputs_q.shape[-1]\n\n  attn_fn = partial(\n    dot_product_attention,\n    attn_fn=attn_fn,\n    qkv_features=qkv_features // num_heads,\n    out_features=out_features,\n    dtype=dtype,\n  )\n  attn_fn = lift.vmap(\n    attn_fn,\n    in_axes=(None, None, None),\n    out_axes=-2,\n    axis_size=num_heads,\n    variable_axes={'params': 0},\n    split_rngs={'params': True, 'dropout': not broadcast_dropout},\n  )\n  for axis in reversed(sorted(batch_axes)):\n    attn_fn = lift.vmap(\n      attn_fn,\n      in_axes=(axis, axis, axis),\n      out_axes=axis,\n      variable_axes={'params': None},\n      split_rngs={'params': False, 'dropout': not broadcast_dropout},\n    )\n\n  y = attn_fn(scope, inputs_q, inputs_kv, bias)\n  return y.mean(axis=-2)\n\n\nclass AttentionTest(absltest.TestCase):\n  def test_attention(self):\n    inputs = jnp.ones((2, 7, 16))\n    model = partial(\n      multi_head_dot_product_attention,\n      num_heads=2,\n      batch_axes=(0,),\n      attn_fn=with_dropout(softmax_attn, 0.1, deterministic=False),\n    )\n\n    rngs = {'params': random.key(0), 'dropout': random.key(1)}\n    y, variables = jax.jit(init(model))(rngs, inputs, inputs)\n    variable_shapes = jax.tree_util.tree_map(jnp.shape, variables['params'])\n    self.assertEqual(y.shape, (2, 7, 16))\n    self.assertEqual(\n      unfreeze(variable_shapes),\n      {\n        'key': {'kernel': (2, 16, 8)},\n        'value': {'kernel': (2, 16, 8)},\n        'query': {'kernel': (2, 16, 8)},\n        'out': {'bias': (2, 16), 'kernel': (2, 8, 16)},\n      },\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/design/core_auto_encoder_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom dataclasses import dataclass\nfrom collections.abc import Callable\n\nimport jax\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\n\nfrom flax.core import Array, Scope, init, nn, unfreeze\n\n\ndef mlp(scope: Scope, x: Array, hidden: int, out: int):\n  x = scope.child(nn.dense, 'hidden')(x, hidden)\n  x = nn.relu(x)\n  return scope.child(nn.dense, 'out')(x, out)\n\n\n@dataclass\nclass AutoEncoder:\n  latents: int\n  features: int\n  hidden: int\n\n  def __call__(self, scope, x):\n    z = self.encode(scope, x)\n    return self.decode(scope, z)\n\n  def encode(self, scope, x):\n    return scope.child(mlp, 'encoder')(x, self.hidden, self.latents)\n\n  def decode(self, scope, z):\n    return scope.child(mlp, 'decoder')(z, self.hidden, self.features)\n\n\ndef module_method(fn, name=None):\n  if name is None:\n    name = fn.__name__ if hasattr(fn, '__name__') else None\n\n  def wrapper(self, *args, **kwargs):\n    scope = self.scope.rewound()\n    mod_fn = lambda scope: fn(self, scope, *args, **kwargs)\n    return scope.child(mod_fn, name)()\n\n  return wrapper\n\n\n@dataclass\nclass AutoEncoder2:\n  scope: Scope\n  latents: int\n  features: int\n  hidden: int\n\n  def __call__(self, x):\n    z = self.encode(x)\n    return self.decode(z)\n\n  @module_method\n  def encode(self, scope, x):\n    return mlp(scope, x, self.hidden, self.latents)\n\n  @module_method\n  def decode(self, scope, z):\n    return mlp(scope, z, self.hidden, self.features)\n\n\n@dataclass\nclass AutoEncoder3:\n  encode: Callable\n  decode: Callable\n\n  @staticmethod\n  def create(scope, hidden: int, latents: int, features: int):\n    enc = scope.child(mlp, 'encode', hidden=hidden, out=latents)\n    dec = scope.child(mlp, 'decode', hidden=hidden, out=features)\n    return AutoEncoder3(enc, dec)\n\n  def __call__(self, x):\n    z = self.encode(x)\n    return self.decode(z)\n\n\nclass AutoEncoderTest(absltest.TestCase):\n  def test_auto_encoder_hp_struct(self):\n    ae = AutoEncoder(latents=2, features=4, hidden=3)\n    x = jnp.ones((1, 4))\n    x_r, variables = init(ae)(random.key(0), x)\n    self.assertEqual(x.shape, x_r.shape)\n    variable_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(\n      variable_shapes,\n      {\n        'encoder': {\n          'hidden': {'kernel': (4, 3), 'bias': (3,)},\n          'out': {'kernel': (3, 2), 'bias': (2,)},\n        },\n        'decoder': {\n          'hidden': {'kernel': (2, 3), 'bias': (3,)},\n          'out': {'kernel': (3, 4), 'bias': (4,)},\n        },\n      },\n    )\n\n  def test_auto_encoder_with_scope(self):\n    ae = lambda scope, x: AutoEncoder2(scope, latents=2, features=4, hidden=3)(\n      x\n    )\n    x = jnp.ones((1, 4))\n\n    x_r, variables = init(ae)(random.key(0), x)\n    self.assertEqual(x.shape, x_r.shape)\n    variable_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(\n      variable_shapes,\n      {\n        'encode': {\n          'hidden': {'kernel': (4, 3), 'bias': (3,)},\n          'out': {'kernel': (3, 2), 'bias': (2,)},\n        },\n        'decode': {\n          'hidden': {'kernel': (2, 3), 'bias': (3,)},\n          'out': {'kernel': (3, 4), 'bias': (4,)},\n        },\n      },\n    )\n\n  def test_auto_encoder_bind_method(self):\n    ae = lambda scope, x: AutoEncoder3.create(\n      scope, latents=2, features=4, hidden=3\n    )(x)\n    x = jnp.ones((1, 4))\n\n    x_r, variables = init(ae)(random.key(0), x)\n    self.assertEqual(x.shape, x_r.shape)\n    variable_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(\n      variable_shapes,\n      {\n        'encode': {\n          'hidden': {'kernel': (4, 3), 'bias': (3,)},\n          'out': {'kernel': (3, 2), 'bias': (2,)},\n        },\n        'decode': {\n          'hidden': {'kernel': (2, 3), 'bias': (3,)},\n          'out': {'kernel': (3, 4), 'bias': (4,)},\n        },\n      },\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/design/core_big_resnets_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom functools import partial\n\nimport jax\nimport numpy as np\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\n\nfrom flax.core import Array, Scope, init, lift, nn, unfreeze\n\ndefault_norm = partial(nn.batch_norm)\n\n\ndef residual_block(scope: Scope, x: Array, conv, norm, act, features: int):\n  residual = x\n  x = scope.child(conv, 'conv_1')(x, features, (3, 3))\n  x = scope.child(norm, 'bn_1')(x)\n  x = act(x)\n  x = scope.child(conv, 'conv_2')(x, features, (3, 3))\n  x = scope.child(norm, 'bn_2')(x)\n  return act(residual + x)\n\n\ndef big_resnet(\n  scope: Scope,\n  x,\n  blocks=(10, 5),\n  dtype=jnp.float32,\n  norm=default_norm,\n  act=nn.relu,\n):\n  conv = partial(nn.conv, bias=False, dtype=dtype)\n  norm = partial(norm, dtype=dtype)\n\n  # a two stage resnet where inner blocks are rematerialized to make sure\n  # memory consumtion grows as O(sqrt(N)) and compute is O(N) where N is the number of blocks..\n  # we use a double scan such that the compiled binary is of size O(1).\n  print('total residual blocks:', np.prod(blocks))\n\n  def body_fn(scope, x):\n    return residual_block(scope, x, conv, norm, act, features=x.shape[-1])\n\n  return lift.remat_scan(\n    body_fn,\n    lengths=blocks,\n    variable_axes={'params': 0, 'batch_stats': 0},\n    split_rngs={'params': True},\n    policy=None,\n  )(scope, x)\n\n\nclass BigResnetTest(absltest.TestCase):\n  def test_big_resnet(self):\n    x = random.normal(random.key(0), (1, 8, 8, 8))\n    y, variables = init(big_resnet)(random.key(1), x)\n    self.assertEqual(y.shape, (1, 8, 8, 8))\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    batch_stats_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['batch_stats'])\n    )\n    self.assertEqual(\n      param_shapes,\n      {\n        'conv_1': {'kernel': (10, 5, 3, 3, 8, 8)},\n        'conv_2': {'kernel': (10, 5, 3, 3, 8, 8)},\n        'bn_1': {'scale': (10, 5, 8), 'bias': (10, 5, 8)},\n        'bn_2': {'scale': (10, 5, 8), 'bias': (10, 5, 8)},\n      },\n    )\n    self.assertEqual(\n      batch_stats_shapes,\n      {\n        'bn_1': {'var': (10, 5, 8), 'mean': (10, 5, 8)},\n        'bn_2': {'var': (10, 5, 8), 'mean': (10, 5, 8)},\n      },\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/design/core_custom_vjp_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom functools import partial\nfrom collections.abc import Callable, Sequence\n\nimport jax\nimport numpy as np\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\n\nfrom flax.core import Array, Scope, apply, init, lift, nn, unfreeze\n\n\ndef mlp_custom_grad(\n  scope: Scope,\n  x: Array,\n  sizes: Sequence[int] = (8, 1),\n  act_fn: Callable[[Array], Array] = nn.relu,\n):\n  f = nn.dense\n\n  def fwd(scope, x, features):\n    y, vjp_fn = lift.vjp(partial(f, features=features), scope, x)\n    return y, vjp_fn\n\n  def bwd(features, res, y_t):\n    del features\n    vjp_fn = res\n    params_t, *input_t = vjp_fn(y_t)\n    params_t = jax.tree_util.tree_map(jnp.sign, params_t)\n    return (params_t, *input_t)\n\n  dense_custom_grad = lift.custom_vjp(\n    f, forward_fn=fwd, backward_fn=bwd, nondiff_argnums=(2,)\n  )\n\n  # hidden layers\n  for size in sizes[:-1]:\n    x = scope.child(dense_custom_grad, prefix='hidden_')(x, size)\n    x = act_fn(x)\n\n  # output layer\n  return scope.child(dense_custom_grad, 'out')(x, sizes[-1])\n\n\nclass CustomVJPTest(absltest.TestCase):\n  def test_custom_vjp(self):\n    x = random.normal(random.key(0), (1, 4))\n    y, variables = init(mlp_custom_grad)(random.key(1), x)\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    loss_fn = lambda p, x: jnp.mean(apply(mlp_custom_grad)(p, x) ** 2)\n    grad = jax.grad(loss_fn)(variables, x)\n    grad_shapes = unfreeze(jax.tree_util.tree_map(jnp.shape, grad['params']))\n    self.assertEqual(y.shape, (1, 1))\n    expected_param_shapes = {\n      'hidden_0': {'kernel': (4, 8), 'bias': (8,)},\n      'out': {'kernel': (8, 1), 'bias': (1,)},\n    }\n    self.assertEqual(param_shapes, expected_param_shapes)\n    self.assertEqual(grad_shapes, expected_param_shapes)\n    for g in jax.tree_util.tree_leaves(grad):\n      self.assertTrue(np.all(g == np.sign(g)))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/design/core_dense_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom dataclasses import dataclass\nfrom typing import Any\n\nimport jax\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\n\nfrom flax import struct\nfrom flax.core import Array, init, nn, unfreeze\n\n\n@dataclass\nclass Dense:\n  features: int\n  bias: bool = True\n  kernel_init: Any = nn.linear.default_kernel_init\n  bias_init: Any = nn.initializers.zeros_init()\n\n  def __call__(self, scope, x):\n    kernel = scope.param(\n      'kernel', self.kernel_init, (x.shape[-1], self.features)\n    )\n    y = x @ kernel\n    if self.bias:\n      bias = scope.param('bias', self.bias_init, (self.features,))\n      y += bias.reshape((1,) * (y.ndim - 1) + (-1,))\n    return y\n\n\n@struct.dataclass\nclass ExplicitDense:\n  kernel: Array\n  bias: Array | None\n\n  # a fully explicit \"scope free\" version\n  @staticmethod\n  def create(\n    rng,\n    in_size,\n    out_size,\n    bias=True,\n    kernel_init=nn.linear.default_kernel_init,\n    bias_init=nn.initializers.zeros_init(),\n  ):\n    k1, k2 = random.split(rng, 2)\n    kernel = kernel_init(k1, (in_size, out_size))\n    if bias:\n      bias = bias_init(k2, (out_size,))\n    else:\n      bias = None\n    return ExplicitDense(kernel, bias)\n\n  # a semi-explicit version where a scope is used to create explicit params\n  @staticmethod\n  def create_in_scope(\n    scope,\n    in_size,\n    out_size,\n    bias=True,\n    kernel_init=nn.linear.default_kernel_init,\n    bias_init=nn.initializers.zeros_init(),\n  ):\n    kernel = scope.param('kernel', kernel_init, (in_size, out_size))\n    if bias:\n      bias = scope.param('bias', bias_init, (out_size,))\n    else:\n      bias = None\n    return ExplicitDense(kernel, bias)\n\n  def __call__(self, x):\n    y = x @ self.kernel\n    if self.bias is not None:\n      y += self.bias.reshape((1,) * (y.ndim - 1) + (-1,))\n    return y\n\n\ndef explicit_mlp(scope, x, sizes=(3, 1)):\n  for i, size in enumerate(sizes):\n    dense = scope.param(f'dense_{i}', ExplicitDense.create, x.shape[-1], size)\n    x = dense(x)\n    if i + 1 < len(sizes):\n      x = nn.relu(x)\n  return x\n\n\ndef semi_explicit_mlp(scope, x, sizes=(3, 1)):\n  for i, size in enumerate(sizes):\n    dense = scope.child(ExplicitDense.create_in_scope, prefix='dense_')(\n      x.shape[-1], size\n    )\n    x = dense(x)\n    if i + 1 < len(sizes):\n      x = nn.relu(x)\n  return x\n\n\nclass DenseTest(absltest.TestCase):\n  def test_dense(self):\n    model = Dense(features=4)\n    x = jnp.ones((1, 3))\n    y, variables = init(model)(random.key(0), x)\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(y.shape, (1, 4))\n    self.assertEqual(\n      param_shapes,\n      {\n        'kernel': (3, 4),\n        'bias': (4,),\n      },\n    )\n\n  def test_explicit_dense(self):\n    x = jnp.ones((1, 3))\n    y, variables = init(explicit_mlp)(random.key(0), x)\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(y.shape, (1, 4))\n    self.assertEqual(\n      param_shapes,\n      {\n        'kernel': (3, 4),\n        'bias': (4,),\n      },\n    )\n\n  def test_explicit_dense(self):\n    x = jnp.ones((1, 4))\n    y, variables = init(explicit_mlp)(random.key(0), x)\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(y.shape, (1, 1))\n    self.assertEqual(\n      param_shapes,\n      {\n        'dense_0': ExplicitDense((4, 3), (3,)),\n        'dense_1': ExplicitDense((3, 1), (1,)),\n      },\n    )\n\n  def test_semi_explicit_dense(self):\n    x = jnp.ones((1, 4))\n    y, variables = init(semi_explicit_mlp)(random.key(0), x)\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(y.shape, (1, 1))\n    self.assertEqual(\n      param_shapes,\n      {\n        'dense_0': {'kernel': (4, 3), 'bias': (3,)},\n        'dense_1': {'kernel': (3, 1), 'bias': (1,)},\n      },\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/design/core_flow_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom dataclasses import dataclass\nfrom typing import Any\nfrom collections.abc import Sequence\n\nimport jax\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\nfrom jax.scipy.linalg import expm\n\nfrom flax.core import Array, Scope, apply, init, nn, unfreeze\n\nInitializer = Any\nFlow = Any\n\n\n@dataclass\nclass DenseFlow:\n  kernel_init: Initializer = nn.linear.default_kernel_init\n  bias_init: Initializer = nn.initializers.zeros_init()\n\n  def params(self, scope: Scope, features: int):\n    kernel = scope.param('kernel', self.kernel_init, (features, features))\n    bias = scope.param('bias', self.bias_init, (features,))\n    return kernel, bias\n\n  def forward(self, scope: Scope, x: Array):\n    kernel, bias = self.params(scope, x.shape[-1])\n    return jnp.dot(x, expm(kernel)) + bias.reshape((1,) * (x.ndim - 1) + (-1,))\n\n  def backward(self, scope: Scope, y: Array):\n    kernel, bias = self.params(scope, y.shape[-1])\n    return jnp.dot(y - bias.reshape((1,) * (y.ndim - 1) + (-1,)), expm(-kernel))\n\n\n@dataclass\nclass StackFlow:\n  flows: Sequence[Flow]\n\n  def forward(self, scope: Scope, x: Array):\n    for i, f in enumerate(self.flows):\n      x = scope.child(f.forward, name=str(i))(x)\n    return x\n\n  def backward(self, scope: Scope, x: Array):\n    for i, f in reversed(tuple(enumerate(self.flows))):\n      x = scope.child(f.backward, name=str(i))(x)\n    return x\n\n\nclass FlowTest(absltest.TestCase):\n  def test_flow(self):\n    x = jnp.ones((1, 3))\n    flow = StackFlow((DenseFlow(),) * 3)\n    y, variables = init(flow.forward)(random.key(0), x)\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(y.shape, (1, 3))\n    self.assertEqual(\n      param_shapes,\n      {\n        '0': {'kernel': (3, 3), 'bias': (3,)},\n        '1': {'kernel': (3, 3), 'bias': (3,)},\n        '2': {'kernel': (3, 3), 'bias': (3,)},\n      },\n    )\n    x_restored = apply(flow.backward)(variables, y)\n    self.assertTrue(jnp.allclose(x, x_restored))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/design/core_resnet_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom functools import partial\n\nimport jax\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\n\nfrom flax.core import Array, Scope, init, nn, unfreeze\n\ndefault_norm = partial(nn.batch_norm)\n\n\ndef residual_block(\n  scope: Scope, x: Array, conv, norm, act, features: int, strides=(1, 1)\n):\n  residual = x\n  x = scope.child(conv, 'conv_1')(x, features, (1, 1))\n  x = scope.child(norm, 'bn_1')(x)\n  x = act(x)\n  x = scope.child(conv, 'conv_2')(x, 4 * features, (3, 3), strides=strides)\n  x = scope.child(norm, 'bn_2')(x)\n  x = act(x)\n  x = scope.child(conv, 'conv_3')(x, 4 * features, (1, 1))\n  x = scope.child(norm, 'bn_3')(x)\n\n  if x.shape != residual.shape:\n    residual = scope.child(conv, 'proj_conv')(\n      residual, 4 * features, (1, 1), strides=strides\n    )\n    residual = scope.child(norm, 'proj_bn')(residual)\n\n  return act(residual + x)\n\n\ndef resnet(\n  scope: Scope,\n  x,\n  block_sizes=(3, 4, 6, 3),\n  features=16,\n  num_classes=1000,\n  dtype=jnp.float32,\n  norm=default_norm,\n  act=nn.relu,\n):\n  conv = partial(nn.conv, bias=False, dtype=dtype)\n  norm = partial(norm, dtype=dtype)\n\n  x = scope.child(conv, 'init_conv')(x, 16, (7, 7), padding=((3, 3), (3, 3)))\n  x = scope.child(norm, 'init_bn')(x)\n  x = act(x)\n  x = nn.max_pool(x, (2, 2), (2, 2), 'SAME')\n\n  for i, size in enumerate(block_sizes):\n    for j in range(size):\n      strides = (1, 1)\n      if i > 0 and j == 0:\n        strides = (2, 2)\n      block_features = features * 2**i\n      block_scope = scope.push(f'block_{i}_{j}')\n      x = residual_block(\n        block_scope, x, conv, norm, act, block_features, strides\n      )\n      # we can access parameters of the sub module by operating on the scope\n      # Example:\n      # block_scope.get_kind('params')['conv_1']['kernel']\n  x = jnp.mean(x, (1, 2))\n  x = scope.child(nn.dense, 'out')(x, num_classes)\n  return x\n\n\nclass ResNetTest(absltest.TestCase):\n  def test_resnet(self):\n    block_sizes = (2, 2)\n    x = random.normal(random.key(0), (1, 64, 64, 3))\n    y, variables = init(resnet)(\n      random.key(1), x, block_sizes=block_sizes, features=16\n    )\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(y.shape, (1, 1000))\n\n    self.assertEqual(\n      param_shapes,\n      {\n        'init_conv': {'kernel': (7, 7, 3, 16)},\n        'init_bn': {'bias': (16,), 'scale': (16,)},\n        'out': {'kernel': (128, 1000), 'bias': (1000,)},\n        'block_0_0': {\n          'conv_1': {'kernel': (1, 1, 16, 16)},\n          'conv_2': {'kernel': (3, 3, 16, 64)},\n          'conv_3': {'kernel': (1, 1, 64, 64)},\n          'bn_1': {'bias': (16,), 'scale': (16,)},\n          'bn_2': {'bias': (64,), 'scale': (64,)},\n          'bn_3': {'bias': (64,), 'scale': (64,)},\n          'proj_conv': {'kernel': (1, 1, 16, 64)},\n          'proj_bn': {'bias': (64,), 'scale': (64,)},\n        },\n        'block_0_1': {\n          'conv_1': {'kernel': (1, 1, 64, 16)},\n          'conv_2': {'kernel': (3, 3, 16, 64)},\n          'conv_3': {'kernel': (1, 1, 64, 64)},\n          'bn_1': {'bias': (16,), 'scale': (16,)},\n          'bn_2': {'bias': (64,), 'scale': (64,)},\n          'bn_3': {'bias': (64,), 'scale': (64,)},\n        },\n        'block_1_0': {\n          'conv_1': {'kernel': (1, 1, 64, 32)},\n          'conv_2': {'kernel': (3, 3, 32, 128)},\n          'conv_3': {'kernel': (1, 1, 128, 128)},\n          'bn_1': {'bias': (32,), 'scale': (32,)},\n          'bn_2': {'bias': (128,), 'scale': (128,)},\n          'bn_3': {'bias': (128,), 'scale': (128,)},\n          'proj_conv': {'kernel': (1, 1, 64, 128)},\n          'proj_bn': {'bias': (128,), 'scale': (128,)},\n        },\n        'block_1_1': {\n          'conv_1': {'kernel': (1, 1, 128, 32)},\n          'conv_2': {'kernel': (3, 3, 32, 128)},\n          'conv_3': {'kernel': (1, 1, 128, 128)},\n          'bn_1': {'bias': (32,), 'scale': (32,)},\n          'bn_2': {'bias': (128,), 'scale': (128,)},\n          'bn_3': {'bias': (128,), 'scale': (128,)},\n        },\n      },\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/design/core_scan_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\n\nfrom flax.core import Array, Scope, init, lift, nn, unfreeze\n\n\ndef mlp_scan(scope: Scope, xs: Array, share_params: bool = False):\n  scope.variable('counter', 'i', jnp.zeros, ())\n\n  def body_fn(scope, c, x):\n    counter = scope.variable('counter', 'i', jnp.zeros, ())\n    counter.value += 1\n    x = scope.child(nn.dense)(x, 1)\n    return c, x\n\n  if share_params:\n    _, ys = lift.scan(\n      body_fn,\n      variable_carry='counter',\n      variable_broadcast='params',\n      split_rngs={'params': False},\n    )(scope, (), xs)\n  else:\n    _, ys = lift.scan(\n      body_fn,\n      variable_carry='counter',\n      variable_axes={'params': 0},\n      split_rngs={'params': True},\n    )(scope, (), xs)\n\n  # output layer\n  return ys\n\n\nclass ScanTest(absltest.TestCase):\n  def test_scan_unshared_params(self):\n    x = random.normal(random.key(0), (1, 4))\n    x = jnp.concatenate([x, x], 0)\n    y, variables = init(mlp_scan)(random.key(1), x, share_params=False)\n\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(variables['counter']['i'], 2)\n    self.assertEqual(\n      param_shapes,\n      {\n        'dense_0': {'kernel': (2, 4, 1), 'bias': (2, 1)},\n      },\n    )\n\n    self.assertNotEqual(y[0], y[1])\n    k1, k2 = variables['params']['dense_0']['kernel']\n    self.assertFalse(jnp.allclose(k1, k2))\n\n  def test_scan_shared_params(self):\n    x = random.normal(random.key(0), (1, 4))\n    x = jnp.concatenate([x, x], 0)\n    y, variables = init(mlp_scan)(random.key(1), x, share_params=True)\n\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(variables['counter']['i'], 2)\n    self.assertEqual(\n      param_shapes,\n      {\n        'dense_0': {'kernel': (4, 1), 'bias': (1,)},\n      },\n    )\n\n    self.assertEqual(y[0], y[1])\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/design/core_tied_autoencoder_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom dataclasses import dataclass\n\nimport jax\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\n\nfrom flax.core import init, lift, nn, unfreeze\n\n\ndef transpose(fn):\n  def trans(variables):\n    return jax.tree_util.tree_map(lambda x: x.T, variables)\n\n  return lift.map_variables(\n    fn, 'params', map_in_fn=trans, map_out_fn=trans, mutable=True\n  )\n\n\n@dataclass\nclass TiedAutoEncoder:\n  latents: int\n  features: int\n\n  def __call__(self, scope, x):\n    z = self.encode(scope, x)\n    return self.decode(scope, z)\n\n  def encode(self, scope, x):\n    return nn.dense(scope, x, self.latents, bias=False)\n\n  def decode(self, scope, z):\n    return transpose(nn.dense)(scope, z, self.features, bias=False)\n\n\nclass TiedAutoEncoderTest(absltest.TestCase):\n  def test_tied_auto_encoder(self):\n    ae = TiedAutoEncoder(latents=2, features=4)\n    x = jnp.ones((1, ae.features))\n    x_r, variables = init(ae)(random.key(0), x)\n\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(\n      param_shapes,\n      {\n        'kernel': (4, 2),\n      },\n    )\n    self.assertEqual(x.shape, x_r.shape)\n\n  def test_init_from_decoder(self):\n    ae = TiedAutoEncoder(latents=2, features=4)\n    z = jnp.ones((1, ae.latents))\n    x_r, variables = init(ae.decode)(random.key(0), z)\n\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(\n      param_shapes,\n      {\n        'kernel': (4, 2),\n      },\n    )\n    self.assertEqual(x_r.shape, (1, 4))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/design/core_vmap_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom collections.abc import Callable, Sequence\n\nimport jax\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\n\nfrom flax.core import Array, Scope, init, lift, nn, unfreeze\n\n\ndef mlp_vmap(\n  scope: Scope,\n  x: Array,\n  sizes: Sequence[int] = (8, 1),\n  act_fn: Callable[[Array], Array] = nn.relu,\n  share_params: bool = False,\n):\n  if share_params:\n    dense_vmap = lift.vmap(\n      nn.dense,\n      in_axes=(0, None),\n      variable_axes={'params': None},\n      split_rngs={'params': False},\n    )\n  else:\n    dense_vmap = lift.vmap(\n      nn.dense,\n      in_axes=(0, None),\n      variable_axes={'params': 0},\n      split_rngs={'params': True},\n    )\n\n  # hidden layers\n  for size in sizes[:-1]:\n    x = scope.child(dense_vmap, prefix='hidden_')(x, size)\n    x = act_fn(x)\n\n  # output layer\n  return scope.child(dense_vmap, 'out')(x, sizes[-1])\n\n\nclass VMapTest(absltest.TestCase):\n  def test_vmap_shared(self):\n    x = random.normal(random.key(0), (1, 4))\n    x = jnp.concatenate([x, x], 0)\n\n    y, variables = init(mlp_vmap)(random.key(1), x, share_params=True)\n\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(\n      param_shapes,\n      {\n        'hidden_0': {'kernel': (4, 8), 'bias': (8,)},\n        'out': {'kernel': (8, 1), 'bias': (1,)},\n      },\n    )\n    self.assertEqual(y.shape, (2, 1))\n    self.assertTrue(jnp.allclose(y[0], y[1]))\n\n  def test_vmap_unshared(self):\n    x = random.normal(random.key(0), (1, 4))\n    x = jnp.concatenate([x, x], 0)\n\n    y, variables = init(mlp_vmap)(random.key(1), x, share_params=False)\n\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(\n      param_shapes,\n      {\n        'hidden_0': {'kernel': (2, 4, 8), 'bias': (2, 8)},\n        'out': {'kernel': (2, 8, 1), 'bias': (2, 1)},\n      },\n    )\n    self.assertEqual(y.shape, (2, 1))\n    self.assertFalse(jnp.allclose(y[0], y[1]))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/core/design/core_weight_std_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom functools import partial\nfrom collections.abc import Sequence\n\nimport jax\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\n\nfrom flax.core import Array, Scope, apply, init, lift, nn, unfreeze\n\n\ndef weight_std(fn, kernel_name='kernel', eps=1e-8):\n  def std(variables):\n    params = variables['params']\n    assert kernel_name in params\n    kernel = params[kernel_name]\n    redux = tuple(range(kernel.ndim - 1))\n    norm = jnp.square(kernel).sum(redux, keepdims=True)\n    std_kernel = kernel / jnp.sqrt(norm + eps)\n    params[kernel_name] = std_kernel\n    return variables\n\n  # map_variables handles a few of nasty edge cases here...\n  # the transformed kind will be immutable inside fn\n  # this way we avoid lost mutations to param\n  # map_variables also avoids accidental reuse of rngs\n  # and it makes sure that other state is updated correctly (not twice during init!)\n  return lift.map_variables(fn, 'params', std, init=True)\n\n\ndef mlp(scope: Scope, x: Array, sizes: Sequence[int] = (8, 1)):\n  std_dense = weight_std(\n    partial(nn.dense, kernel_init=nn.initializers.normal(stddev=1e5))\n  )\n  for size in sizes[:-1]:\n    x = scope.child(std_dense, prefix='hidden_')(x, size)\n  return scope.child(nn.dense, 'out')(x, sizes[-1])\n\n\nclass WeightStdTest(absltest.TestCase):\n  def test_weight_std(self):\n    x = random.normal(\n      random.key(0),\n      (\n        1,\n        4,\n      ),\n    )\n    y, variables = init(mlp)(random.key(1), x)\n\n    param_shapes = unfreeze(\n      jax.tree_util.tree_map(jnp.shape, variables['params'])\n    )\n    self.assertEqual(\n      param_shapes,\n      {\n        'hidden_0': {'kernel': (4, 8), 'bias': (8,)},\n        'out': {'kernel': (8, 1), 'bias': (1,)},\n      },\n    )\n    self.assertEqual(y.shape, (1, 1))\n    self.assertTrue(y.ravel() < 1.0)\n\n    y2 = apply(mlp)(variables, x)\n    self.assertTrue(jnp.allclose(y, y2))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/cursor_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.struct.\"\"\"\n\n\nimport dataclasses\nfrom typing import Any, NamedTuple\n\nimport jax\nimport jax.numpy as jnp\nimport optax\nfrom absl.testing import absltest\n\nimport flax\nimport flax.linen as nn\nfrom flax.core import freeze\nfrom flax.cursor import AccessType, _traverse_tree, cursor\nfrom flax.errors import CursorFindError, TraverseTreeError\nfrom flax.training import train_state\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass GenericTuple(NamedTuple):\n  x: Any\n  y: Any = None\n  z: Any = None\n\n\n@dataclasses.dataclass\nclass GenericDataClass:\n  x: Any\n  y: Any = None\n  z: Any = None\n\n\nclass CursorTest(absltest.TestCase):\n  def test_repr(self):\n    g = GenericTuple(1, 'a', (2, 'b'))\n    c = cursor(\n      {'a': {1: {(2, 3): 'z', 4: g, '6': (7, 8)}, 'b': [1, 2, 3]}, 'z': -1}\n    )\n    self.assertEqual(\n      repr(c),\n      \"\"\"Cursor(\n  _obj={'a': {1: {(2, 3): 'z', 4: GenericTuple(x=1, y='a', z=(2, 'b')), '6': (7, 8)}, 'b': [1, 2, 3]}, 'z': -1},\n  _changes={}\n)\"\"\",\n    )\n\n    # test overwriting\n    c['z'] = -2\n    c['z'] = -3\n    c['a']['b'][1] = -2\n    c['a']['b'] = None\n\n    # test deep mutation\n    c['a'][1][4].x = (2, 4, 6)\n    c['a'][1][4].z[0] = flax.core.freeze({'a': 1, 'b': {'c': 2, 'd': 3}})\n\n    self.assertEqual(\n      repr(c),\n      \"\"\"Cursor(\n  _obj={'a': {1: {(2, 3): 'z', 4: GenericTuple(x=1, y='a', z=(2, 'b')), '6': (7, 8)}, 'b': [1, 2, 3]}, 'z': -1},\n  _changes={\n    'z': Cursor(\n           _obj=-3,\n           _changes={}\n         ),\n    'a': Cursor(\n           _obj={1: {(2, 3): 'z', 4: GenericTuple(x=1, y='a', z=(2, 'b')), '6': (7, 8)}, 'b': [1, 2, 3]},\n           _changes={\n             'b': Cursor(\n                    _obj=None,\n                    _changes={}\n                  ),\n             1: Cursor(\n                  _obj={(2, 3): 'z', 4: GenericTuple(x=1, y='a', z=(2, 'b')), '6': (7, 8)},\n                  _changes={\n                    4: Cursor(\n                         _obj=GenericTuple(x=1, y='a', z=(2, 'b')),\n                         _changes={\n                           'x': Cursor(\n                                  _obj=(2, 4, 6),\n                                  _changes={}\n                                ),\n                           'z': Cursor(\n                                  _obj=(2, 'b'),\n                                  _changes={\n                                    0: Cursor(\n                                         _obj=FrozenDict({\n                                             a: 1,\n                                             b: {\n                                                 c: 2,\n                                                 d: 3,\n                                             },\n                                         }),\n                                         _changes={}\n                                       )\n                                  }\n                                )\n                         }\n                       )\n                  }\n                )\n           }\n         )\n  }\n)\"\"\",\n    )\n\n  def test_magic_methods(self):\n    def same_value(v1, v2):\n      if isinstance(v1, tuple):\n        return all([\n            jnp.all(jax.tree_util.tree_map(lambda x, y: x == y, e1, e2))\n            for e1, e2 in zip(v1, v2)\n        ])\n      return jnp.all(jax.tree_util.tree_map(lambda x, y: x == y, v1, v2))\n\n    list_obj = [(1, 2), (3, 4)]\n    for l, tuple_wrap in ((list_obj, lambda x: x), (tuple(list_obj), tuple)):\n      c = cursor(l)\n      # test __len__\n      self.assertTrue(same_value(len(c), len(l)))\n      # test __iter__\n      for i, child_c in enumerate(c):\n        child_c[1] += i + 1\n      self.assertEqual(c.build(), tuple_wrap([(1, 3), (3, 6)]))\n      # test __reversed__\n      for i, child_c in enumerate(reversed(c)):\n        child_c[1] += i + 1\n      self.assertEqual(c.build(), tuple_wrap([(1, 5), (3, 7)]))\n    # test __iter__ error\n    with self.assertRaisesRegex(\n      NotImplementedError,\n      '__iter__ method only implemented for tuples and lists, not type <class'\n      \" 'dict'>\",\n    ):\n      c = cursor({'a': 1, 'b': 2})\n      for key in c:\n        c[key] *= -1\n    # test __iter__ error\n    with self.assertRaisesRegex(\n      NotImplementedError,\n      '__reversed__ method only implemented for tuples and lists, not type'\n      \" <class 'dict'>\",\n    ):\n      c = cursor({'a': 1, 'b': 2})\n      for key in reversed(c):\n        c[key] *= -1\n\n    for obj_value in (2, jnp.array([[1, -2], [3, 4]])):\n      for c in (\n        cursor(obj_value),\n        cursor([obj_value])[0],\n        cursor((obj_value,))[0],\n        cursor({0: obj_value})[0],\n        cursor(flax.core.freeze({0: obj_value}))[0],\n        cursor(GenericTuple(x=obj_value)).x,\n        cursor(GenericDataClass(x=obj_value)).x,\n      ):\n        # test __neg__\n        self.assertTrue(same_value(-c, -obj_value))\n        # test __pos__\n        self.assertTrue(same_value(+c, +obj_value))\n        # test __abs__\n        self.assertTrue(same_value(abs(-c), abs(-obj_value)))\n        # test __invert__\n        self.assertTrue(same_value(~c, ~obj_value))\n        # test __round__\n        self.assertTrue(same_value(round(c + 0.123), round(obj_value + 0.123)))\n        self.assertTrue(\n          same_value(round(c + 0.123, 2), round(obj_value + 0.123, 2))\n        )\n\n        for other_value in (3, jnp.array([[5, 6], [7, 8]])):\n          # test __add__\n          self.assertTrue(same_value(c + other_value, obj_value + other_value))\n          # test __radd__\n          self.assertTrue(same_value(other_value + c, other_value + obj_value))\n          # test __sub__\n          self.assertTrue(same_value(c - other_value, obj_value - other_value))\n          # test __rsub__\n          self.assertTrue(same_value(other_value - c, other_value - obj_value))\n          # test __mul__\n          self.assertTrue(same_value(c * other_value, obj_value * other_value))\n          # test __rmul__\n          self.assertTrue(same_value(other_value * c, other_value * obj_value))\n          # test __truediv__\n          self.assertTrue(same_value(c / other_value, obj_value / other_value))\n          # test __rtruediv__\n          self.assertTrue(same_value(other_value / c, other_value / obj_value))\n          # test __floordiv__\n          self.assertTrue(\n            same_value(c // other_value, obj_value // other_value)\n          )\n          # test __rfloordiv__\n          self.assertTrue(\n            same_value(other_value // c, other_value // obj_value)\n          )\n          # test __mod__\n          self.assertTrue(same_value(c % other_value, obj_value % other_value))\n          # test __rmod__\n          self.assertTrue(same_value(other_value % c, other_value % obj_value))\n          # test __divmod__\n          self.assertTrue(\n            same_value(divmod(c, other_value), divmod(obj_value, other_value))\n          )\n          # test __rdivmod__\n          self.assertTrue(\n            same_value(divmod(other_value, c), divmod(other_value, obj_value))\n          )\n          # test __pow__\n          self.assertTrue(\n            same_value(pow(c, other_value), pow(obj_value, other_value))\n          )\n          # test __rpow__\n          self.assertTrue(\n            same_value(pow(other_value, c), pow(other_value, obj_value))\n          )\n          # test __lshift__\n          self.assertTrue(\n            same_value(c << other_value, obj_value << other_value)\n          )\n          # test __rlshift__\n          self.assertTrue(\n            same_value(other_value << c, other_value << obj_value)\n          )\n          # test __rshift__\n          self.assertTrue(\n            same_value(c >> other_value, obj_value >> other_value)\n          )\n          # test __rrshift__\n          self.assertTrue(\n            same_value(other_value >> c, other_value >> obj_value)\n          )\n          # test __and__\n          self.assertTrue(same_value(c & other_value, obj_value & other_value))\n          # test __rand__\n          self.assertTrue(same_value(other_value & c, other_value & obj_value))\n          # test __xor__\n          self.assertTrue(same_value(c ^ other_value, obj_value ^ other_value))\n          # test __rxor__\n          self.assertTrue(same_value(other_value ^ c, other_value ^ obj_value))\n          # test __or__\n          self.assertTrue(same_value(c | other_value, obj_value | other_value))\n          # test __ror__\n          self.assertTrue(same_value(other_value | c, other_value | obj_value))\n\n          if isinstance(obj_value, jax.Array) and isinstance(\n            other_value, jax.Array\n          ):\n            # test __matmul__\n            self.assertTrue(\n              same_value(c @ other_value, obj_value @ other_value)\n            )\n            # test __rmatmul__\n            self.assertTrue(\n              same_value(other_value @ c, other_value @ obj_value)\n            )\n\n          # test __lt__\n          self.assertTrue(same_value(c < other_value, obj_value < other_value))\n          self.assertTrue(same_value(other_value < c, other_value < obj_value))\n          # test __le__\n          self.assertTrue(\n            same_value(c <= other_value, obj_value <= other_value)\n          )\n          self.assertTrue(\n            same_value(other_value <= c, other_value <= obj_value)\n          )\n          # test __eq__\n          self.assertTrue(\n            same_value(c == other_value, obj_value == other_value)\n          )\n          self.assertTrue(\n            same_value(other_value == c, other_value == obj_value)\n          )\n          # test __ne__\n          self.assertTrue(\n            same_value(c != other_value, obj_value != other_value)\n          )\n          self.assertTrue(\n            same_value(other_value != c, other_value != obj_value)\n          )\n          # test __gt__\n          self.assertTrue(same_value(c > other_value, obj_value > other_value))\n          self.assertTrue(same_value(other_value > c, other_value > obj_value))\n          # test __ge__\n          self.assertTrue(\n            same_value(c >= other_value, obj_value >= other_value)\n          )\n          self.assertTrue(\n            same_value(other_value >= c, other_value >= obj_value)\n          )\n\n  def test_path(self):\n    c = cursor(\n      GenericTuple(\n        x=[\n          0,\n          {'a': 1, 'b': (2, 3), ('c', 'd'): [4, 5]},\n          (100, 200),\n          [3, 4, 5],\n        ],\n        y=train_state.TrainState.create(\n          apply_fn=lambda x: x,\n          params=freeze({'a': 1, 'b': (2, 3), 'c': [4, 5]}),\n          tx=optax.adam(1e-3),\n        ),\n      )\n    )\n    self.assertEqual(c.x[1][('c', 'd')][0]._path, \".x[1][('c', 'd')][0]\")\n    self.assertEqual(c.x[2][1]._path, '.x[2][1]')\n    self.assertEqual(c.y.params['b'][1]._path, \".y.params['b'][1]\")\n\n    # test path when first access type is item access\n    c = cursor([1, GenericTuple('a', 2), (3, 4)])\n    self.assertEqual(c[1].x._path, '[1].x')\n    self.assertEqual(c[2][0]._path, '[2][0]')\n\n  def test_traverse_tree(self):\n    c = cursor(\n      GenericTuple(\n        x=[\n          0,\n          {'a': 1, 'b': (2, 3), ('c', 'd'): [4, 5]},\n          (100, 200),\n          [3, 4, 5],\n        ],\n        y=3,\n      )\n    )\n\n    def update_fn(path, value):\n      if value == 4:\n        return -4\n      return value\n\n    def cond_fn(path, value):\n      return value == 3\n\n    with self.assertRaisesRegex(\n      TraverseTreeError,\n      'Both update_fn and cond_fn are None. Exactly one of them must be'\n      ' None.',\n    ):\n      next(_traverse_tree((), c._obj))\n    with self.assertRaisesRegex(\n      TraverseTreeError,\n      'Both update_fn and cond_fn are not None. Exactly one of them must be'\n      ' not None.',\n    ):\n      next(_traverse_tree((), c._obj, update_fn=update_fn, cond_fn=cond_fn))\n\n    (p, v), (p2, v2) = _traverse_tree((), c._obj, update_fn=update_fn)\n    self.assertEqual(\n      p,\n      (\n        ('x', AccessType.ATTR),\n        (1, AccessType.ITEM),\n        (('c', 'd'), AccessType.ITEM),\n        (0, AccessType.ITEM),\n      ),\n    )\n    self.assertEqual(v, -4)\n    self.assertEqual(\n      p2, (('x', AccessType.ATTR), (3, AccessType.ITEM), (1, AccessType.ITEM))\n    )\n    self.assertEqual(v2, -4)\n\n    p, p2, p3 = _traverse_tree((), c._obj, cond_fn=cond_fn)\n    self.assertEqual(\n      p,\n      (\n        ('x', AccessType.ATTR),\n        (1, AccessType.ITEM),\n        ('b', AccessType.ITEM),\n        (1, AccessType.ITEM),\n      ),\n    )\n    self.assertEqual(\n      p2, (('x', AccessType.ATTR), (3, AccessType.ITEM), (0, AccessType.ITEM))\n    )\n    self.assertEqual(p3, (('y', AccessType.ATTR),))\n\n  def test_set_and_build(self):\n    # test regular dict and FrozenDict\n    dict_obj = {'a': 1, 'b': (2, 3), 'c': [4, 5]}\n    for d, freeze_wrap in ((dict_obj, lambda x: x), (freeze(dict_obj), freeze)):\n      # set API\n      self.assertEqual(\n        cursor(d)['b'][0].set(10),\n        freeze_wrap({'a': 1, 'b': (10, 3), 'c': [4, 5]}),\n      )\n      # build API\n      c = cursor(d)\n      c['b'][0] = 20\n      c['a'] = (100, 200)\n      d2 = c.build()\n      self.assertEqual(\n        d2, freeze_wrap({'a': (100, 200), 'b': (20, 3), 'c': [4, 5]})\n      )\n    self.assertEqual(\n      dict_obj, {'a': 1, 'b': (2, 3), 'c': [4, 5]}\n    )  # make sure original object is unchanged\n\n    # test list and tuple\n    list_obj = [0, dict_obj, (1, 2), [3, 4, 5]]\n    for l, tuple_wrap in ((list_obj, lambda x: x), (tuple(list_obj), tuple)):\n      # set API\n      self.assertEqual(\n        cursor(l)[1]['b'][0].set(10),\n        tuple_wrap([0, {'a': 1, 'b': (10, 3), 'c': [4, 5]}, (1, 2), [3, 4, 5]]),\n      )\n      # build API\n      c = cursor(l)\n      c[1]['b'][0] = 20\n      c[2] = (100, 200)\n      l2 = c.build()\n      self.assertEqual(\n        l2,\n        tuple_wrap(\n          [0, {'a': 1, 'b': (20, 3), 'c': [4, 5]}, (100, 200), [3, 4, 5]]\n        ),\n      )\n    self.assertEqual(\n      list_obj, [0, {'a': 1, 'b': (2, 3), 'c': [4, 5]}, (1, 2), [3, 4, 5]]\n    )  # make sure original object is unchanged\n\n    # test TrainState\n    state = train_state.TrainState.create(\n      apply_fn=lambda x: x,\n      params=dict_obj,\n      tx=optax.adam(1e-3),\n    )\n    # set API\n    self.assertEqual(\n      cursor(state).params['b'][0].set(10).params,\n      {'a': 1, 'b': (10, 3), 'c': [4, 5]},\n    )\n    # build API\n    new_fn = lambda x: x + 1\n    c = cursor(state)\n    c.apply_fn = new_fn\n    c.params['b'][0] = 20\n    c.params['a'] = (100, 200)\n    state2 = c.build()\n    self.assertEqual(state2.apply_fn, new_fn)\n    self.assertEqual(\n      state2.params, {'a': (100, 200), 'b': (20, 3), 'c': [4, 5]}\n    )\n\n    self.assertEqual(\n      dict_obj, {'a': 1, 'b': (2, 3), 'c': [4, 5]}\n    )  # make sure original object is unchanged\n\n    # test NamedTuple\n    # set API\n    t = GenericTuple(GenericTuple(0))\n    self.assertEqual(cursor(t).x.x.set(1), GenericTuple(GenericTuple(1)))\n\n    # build API\n    c = cursor(t)\n    c.x.x = 2\n    c.x.y = 3\n    c.y = 4\n    t2 = c.build()\n    self.assertEqual(t2, GenericTuple(GenericTuple(2, 3), 4))\n\n    self.assertEqual(\n      t, GenericTuple(GenericTuple(0))\n    )  # make sure original object is unchanged\n\n  def test_apply_update(self):\n    # test list and tuple\n    def update_fn(path, value):\n      \"\"\"Multiply the first element of all leaf nodes of the pytree by -1.\"\"\"\n      if path[-1] == '0' and isinstance(value, int):\n        return value * -1\n      return value\n\n    for tuple_wrap in (lambda x: x, tuple):\n      l = tuple_wrap([tuple_wrap([1, 2]), tuple_wrap([3, 4])])\n      c = cursor(l)\n      l2 = c.apply_update(update_fn).build()\n      self.assertEqual(\n        l2, tuple_wrap([tuple_wrap([-1, 2]), tuple_wrap([-3, 4])])\n      )\n      self.assertEqual(\n        l, tuple_wrap([tuple_wrap([1, 2]), tuple_wrap([3, 4])])\n      )  # make sure the original object is unchanged\n\n    # test regular dict and FrozenDict\n    def update_fn(path, value):\n      \"\"\"Multiply all dense kernel params by 2 and add 1.\n      Subtract the Dense_1 bias param by 1.\"\"\"\n      if 'kernel' in path:\n        return value * 2 + 1\n      elif 'Dense_1' in path and 'bias' in path:\n        return value - 1\n      return value\n\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = nn.Dense(3)(x)\n        x = nn.relu(x)\n        x = nn.Dense(3)(x)\n        x = nn.relu(x)\n        x = nn.Dense(3)(x)\n        x = nn.relu(x)\n        return x\n\n    for freeze_wrap in (lambda x: x, freeze):\n      params = freeze_wrap(\n        Model().init(jax.random.key(0), jnp.empty((1, 2)))['params']\n      )\n\n      c = cursor(params)\n      params2 = c.apply_update(update_fn).build()\n      for layer in ('Dense_0', 'Dense_1', 'Dense_2'):\n        self.assertTrue(\n          (params2[layer]['kernel'] == 2 * params[layer]['kernel'] + 1).all()\n        )\n        if layer == 'Dense_1':\n          self.assertTrue(\n            (params2[layer]['bias'] == jnp.array([-1, -1, -1])).all()\n          )\n        else:\n          self.assertTrue(\n            (params2[layer]['bias'] == params[layer]['bias']).all()\n          )\n      self.assertTrue(\n        jax.tree_util.tree_all(\n          jax.tree_util.tree_map(\n            lambda x, y: (x == y).all(),\n            params,\n            freeze_wrap(\n              Model().init(jax.random.key(0), jnp.empty((1, 2)))['params']\n            ),\n          )\n        )\n      )  # make sure original params are unchanged\n\n    # test TrainState\n    def update_fn(path, value):\n      \"\"\"Replace params with empty dictionary.\"\"\"\n      if 'params' in path:\n        return {}\n      return value\n\n    state = train_state.TrainState.create(\n      apply_fn=lambda x: x,\n      params={'a': 1, 'b': 2},\n      tx=optax.adam(1e-3),\n    )\n    c = cursor(state)\n    state2 = c.apply_update(update_fn).build()\n    self.assertEqual(state2.params, {})\n    self.assertEqual(\n      state.params, {'a': 1, 'b': 2}\n    )  # make sure original params are unchanged\n\n    # test NamedTuple\n    def update_fn(path, value):\n      \"\"\"Add 5 to all x-attribute values that are ints.\"\"\"\n      if path[-1] == 'x' and isinstance(value, int):\n        return value + 5\n      return value\n\n    t = GenericTuple(GenericTuple(0, 1), GenericTuple(2, 3))\n    c = cursor(t)\n    t2 = c.apply_update(update_fn).build()\n    self.assertEqual(t2, GenericTuple(GenericTuple(5, 1), GenericTuple(7, 3)))\n    self.assertEqual(\n      t, GenericTuple(GenericTuple(0, 1), GenericTuple(2, 3))\n    )  # make sure original object is unchanged\n\n  def test_apply_update_root_node_unmodified(self):\n    def update_fn(path, value):\n      if isinstance(value, list):\n        value = value.copy()\n        value.append(-1)\n      return value\n\n    l = [[1, 2], [3, 4], 5]\n    l2 = cursor(l).apply_update(update_fn).build()\n    self.assertEqual(l2, [[1, 2, -1], [3, 4, -1], 5])\n\n  def test_multi_modify(self):\n    d = {'a': 1, 'b': (2, 3), 'c': [4, 5]}\n    c = cursor(d)\n    # test multiple changes on same element\n    c['b'][0] = 6\n    c['b'][0] = 7\n    d2 = c.build()\n    self.assertEqual(d2, {'a': 1, 'b': (7, 3), 'c': [4, 5]})\n    # test nested changes\n    c['a'] = (100, 200)\n    c['a'][1] = -1\n    d3 = c.build()\n    self.assertEqual(d3, {'a': (100, -1), 'b': (7, 3), 'c': [4, 5]})\n\n  def test_hidden_change(self):\n    # test list\n    l = [1, 2]\n    c = cursor(l)\n    c[0] = 100\n    l[1] = -1\n    l2 = c.build()\n    self.assertEqual(\n      l2, [100, -1]\n    )  # change in l affects l2 (this is expected behavior)\n    self.assertEqual(l, [1, -1])\n\n    # test regular dict\n    d = {'a': 1, 'b': 2}\n    c = cursor(d)\n    c['a'] = 100\n    d['b'] = -1\n    d2 = c.build()\n    self.assertEqual(\n      d2, {'a': 100, 'b': -1}\n    )  # change in d affects d2 (this is expected behavior)\n    self.assertEqual(d, {'a': 1, 'b': -1})\n\n    # test TrainState\n    params = {'a': 1, 'b': 2}\n    state = train_state.TrainState.create(\n      apply_fn=lambda x: x,\n      params=params,\n      tx=optax.adam(1e-3),\n    )\n    c = cursor(state)\n    c.params['a'] = 100\n    params['b'] = -1\n    state2 = c.build()\n    self.assertEqual(\n      state2.params, {'a': 100, 'b': -1}\n    )  # change in state affects state2 (this is expected behavior)\n    self.assertEqual(state.params, {'a': 1, 'b': -1})\n\n  def test_named_tuple_multi_access(self):\n    t = GenericTuple(GenericTuple(0, 1), GenericTuple(2, 3))\n    c = cursor(t)\n    c.x.x = 4\n    c[0].y = 5\n    c.y.x = 6\n    c.y[1] = 7\n    self.assertEqual(\n      c.build(), GenericTuple(GenericTuple(4, 5), GenericTuple(6, 7))\n    )\n\n    c[0][1] = -5\n    self.assertEqual(\n      c.build(), GenericTuple(GenericTuple(4, -5), GenericTuple(6, 7))\n    )\n    c.x[1] = -6\n    self.assertEqual(\n      c.build(), GenericTuple(GenericTuple(4, -6), GenericTuple(6, 7))\n    )\n    c.x.y = -7\n    self.assertEqual(\n      c.build(), GenericTuple(GenericTuple(4, -7), GenericTuple(6, 7))\n    )\n    c[0].y = -8\n    self.assertEqual(\n      c.build(), GenericTuple(GenericTuple(4, -8), GenericTuple(6, 7))\n    )\n\n  def test_find(self):\n    c = cursor(\n      GenericTuple(\n        x=[\n          0,\n          {'a': 1, 'b': (2, 3), ('c', 'd'): [4, 5]},\n          (100, 200),\n          [3, 4, 5],\n        ],\n        y=train_state.TrainState.create(\n          apply_fn=lambda x: x,\n          params=freeze({'a': 1, 'b': (2, 3), 'c': [4, 5]}),\n          tx=optax.adam(1e-3),\n        ),\n      )\n    )\n\n    with self.assertRaisesRegex(\n      CursorFindError,\n      'More than one object found given the conditions of the cond_fn\\\\. '\n      'The first two objects found have the following paths: '\n      \"\\\\.x\\\\[1]\\\\['b'] and \\\\.y\\\\.params\\\\['b'] \",\n    ):\n      c.find(lambda path, value: 'b' in path and isinstance(value, tuple))\n    with self.assertRaisesRegex(\n      CursorFindError,\n      'No object found given the conditions of the cond_fn\\\\.',\n    ):\n      c.find(lambda path, value: 'b' in path and isinstance(value, str))\n\n    self.assertEqual(\n      c.find(lambda path, value: path.endswith('params/b'))[1].set(30).y.params,\n      freeze({'a': 1, 'b': (2, 30), 'c': [4, 5]}),\n    )\n\n  def test_find_all(self):\n    # test list and tuple\n    def cond_fn(path, value):\n      \"\"\"Get all lists that are not the first element in its parent.\"\"\"\n      return path[-1] != '0' and isinstance(value, (tuple, list))\n\n    for tuple_wrap in (lambda x: x, tuple):\n      l = tuple_wrap(\n        [tuple_wrap([1, 2]), tuple_wrap([3, 4]), tuple_wrap([5, 6])]\n      )\n      c = cursor(l)\n      c2, c3 = c.find_all(cond_fn)\n      c2[0] *= -1\n      c3[1] *= -2\n      self.assertEqual(\n        c.build(),\n        tuple_wrap(\n          [tuple_wrap([1, 2]), tuple_wrap([-3, 4]), tuple_wrap([5, -12])]\n        ),\n      )\n      self.assertEqual(\n        l,\n        tuple_wrap(\n          [tuple_wrap([1, 2]), tuple_wrap([3, 4]), tuple_wrap([5, 6])]\n        ),\n      )  # make sure the original object is unchanged\n\n    # test regular dict and FrozenDict\n    def cond_fn(path, value):\n      \"\"\"Get the second and third dense params.\"\"\"\n      return 'Dense_1' in path or 'Dense_2' in path\n\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = nn.Dense(3)(x)\n        x = nn.relu(x)\n        x = nn.Dense(3)(x)\n        x = nn.relu(x)\n        x = nn.Dense(3)(x)\n        x = nn.relu(x)\n        return x\n\n    for freeze_wrap in (lambda x: x, freeze):\n      params = freeze_wrap(\n        Model().init(jax.random.PRNGKey(0), jnp.empty((1, 2)))['params']\n      )\n      c = cursor(params)\n      for i, c2 in enumerate(c.find_all(cond_fn)):\n        self.assertEqual(\n          c2['kernel'].set(123)[f'Dense_{i+1}'],\n          freeze_wrap({'kernel': 123, 'bias': params[f'Dense_{i+1}']['bias']}),\n        )\n      self.assertTrue(\n        jax.tree_util.tree_all(\n          jax.tree_util.tree_map(\n            lambda x, y: (x == y).all(),\n            params,\n            freeze_wrap(\n              Model().init(jax.random.PRNGKey(0), jnp.empty((1, 2)))['params']\n            ),\n          )\n        )\n      )  # make sure original params are unchanged\n\n    # test TrainState\n    def cond_fn(path, value):\n      \"\"\"Find TrainState params.\"\"\"\n      return 'params' in path\n\n    state = train_state.TrainState.create(\n      apply_fn=lambda x: x,\n      params={'a': 1, 'b': 2},\n      tx=optax.adam(1e-3),\n    )\n    c = cursor(state)\n    c2 = list(c.find_all(cond_fn))\n    self.assertEqual(len(c2), 1)\n    c2 = c2[0]\n    self.assertEqual(c2['b'].set(-1).params, {'a': 1, 'b': -1})\n    self.assertEqual(\n      state.params, {'a': 1, 'b': 2}\n    )  # make sure original params are unchanged\n\n    # test NamedTuple\n    def cond_fn(path, value):\n      \"\"\"Get all GenericTuples that have int x-attribute values.\"\"\"\n      return isinstance(value, GenericTuple) and isinstance(value.x, int)\n\n    t = GenericTuple(\n      GenericTuple(0, 'a'), GenericTuple(1, 'b'), GenericTuple('c', 2)\n    )\n    c = cursor(t)\n    c2, c3 = c.find_all(cond_fn)\n    c2.x += 5\n    c3.x += 6\n    self.assertEqual(\n      c.build(),\n      GenericTuple(\n        GenericTuple(5, 'a'), GenericTuple(7, 'b'), GenericTuple('c', 2)\n      ),\n    )\n    self.assertEqual(\n      t,\n      GenericTuple(\n        GenericTuple(0, 'a'), GenericTuple(1, 'b'), GenericTuple('c', 2)\n      ),\n    )  # make sure original object is unchanged\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/download_dataset_metadata.sh",
    "content": "#!/bin/bash\n# If you get an error like:\n#   Cloning into 'datasets'...\n#   fatal: cannot change to 'https://github.com/tensorflow/datasets/': No such file or directory\n#   error: failed to initialize sparse-checkout\n# This mean your git version is outdated. Just update it.\n\n\nset -e\n\n# Download TFDS metadata to flax/.tfds/metadata directory.\n# This allows the tests to specify the `data_dir` when using tfds.testing.mock_data().\ncd \"$( dirname \"$0\" )\"\n\nif [ -d \"../.tfds/metadata\" ]; then\n  echo 'TFDS metadata already exists.';\nelse\n  echo 'TFDS metadata does not exist. Downloading...';\n  git clone --branch v4.8.2 --depth 3 --filter=blob:none --sparse https://github.com/tensorflow/datasets/\n  cd datasets\n  git sparse-checkout set tensorflow_datasets/testing/metadata\n  mkdir ../../.tfds\n  mv tensorflow_datasets/testing/metadata/ ../../.tfds/metadata/\n  cd ..\n  rm -rf datasets\nfi\n"
  },
  {
    "path": "tests/early_stopping_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.training.early_stopping.\"\"\"\n\n\nimport jax\nfrom absl.testing import absltest\n\nfrom flax.training import early_stopping\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass EarlyStoppingTests(absltest.TestCase):\n  def test_update(self):\n    es = early_stopping.EarlyStopping(min_delta=0, patience=0)\n\n    for i in range(2):\n      improve_steps = 0\n      for step in range(10):\n        metric = 1.0\n        es = es.update(metric)\n        if not es.has_improved:\n          improve_steps += 1\n        if es.should_stop:\n          break\n\n      self.assertEqual(improve_steps, 1)\n      self.assertEqual(step, 1)\n\n      es = es.reset()  # ensure object is reusable if reset.\n\n  def test_patience(self):\n    es = early_stopping.EarlyStopping(min_delta=0, patience=0)\n    patient_es = early_stopping.EarlyStopping(min_delta=0, patience=6)\n    for step in range(10):\n      metric = 1.0\n      es = es.update(metric)\n      if es.should_stop:\n        break\n\n    self.assertEqual(step, 1)\n\n    for patient_step in range(10):\n      metric = 1.0\n      patient_es = patient_es.update(metric)\n      if patient_es.should_stop:\n        break\n\n    self.assertEqual(patient_step, 7)\n\n  def test_delta(self):\n    es = early_stopping.EarlyStopping(min_delta=0, patience=0)\n    delta_es = early_stopping.EarlyStopping(min_delta=1e-3, patience=0)\n    delta_patient_es = early_stopping.EarlyStopping(min_delta=1e-3, patience=1)\n    metric = 1.0\n    for step in range(100):\n      metric -= 1e-4\n      es = es.update(metric)\n      if es.should_stop:\n        break\n\n    self.assertEqual(step, 99)\n\n    metric = 1.0\n    for step in range(100):\n      metric -= 1e-4\n      delta_es = delta_es.update(metric)\n      if delta_es.should_stop:\n        break\n\n    self.assertEqual(step, 1)\n\n    metrics = [\n      0.01,\n      0.005,\n      0.0033,\n      0.0025,\n      0.002,\n      0.0017,\n      0.0014,\n      0.0012,\n      0.0011,\n      0.001,\n    ]\n    improvement_steps = 0\n    for step in range(10):\n      metric = metrics[step]\n      delta_patient_es = delta_patient_es.update(metric)\n      if delta_patient_es.has_improved:\n        improvement_steps += 1\n      if delta_patient_es.should_stop:\n        break\n\n    self.assertEqual(improvement_steps, 4)  # steps 0, 1, 2, 4\n    self.assertEqual(step, 6)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/flaxlib_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\n# TODO: Re-enable this test after setting up CI build for flaxlib CC.\n\n# from absl.testing import absltest\n# import flaxlib\n\n\n# class TestFlaxlib(absltest.TestCase):\n\n#   def test_flaxlib(self):\n#     self.assertEqual(flaxlib.sum_as_string(1, 2), '3')\n"
  },
  {
    "path": "tests/import_test.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"attachments\": {},\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Test Import in Colab\\n\",\n    \"\\n\",\n    \"\\\"Run all\\\" to test that all the Flax imports work in head.\\n\",\n    \"\\n\",\n    \"Change runtime type as needed.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Colab runtimes are pre-built with JAX/Flax:\\n\",\n    \"!pip freeze | egrep 'jax|flax'\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# Install from head\\n\",\n    \"!pip install git+https://github.com/google/flax.git\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Check versions after installing Flax from Github:\\n\",\n    \"!pip freeze | egrep 'jax|flax'\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Verify we can import everything.\\n\",\n    \"import flax\\n\",\n    \"from flax.training import (checkpoints, dynamic_scale, early_stopping, lr_schedule,\\n\",\n    \"                           orbax_utils, prefetch_iterator, train_state, common_utils)\\n\",\n    \"from flax.metrics import tensorboard\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.9.15\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "tests/io_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.io.\"\"\"\n\nimport os\nimport tempfile\n\nimport jax\nimport tensorflow as tf\nfrom absl.testing import absltest, parameterized\n\nfrom flax import errors, io\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass IOTest(parameterized.TestCase):\n  @parameterized.parameters(\n    {'backend_mode': io.BackendMode.DEFAULT},\n    {'backend_mode': io.BackendMode.TF},\n  )\n  def test_override(self, backend_mode):\n    with io.override_mode(backend_mode):\n      self.assertEqual(io.io_mode, backend_mode)\n\n  @parameterized.parameters(\n    {'write_mode': io.BackendMode.DEFAULT, 'read_mode': io.BackendMode.TF},\n    {'write_mode': io.BackendMode.TF, 'read_mode': io.BackendMode.DEFAULT},\n  )\n  def test_GFile(self, write_mode, read_mode):\n    test_string = b'testing write and read'\n    with tempfile.TemporaryDirectory() as temp_dir_path:\n      test_path = os.path.join(temp_dir_path, 'test')\n\n      with io.override_mode(write_mode):\n        with io.GFile(test_path, 'wb') as file:\n          file.write(test_string)\n\n      with io.override_mode(read_mode):\n        with io.GFile(test_path, 'rb') as file:\n          self.assertEqual(file.read(), test_string)\n\n  def test_listdir(self):\n    with tempfile.TemporaryDirectory() as temp_dir_path:\n      os.mkdir(os.path.join(temp_dir_path, 'a'))\n      os.mkdir(os.path.join(temp_dir_path, 'as'))\n      os.mkdir(os.path.join(temp_dir_path, 'af'))\n      os.mkdir(os.path.join(temp_dir_path, 'test'))\n      os.mkdir(os.path.join(temp_dir_path, 'at'))\n\n      with io.override_mode(io.BackendMode.DEFAULT):\n        default_dir_set = set(io.listdir(temp_dir_path))\n\n      with io.override_mode(io.BackendMode.TF):\n        tf_dir_set = set(io.listdir(temp_dir_path))\n\n      self.assertEqual(default_dir_set, tf_dir_set)\n\n  @parameterized.parameters(\n    {'create_temp_fn': tempfile.TemporaryDirectory},\n    {'create_temp_fn': tempfile.NamedTemporaryFile},\n  )\n  def test_isdir(self, create_temp_fn):\n    with create_temp_fn() as temp:\n      path = temp.name if hasattr(temp, 'name') else temp\n\n      with io.override_mode(io.BackendMode.DEFAULT):\n        default_isdir = io.isdir(path)\n\n      with io.override_mode(io.BackendMode.TF):\n        tf_isdir = io.isdir(path)\n\n      self.assertEqual(default_isdir, tf_isdir)\n\n  def test_copy(self):\n    test_string = b'testing copy'\n    with tempfile.TemporaryDirectory() as temp_dir_path:\n      test_path = os.path.join(temp_dir_path, 'test')\n      copy1_path = os.path.join(temp_dir_path, 'copy1')\n      copy2_path = os.path.join(temp_dir_path, 'copy2')\n\n      with io.GFile(test_path, 'wb') as file:\n        file.write(test_string)\n\n      with io.override_mode(io.BackendMode.DEFAULT):\n        io.copy(test_path, copy1_path)\n\n      with io.override_mode(io.BackendMode.TF):\n        io.copy(copy1_path, copy2_path)\n\n      with io.GFile(copy2_path, 'rb') as file:\n        self.assertEqual(file.read(), test_string)\n\n  @parameterized.parameters(\n    {\n      'backend_mode': io.BackendMode.DEFAULT,\n      'error_type': errors.AlreadyExistsError,\n    },\n    {\n      'backend_mode': io.BackendMode.TF,\n      'error_type': tf.errors.AlreadyExistsError,\n    },\n  )\n  def test_copy_raises_error(self, backend_mode, error_type):\n    with tempfile.NamedTemporaryFile() as temp_file:\n      with io.override_mode(backend_mode):\n        with self.assertRaises(error_type):\n          io.copy(temp_file.name, temp_file.name)\n\n  def test_rename(self):\n    with tempfile.TemporaryDirectory() as temp_dir_path:\n      test_path = os.path.join(temp_dir_path, 'test')\n      rename1_path = os.path.join(temp_dir_path, 'rename1')\n      rename2_path = os.path.join(temp_dir_path, 'rename2')\n\n      with io.GFile(test_path, 'wb') as file:\n        file.write(b'placeholder text')\n\n      with io.override_mode(io.BackendMode.DEFAULT):\n        io.rename(test_path, rename1_path)\n\n      with io.override_mode(io.BackendMode.TF):\n        io.rename(rename1_path, rename2_path)\n\n      with io.GFile(rename2_path, 'rb') as file:\n        self.assertTrue(os.path.exists(rename2_path))\n\n  @parameterized.parameters(\n    {\n      'backend_mode': io.BackendMode.DEFAULT,\n      'error_type': errors.AlreadyExistsError,\n    },\n    {\n      'backend_mode': io.BackendMode.TF,\n      'error_type': tf.errors.AlreadyExistsError,\n    },\n  )\n  def test_rename_raises_error(self, backend_mode, error_type):\n    with tempfile.NamedTemporaryFile() as temp_file:\n      with io.override_mode(backend_mode):\n        with self.assertRaises(error_type):\n          io.rename(temp_file.name, temp_file.name)\n\n  def test_exists(self):\n    with tempfile.NamedTemporaryFile() as temp_file:\n      with io.override_mode(io.BackendMode.DEFAULT):\n        default_exists = io.exists(temp_file.name)\n\n      with io.override_mode(io.BackendMode.TF):\n        tf_exists = io.exists(temp_file.name)\n\n      self.assertEqual(default_exists, tf_exists)\n\n  @parameterized.parameters(\n    {'backend_mode': io.BackendMode.DEFAULT},\n    {'backend_mode': io.BackendMode.TF},\n  )\n  def test_makedirs(self, backend_mode):\n    with tempfile.TemporaryDirectory() as temp_dir_path:\n      test_dir_path = os.path.join(temp_dir_path, 'test_dir')\n\n      with io.override_mode(backend_mode):\n        io.makedirs(test_dir_path)\n      self.assertTrue(\n        os.path.exists(test_dir_path) and (os.path.isdir(test_dir_path))\n      )\n\n  def test_glob(self):\n    with tempfile.TemporaryDirectory() as temp_dir_path:\n      os.mkdir(os.path.join(temp_dir_path, 'a'))\n      os.mkdir(os.path.join(temp_dir_path, 'as'))\n      os.mkdir(os.path.join(temp_dir_path, 'af'))\n      os.mkdir(os.path.join(temp_dir_path, 'test'))\n      os.mkdir(os.path.join(temp_dir_path, 'at'))\n\n    with io.override_mode(io.BackendMode.DEFAULT):\n      default_glob_set = set(io.glob('a*/'))\n\n    with io.override_mode(io.BackendMode.TF):\n      tf_glob_set = set(io.glob('a*/'))\n\n    self.assertEqual(default_glob_set, tf_glob_set)\n\n  @parameterized.parameters(\n    {'backend_mode': io.BackendMode.DEFAULT},\n    {'backend_mode': io.BackendMode.TF},\n  )\n  def test_remove(self, backend_mode):\n    with tempfile.TemporaryDirectory() as temp_dir_path:\n      test_path = os.path.join(temp_dir_path, 'test')\n\n      with io.GFile(test_path, 'wb') as file:\n        file.write(b'placeholder text')\n\n      with io.override_mode(backend_mode):\n        io.remove(test_path)\n\n      self.assertTrue(not os.path.exists(test_path))\n\n  @parameterized.parameters(\n    {'backend_mode': io.BackendMode.DEFAULT},\n    {'backend_mode': io.BackendMode.TF},\n  )\n  def test_rmtree(self, backend_mode):\n    with tempfile.TemporaryDirectory() as temp_dir_path:\n      dir0_path = os.path.join(temp_dir_path, 'dir0')\n\n      os.mkdir(dir0_path)\n      os.mkdir(os.path.join(dir0_path, 'dir1'))\n      os.mkdir(os.path.join(dir0_path, 'dir1', 'dir2'))\n      os.mkdir(os.path.join(dir0_path, 'dir1', 'dir3'))\n      os.mkdir(os.path.join(dir0_path, 'dir4'))\n      os.mkdir(os.path.join(dir0_path, 'dir4', 'dir5'))\n      os.mkdir(os.path.join(dir0_path, 'dir6'))\n\n      with io.override_mode(backend_mode):\n        io.rmtree(dir0_path)\n\n      self.assertTrue(not os.path.exists(dir0_path))\n\n  @parameterized.parameters(\n    {'backend_mode': io.BackendMode.DEFAULT},\n    {'backend_mode': io.BackendMode.TF},\n  )\n  def test_getsize(self, backend_mode):\n    with tempfile.TemporaryDirectory() as temp_dir_path:\n      test_path = os.path.join(temp_dir_path, 'test')\n\n      content = b'placeholder text'\n      with io.GFile(test_path, 'wb') as file:\n        file.write(content)\n\n      with io.override_mode(backend_mode):\n        size = io.getsize(test_path)\n\n      self.assertEqual(size, len(content))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/jax_utils_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.jax_utils.\"\"\"\n\nfrom functools import partial\nimport os\nos.environ['XLA_FLAGS'] = '--xla_force_host_platform_device_count=4'\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom flax import jax_utils\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\n\nNDEV = 4\n\n\ndef assert_max_traces(n):\n  \"\"\"Decorator to assert that a function is traced at most n times.\"\"\"\n  from functools import wraps\n  def decorator(fn):\n    trace_count = {'count': 0}\n\n    @wraps(fn)\n    def wrapped(*args, **kwargs):\n      trace_count['count'] += 1\n      if trace_count['count'] > n:\n        raise AssertionError(\n          f\"Function was traced {trace_count['count']} times, \"\n          f\"expected at most {n} traces\"\n        )\n      return fn(*args, **kwargs)\n\n    wrapped.trace_count = trace_count\n    return wrapped\n  return decorator\n\n\nclass PadShardUnpadTest(parameterized.TestCase):\n  BATCH_SIZES = [NDEV, NDEV + 1, NDEV - 1, 5 * NDEV, 5 * NDEV + 1, 5 * NDEV - 1]\n  DTYPES = [np.float32, np.uint8, jax.numpy.bfloat16, np.int32]\n\n\n\n  @parameterized.product(dtype=DTYPES, bs=BATCH_SIZES)\n  def test_basics(self, dtype, bs):\n    # Just tests that basic calling works without exploring caveats.\n    @partial(jax_utils.pad_shard_unpad, static_argnums=())\n    def add(a, b):\n      b = jnp.asarray(b, dtype=dtype)\n      return a + b\n\n    x = np.arange(bs, dtype=dtype)\n    y = add(x, 10 * x)\n    self.assertEqual(y.dtype, x.dtype)\n    np.testing.assert_allclose(np.float64(y), np.float64(x + 10 * x))\n\n  @parameterized.product(dtype=DTYPES, bs=BATCH_SIZES)\n  def test_trees(self, dtype, bs):\n    # Just tests that basic calling works without exploring caveats.\n    @partial(jax_utils.pad_shard_unpad, static_argnums=())\n    def add(a, b):\n      return a['a'] + b[0]\n\n    x = jnp.arange(bs, dtype=dtype)\n    y = add(dict(a=x), (10 * x,))\n    self.assertEqual(y.dtype, x.dtype)\n    np.testing.assert_allclose(np.float64(y), np.float64(x + 10 * x))\n\n  @parameterized.parameters(DTYPES)\n  def test_min_device_batch_avoids_recompile(self, dtype):\n    @partial(jax_utils.pad_shard_unpad, static_argnums=())\n    @jax.jit\n    @assert_max_traces(n=1)\n    def add(a, b):\n      b = jnp.asarray(b, dtype=dtype)\n      return a + b\n\n    for bs in self.BATCH_SIZES:\n      x = jnp.arange(bs, dtype=dtype)\n      y = add(x, 10 * x, min_device_batch=9)  # pylint: disable=unexpected-keyword-arg\n      self.assertEqual(y.dtype, x.dtype)\n      np.testing.assert_allclose(np.float64(y), np.float64(x + 10 * x))\n\n  @parameterized.product(dtype=DTYPES, bs=BATCH_SIZES)\n  def test_static_argnum(self, dtype, bs):\n    @partial(jax_utils.pad_shard_unpad, static_argnums=(1,))\n    def add(a, b):\n      return a + jnp.asarray(b, dtype=dtype)\n\n    x = jnp.arange(bs, dtype=dtype)\n    y = add(x, 10)\n    self.assertEqual(y.dtype, x.dtype)\n    np.testing.assert_allclose(np.float64(y), np.float64(x + 10))\n\n  @parameterized.product(dtype=DTYPES, bs=BATCH_SIZES)\n  def test_static_argnames(self, dtype, bs):\n    # In this test, leave static_argnums at the default value too, in order to\n    # test the default/most canonical path where `params` are the first arg.\n    @partial(jax_utils.pad_shard_unpad, static_argnames=('b',))\n    def add(params, a, *, b):\n      params = jnp.asarray(params, dtype=dtype)\n      b = jnp.asarray(b, dtype=dtype)\n      return params * a + b\n\n    x = jnp.arange(bs, dtype=dtype)\n    y = add(5, x, b=10)\n    self.assertEqual(y.dtype, x.dtype)\n    np.testing.assert_allclose(np.float64(y), np.float64(5 * x + 10))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/initializers_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.linen.initializers.\"\"\"\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom absl.testing import absltest, parameterized\nfrom jax import random\n\nfrom flax import linen as nn\nfrom flax.linen import initializers\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass InitializersTest(parameterized.TestCase):\n  @parameterized.parameters(\n    {\n      'builder_fn': initializers.zeros_init,\n      'params_shape': (2, 3),\n      'expected_params': jnp.zeros((2, 3)),\n    },\n    {\n      'builder_fn': initializers.ones_init,\n      'params_shape': (3, 2),\n      'expected_params': jnp.ones((3, 2)),\n    },\n  )\n  def test_call_builder(self, builder_fn, params_shape, expected_params):\n    params = builder_fn()(random.key(42), params_shape, jnp.float32)\n    np.testing.assert_allclose(params, expected_params)\n\n  @parameterized.parameters(\n    {\n      'builder_fn': initializers.zeros_init,\n      'expected_params': jnp.zeros((2, 5)),\n    },\n    {\n      'builder_fn': initializers.ones_init,\n      'expected_params': jnp.ones((2, 5)),\n    },\n  )\n  def test_kernel_builder(self, builder_fn, expected_params):\n    layer = nn.Dense(5, kernel_init=builder_fn())\n    params = layer.init(random.key(42), jnp.empty((3, 2)))['params']\n    np.testing.assert_allclose(params['kernel'], expected_params)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/kw_only_dataclasses_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for kw_only_dataclasses.\"\"\"\n\nimport dataclasses\nimport inspect\n\nfrom absl.testing import absltest\n\nfrom flax.linen import kw_only_dataclasses\n\n\nclass KwOnlyDataclassesTest(absltest.TestCase):\n  def test_kwonly_args_moved_to_end(self):\n    @kw_only_dataclasses.dataclass\n    class TestClass:\n      a: int = 1\n      b: int = kw_only_dataclasses.field(default=2, kw_only=True)\n      c: int = 3\n\n    params = inspect.signature(TestClass.__init__).parameters\n    self.assertEqual(list(params), ['self', 'a', 'c', 'b'])\n    self.assertEqual(params['a'].default, 1)\n    self.assertEqual(params['b'].default, 2)\n    self.assertEqual(params['c'].default, 3)\n\n    v1 = TestClass()\n    self.assertDictEqual(dataclasses.asdict(v1), dict(a=1, b=2, c=3))\n\n    v2 = TestClass(b=20)\n    self.assertDictEqual(dataclasses.asdict(v2), dict(a=1, b=20, c=3))\n\n    v3 = TestClass(1, 30)\n    self.assertDictEqual(dataclasses.asdict(v3), dict(a=1, b=2, c=30))\n\n  def test_base_optional_subclass_required(self):\n    @kw_only_dataclasses.dataclass\n    class Parent:\n      a: int = kw_only_dataclasses.field(default=2, kw_only=True)\n\n    @kw_only_dataclasses.dataclass\n    class Child(Parent):\n      b: int\n\n    child_params = inspect.signature(Child.__init__).parameters\n    self.assertEqual(list(child_params), ['self', 'b', 'a'])\n    self.assertEqual(child_params['a'].default, 2)\n    self.assertEqual(child_params['b'].default, inspect.Parameter.empty)\n\n    v1 = Child(4)\n    self.assertDictEqual(dataclasses.asdict(v1), dict(a=2, b=4))\n\n    v2 = Child(4, a=5)  # pylint: disable=too-many-function-args\n    self.assertDictEqual(dataclasses.asdict(v2), dict(a=5, b=4))\n\n  def test_subclass_overrides_base(self):\n    # Note: if a base class declares a field as keyword-only, then\n    # subclasses don't need to also declare it as keyword-only.\n\n    @kw_only_dataclasses.dataclass\n    class A:\n      x: int = kw_only_dataclasses.field(default=1, kw_only=True)\n\n    @kw_only_dataclasses.dataclass\n    class B(A):\n      size: float\n      y: int = kw_only_dataclasses.field(default=3, kw_only=True)\n      x: int = 2\n\n    @kw_only_dataclasses.dataclass\n    class C(B):\n      name: str\n\n    a_params = inspect.signature(A.__init__).parameters\n    b_params = inspect.signature(B.__init__).parameters\n    c_params = inspect.signature(C.__init__).parameters\n\n    self.assertEqual(list(a_params), ['self', 'x'])\n    self.assertEqual(list(b_params), ['self', 'size', 'x', 'y'])\n    self.assertEqual(list(c_params), ['self', 'size', 'name', 'x', 'y'])\n\n    self.assertEqual(a_params['x'].default, 1)\n    self.assertEqual(b_params['x'].default, 2)\n    self.assertEqual(b_params['y'].default, 3)\n    self.assertEqual(b_params['size'].default, inspect.Parameter.empty)\n    self.assertEqual(c_params['x'].default, 2)\n    self.assertEqual(c_params['y'].default, 3)\n    self.assertEqual(c_params['name'].default, inspect.Parameter.empty)\n    self.assertEqual(c_params['size'].default, inspect.Parameter.empty)\n\n    value = C(4, 'foo')  # pylint: disable=too-many-function-args\n    self.assertDictEqual(\n      dataclasses.asdict(value), dict(name='foo', size=4, x=2, y=3)\n    )\n\n  def test_kwonly_marker(self):\n    @kw_only_dataclasses.dataclass\n    class A:\n      x: float\n      _: kw_only_dataclasses.KW_ONLY\n      a: int = 5\n      b: int = kw_only_dataclasses.field(default=2)\n      c: int = kw_only_dataclasses.field(default=2, kw_only=True)\n\n    @kw_only_dataclasses.dataclass\n    class B(A):\n      z: str\n\n    a_params = inspect.signature(A.__init__).parameters\n    b_params = inspect.signature(B.__init__).parameters\n    self.assertEqual(list(a_params), ['self', 'x', 'a', 'b', 'c'])\n    self.assertEqual(list(b_params), ['self', 'x', 'z', 'a', 'b', 'c'])\n\n  def test_whatever(self):\n    import abc\n    from collections.abc import Iterator, Iterable\n    import io\n    from typing import Protocol, TypeVar\n\n    T = TypeVar(\"T\")\n\n    class CheckpointableIterator(Iterator[T], Protocol[T]):\n      pass\n\n    isinstance(io.TextIOBase, Iterable)\n\n    from flax import linen as nn\n\n    class Steppable(metaclass=abc.ABCMeta):\n      path: str\n\n    class SequenceLayer(nn.Module, Steppable):\n      pass\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/linen_activation_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.linen.activation.\"\"\"\n\nfrom absl.testing import absltest\nfrom flax import linen as nn\nimport jax\nfrom jax import random\nimport jax.numpy as jnp\nimport numpy as np\n\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass ActivationTest(absltest.TestCase):\n\n  def test_prelu(self):\n    rng = random.key(0)\n    key, skey_1, skey_2 = jax.random.split(rng, 3)\n    x = jax.random.uniform(skey_1, (4, 6, 5)) - 0.5\n    act = nn.PReLU()\n    y, params = act.init_with_output(skey_2, x)\n    expected_y = jnp.where(x < 0, x * act.negative_slope_init, x)\n    init_negative_slope = params['params']['negative_slope']\n    expected_negative_slope = jnp.array(\n        act.negative_slope_init, dtype=jnp.float32\n    )\n\n    self.assertEqual(y.shape, x.shape)\n    np.testing.assert_array_almost_equal(expected_y, y)\n    np.testing.assert_array_equal(init_negative_slope, expected_negative_slope)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/linen_attention_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.linen.attention.\"\"\"\n\nfrom absl.testing import absltest, parameterized\nfrom flax import errors, jax_utils\nfrom flax import linen as nn\nfrom flax.core import pop\nimport jax\nfrom jax import lax, random\nfrom jax.nn import initializers\nimport jax.numpy as jnp\nimport numpy as np\n\ntry:\n  # JAX v0.8.0 and newer\n  from jax import enable_x64\nexcept ImportError:\n  from jax.experimental import enable_x64\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass AttentionTest(parameterized.TestCase):\n  def test_multihead_self_attention(self):\n    rng = random.key(0)\n    x = jnp.ones((4, 6, 5))\n    sa_module = nn.MultiHeadDotProductAttention(\n      num_heads=8,\n      qkv_features=16,\n      kernel_init=initializers.ones,\n      bias_init=initializers.zeros,\n      deterministic=False,\n    )\n    y, _ = sa_module.init_with_output(rng, x)\n    self.assertEqual(y.shape, x.shape)\n    self.assertEqual(y.dtype, jnp.float32)\n\n  def test_dtype_infer(self):\n    rng = random.key(0)\n    x = jnp.ones((4, 6, 5), jnp.complex64)\n    sa_module = nn.MultiHeadDotProductAttention(\n      num_heads=8,\n      qkv_features=16,\n      kernel_init=initializers.ones,\n      bias_init=initializers.zeros,\n      deterministic=False,\n    )\n    y, _ = sa_module.init_with_output(rng, x)\n    self.assertEqual(y.shape, x.shape)\n    self.assertEqual(y.dtype, jnp.complex64)\n\n  def test_multihead_encoder_decoder_attention(self):\n    rng = random.key(0)\n    q = jnp.ones((4, 2, 3, 5))\n    sa_module = nn.MultiHeadDotProductAttention(\n      num_heads=8,\n      qkv_features=16,\n      kernel_init=initializers.ones,\n      bias_init=initializers.zeros,\n      deterministic=False,\n    )\n    y, _ = sa_module.init_with_output(rng, q)\n    self.assertEqual(y.shape, q.shape)\n\n  def test_mha_out_initializers(self):\n    rng = random.key(0)\n    q = jnp.ones((4, 2, 3, 5))\n    sa_module = nn.MultiHeadDotProductAttention(\n      num_heads=8,\n      qkv_features=16,\n      kernel_init=initializers.ones,\n      out_kernel_init=initializers.zeros,\n      bias_init=initializers.zeros,\n      out_bias_init=initializers.ones,\n      deterministic=False,\n    )\n    variables = sa_module.init(rng, q)\n    params = variables['params']\n    # test kernels\n    np.testing.assert_allclose(params['query']['kernel'], 1.0)\n    np.testing.assert_allclose(params['key']['kernel'], 1.0)\n    np.testing.assert_allclose(params['value']['kernel'], 1.0)\n    np.testing.assert_allclose(params['out']['kernel'], 0.0)\n    # test biases\n    np.testing.assert_allclose(params['query']['bias'], 0.0)\n    np.testing.assert_allclose(params['key']['bias'], 0.0)\n    np.testing.assert_allclose(params['value']['bias'], 0.0)\n    np.testing.assert_allclose(params['out']['bias'], 1.0)\n\n  def test_multihead_self_attention_w_dropout(self):\n    rng = random.key(0)\n    x = jnp.ones((4, 2, 3, 5))\n    sa_module = nn.MultiHeadDotProductAttention(\n      num_heads=8,\n      qkv_features=16,\n      kernel_init=initializers.ones,\n      bias_init=initializers.zeros,\n      dropout_rate=0.1,\n      deterministic=False,\n    )\n    rng1, rng2 = random.split(rng)\n    rngs = {'params': rng1, 'dropout': rng2}\n    y, _ = sa_module.init_with_output(rngs, x)\n    self.assertEqual(y.shape, x.shape)\n\n  def test_multihead_self_attention_explicit_dropout(self):\n    def clone(key):\n      return jax.tree.map(jax.random.clone, key)\n\n    class Foo(nn.Module):\n      attention_kwargs: dict\n\n      @nn.compact\n      def __call__(self, x, dropout_rng=None):\n        a = nn.MultiHeadDotProductAttention(**self.attention_kwargs)(\n          x, x, dropout_rng=dropout_rng\n        )\n        if dropout_rng is not None:\n          dropout_rng = clone(dropout_rng)\n        b = nn.MultiHeadDotProductAttention(**self.attention_kwargs)(\n          x, x, dropout_rng=dropout_rng\n        )\n        return a, b\n\n    module = Foo(\n      dict(\n        num_heads=8,\n        qkv_features=16,\n        kernel_init=initializers.ones,\n        bias_init=initializers.zeros,\n        dropout_rate=0.5,\n        deterministic=False,\n      )\n    )\n    rng1, rng2, rng3, rng4 = random.split(random.key(0), 4)\n    x = jnp.ones((4, 2, 3, 5))\n    rngs = {'params': rng1, 'dropout': rng2}\n    v = module.init(rngs, x)\n    a, b = module.apply(v, x, rngs=clone(rngs))\n    c, d = module.apply(v, x, rngs={'dropout': clone(rng2)})\n    e, f = module.apply(v, x, rngs={'dropout': rng3})\n    self.assertFalse((a == b).all())\n    self.assertTrue((a == c).all())\n    self.assertTrue((b == d).all())\n    self.assertFalse((a == e).all())\n    self.assertFalse((b == f).all())\n    a, b = module.apply(v, x, rngs=clone(rngs), dropout_rng=rng4)\n    self.assertTrue((a == b).all())\n    a, b = module.apply(v, x, dropout_rng=clone(rng4))\n    self.assertTrue((a == b).all())\n    self.assertTrue(a.shape == b.shape == x.shape)\n\n  def test_multihead_self_attention_w_dropout_disabled(self):\n    rng = random.key(0)\n    x = jnp.ones((4, 2, 3, 5))\n    sa_module0 = nn.MultiHeadDotProductAttention(\n      num_heads=8,\n      qkv_features=16,\n      kernel_init=initializers.ones,\n      bias_init=initializers.zeros,\n      dropout_rate=0.0,\n      deterministic=True,\n    )\n    rng1, rng2, rng3, rng4 = random.split(rng, 4)\n    rngs1 = {'params': rng1, 'dropout': rng2}\n    rngs2 = {'params': rng3, 'dropout': rng4}\n    y1, vs = sa_module0.init_with_output(rngs1, x)\n    y2, _ = sa_module0.init_with_output(rngs2, x)\n    np.testing.assert_allclose(y1, y2)\n    y3 = sa_module0.apply(vs, x, rngs=rngs1)\n    y4 = sa_module0.apply(vs, x, rngs=rngs2)\n    np.testing.assert_allclose(y3, y4)\n    sa_module1 = nn.MultiHeadDotProductAttention(\n      num_heads=8,\n      qkv_features=16,\n      kernel_init=initializers.ones,\n      bias_init=initializers.zeros,\n      dropout_rate=0.0,\n    )\n    y5 = sa_module1.apply(vs, x, deterministic=True, rngs=rngs1)\n    y6 = sa_module1.apply(vs, x, deterministic=True, rngs=rngs2)\n    np.testing.assert_allclose(y5, y6)\n    sa_module2 = nn.MultiHeadDotProductAttention(\n      num_heads=8,\n      qkv_features=16,\n      kernel_init=initializers.ones,\n      bias_init=initializers.zeros,\n      dropout_rate=0.5,\n    )\n    y7 = sa_module2.apply(vs, x, deterministic=True, rngs=rngs1)\n    y8 = sa_module2.apply(vs, x, deterministic=True, rngs=rngs2)\n    np.testing.assert_allclose(y7, y8)\n\n  def test_causal_mask_1d(self):\n    \"\"\"Tests autoregressive masking for 1d attention.\"\"\"\n    x = jnp.ones((3, 16))  # (bs1, length)\n    mask_1d = nn.attention.make_causal_mask(x)\n    ts = np.arange(16)\n    mask_1d_simple = (ts[:, None] >= ts[None, :])[None, None, :, :]\n    mask_1d_simple = jnp.broadcast_to(mask_1d_simple, (3, 1, 16, 16))\n    np.testing.assert_allclose(\n      mask_1d,\n      mask_1d_simple,\n    )\n\n  @parameterized.parameters([((5,), (1,)), ((6, 5), (2,))])\n  def test_decoding(self, spatial_shape, attn_dims):\n    bs = 2\n    num_heads = 3\n    num_features = 4\n    rng = random.key(0)\n    key1, key2 = random.split(rng)\n    inputs = random.normal(\n      key1, (bs,) + spatial_shape + (num_heads * num_features,)\n    )\n    module = nn.MultiHeadDotProductAttention(\n      num_heads=num_heads,\n      qkv_features=num_heads * num_features,\n      precision=lax.Precision.HIGHEST,\n      deterministic=False,\n      decode=False,\n    )\n    decode_module = module.clone(decode=True)\n\n    initial_vars = decode_module.init(key2, inputs)\n    state, params = pop(initial_vars, 'params')\n    causal_mask = nn.attention.make_causal_mask(jnp.ones((bs,) + spatial_shape))\n    y_ref = jax.jit(lambda x, y: module.apply(initial_vars, x, mask=y))(\n      inputs, causal_mask\n    )\n\n    # feed the inputs sequentially to simulate decoding\n    def body_fn(state, x):\n      y, state = decode_module.apply(\n        {'params': params, **state}, x, mutable=['cache']\n      )\n      return state, y\n\n    # scan_in_dim supports scanning multiple dims\n    _, y = jax_utils.scan_in_dim(\n      body_fn, state, inputs, axis=attn_dims, keepdims=True\n    )\n\n    np.testing.assert_allclose(y_ref, y, atol=1e-5)\n\n  def test_autoregressive_receptive_field_1d(self):\n    \"\"\"Tests the autoregressive self-attention receptive field.\"\"\"\n    rng = random.key(0)\n    rng1, rng2 = random.split(rng, num=2)\n\n    length = 10\n    dim = 1\n    num_heads = 1\n    input_shape = (1, length, dim)\n    inputs = random.normal(rng2, input_shape)\n\n    module = nn.MultiHeadDotProductAttention(\n      num_heads=num_heads,\n      kernel_init=jax.nn.initializers.ones,\n      deterministic=False,\n    )\n\n    initial_vars = module.init(rng1, inputs)\n    causal_mask = nn.attention.make_causal_mask(jnp.ones(input_shape[:-1]))\n\n    def model_loss(inputs, pos):\n      out = module.apply(initial_vars, inputs, mask=causal_mask)\n      assert out.shape == input_shape\n      assert len(out.shape) == 3\n      return out[0, pos, :].sum()\n\n    grad_fn = jax.jit(jax.grad(model_loss))\n\n    def get_receptive_field_1d(pos):\n      g = grad_fn(inputs, pos)[0, :, :]\n      return jnp.any((jnp.abs(g) > 1e-5).astype(jnp.uint32), axis=-1)\n\n    for i in range(length):\n      deps = get_receptive_field_1d(i)\n      assert (deps[:i] == 1).all(), (\n        'Receptive Field Error: Some of the '\n        'previous postions are not reachable '\n        'in autoregressive self-attention.'\n      )\n      if i != length - 1:\n        k = i + 1\n        assert (deps[k:] == 0).all(), (\n          'Receptive Field Error: Some of the '\n          'future postions are reachable in '\n          'autoregressive self-attention.'\n        )\n\n  def test_multihead_kv_args(self):\n    key1, key2 = random.split(random.key(0), 2)\n    query = random.uniform(key1, (3, 5))\n    key_value = random.uniform(key2, (9, 5))\n    module = nn.MultiHeadDotProductAttention(\n      num_heads=8,\n      qkv_features=16,\n      kernel_init=initializers.ones,\n      bias_init=initializers.zeros,\n      deterministic=False,\n    )\n    key = lambda: random.key(43279)\n    y0, v0 = module.init_with_output(\n      key(), query, inputs_k=key_value, inputs_v=key_value\n    )\n    y1, v1 = module.init_with_output(key(), query, inputs_k=key_value)\n    with self.assertWarnsRegex(\n      DeprecationWarning, 'The inputs_kv arg will be deprecated soon.'\n    ):\n      y2, v2 = module.init_with_output(key(), query, inputs_kv=key_value)\n    self.assertTrue((y0 == y1).all() and (y1 == y2).all())\n    self.assertTrue(\n        jax.tree_util.tree_all(\n            jax.tree_util.tree_map(\n                lambda x, y, z: (x == y).all() and (y == z).all(), v0, v1, v2\n            )\n        )\n    )\n\n    with self.assertRaisesRegex(\n      ValueError, '`inputs_k` cannot be None if `inputs_v` is not None.'\n    ):\n      y3, v3 = module.init_with_output(key(), query, inputs_v=key_value)\n    with self.assertRaisesRegex(\n      ValueError,\n      'If either `inputs_k` or `inputs_v` is not None, `inputs_kv` must be None.',\n    ):\n      y3, v3 = module.init_with_output(\n        key(), query, inputs_kv=key_value, inputs_v=key_value\n      )\n    with self.assertRaisesRegex(\n      ValueError,\n      'If either `inputs_k` or `inputs_v` is not None, `inputs_kv` must be None.',\n    ):\n      y3, v3 = module.init_with_output(\n        key(), query, key_value, key_value, inputs_kv=key_value\n      )\n\n  def test_multihead_mask_warning(self):\n    rng = random.key(0)\n    rng1, rng2 = random.split(rng, num=2)\n\n    length = 10\n    dim = 1\n    num_heads = 1\n    input_shape = (1, length, dim)\n    query = key = random.normal(rng2, input_shape)\n\n    module = nn.MultiHeadDotProductAttention(\n      num_heads=num_heads,\n      kernel_init=jax.nn.initializers.ones,\n      deterministic=False,\n    )\n\n    initial_vars = module.init(rng1, query, key)\n    causal_mask = nn.attention.make_causal_mask(jnp.ones(input_shape[:-1]))\n\n    module.apply(initial_vars, query, key, mask=causal_mask)\n    with self.assertWarnsRegex(\n      DeprecationWarning,\n      \"the function signature of MultiHeadDotProductAttention's `__call__` method has changed\",\n    ):\n      with self.assertRaises(errors.ScopeParamShapeError):\n        module.apply(initial_vars, query, key, causal_mask)\n\n  def test_multihead_sow_attention_weights(self):\n    rng = random.key(0)\n    x = jnp.ones((4, 6, 5))\n\n    class Model(nn.Module):\n      attention_kwargs: dict\n\n      @nn.compact\n      def __call__(self, x, sow_weights=False):\n        x = nn.MultiHeadDotProductAttention(**self.attention_kwargs)(\n          x, sow_weights=sow_weights\n        )\n        x = nn.MultiHeadDotProductAttention(**self.attention_kwargs)(x)\n        x = nn.MultiHeadDotProductAttention(**self.attention_kwargs)(\n          x, sow_weights=sow_weights\n        )\n        return x\n\n    module = Model(\n      dict(\n        num_heads=8,\n        qkv_features=16,\n        kernel_init=initializers.ones,\n        bias_init=initializers.zeros,\n        deterministic=False,\n      )\n    )\n    v = module.init(rng, x)\n    _, intermediates = module.apply(\n      v, x, mutable=['intermediates'], sow_weights=True\n    )\n    self.assertEqual(\n      intermediates['intermediates']['MultiHeadDotProductAttention_0'][\n        'attention_weights'\n      ][0].shape,\n      (4, 8, 6, 6),\n    )\n    self.assertNotIn(\n      'MultiHeadDotProductAttention_1', intermediates['intermediates']\n    )\n    self.assertEqual(\n      intermediates['intermediates']['MultiHeadDotProductAttention_2'][\n        'attention_weights'\n      ][0].shape,\n      (4, 8, 6, 6),\n    )\n    _, intermediates = module.apply(\n      v, x, mutable=['intermediates'], sow_weights=False\n    )\n    self.assertNotIn('intermediates', intermediates)\n\n  def test_autoregressive_decode_with_x64(self):\n    with enable_x64():\n      x = jnp.ones((1, 4, 4))\n      module = nn.MultiHeadDotProductAttention(\n          num_heads=2,\n          qkv_features=4,\n          decode=True\n        )\n\n      rng = random.PRNGKey(0)\n      variables = module.init(rng, x, x, x)\n      params, cache = variables['params'], variables['cache']\n      y1, updates = module.apply(\n          { 'params': params, 'cache': cache },\n          x[:, :1, :],\n          mutable=['cache']\n        )\n      cache = updates['cache']\n      y2, updates = module.apply(\n          { 'params': params, 'cache': cache },\n          x[:, 1:2, :],\n          mutable=['cache']\n        )\n      assert y1.shape == (1, 1, 4)\n      assert y2.shape == (1, 1, 4)\n\n  def test_attention_alias_equivalence(self):\n    key1, key2 = random.split(random.key(0), 2)\n    query = random.uniform(key1, (3, 5))\n    key_value = random.uniform(key2, (9, 5))\n    attention_kwargs = dict(\n      num_heads=8,\n      qkv_features=16,\n      kernel_init=initializers.lecun_normal(),\n      bias_init=initializers.uniform(),\n      deterministic=False,\n    )\n    module1 = nn.MultiHeadDotProductAttention(**attention_kwargs)\n    module2 = nn.MultiHeadAttention(**attention_kwargs)\n    key = lambda: random.key(43279)\n    out1, v1 = module1.init_with_output(key(), query, key_value)\n    out2, v2 = module2.init_with_output(key(), query, key_value, key_value)\n    self.assertTrue((out1 == out2).all())\n    self.assertTrue(\n        jax.tree_util.tree_all(\n            jax.tree_util.tree_map(lambda x, y: (x == y).all(), v1, v2)\n        )\n    )\n\n  def test_attention_alias_submodule(self):\n    key1, key2 = random.split(random.key(0), 2)\n    query = random.uniform(key1, (3, 5))\n    key_value = random.uniform(key2, (9, 5))\n    attention_kwargs = dict(\n      num_heads=8,\n      qkv_features=16,\n      kernel_init=initializers.lecun_normal(),\n      bias_init=initializers.uniform(),\n      deterministic=False,\n    )\n\n    class Foo1(nn.Module):\n      attention_kwargs: dict\n\n      @nn.compact\n      def __call__(self, query, key):\n        return nn.MultiHeadDotProductAttention(**self.attention_kwargs)(\n          query, key\n        )\n\n    class Foo2(nn.Module):\n      attention_kwargs: dict\n\n      @nn.compact\n      def __call__(self, query, key, value):\n        return nn.MultiHeadAttention(**self.attention_kwargs)(query, key, value)\n\n    key = lambda: random.key(5478392)\n    module1 = Foo1(attention_kwargs)\n    module2 = Foo2(attention_kwargs)\n    out1, v1 = module1.init_with_output(key(), query, key_value)\n    out2, v2 = module2.init_with_output(key(), query, key_value, key_value)\n\n    # test different output and variables if layer names are different\n    self.assertTrue((out1 != out2).all())\n    v2['params']['MultiHeadDotProductAttention_0'] = v2['params'][\n      'MultiHeadAttention_0'\n    ]\n    del v2['params']['MultiHeadAttention_0']\n    self.assertTrue(\n        jax.tree_util.tree_all(\n            jax.tree_util.tree_map(lambda x, y: (x != y).all(), v1, v2)\n        )\n    )\n\n    # test same output if variables are the same\n    v2 = jax.tree_util.tree_map(lambda x: x, v1)\n    v2['params']['MultiHeadAttention_0'] = v2['params'][\n      'MultiHeadDotProductAttention_0'\n    ]\n    del v2['params']['MultiHeadDotProductAttention_0']\n    out2 = module2.apply(v2, query, key_value, key_value)\n    self.assertTrue((out1 == out2).all())\n\n    # test same output and variables if names are the same\n    class Foo2(nn.Module):\n      attention_kwargs: dict\n\n      @nn.compact\n      def __call__(self, query, key, value):\n        return nn.MultiHeadAttention(\n          **self.attention_kwargs, name='MultiHeadDotProductAttention_0'\n        )(query, key, value)\n\n    module2 = Foo2(attention_kwargs)\n    out2, v2 = module2.init_with_output(key(), query, key_value, key_value)\n    self.assertTrue((out1 == out2).all())\n    self.assertTrue(\n        jax.tree_util.tree_all(\n            jax.tree_util.tree_map(lambda x, y: (x == y).all(), v1, v2)\n        )\n    )\n\n  @parameterized.parameters(\n      {'force_fp32': True, 'attn_weights_dtype': jnp.float32},\n      {'force_fp32': False, 'attn_weights_dtype': jnp.bfloat16},\n  )\n  def test_mixed_precision_multihead_attention(\n      self, force_fp32, attn_weights_dtype\n  ):\n    input_key, params_key, dropout_key = random.split(random.key(0), 3)\n    x = random.uniform(input_key, (2, 4))\n    attention_kwargs = dict(\n        num_heads=2,\n        qkv_features=4,\n        kernel_init=initializers.lecun_normal(),\n        bias_init=initializers.uniform(),\n        force_fp32_for_softmax=force_fp32,\n        deterministic=False,\n        dtype=jnp.bfloat16,\n    )\n    mha = nn.MultiHeadDotProductAttention(**attention_kwargs)\n    init_vars = mha.init({'params': params_key, 'dropout': dropout_key}, x)\n    _, updated_vars = mha.apply(\n        init_vars, x, mutable=['intermediates'], sow_weights=True\n    )\n    self.assertEqual(\n        updated_vars['intermediates']['attention_weights'][0].dtype,\n        attn_weights_dtype,\n    )\n\n  @parameterized.parameters(\n      (lax.Precision.DEFAULT, None),\n      (None, jax.lax.dot_general),\n  )\n  def test_dot_product_attention_precision_and_einsum_override(\n      self, precision, einsum_dot_general\n  ):\n    # Test that we raise a ValueError if the user specifies both\n    # `precision` and/or `einsum_dot_general` and `qk_attn_weights_einsum`.\n    einsum_cls = lambda: jnp.einsum\n    self.assertRaises(\n        ValueError,\n        nn.dot_product_attention,\n        query=jnp.ones((1, 4, 2)),\n        key=jnp.ones((1, 4, 2)),\n        value=jnp.ones((1, 4, 2)),\n        precision=precision,\n        einsum_dot_general=einsum_dot_general,\n        qk_attn_weights_einsum=einsum_cls,\n        attn_weights_value_einsum=einsum_cls,\n    )\n\n  @parameterized.parameters(\n      (lambda: jax.lax.dot_general, None),\n      (None, lambda: jax.lax.dot_general),\n  )\n  def test_dot_product_attention_specify_einsums_together(\n      self, qk_attn_weights_einsum, attn_weights_value_einsum\n  ):\n    # Test that we raise a ValueError if the user specifies only one of\n    # `qk_attn_weights_einsum` and `attn_weights_value_einsum`.\n    self.assertRaises(\n        ValueError,\n        nn.dot_product_attention,\n        query=jnp.ones((1, 4, 2)),\n        key=jnp.ones((1, 4, 2)),\n        value=jnp.ones((1, 4, 2)),\n        qk_attn_weights_einsum=qk_attn_weights_einsum,\n        attn_weights_value_einsum=attn_weights_value_einsum,\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/linen_batch_apply_test.py",
    "content": "# Copyright 2023 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.linen.batch_apply.\"\"\"\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom absl.testing import absltest, parameterized\n\nfrom flax import linen as nn\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass BatchApplyTest(parameterized.TestCase):\n  @parameterized.parameters(\n    {'fn': lambda a, b: a + b.reshape(1, -1)},\n    {'fn': lambda a, b: jnp.dot(a, b)},\n  )\n  def test_batchapply(self, fn):\n    a = jax.random.normal(jax.random.key(0), [2, 3, 4])\n    b = jax.random.normal(jax.random.key(1), [4])\n\n    def raises(a, b):\n      if len(a.shape) != 2:\n        raise ValueError('a must be shape 2')\n      if len(b.shape) != 1:\n        raise ValueError('b must be shape 1')\n      return fn(a, b)\n\n    out = nn.BatchApply(raises)(a, b)\n    expected_merged_leading = raises(a.reshape(2 * 3, 4), b)\n    expected = expected_merged_leading.reshape(\n      (2, 3) + expected_merged_leading.shape[1:]\n    )\n    np.testing.assert_array_equal(out, expected)\n\n  def test_batchapply_accepts_float(self):\n    def raises(a, b):\n      if len(a.shape) != 2:\n        raise ValueError('a must be shape 2')\n      return a + b\n\n    out = nn.BatchApply(raises)(jnp.ones([2, 3, 4]), 2.0)\n    np.testing.assert_array_equal(out, 3 * jnp.ones([2, 3, 4]))\n\n  def test_batchapply_accepts_none(self):\n    def raises(a, b):\n      if a is not None:\n        raise ValueError('a must be None.')\n      if len(b.shape) != 2:\n        raise ValueError('b must be shape 2')\n      return 3 * b\n\n    out = nn.BatchApply(raises)(None, jnp.ones([2, 3, 4]))\n    np.testing.assert_array_equal(out, 3 * jnp.ones([2, 3, 4]))\n\n  def test_batchapply_raises(self):\n    with self.assertRaisesRegex(ValueError, 'requires at least one input'):\n      nn.BatchApply(lambda: 1)()\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/linen_combinators_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.linen.combinators.\"\"\"\n\nfrom typing import Any\nfrom collections.abc import Sequence\n\nimport jax\nimport numpy as np\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\n\nfrom flax import linen as nn\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass MLP(nn.Module):\n  layer_sizes: Sequence[int]\n  activation: Any | None = None\n  activation_final: Any | None = None\n\n  @nn.compact\n  def __call__(self, inputs):\n    x = inputs\n    for layer_size in self.layer_sizes[:-1]:\n      x = nn.Dense(\n        features=layer_size, kernel_init=nn.initializers.ones_init()\n      )(x)\n      if self.activation is not None:\n        x = self.activation(x)\n    x = nn.Dense(\n      features=self.layer_sizes[-1], kernel_init=nn.initializers.ones_init()\n    )(x)\n    if self.activation_final is None:\n      return x\n    return self.activation_final(x)\n\n\nclass AttentionTuple(nn.Module):\n  num_heads: int = 2\n  qkv_features: int = 16\n\n  @nn.compact\n  def __call__(self, query, key_value):\n    output = nn.MultiHeadDotProductAttention(\n      num_heads=self.num_heads, qkv_features=self.qkv_features\n    )(query, key_value)\n    return output, key_value\n\n\nclass AttentionDict(nn.Module):\n  num_heads: int = 2\n  qkv_features: int = 16\n\n  @nn.compact\n  def __call__(self, query, key_value):\n    output = nn.MultiHeadDotProductAttention(\n      num_heads=self.num_heads, qkv_features=self.qkv_features\n    )(query, key_value)\n    return dict(query=output, key_value=key_value)\n\n\nclass SequentialTest(absltest.TestCase):\n  def test_construction(self):\n    sequential = nn.Sequential([nn.Dense(4), nn.Dense(2)])\n    key1, key2 = random.split(random.key(0), 2)\n    x = random.uniform(key1, (3, 1, 5))\n    params = sequential.init(key2, x)\n    output = sequential.apply(params, x)\n    self.assertEqual(output.shape, (3, 1, 2))\n\n  def test_fails_if_layers_empty(self):\n    sequential = nn.Sequential([])\n    with self.assertRaisesRegex(ValueError, 'Empty Sequential module'):\n      sequential.init(random.key(42), jnp.ones((3, 5)))\n\n  def test_same_output_as_mlp(self):\n    sequential = nn.Sequential(\n      [\n        nn.Dense(4, kernel_init=nn.initializers.ones_init()),\n        nn.Dense(8, kernel_init=nn.initializers.ones_init()),\n        nn.Dense(2, kernel_init=nn.initializers.ones_init()),\n      ]\n    )\n    mlp = MLP(layer_sizes=[4, 8, 2])\n\n    key1, key2 = random.split(random.key(0), 2)\n    x = random.uniform(key1, (3, 5))\n    params_1 = sequential.init(key2, x)\n    params_2 = mlp.init(key2, x)\n\n    output_1 = sequential.apply(params_1, x)\n    output_2 = mlp.apply(params_2, x)\n    np.testing.assert_array_equal(output_1, output_2)\n\n  def test_same_output_as_mlp_with_activation(self):\n    sequential = nn.Sequential(\n      [\n        nn.Dense(4, kernel_init=nn.initializers.ones_init()),\n        nn.relu,\n        nn.Dense(8, kernel_init=nn.initializers.ones_init()),\n        nn.relu,\n        nn.Dense(2, kernel_init=nn.initializers.ones_init()),\n        nn.log_softmax,\n      ]\n    )\n\n    mlp = MLP(\n      layer_sizes=[4, 8, 2],\n      activation=nn.relu,\n      activation_final=nn.log_softmax,\n    )\n\n    key1, key2 = random.split(random.key(0), 2)\n    x = random.uniform(key1, (3, 5))\n    params_1 = sequential.init(key2, x)\n    params_2 = mlp.init(key2, x)\n\n    output_1 = sequential.apply(params_1, x)\n    output_2 = mlp.apply(params_2, x)\n    np.testing.assert_array_equal(output_1, output_2)\n\n  def test_tuple_output(self):\n    sequential = nn.Sequential(\n      [\n        AttentionTuple(),\n        AttentionTuple(),\n      ]\n    )\n\n    key1, key2, key3 = random.split(random.key(0), 3)\n    query = random.uniform(key1, (3, 5))\n    key_value = random.uniform(key2, (9, 5))\n    params_1 = sequential.init(key3, query, key_value)\n    outputs = sequential.apply(params_1, query, key_value)\n    np.testing.assert_equal(len(outputs), 2)\n    out_query, out_key_value = outputs\n    np.testing.assert_equal(out_query.shape, (3, 5))\n    np.testing.assert_equal(out_key_value.shape, (9, 5))\n\n  def test_dict_output(self):\n    sequential = nn.Sequential(\n      [\n        AttentionDict(),\n        AttentionDict(),\n      ]\n    )\n\n    key1, key2, key3 = random.split(random.key(0), 3)\n    query = random.uniform(key1, (3, 5))\n    key_value = random.uniform(key2, (9, 5))\n    params_1 = sequential.init(key3, query, key_value)\n    outputs = sequential.apply(params_1, query, key_value)\n    np.testing.assert_equal(len(outputs), 2)\n    out_query, out_key_value = outputs['query'], outputs['key_value']\n    np.testing.assert_equal(out_query.shape, (3, 5))\n    np.testing.assert_equal(out_key_value.shape, (9, 5))\n\n  def test_sequential_compact(self):\n\n    mlp = nn.Sequential([\n        lambda x: nn.Dense(x.shape[-1])(x),\n        nn.relu,\n        lambda x: nn.Dense(x.shape[-1])(x),\n        nn.relu,\n        lambda x: nn.Dense(x.shape[-1])(x),\n    ])\n\n    params = mlp.init(random.key(0), jnp.ones((3, 5)))['params']\n\n    self.assertIn('Dense_0', params)\n    self.assertIn('Dense_1', params)\n    self.assertIn('Dense_2', params)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/linen_dtypes_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.linen.dtypes.\"\"\"\n\n\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\n\nfrom flax.linen import dtypes\n\ntry:\n  # JAX v0.8.0 and newer\n  from jax import enable_x64\nexcept ImportError:\n  from jax.experimental import enable_x64\n\ndefault_float_dtype = jnp.result_type(1.0)\n\n\nclass DtypesTest(absltest.TestCase):\n  def test_no_inexact_dtype(self):\n    i32 = jnp.int32(1.0)\n    self.assertEqual(dtypes.canonicalize_dtype(i32, inexact=False), jnp.int32)\n\n  def test_inexact_dtype(self):\n    with enable_x64():\n      i64 = jnp.int64(1)\n      self.assertEqual(dtypes.canonicalize_dtype(i64), jnp.float32)\n    i32 = jnp.int32(1)\n    self.assertEqual(dtypes.canonicalize_dtype(i32), jnp.float32)\n    i16 = jnp.int16(1.0)\n    self.assertEqual(dtypes.canonicalize_dtype(i16), jnp.float32)\n\n  def test_explicit_downcast(self):\n    f32 = jnp.float32(1.0)\n    (x,) = dtypes.promote_dtype(f32, dtype=jnp.float16)\n    self.assertEqual(x.dtype, jnp.float16)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/linen_linear_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.linen.linear.\"\"\"\n\nimport functools\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom absl.testing import absltest, parameterized\nfrom jax import random\nfrom jax.nn import initializers\n\nfrom flax import linen as nn\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass LinearTest(parameterized.TestCase):\n  def test_dense(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((1, 3))\n    dense_module = nn.Dense(\n      features=4,\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, _ = dense_module.init_with_output(rng, x)\n    self.assertEqual(y.shape, (1, 4))\n    self.assertEqual(y.dtype, jnp.float32)\n    np.testing.assert_allclose(y, np.full((1, 4), 4.0))\n\n  def test_dense_extra_batch_dims(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((1, 2, 3))\n    dense_module = nn.Dense(\n      features=4,\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, _ = dense_module.init_with_output(rng, x)\n    np.testing.assert_allclose(y, np.full((1, 2, 4), 4.0))\n\n  def test_dense_no_bias(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((1, 3))\n    dense_module = nn.Dense(\n      features=4,\n      use_bias=False,\n      kernel_init=initializers.ones,\n    )\n    y, _ = dense_module.init_with_output(rng, x)\n    np.testing.assert_allclose(y, np.full((1, 4), 3.0))\n\n  def test_dense_is_dense_general(self):\n    x = jax.random.normal(random.key(0), (5, 3))\n    dense_module = nn.Dense(\n      features=4,\n      use_bias=True,\n      bias_init=initializers.normal(),\n    )\n    y1, _ = dense_module.init_with_output(dict(params=random.key(1)), x)\n    dg_module = nn.DenseGeneral(\n      features=4,\n      use_bias=True,\n      bias_init=initializers.normal(),\n    )\n    y2, _ = dg_module.init_with_output(dict(params=random.key(1)), x)\n\n    np.testing.assert_allclose(y1, y2)\n\n  def test_dense_general_batch_dim_raises(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((1, 3, 2, 5))\n    with self.assertRaises(ValueError):\n      dg_module = nn.DenseGeneral(\n        features=4,\n        batch_dims=(0, 2),\n        kernel_init=initializers.ones,\n        bias_init=initializers.ones,\n      )\n      dg_module.init_with_output(rng, x)\n\n  def test_dense_general_two_out(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((1, 3))\n    dg_module = nn.DenseGeneral(\n      features=(2, 2),\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, _ = dg_module.init_with_output(rng, x)\n    np.testing.assert_allclose(y, np.full((1, 2, 2), 4.0))\n\n  def test_dense_general_two_in(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((1, 2, 2))\n    dg_module = nn.DenseGeneral(\n      features=3,\n      axis=(-2, 2),\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, _ = dg_module.init_with_output(rng, x)\n    np.testing.assert_allclose(y, np.full((1, 3), 5.0))\n\n  def test_dense_general_batch_dim(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((2, 1, 3, 5))\n\n    state = {'counter': 0.0}\n\n    def _counter_init(rng, shape, dtype, state):\n      del rng, dtype\n      state['counter'] += 1.0\n      return jnp.full(shape, state['counter'])\n\n    counter_init = functools.partial(_counter_init, state=state)\n\n    dg_module = nn.DenseGeneral(\n      features=7,\n      axis=(3, -2),\n      batch_dims=0,\n      bias_init=initializers.ones,\n      kernel_init=counter_init,\n    )\n    y, _ = dg_module.init_with_output(rng, x)\n    target = np.full((2, 1, 7), 16.0)\n    np.testing.assert_allclose(y, target)\n\n  @parameterized.parameters(\n    [\n      ((-2, 3), (), 'bijk,jklm->bilm'),\n      ((3, -2), (), 'bijk,jklm->bilm'),\n      ((-2, 3), (0,), 'bijk,bjklm->bilm'),\n    ]\n  )\n  def test_dense_general_vs_numpy(self, axis, batch_dims, einsum_expr):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((16, 8, 9, 10))\n\n    dg_module = nn.DenseGeneral(\n      features=(11, 12),\n      axis=axis,\n      batch_dims=batch_dims,\n      bias_init=initializers.ones,\n      kernel_init=initializers.normal(),\n    )\n    y, initial_params = dg_module.init_with_output(rng, x)\n    target = np.einsum(einsum_expr, x, initial_params['params']['kernel']) + 1.0\n    np.testing.assert_allclose(y, target, atol=1e-6)\n\n  def test_complex_params_dense(self):\n    dense = nn.Dense(features=2, param_dtype=jnp.complex64)\n    x = jnp.ones((1, 2), jnp.float32)\n    variables = dense.init(random.key(0), x)\n    self.assertEqual(variables['params']['kernel'].dtype, jnp.complex64)\n    self.assertEqual(variables['params']['bias'].dtype, jnp.complex64)\n    y = dense.apply(variables, x)\n    self.assertEqual(y.dtype, jnp.complex64)\n\n  def test_complex_input_dense(self):\n    dense = nn.Dense(features=2)\n    x = jnp.ones((1, 2), jnp.complex64)\n    variables = dense.init(random.key(0), x)\n    self.assertEqual(variables['params']['kernel'].dtype, jnp.float32)\n    self.assertEqual(variables['params']['bias'].dtype, jnp.float32)\n    y = dense.apply(variables, x)\n    self.assertEqual(y.dtype, jnp.complex64)\n\n  @parameterized.parameters(\n    [\n      (\n        'abc,cde->abde',\n        (3, 4, 5),\n        (5, 6, 7),\n        (3, 4, 6, 7),\n        (6, 7),\n        (1, 1, 6, 7),\n      ),\n      (\n        'abcd,abcd->abcd',\n        (3, 4, 5, 6),\n        (3, 4, 5, 6),\n        (3, 4, 5, 6),\n        (3, 4, 5, 6),\n        (3, 4, 5, 6),\n      ),\n      (\n        'abcd,abcd->abd',\n        (3, 4, 5, 6),\n        (3, 4, 5, 6),\n        (3, 4, 6),\n        (3, 4, 6),\n        (3, 4, 6),\n      ),\n      (\n        'abcd,cdef->abef',\n        (3, 4, 5, 6),\n        (5, 6, 7, 8),\n        (3, 4, 7, 8),\n        (7, 8),\n        (1, 1, 7, 8),\n      ),\n      (\n        'abcd,eafc->bfed',\n        (3, 4, 5, 6),\n        (7, 3, 8, 5),\n        (4, 8, 7, 6),\n        (8, 7),\n        (1, 8, 7, 1),\n      ),\n      (\n        'abcd,cbedf->abfe',\n        (3, 4, 5, 6),\n        (5, 4, 7, 6, 8),\n        (3, 4, 8, 7),\n        (4, 8, 7),\n        (1, 4, 8, 7),\n      ),\n      (\n        'ab...,bc...->ac...',\n        (3, 4, 6),\n        (4, 5, 6),\n        (3, 5, 6),\n        (5, 6),\n        (1, 5, 6),\n      ),\n      (\n        'd...ab,bc...->ad...c',\n        (8, 6, 7, 3, 4),\n        (4, 5, 6, 7),\n        (3, 8, 6, 7, 5),\n        (6, 7, 5),\n        (1, 1, 6, 7, 5),\n      ),\n      (\n        'd...ab,bc...->adc',\n        (8, 6, 7, 3, 4),\n        (4, 5, 6, 7),\n        (3, 8, 5),\n        (5,),\n        (1, 1, 5),\n      ),\n      (\n        'abd...,bc...->ac...',\n        (3, 4, 6),\n        (4, 5, 6),\n        (3, 5, 6),\n        (5, 6),\n        (1, 5, 6),\n      ),\n      (\n        'a...d,ej...f->adef',\n        (3, 4, 5, 6),\n        (7, 4, 5, 8),\n        (3, 6, 7, 8),\n        (7, 8),\n        (1, 1, 7, 8),\n      ),\n      (\n        'ab...d,ej...f->ad...f',\n        (3, 4, 5, 6),\n        (7, 4, 5, 8),\n        (3, 6, 5, 8),\n        (5, 8),\n        (1, 1, 5, 8),\n      ),\n    ]\n  )\n  def test_einsum_init_apply(\n    self,\n    einsum_str,\n    lhs_shape,\n    rhs_shape,\n    expected_result_shape,\n    expected_bias_shape,\n    bias_broadcast_shape,\n  ):\n    layer = nn.Einsum(rhs_shape, einsum_str, bias_init=nn.initializers.normal())\n    x = jax.random.normal(jax.random.key(0), lhs_shape)\n\n    v = layer.init(jax.random.key(1), x)\n    self.assertEqual(rhs_shape, v['params']['kernel'].shape)\n    self.assertEqual(expected_bias_shape, v['params']['bias'].shape)\n\n    out = layer.apply(v, x)\n    self.assertEqual(out.shape, expected_result_shape)\n    expected_out = jnp.einsum(einsum_str, x, v['params']['kernel']) + v[\n      'params'\n    ]['bias'].reshape(bias_broadcast_shape)\n    np.testing.assert_allclose(out, expected_out)\n\n  @parameterized.parameters(\n    [\n      (\n        ('abd,bce->ace', 'abd,bc...->ac...', 'abd...,bc...->ac...'),\n        (3, 4, 6),\n        (4, 5, 6),\n      ),\n      (\n        (\n          'abcd,ejcf->adef',\n          'ab...d,ej...f->adef',\n          'ab...d,e...f->adef',\n          'a...d,ej...f->adef',\n        ),\n        (3, 4, 5, 6),\n        (7, 4, 5, 8),\n      ),\n      (\n        ('abcd,ejcf->adcf', 'ab...d,ej...f->ad...f'),\n        (3, 4, 5, 6),\n        (7, 4, 5, 8),\n      ),\n    ]\n  )\n  def test_einsum_ellipsis_equivalence(\n    self, einsum_str_list, lhs_shape, rhs_shape\n  ):\n    x = jax.random.uniform(jax.random.key(0), lhs_shape)\n    layer = nn.Einsum(\n      rhs_shape, einsum_str_list[0], bias_init=nn.initializers.normal()\n    )\n    v = layer.init(jax.random.key(1), x)\n    out = layer.apply(v, x)\n\n    for einsum_str in einsum_str_list[1:]:\n      layer2 = nn.Einsum(\n        rhs_shape, einsum_str, bias_init=nn.initializers.normal()\n      )\n      v2 = layer2.init(jax.random.key(1), x)\n      np.testing.assert_allclose(v['params']['kernel'], v2['params']['kernel'])\n      np.testing.assert_allclose(v['params']['bias'], v2['params']['bias'])\n      np.testing.assert_allclose(out, layer2.apply(v2, x))\n\n  def test_einsum_str_arg(self):\n    einsum_str = 'abc,cde->abde'\n    x = jax.random.normal(jax.random.key(0), (3, 4, 5))\n\n    constructed_layer = nn.Einsum(\n      (5, 6, 7), einsum_str, bias_init=nn.initializers.normal()\n    )\n    constructed_v = constructed_layer.init(jax.random.key(1), x)\n    constructed_out = constructed_layer.apply(constructed_v, x)\n\n    called_layer = nn.Einsum((5, 6, 7), bias_init=nn.initializers.normal())\n    called_v = called_layer.init(jax.random.key(1), x, einsum_str)\n    called_out = called_layer.apply(called_v, x, einsum_str)\n\n    np.testing.assert_allclose(\n      constructed_v['params']['kernel'], called_v['params']['kernel']\n    )\n    np.testing.assert_allclose(\n      constructed_v['params']['bias'], called_v['params']['bias']\n    )\n    np.testing.assert_allclose(constructed_out, called_out)\n\n    with self.assertRaisesWithLiteralMatch(\n      ValueError,\n      'Parameter \"einsum_str\" was passed to the constructor and at call time. Should be passed just once.',\n    ):\n      constructed_layer.init(jax.random.key(1), x, einsum_str)\n    with self.assertRaisesWithLiteralMatch(\n      ValueError,\n      'Parameter \"einsum_str\" must be passed to the constructor or at call time.',\n    ):\n      called_layer.init(jax.random.key(1), x)\n\n  def test_einsum_space_str(self):\n    x = jax.random.normal(jax.random.key(0), (3, 4, 5))\n\n    layer1 = nn.Einsum(\n      (5, 6, 7), 'abc,cde->abde', bias_init=nn.initializers.normal()\n    )\n    v1 = layer1.init(jax.random.key(1), x)\n    out1 = layer1.apply(v1, x)\n\n    layer2 = nn.Einsum(\n      (5, 6, 7),\n      '  ab c  , c d e    -  >a b d  e  ',\n      bias_init=nn.initializers.normal(),\n    )\n    v2 = layer2.init(jax.random.key(1), x)\n    out2 = layer2.apply(v1, x)\n\n    np.testing.assert_allclose(v1['params']['kernel'], v2['params']['kernel'])\n    np.testing.assert_allclose(v1['params']['bias'], v2['params']['bias'])\n    np.testing.assert_allclose(out1, out2)\n\n  @parameterized.parameters(\n    [\n      ('abc,cde', '`einsum_str` equation must be explicit and include \"->\".'),\n      (\n        'abc->',\n        f'`einsum_str` equation must have exactly two operands and therefore, exactly one comma character, instead of 0',\n      ),\n      (\n        'abc,cde,efg->abdfg',\n        f'`einsum_str` equation must have exactly two operands and therefore, exactly one comma character, instead of 2',\n      ),\n    ]\n  )\n  def test_einsum_error(self, einsum_str, error_msg):\n    x = jax.random.normal(jax.random.key(0), (3, 4, 5))\n\n    layer = nn.Einsum((5, 6, 7), einsum_str)\n    with self.assertRaisesRegex(ValueError, error_msg):\n      layer.init(jax.random.key(1), x)\n\n  @parameterized.product(use_bias=(True, False))\n  def test_conv(self, use_bias):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((1, 8, 3))\n    conv_module = nn.Conv(\n      features=4,\n      use_bias=use_bias,\n      kernel_size=(3,),\n      padding='VALID',\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 3, 4))\n    expected = 10.0 if use_bias else 9.0\n    np.testing.assert_allclose(y, np.full((1, 6, 4), expected))\n\n  @parameterized.product(use_bias=(True, False))\n  def test_multibatch_input_conv(self, use_bias):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((2, 5, 8, 3))\n    conv_module = nn.Conv(\n      features=4,\n      use_bias=use_bias,\n      kernel_size=(3,),\n      padding='VALID',\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 3, 4))\n    expected = 10.0 if use_bias else 9.0\n    np.testing.assert_allclose(y, np.full((2, 5, 6, 4), expected))\n\n  def test_conv_local(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((1, 8, 2))\n    conv_module = nn.ConvLocal(\n      features=4,\n      kernel_size=(3,),\n      padding='VALID',\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n    self.assertEqual(initial_params['params']['kernel'].shape, (6, 3 * 2, 4))\n    np.testing.assert_allclose(y, np.full((1, 6, 4), 7.0))\n\n  def test_single_input_conv(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((8, 3))\n    conv_module = nn.Conv(\n      features=4,\n      kernel_size=(3,),\n      padding='VALID',\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 3, 4))\n    np.testing.assert_allclose(y, np.full((6, 4), 10.0))\n\n  def test_single_input_masked_conv(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((8, 3))\n    m = jnp.tril(jnp.ones((3, 3, 4)))\n    conv_module = nn.Conv(\n      features=4,\n      kernel_size=(3,),\n      padding='VALID',\n      mask=m,\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    expected = jnp.array(\n      [\n        [10.0, 7.0, 4.0, 1.0],\n        [10.0, 7.0, 4.0, 1.0],\n        [10.0, 7.0, 4.0, 1.0],\n        [10.0, 7.0, 4.0, 1.0],\n        [10.0, 7.0, 4.0, 1.0],\n        [10.0, 7.0, 4.0, 1.0],\n      ]\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 3, 4))\n    np.testing.assert_allclose(y, expected)\n\n  def test_single_input_conv_local(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((8, 2))\n    conv_module = nn.ConvLocal(\n      features=4,\n      kernel_size=(3,),\n      padding='VALID',\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n    self.assertEqual(initial_params['params']['kernel'].shape, (6, 3 * 2, 4))\n    np.testing.assert_allclose(y, np.full((6, 4), 7.0))\n\n  def test_group_conv(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((1, 8, 4))\n    conv_module = nn.Conv(\n      features=4,\n      kernel_size=(3,),\n      feature_group_count=2,\n      padding='VALID',\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 2, 4))\n    np.testing.assert_allclose(y, np.full((1, 6, 4), 7.0))\n\n  @parameterized.product(\n    n_batch=(1, 3),\n    n_features=(1, 2),\n    kernel_size=(1, 2, 3, 9),\n    n_input_features=(1, 3),\n    input_size=(1, 8, 16),\n    module=(nn.Conv, nn.ConvLocal),\n  )\n  def test_circular_conv_1d_constant(\n    self,\n    n_batch,\n    n_features,\n    kernel_size,\n    n_input_features,\n    input_size,\n    module,\n  ):\n    \"\"\"\n    Test 1D convolution with circular padding: filter with all elements equal\n    to 1 applied on an input with all elements equal to 1.\n    Result should have the same shape as input (except for the feature\n    dimension) and have all elements equal to\n    `n_input_features * kernel_lin_size`.\n    \"\"\"\n    rng = dict(params=random.key(0))\n    x = jnp.ones((n_batch, input_size, n_input_features))\n    conv_module = module(\n      features=n_features,\n      kernel_size=(kernel_size,),\n      padding='CIRCULAR',\n      kernel_init=initializers.ones,\n      bias_init=initializers.zeros,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    kernel_shape = self._get_kernel_shape(\n      x.shape, (kernel_size,), module, n_features\n    )\n\n    self.assertEqual(\n      initial_params['params']['kernel'].shape,\n      kernel_shape,\n    )\n    correct_ans = np.full(\n      (n_batch, input_size, n_features), kernel_size * n_input_features\n    )\n    np.testing.assert_allclose(y, correct_ans)\n\n  def _get_kernel_shape(self, input_shape, kernel_size, module, n_features):\n    if module == nn.Conv:\n      kernel_shape = kernel_size + (input_shape[-1], n_features)\n    elif module == nn.ConvLocal:\n      kernel_shape = input_shape[1:-1] + (\n        input_shape[-1] * np.prod(kernel_size),\n        n_features,\n      )\n    else:\n      raise ValueError(module)\n    return kernel_shape\n\n  @parameterized.product(\n    n_batch=(1, 3),\n    n_features=(1, 2, 10),\n    kernel_lin_size=(1, 2, 3, 9),\n    n_input_features=(1, 5),\n    input_x_size=(14,),\n    input_y_size=(5, 10),\n    module=(nn.Conv, nn.ConvLocal),\n  )\n  def test_circular_conv_2d_constant(\n    self,\n    n_batch,\n    n_features,\n    kernel_lin_size,\n    n_input_features,\n    input_x_size,\n    input_y_size,\n    module,\n  ):\n    \"\"\"\n    Test 2D convolution with circular padding: square filter with all elements\n    equal to 1 applied on an input with all elements equal to 1.\n    Result should have the same shape as input (except for the feature\n    dimension) and have all elements equal to\n    `n_input_features * kernel_lin_size^2`.\n    \"\"\"\n    rng = dict(params=random.key(0))\n    x = jnp.ones((n_batch, input_x_size, input_y_size, n_input_features))\n    kernel_size = (kernel_lin_size, kernel_lin_size)\n    conv_module = module(\n      features=n_features,\n      kernel_size=kernel_size,\n      padding='CIRCULAR',\n      kernel_init=initializers.ones,\n      bias_init=initializers.zeros,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    kernel_shape = self._get_kernel_shape(\n      x.shape, kernel_size, module, n_features\n    )\n\n    self.assertEqual(\n      initial_params['params']['kernel'].shape,\n      kernel_shape,\n    )\n    correct_ans = np.full(\n      (n_batch, input_x_size, input_y_size, n_features),\n      kernel_lin_size * kernel_lin_size * n_input_features,\n    )\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_circular_conv_1d_custom(self):\n    \"\"\"Test 1d convolution with circular padding and a stride.\"\"\"\n    rng = dict(params=random.key(0))\n    x = np.arange(1, 6)\n    x = np.expand_dims(x, (0, 2))\n    kernel = np.array((1, 2, 1))\n    kernel = np.expand_dims(kernel, (1, 2))\n\n    conv_module = nn.Conv(\n      features=1,\n      kernel_size=(3,),\n      strides=(3,),\n      padding='CIRCULAR',\n      kernel_init=lambda *_: kernel,\n      bias_init=initializers.zeros,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 1, 1))\n    # Compare with manually computed convolution\n    correct_ans = np.array((5 + 2 * 1 + 2, 3 + 2 * 4 + 5))\n    correct_ans = np.expand_dims(correct_ans, (0, 2))\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_circular_conv_local_1d_custom(self):\n    \"\"\"\n    Test 1d local convolution with circular padding and a stride\n    \"\"\"\n    rng = dict(params=random.key(0))\n    x = np.arange(1, 6)\n    x = np.expand_dims(x, (0, 2))\n    kernel = np.array(((-1, 2, 3), (4, 5, 6)))\n    kernel = np.expand_dims(kernel, (2,))\n    conv_module = nn.ConvLocal(\n      features=1,\n      kernel_size=(3,),\n      strides=(3,),\n      padding='CIRCULAR',\n      kernel_init=lambda *_: kernel,\n      bias_init=initializers.zeros,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(initial_params['params']['kernel'].shape, (2, 3, 1))\n    # Compare with manually computed convolution\n    correct_ans = np.array((-1 * 5 + 2 * 1 + 3 * 2, 4 * 3 + 5 * 4 + 6 * 5))\n    correct_ans = np.expand_dims(correct_ans, (0, 2))\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_circular_conv_1d_dilation(self):\n    \"\"\"Test 1d convolution with circular padding and kernel dilation.\"\"\"\n    rng = dict(params=random.key(0))\n    x = np.arange(1, 6)\n    x = np.expand_dims(x, (0, 2))\n    kernel = np.array((1, 2, 1))\n    kernel = np.expand_dims(kernel, (1, 2))\n\n    conv_module = nn.Conv(\n      features=1,\n      kernel_size=(3,),\n      padding='CIRCULAR',\n      kernel_init=lambda *_: kernel,\n      bias_init=initializers.zeros,\n      kernel_dilation=(3,),\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 1, 1))\n    # Compare with manually computed convolution\n    correct_ans = np.array(\n      (\n        3 + 2 * 1 + 4,\n        4 + 2 * 2 + 5,\n        5 + 2 * 3 + 1,\n        1 + 2 * 4 + 2,\n        2 + 2 * 5 + 3,\n      )\n    )\n    correct_ans = np.expand_dims(correct_ans, (0, 2))\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_circular_conv_local_1d_dilation(self):\n    \"\"\"\n    Test 1d local convolution with circular padding and kernel dilation\n    \"\"\"\n    rng = dict(params=random.key(0))\n    x = np.arange(1, 6)\n    x = np.expand_dims(x, (0, 2))\n    kernel = np.array(\n      ((1, 2, 1), (3, 4, 5), (-1, 1, 2), (2, 3, 4), (-1, -2, -3))\n    )\n    kernel = np.expand_dims(kernel, (2,))\n\n    conv_module = nn.ConvLocal(\n      features=1,\n      kernel_size=(3,),\n      padding='CIRCULAR',\n      kernel_init=lambda *_: kernel,\n      bias_init=initializers.zeros,\n      kernel_dilation=(3,),\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(initial_params['params']['kernel'].shape, (5, 3, 1))\n    # Compare with manually computed convolution\n    correct_ans = np.array(\n      (\n        1 * 3 + 2 * 1 + 1 * 4,\n        3 * 4 + 4 * 2 + 5 * 5,\n        -1 * 5 + 1 * 3 + 2 * 1,\n        2 * 1 + 3 * 4 + 4 * 2,\n        -1 * 2 + -2 * 5 + -3 * 3,\n      )\n    )\n    correct_ans = np.expand_dims(correct_ans, (0, 2))\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_circular_conv_2d_custom(self):\n    \"\"\"Test 2d convolution with circular padding on a 3x3 example.\"\"\"\n    rng = dict(params=random.key(0))\n    x = np.array(((1, 2, 3), (4, 5, 6), (7, 8, 9)))\n    x = np.expand_dims(x, (0, 3))\n    kernel = np.array(((0, 1, 0), (1, 2, 1), (0, 1, 0)))\n    kernel = np.expand_dims(kernel, (2, 3))\n\n    conv_module = nn.Conv(\n      features=1,\n      kernel_size=(3, 3),\n      padding='CIRCULAR',\n      kernel_init=lambda *_: kernel,\n      bias_init=initializers.zeros,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 3, 1, 1))\n    # Compare with manually computed convolution\n    correct_ans = np.array(\n      (\n        (2 * 1 + 7 + 2 + 4 + 3, 2 * 2 + 8 + 3 + 5 + 1, 2 * 3 + 9 + 1 + 6 + 2),\n        (2 * 4 + 1 + 5 + 7 + 6, 2 * 5 + 2 + 6 + 8 + 4, 2 * 6 + 3 + 4 + 9 + 5),\n        (2 * 7 + 4 + 8 + 1 + 9, 2 * 8 + 5 + 9 + 2 + 7, 2 * 9 + 6 + 7 + 3 + 8),\n      )\n    )\n    correct_ans = np.expand_dims(correct_ans, (0, 3))\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_circular_conv_local_2d_custom(self):\n    \"\"\"\n    Test 2d local convolution with circular padding on a 3x3 example\n    \"\"\"\n    rng = dict(params=random.key(0))\n    x = np.array(((1, 2, 3), (4, 5, 6), (7, 8, 9)))\n    x = np.expand_dims(x, (0, 3))\n    kernel = np.array(\n      (\n        (\n          ((0, 1, 0), (1, 2, 1), (0, 1, 0)),\n          ((0, 1, 0), (1, 3, 1), (0, 1, 0)),\n          ((0, 1, 0), (1, 4, 1), (0, 1, 0)),\n        ),\n        (\n          ((0, 1, 0), (1, 5, 1), (0, 1, 0)),\n          ((0, 1, 0), (1, 6, 1), (0, 1, 0)),\n          ((0, 1, 0), (1, 7, 1), (0, 1, 0)),\n        ),\n        (\n          ((0, 1, 0), (1, 8, 1), (0, 1, 0)),\n          ((0, 1, 0), (1, 9, 1), (0, 1, 0)),\n          ((0, 1, 0), (1, 10, 1), (0, 1, 0)),\n        ),\n      )\n    )\n    kernel = np.expand_dims(kernel, (3,))\n    kernel = np.reshape(kernel, (3, 3, 9, 1))\n\n    conv_module = nn.ConvLocal(\n      features=1,\n      kernel_size=(3, 3),\n      padding='CIRCULAR',\n      kernel_init=lambda *_: kernel,\n      bias_init=initializers.zeros,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 3, 9, 1))\n    # Compare with manually computed convolution\n    correct_ans = np.array(\n      (\n        (2 * 1 + 7 + 2 + 4 + 3, 3 * 2 + 8 + 3 + 5 + 1, 4 * 3 + 9 + 1 + 6 + 2),\n        (5 * 4 + 1 + 5 + 7 + 6, 6 * 5 + 2 + 6 + 8 + 4, 7 * 6 + 3 + 4 + 9 + 5),\n        (8 * 7 + 4 + 8 + 1 + 9, 9 * 8 + 5 + 9 + 2 + 7, 10 * 9 + 6 + 7 + 3 + 8),\n      )\n    )\n    correct_ans = np.expand_dims(correct_ans, (0, 3))\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_reflect_conv_1d_custom(self):\n    \"\"\"Test 1d convolution with reflection padding and a stride.\"\"\"\n    rng = dict(params=random.key(0))\n    x = np.arange(1, 6)\n    x = np.expand_dims(x, (0, 2))\n    kernel = np.array((1, 2, 1))\n    kernel = np.expand_dims(kernel, (1, 2))\n\n    conv_module = nn.Conv(\n      features=1,\n      kernel_size=(3,),\n      strides=(2,),\n      padding='REFLECT',\n      kernel_init=lambda *_: kernel,\n      bias_init=initializers.zeros,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 1, 1))\n    # Compare with manually computed convolution\n    correct_ans = np.array((2 + 2 * 1 + 2, 2 + 2 * 3 + 4, 4 + 2 * 5 + 4))\n    correct_ans = np.expand_dims(correct_ans, (0, 2))\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_reflect_conv_2d_custom(self):\n    \"\"\"Test 2d convolution with reflect padding on a 3x3 example.\"\"\"\n    rng = dict(params=random.key(0))\n    x = np.array(((1, 2, 3), (4, 5, 6), (7, 8, 9)))\n    x = np.expand_dims(x, (0, 3))\n    kernel = np.array(((0, 1, 0), (1, 2, 1), (0, 1, 0)))\n    kernel = np.expand_dims(kernel, (2, 3))\n\n    conv_module = nn.Conv(\n      features=1,\n      kernel_size=(3, 3),\n      padding='REFLECT',\n      kernel_init=lambda *_: kernel,\n      bias_init=initializers.zeros,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 3, 1, 1))\n    # Compare with manually computed convolution\n    correct_ans = np.array(\n      (\n        (2 * 1 + 4 + 2 + 4 + 2, 2 * 2 + 5 + 3 + 5 + 1, 2 * 3 + 6 + 2 + 6 + 2),\n        (2 * 4 + 1 + 5 + 7 + 5, 2 * 5 + 2 + 6 + 8 + 4, 2 * 6 + 3 + 5 + 9 + 5),\n        (2 * 7 + 4 + 8 + 8 + 4, 2 * 8 + 5 + 9 + 5 + 7, 2 * 9 + 6 + 8 + 6 + 8),\n      )\n    )\n    correct_ans = np.expand_dims(correct_ans, (0, 3))\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_causal_conv1d(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((1, 8, 4))\n    conv_module = nn.Conv(\n      features=4,\n      kernel_size=(3,),\n      padding='CAUSAL',\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, _ = conv_module.init_with_output(rng, x)\n    correct_ans = np.array(\n      [\n        [\n          [5.0, 5.0, 5.0, 5.0],\n          [9.0, 9.0, 9.0, 9.0],\n          [13.0, 13.0, 13.0, 13.0],\n          [13.0, 13.0, 13.0, 13.0],\n          [13.0, 13.0, 13.0, 13.0],\n          [13.0, 13.0, 13.0, 13.0],\n          [13.0, 13.0, 13.0, 13.0],\n          [13.0, 13.0, 13.0, 13.0],\n        ]\n      ]\n    )\n    np.testing.assert_allclose(y, correct_ans)\n    np.testing.assert_array_equal(correct_ans.shape, y.shape)\n\n  @parameterized.product(\n    use_bias=(True, False),\n  )\n  def test_conv_transpose(self, use_bias):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((1, 8, 3))\n    conv_transpose_module = nn.ConvTranspose(\n      features=4,\n      use_bias=use_bias,\n      kernel_size=(3,),\n      padding='VALID',\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, initial_params = conv_transpose_module.init_with_output(rng, x)\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 3, 4))\n    correct_ans = np.array(\n      [\n        [\n          [4.0, 4.0, 4.0, 4.0],\n          [7.0, 7.0, 7.0, 7.0],\n          [10.0, 10.0, 10.0, 10.0],\n          [10.0, 10.0, 10.0, 10.0],\n          [10.0, 10.0, 10.0, 10.0],\n          [10.0, 10.0, 10.0, 10.0],\n          [10.0, 10.0, 10.0, 10.0],\n          [10.0, 10.0, 10.0, 10.0],\n          [7.0, 7.0, 7.0, 7.0],\n          [4.0, 4.0, 4.0, 4.0],\n        ]\n      ]\n    )\n    if not use_bias:\n      correct_ans -= 1.0\n    np.testing.assert_allclose(y, correct_ans)\n\n  @parameterized.product(\n    use_bias=(True, False),\n  )\n  def test_multibatch_input_conv_transpose(self, use_bias):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((2, 5, 8, 3))\n    conv_transpose_module = nn.ConvTranspose(\n      features=4,\n      use_bias=use_bias,\n      kernel_size=(3,),\n      padding='VALID',\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, initial_params = conv_transpose_module.init_with_output(rng, x)\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 3, 4))\n    correct_ans = np.array(\n      [\n        [\n          [4.0, 4.0, 4.0, 4.0],\n          [7.0, 7.0, 7.0, 7.0],\n          [10.0, 10.0, 10.0, 10.0],\n          [10.0, 10.0, 10.0, 10.0],\n          [10.0, 10.0, 10.0, 10.0],\n          [10.0, 10.0, 10.0, 10.0],\n          [10.0, 10.0, 10.0, 10.0],\n          [10.0, 10.0, 10.0, 10.0],\n          [7.0, 7.0, 7.0, 7.0],\n          [4.0, 4.0, 4.0, 4.0],\n        ]\n      ]\n    )\n    correct_ans = np.repeat(correct_ans[None], repeats=2, axis=0)\n    correct_ans = np.repeat(correct_ans, repeats=5, axis=1)\n    if not use_bias:\n      correct_ans -= 1.0\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_single_input_conv_transpose(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((8, 3))\n    conv_transpose_module = nn.ConvTranspose(\n      features=4,\n      kernel_size=(3,),\n      padding='VALID',\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, initial_params = conv_transpose_module.init_with_output(rng, x)\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 3, 4))\n    correct_ans = np.array(\n      [\n        [4.0, 4.0, 4.0, 4.0],\n        [7.0, 7.0, 7.0, 7.0],\n        [10.0, 10.0, 10.0, 10.0],\n        [10.0, 10.0, 10.0, 10.0],\n        [10.0, 10.0, 10.0, 10.0],\n        [10.0, 10.0, 10.0, 10.0],\n        [10.0, 10.0, 10.0, 10.0],\n        [10.0, 10.0, 10.0, 10.0],\n        [7.0, 7.0, 7.0, 7.0],\n        [4.0, 4.0, 4.0, 4.0],\n      ]\n    )\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_single_input_masked_conv_transpose(self):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((8, 3))\n    m = jnp.tril(jnp.ones((3, 3, 4)))\n    conv_transpose_module = nn.ConvTranspose(\n      features=4,\n      kernel_size=(3,),\n      padding='VALID',\n      mask=m,\n      kernel_init=initializers.ones,\n      bias_init=initializers.ones,\n    )\n    y, initial_params = conv_transpose_module.init_with_output(rng, x)\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 3, 4))\n    correct_ans = np.array(\n      [\n        [4.0, 3.0, 2.0, 1.0],\n        [7.0, 5.0, 3.0, 1.0],\n        [10.0, 7.0, 4.0, 1.0],\n        [10.0, 7.0, 4.0, 1.0],\n        [10.0, 7.0, 4.0, 1.0],\n        [10.0, 7.0, 4.0, 1.0],\n        [10.0, 7.0, 4.0, 1.0],\n        [10.0, 7.0, 4.0, 1.0],\n        [7.0, 5.0, 3.0, 1.0],\n        [4.0, 3.0, 2.0, 1.0],\n      ]\n    )\n    np.testing.assert_allclose(y, correct_ans)\n\n  @parameterized.product(\n    n_batch=(1, 3),\n    n_features=(1, 2),\n    kernel_size=(1, 2, 3, 9),\n    n_input_features=(1, 3),\n    input_size=(1, 8, 16),\n  )\n  def test_circular_conv_transpose_1d_constant(\n    self, n_batch, n_features, kernel_size, n_input_features, input_size\n  ):\n    \"\"\"\n    Test 1D transposed convolution with circular padding: filter with all\n    elements equal to 1 applied on an input with all elements equal to 1.\n    Result should have the same shape as input (except for the feature\n    dimension) and have all elements equal to\n    `n_input_features * kernel_lin_size`.\n    \"\"\"\n    rng = dict(params=random.key(0))\n    x = jnp.ones((n_batch, input_size, n_input_features))\n    conv_module = nn.ConvTranspose(\n      features=n_features,\n      kernel_size=(kernel_size,),\n      padding='CIRCULAR',\n      kernel_init=initializers.ones,\n      bias_init=initializers.zeros,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(\n      initial_params['params']['kernel'].shape,\n      (kernel_size, n_input_features, n_features),\n    )\n    correct_ans = np.full(\n      (n_batch, input_size, n_features), kernel_size * n_input_features\n    )\n    np.testing.assert_allclose(y, correct_ans)\n\n  @parameterized.product(\n    n_batch=(1, 3),\n    n_features=(1, 2, 10),\n    kernel_lin_size=(1, 2, 3, 9),\n    n_input_features=(1, 5),\n    input_x_size=(14,),\n    input_y_size=(5, 10),\n  )\n  def test_circular_conv_transpose_2d_constant(\n    self,\n    n_batch,\n    n_features,\n    kernel_lin_size,\n    n_input_features,\n    input_x_size,\n    input_y_size,\n  ):\n    \"\"\"\n    Test 2D transposed convolution with circular padding: square filter with all\n    elements equal to 1 applied on an input with all elements equal to 1.\n    Result should have the same shape as input (except for the feature\n    dimension) and have all elements equal to\n    `n_input_features * kernel_lin_size^2`.\n    \"\"\"\n    rng = dict(params=random.key(0))\n    x = jnp.ones((n_batch, input_x_size, input_y_size, n_input_features))\n    conv_module = nn.ConvTranspose(\n      features=n_features,\n      kernel_size=(kernel_lin_size, kernel_lin_size),\n      padding='CIRCULAR',\n      kernel_init=initializers.ones,\n      bias_init=initializers.zeros,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(\n      initial_params['params']['kernel'].shape,\n      (kernel_lin_size, kernel_lin_size, n_input_features, n_features),\n    )\n    correct_ans = np.full(\n      (n_batch, input_x_size, input_y_size, n_features),\n      kernel_lin_size * kernel_lin_size * n_input_features,\n    )\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_circular_conv_transpose_2d_with_vmap(self):\n    layer = nn.ConvTranspose(features=5, kernel_size=(3,), padding='CIRCULAR')\n\n    # this is ok\n    sample_input = jnp.ones((1, 32, 2))\n    out, vars = layer.init_with_output(jax.random.key(0), sample_input)\n    self.assertEqual(out.shape, (1, 32, 5))\n\n    batch_input = jnp.ones((8, 32, 2))\n    batch_apply = jax.vmap(layer.apply, in_axes=(None, 0))\n\n    # this breaks with the error provided\n    batch_out = batch_apply(vars, batch_input)\n    self.assertEqual(batch_out.shape, (8, 32, 5))\n\n  def test_circular_conv_transpose_1d_custom(self):\n    \"\"\"Test 1d transposed convolution with circular padding and a stride.\"\"\"\n    rng = dict(params=random.key(0))\n    x = np.arange(1, 6)\n    x = np.expand_dims(x, (0, 2))\n    kernel = np.array((1, 2, 1))\n    kernel = np.expand_dims(kernel, (1, 2))\n\n    conv_module = nn.ConvTranspose(\n      features=1,\n      kernel_size=(3,),\n      strides=(3,),\n      padding='CIRCULAR',\n      kernel_init=lambda *_: kernel,\n      bias_init=initializers.zeros,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 1, 1))\n    # Compare with manually computed convolution\n    correct_ans = np.array(\n      (  # pyformat: disable\n        1 * 1,\n        1 * 2,\n        1 * 1,\n        2 * 1,\n        2 * 2,\n        2 * 1,\n        3 * 1,\n        3 * 2,\n        3 * 1,\n        4 * 1,\n        4 * 2,\n        4 * 1,\n        5 * 1,\n        5 * 2,\n        5 * 1,\n      )\n    )\n    correct_ans = np.expand_dims(correct_ans, (0, 2))\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_circular_conv_transpose_2d_custom(self):\n    \"\"\"Test 2d transposed convolution with circular padding on a 3x3 example.\"\"\"\n    rng = dict(params=random.key(0))\n    x = np.array(\n      (\n        (1, 2, 3),\n        (4, 5, 6),\n        (7, 8, 9),\n      )\n    )\n    x = np.expand_dims(x, (0, 3))\n    kernel = np.array(((0, 1, 0), (1, 2, 1), (0, 1, 0)))\n    kernel = np.expand_dims(kernel, (2, 3))\n\n    conv_module = nn.ConvTranspose(\n      features=1,\n      kernel_size=(3, 3),\n      padding='CIRCULAR',\n      kernel_init=lambda *_: kernel,\n      bias_init=initializers.zeros,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(initial_params['params']['kernel'].shape, (3, 3, 1, 1))\n    # Compare with manually computed convolution\n    correct_ans = np.array(\n      (\n        (18, 21, 24),\n        (27, 30, 33),\n        (36, 39, 42),\n      )\n    )\n    correct_ans = np.expand_dims(correct_ans, (0, 3))\n    np.testing.assert_allclose(y, correct_ans)\n\n  def test_circular_conv_transpose_2d_custom_bias(self):\n    \"\"\"Test 2d transposed convolution with circular padding on a 2x2 example with bias.\"\"\"\n    rng = dict(params=random.key(0))\n    x = np.array(((1, 2), (3, 4)))\n    x = np.expand_dims(x, (0, 3))\n    kernel = np.array(\n      (\n        (1, 2),\n        (3, 4),\n      )\n    )\n    kernel = np.expand_dims(kernel, (2, 3))\n\n    conv_module = nn.ConvTranspose(\n      features=1,\n      kernel_size=(2, 2),\n      padding='CIRCULAR',\n      kernel_init=lambda *_: kernel,\n      bias_init=initializers.ones,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n\n    self.assertEqual(initial_params['params']['kernel'].shape, (2, 2, 1, 1))\n    # Compare with manually computed convolution\n    correct_ans = np.array(\n      (\n        (21, 23),\n        (29, 31),\n      )\n    )\n    correct_ans = np.expand_dims(correct_ans, (0, 3))\n    np.testing.assert_allclose(y, correct_ans)\n\n  @parameterized.product(use_bias=(True, False))\n  def test_transpose_kernel_conv_transpose(self, use_bias):\n    rng = dict(params=random.key(0))\n    x = jnp.ones((1, 15, 15, 3))\n    conv_module = nn.ConvTranspose(\n      features=4,\n      use_bias=use_bias,\n      strides=2,\n      kernel_size=(6, 6),\n      padding='CIRCULAR',\n      transpose_kernel=True,\n    )\n    y, initial_params = conv_module.init_with_output(rng, x)\n    self.assertEqual(initial_params['params']['kernel'].shape, (6, 6, 4, 3))\n    self.assertEqual(y.shape, (1, 30, 30, 4))\n\n  @parameterized.product(module=(nn.Conv, nn.ConvLocal, nn.ConvTranspose))\n  def test_int_kernel_equality(self, module):\n    conv_int = module(features=4, kernel_size=3)\n    conv_seq = module(features=4, kernel_size=(3,))\n    x = jnp.ones((8, 3))\n    self.assertTrue(\n        jax.tree_util.tree_all(\n            jax.tree_util.tree_map(\n                lambda x, y: (x == y).all(),\n                conv_int.init(random.key(0), x),\n                conv_seq.init(random.key(0), x),\n            )\n        )\n    )\n\n  def test_embed(self):\n    rng = dict(params=random.key(0))\n    x = jnp.arange(4)[None]\n    dummy_embedding = jnp.broadcast_to(jnp.arange(4)[..., None], (4, 3)).astype(\n      jnp.float32\n    )\n    embed_module = nn.Embed(\n      num_embeddings=4,\n      features=3,\n      embedding_init=lambda rng, shape, dtype: dummy_embedding,\n    )\n    y, initial_params = embed_module.init_with_output(rng, x)\n    np.testing.assert_allclose(y, dummy_embedding[None])\n    z = embed_module.apply(\n      initial_params, jnp.ones((3,)), method=embed_module.attend\n    )\n    np.testing.assert_allclose(z, 3.0 * jnp.arange(4))\n\n  def test_embed_numpy(self):\n    rng = dict(params=random.key(0))\n    x = jnp.arange(4)[None]\n    dummy_embedding = np.broadcast_to(np.arange(4)[..., None], (4, 3)).astype(\n      np.float32\n    )\n    embed_module = nn.Embed(\n      num_embeddings=4,\n      features=3,\n      embedding_init=lambda rng, shape, dtype: dummy_embedding,\n    )\n    y, initial_params = embed_module.init_with_output(rng, x)\n    np.testing.assert_allclose(y, dummy_embedding[None])\n    z = embed_module.apply(\n      initial_params, jnp.ones((3,)), method=embed_module.attend\n    )\n    np.testing.assert_allclose(z, 3.0 * jnp.arange(4))\n\n  def test_embed_hash(self):\n    self.assertEqual(hash(nn.Embed(2, 3)), hash(nn.Embed(2, 3)))\n    self.assertNotEqual(hash(nn.Embed(3, 4)), hash(nn.Embed(2, 3)))\n\n  def test_non_final_axis(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return nn.DenseGeneral(features=6, axis=1, name='dense')(x)\n\n    x = jnp.ones((2, 4, 8))\n    y, variables = Foo().init_with_output(random.key(0), x)\n    self.assertEqual(\n      jax.tree_util.tree_map(jnp.shape, variables['params']),\n      {'dense': {'kernel': (4, 6), 'bias': (6,)}},\n    )\n    self.assertEqual(y.shape, (2, 8, 6))\n\n  def test_non_final_axes(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return nn.DenseGeneral(features=6, axis=(0, 1), name='dense')(x)\n\n    x = jnp.ones((2, 4, 8))\n    y, variables = Foo().init_with_output(random.key(0), x)\n    self.assertEqual(\n      jax.tree_util.tree_map(jnp.shape, variables['params']),\n      {'dense': {'kernel': (2, 4, 6), 'bias': (6,)}},\n    )\n    self.assertEqual(y.shape, (8, 6))\n\n  def test_canonicalize_padding(self):\n    def test_pad(pad, rank, expected=None):\n      if expected is None:\n        with self.assertRaises(ValueError):\n          nn.linear.canonicalize_padding(pad, rank)\n      else:\n        self.assertEqual(nn.linear.canonicalize_padding(pad, rank), expected)\n\n    test_pad('SAME', 2, 'SAME')\n    test_pad(2, 3, [(2, 2), (2, 2), (2, 2)])\n    test_pad((2, 2), 3)\n    test_pad((2, 2), 1)\n    test_pad([1, (2, 3)], 2, [(1, 1), (2, 3)])\n    test_pad([None, (1, 2)], 2)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/linen_meta_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for linen_meta.\"\"\"\n\nimport jax\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\nfrom jax.experimental import mesh_utils\nfrom jax.sharding import Mesh, PartitionSpec\n\nfrom flax import linen as nn\n\n\nclass LinenMetaTest(absltest.TestCase):\n  def test_boxed_param(self):\n    class Bar(nn.Module):\n      @nn.compact\n      def __call__(mdl_self, x):  # pylint: disable=no-self-argument\n        kernel_init = nn.with_partitioning(\n          nn.initializers.ones_init(), ('in', 'out')\n        )\n        kernel = mdl_self.param('kernel', kernel_init, (x.shape[-1], 2))\n        kernel_box = mdl_self.get_variable('params', 'kernel')\n        self.assertIsInstance(kernel_box, nn.Partitioned)\n        self.assertEqual(kernel_box.names, ('in', 'out'))\n        return x @ kernel\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, xs):\n        return nn.vmap(\n          Bar,\n          in_axes=0,\n          variable_axes={'params': 0},\n          split_rngs={'params': True},\n          metadata_params={nn.PARTITION_NAME: 'batch'},\n        )(name='bar')(xs)\n\n    m = Foo()\n    variables = m.init(random.key(0), jnp.zeros((8, 3)))\n    self.assertEqual(\n      variables['params']['bar']['kernel'].names, ('batch', 'in', 'out')\n    )\n\n  def test_boxed_variable(self):\n    class Bar(nn.Module):\n      @nn.compact\n      def __call__(mdl_self, x):  # pylint: disable=no-self-argument\n        kernel_init = nn.with_partitioning(\n          nn.initializers.ones_init(), ('in', 'out')\n        )\n        kernel = mdl_self.variable(\n          'params',\n          'kernel',\n          kernel_init,\n          mdl_self.make_rng('params'),\n          (x.shape[-1], 2),\n        )\n        kernel.value += 1.0\n        self.assertEqual(kernel.value.sum(), kernel.value.size * 2)\n        kernel_box = mdl_self.get_variable('params', 'kernel')\n        self.assertIsInstance(kernel_box, nn.Partitioned)\n        self.assertEqual(kernel_box.names, ('in', 'out'))\n        return x @ kernel.value\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, xs):\n        return nn.vmap(\n          Bar,\n          in_axes=0,\n          variable_axes={'params': 0},\n          split_rngs={'params': True},\n          metadata_params={nn.PARTITION_NAME: 'batch'},\n        )(name='bar')(xs)\n\n    m = Foo()\n    variables = m.init(random.key(0), jnp.zeros((8, 3)))\n    self.assertEqual(\n      variables['params']['bar']['kernel'].names, ('batch', 'in', 'out')\n    )\n\n  # def test_boxed_variable(self):\n  #   def f(scope, xs):\n  #     def g(scope, x):\n  # kernel_init = nn.with_partitioning(nn.initializers.ones_init(),\n  #                                      ('in', 'out'))\n  # kernel = scope.variable('params', 'kernel', kernel_init,\n  #                         scope.make_rng('params'), (x.shape[-1], 2))\n  # kernel.value += 1.\n  # self.assertEqual(kernel.value.sum(), kernel.value.size * 2)\n  # kernel_box = scope.get_variable('params', 'kernel')\n  # self.assertIsInstance(kernel_box, nn.Partitioned)\n  # self.assertEqual(kernel_box.names, ('in', 'out'))\n  # return x @ kernel.value\n\n  #     nn.vmap(\n  #         g, in_axes=0,\n  #         variable_axes={'params': 0}, split_rngs={'params': True},\n  #         metadata_params={nn.PARTITION_NAME: 'batch'})(scope, xs)\n\n  #   _, variables = init(f)(random.key(0), jnp.zeros((8, 3)))\n  #   self.assertEqual(variables['params']['kernel'].names,\n  #                    ('batch', 'in', 'out'))\n\n  def test_pjit_scan_over_layers(self):\n    class MLP(nn.Module):\n      hidden_size: int\n\n      @nn.compact\n      def __call__(self, x):\n        ki = nn.linear.default_kernel_init\n        h = nn.Dense(\n          self.hidden_size,\n          kernel_init=nn.with_partitioning(ki, ('data', 'model')),\n        )(x)\n        h = nn.relu(h)\n        return nn.Dense(\n          x.shape[-1], kernel_init=nn.with_partitioning(ki, ('model', 'data'))\n        )(h)\n\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        def body(_, c):\n          c = MLP(512)(c)\n          return c, ()\n\n        c, _ = nn.scan(\n          body,\n          variable_axes={'params': 0},\n          split_rngs={'params': 0},\n          length=8,\n          metadata_params={nn.PARTITION_NAME: None},\n        )(self, x)\n        return c\n\n    devs = mesh_utils.create_device_mesh((jax.device_count(), 1))\n    mesh = Mesh(devs, ['data', 'model'])\n    model = Model()\n    x = jnp.ones((8, 128))\n    spec = nn.get_partition_spec(jax.eval_shape(model.init, random.key(0), x))\n    self.assertEqual(\n      spec,\n      {\n        'params': {\n          'MLP_0': {\n            'Dense_0': {\n              'bias': PartitionSpec(),\n              'kernel': PartitionSpec(None, 'data', 'model'),\n            },\n            'Dense_1': {\n              'bias': PartitionSpec(),\n              'kernel': PartitionSpec(None, 'model', 'data'),\n            },\n          },\n        },\n      },\n    )\n    x_spec = PartitionSpec('data', 'model')\n    f = lambda x: jax.sharding.NamedSharding(mesh, x)\n    key_spec = PartitionSpec()\n    init_fn = jax.jit(\n        model.init,\n        in_shardings=jax.tree_util.tree_map(f, (key_spec, x_spec)),\n        out_shardings=jax.tree_util.tree_map(f, spec),\n    )\n    variables = init_fn(random.key(0), x)\n    apply_fn = jax.jit(\n        model.apply,\n        in_shardings=jax.tree_util.tree_map(f, (spec, x_spec)),\n        out_shardings=jax.tree_util.tree_map(f, x_spec),\n    )\n    y = apply_fn(variables, x)\n    self.assertEqual(y.shape, (8, 128))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/linen_module_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.linen.\"\"\"\n\nimport contextlib\nimport copy\nimport dataclasses\nimport enum\nimport functools\nimport gc\nimport inspect\nimport operator\nimport sys\nfrom tempfile import TemporaryDirectory\nfrom typing import (\n  Any,\n  Generic,\n  NamedTuple,\n  TypeVar,\n  get_type_hints,\n)\nfrom collections.abc import Callable, Mapping, Sequence\nfrom unittest.mock import patch\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom absl.testing import absltest\nfrom jax import random\nfrom jax.nn import initializers\n\nfrom flax import config, errors, struct\nfrom flax import linen as nn\nfrom flax.core import FrozenDict, Scope, freeze\nfrom flax.linen import compact\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\ndef tree_equals(x, y):\n  return jax.tree_util.tree_all(jax.tree_util.tree_map(operator.eq, x, y))\n\n\n@contextlib.contextmanager\ndef set_config(option: str, value: bool):\n  old_value = getattr(config, option)\n  try:\n    config.update(option, value)\n    yield None\n  finally:\n    config.update(option, old_value)\n\n\nclass DummyModule(nn.Module):\n  @compact\n  def __call__(self, x):\n    bias = self.param('bias', initializers.ones, x.shape)\n    return x + bias\n\n\nclass Dense(nn.Module):\n  features: int\n\n  @compact\n  def __call__(self, x):\n    kernel = self.param(\n      'kernel', initializers.lecun_normal(), (x.shape[-1], self.features)\n    )\n    y = jnp.dot(x, kernel)\n    return y\n\n\nclass IdentityModule(nn.Module):\n  def __call__(self, x):\n    return x\n\n\nclass RaisesModule(nn.Module):\n  def __call__(self):\n    assert False\n\n\nclass ModuleTest(absltest.TestCase):\n  def test_init_module(self):\n    rngkey = jax.random.key(0)\n    x = jnp.array([1.0])\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    y = DummyModule(parent=scope)(x)\n    params = scope.variables()['params']\n    y2 = DummyModule(parent=scope.rewound())(x)\n    np.testing.assert_allclose(y, y2)\n    np.testing.assert_allclose(y, jnp.array([2.0]))\n    self.assertEqual(params, {'bias': jnp.array([1.0])})\n\n  def test_lazy_init(self):\n    class Foo(nn.Module):\n      @compact\n      def __call__(self, x):\n        k = self.param(\n          'kernel', nn.initializers.lecun_normal(), (x.shape[-1], x.shape[-1])\n        )\n        return x @ k\n\n    # provide a massive input message which would OOM if any compute ops were actually executed\n    variables = Foo().lazy_init(\n      random.key(0),\n      jax.ShapeDtypeStruct((1024 * 1024 * 1024, 128), jnp.float32),\n    )\n    self.assertEqual(variables['params']['kernel'].shape, (128, 128))\n\n  def test_lazy_init_fails_on_data_dependence(self):\n    class Foo(nn.Module):\n      @compact\n      def __call__(self, x):\n        k = self.param('kernel', lambda _: x)\n        return x * k\n\n    with self.assertRaises(errors.LazyInitError):\n      Foo().lazy_init(random.key(0), jax.ShapeDtypeStruct((8, 4), jnp.float32))\n\n  def test_arg_module(self):\n    rngkey = jax.random.key(0)\n    x = jnp.ones((10,))\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    y = Dense(3, parent=scope)(x)\n    params = scope.variables()['params']\n    y2 = Dense(3, parent=scope.rewound())(x)\n    np.testing.assert_allclose(y, y2)\n    self.assertEqual(params['kernel'].shape, (10, 3))\n\n  def test_util_fun(self):\n    rngkey = jax.random.key(0)\n\n    class MLP(nn.Module):\n      @compact\n      def __call__(self, x):\n        x = self._mydense(x)\n        x = self._mydense(x)\n        return x\n\n      def _mydense(self, x):\n        return Dense(3)(x)\n\n    x = jnp.ones((10,))\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    y = MLP(parent=scope)(x)\n    params = scope.variables()['params']\n    y2 = MLP(parent=scope.rewound())(x)\n    np.testing.assert_allclose(y, y2)\n    param_shape = jax.tree_util.tree_map(jnp.shape, params)\n    self.assertEqual(\n      param_shape,\n      {'Dense_0': {'kernel': (10, 3)}, 'Dense_1': {'kernel': (3, 3)}},\n    )\n\n  def test_nested_module_reuse(self):\n    rngkey = jax.random.key(0)\n\n    class MLP(nn.Module):\n      @compact\n      def __call__(self, x):\n        x = self._mydense(x)\n        x = self._mydense(x)\n        return x\n\n      def _mydense(self, x):\n        return Dense(3)(x)\n\n    class Top(nn.Module):\n      @compact\n      def __call__(self, x):\n        mlp = MLP()\n        y = mlp(x)\n        z = mlp(x)\n        return y + z\n\n    x = jnp.ones((10,))\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    y = Top(parent=scope)(x)\n    params = scope.variables()['params']\n    y2 = Top(parent=scope.rewound())(x)\n    np.testing.assert_allclose(y, y2)\n    param_shape = jax.tree_util.tree_map(jnp.shape, params)\n    self.assertEqual(\n      param_shape,\n      {\n        'MLP_0': {\n          'Dense_0': {'kernel': (10, 3)},\n          'Dense_1': {'kernel': (3, 3)},\n        }\n      },\n    )\n\n  def test_setup_dict_assignment(self):\n    rngkey = jax.random.key(0)\n\n    class MLP(nn.Module):\n      def setup(self):\n        self.lyrs1 = {\n          'a': Dense(3),\n          'b': Dense(3),\n        }\n        self.lyrs2 = [Dense(3), Dense(3)]\n\n      def __call__(self, x):\n        y = self.lyrs1['a'](x)\n        z = self.lyrs1['b'](y)\n        return z\n\n    x = jnp.ones((10,))\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    y = MLP(parent=scope)(x)\n    params = scope.variables()['params']\n    y2 = MLP(parent=scope.rewound())(x)\n    np.testing.assert_allclose(y, y2)\n    param_shape = jax.tree_util.tree_map(jnp.shape, params)\n    self.assertEqual(\n      param_shape,\n      {'lyrs1_a': {'kernel': (10, 3)}, 'lyrs1_b': {'kernel': (3, 3)}},\n    )\n\n  def test_setup_dict_nonstring_keys(self):\n    class Foo(nn.Module):\n      def setup(self):\n        self.a = {(1, 2): nn.Dense(2)}  # Tuple as key.\n\n      @nn.compact\n      def __call__(self, x):\n        return self.a[(1, 2)](x)\n\n    foo = Foo()\n    x = jnp.ones(shape=(1, 3))\n    params = foo.init(random.key(0), x)['params']\n    param_shape = jax.tree_util.tree_map(jnp.shape, params)\n    self.assertEqual(\n      param_shape, {'a_(1, 2)': {'kernel': (3, 2), 'bias': (2,)}}\n    )\n\n  def test_setup_frozen_dict_nonstring_keys(self):\n    a = {1: 2}\n\n    class Foo(nn.Module):\n      def setup(self):\n        self.a = FrozenDict(a)  # int as key.\n\n      @nn.compact\n      def __call__(self, x):\n        return self.a[x]\n\n    foo = Foo()\n    x = 1\n    params = foo.init(random.key(0), x)\n    assert foo.apply(params, x) == a[x]\n\n  def test_setup_dict_nonstring_keys_in_state(self):\n    class Foo(nn.Module):\n      a: dict[int, int]  # int as key.\n\n      @nn.compact\n      def __call__(self, x):\n        return self.a[x]\n\n    a = {1: 2}\n    foo = Foo(a)\n    x = 1\n    params = foo.init(random.key(0), x)\n    assert foo.apply(params, x) == a[x]\n\n  def test_setup_cloning(self):\n    class MLP(nn.Module):\n      def setup(self):\n        self.dense = Dense(3)\n\n    scope = Scope({})\n    unused_clone = MLP(parent=scope).clone()\n\n  def test_submodule_attr(self):\n    rngkey = jax.random.key(0)\n\n    class Inner(nn.Module):\n      @compact\n      def __call__(self):\n        self.param('x', lambda rng: 40)\n\n    class Outer(nn.Module):\n      inner: nn.Module\n\n      def __call__(self):\n        return self.inner()\n\n    class Wrapper(nn.Module):\n      def setup(self):\n        self.inner = Inner()\n        self.outer = Outer(self.inner)\n\n      def __call__(self):\n        return self.outer()\n\n    scope = Scope({'params': {}}, rngs={'params': rngkey}, mutable=['params'])\n    # Make sure this doesn't raise \"Can't attach to remote parent\"\n    wrapper = Wrapper(parent=scope)\n    wrapper()\n\n    # Make sure that variables are registered at the level of the\n    # Wrapper submodule, not the Outer submodule.\n    self.assertEqual(40, scope.variables()['params']['inner']['x'])\n\n  def test_param_in_setup(self):\n    rngkey = jax.random.key(0)\n\n    class DummyModuleWithoutCompact(nn.Module):\n      xshape: tuple[int, ...]\n\n      def setup(self):\n        self.bias = self.param('bias', initializers.ones, self.xshape)\n\n      def __call__(self, x):\n        return x + self.bias\n\n    x = jnp.array([1.0])\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    y = DummyModuleWithoutCompact(x.shape, parent=scope)(x)\n    params = scope.variables()['params']\n    y2 = DummyModuleWithoutCompact(x.shape, parent=scope.rewound())(x)\n    np.testing.assert_allclose(y, y2)\n    np.testing.assert_allclose(y, jnp.array([2.0]))\n    self.assertEqual(params, {'bias': jnp.array([1.0])})\n\n  def test_init_outside_setup_without_compact(self):\n    rngkey = jax.random.key(0)\n\n    class DummyModuleWithoutCompact(nn.Module):\n      def __call__(self, x):\n        bias = self.param('bias', initializers.ones, x.shape)\n        return x + bias\n\n    x = jnp.array([1.0])\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    with self.assertRaisesRegex(ValueError, 'must be initialized.*setup'):\n      unused_y = DummyModuleWithoutCompact(parent=scope)(x)\n\n  def test_init_outside_call(self):\n    rngkey = jax.random.key(0)\n\n    class Dummy(nn.Module):\n      @compact\n      def __call__(self, x):\n        bias = self.param('bias', initializers.ones, x.shape)\n        return x + bias\n\n      def foo(self, x):\n        bias = self.param('bias', initializers.ones, x.shape)\n        return x + bias\n\n    x = jnp.array([1.0])\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    with self.assertRaisesRegex(ValueError, 'must be initialized.*setup'):\n      unused_y = Dummy(parent=scope).foo(x)\n\n  def test_setup_call_var_collision(self):\n    rngkey = jax.random.key(0)\n\n    class Dummy(nn.Module):\n      xshape: tuple[int, ...]\n\n      def setup(self):\n        self.bias = self.param('bias', initializers.ones, self.xshape)\n\n      @compact\n      def __call__(self, x):\n        unused_bias = self.param('bias', initializers.ones, x.shape)\n        return x + self.bias\n\n    x = jnp.array([1.0])\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    msg = 'Could not create param \"bias\" in Module Dummy: Name in use'\n    with self.assertRaisesRegex(errors.NameInUseError, msg):\n      unused_y = Dummy(x.shape, parent=scope)(x)\n\n  def test_call_var_collision(self):\n    rngkey = jax.random.key(0)\n\n    class Dummy(nn.Module):\n      xshape: tuple[int, ...]\n\n      @compact\n      def __call__(self, x):\n        bias = self.param('bias', initializers.ones, self.xshape)\n        bias = self.param('bias', initializers.ones, self.xshape)\n        return x + bias\n\n    x = jnp.array([1.0])\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    msg = 'Could not create param \"bias\" in Module Dummy: Name in use'\n    with self.assertRaisesRegex(errors.NameInUseError, msg):\n      unused_y = Dummy(x.shape, parent=scope)(x)\n\n  def test_setup_var_collision(self):\n    rngkey = jax.random.key(0)\n\n    class Dummy(nn.Module):\n      xshape: tuple[int, ...]\n\n      def setup(self):\n        self.bias = self.param('bias', initializers.ones, self.xshape)\n        self.bias = self.param('bias', initializers.ones, self.xshape)\n\n      def __call__(self, x):\n        return x + self.bias\n\n    x = jnp.array([1.0])\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    msg = 'Could not create param \"bias\" in Module Dummy: Name in use'\n    with self.assertRaisesRegex(errors.NameInUseError, msg):\n      unused_y = Dummy(x.shape, parent=scope)(x)\n\n  def test_setattr_name_var_disagreement_allowed_in_lists(self):\n    rngkey = jax.random.key(0)\n\n    class Dummy(nn.Module):\n      xshape: tuple[int, ...]\n\n      def setup(self):\n        self.biases = [\n          self.param(f'bias_{i}', initializers.ones, self.xshape)\n          for i in range(4)\n        ]\n\n      def __call__(self, x):\n        return x + self.biases[0]\n\n    x = jnp.array([1.0])\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    y = Dummy(x.shape, parent=scope)(x)\n    self.assertEqual(y, jnp.array([2.0]))\n\n  def test_setattr_name_var_disagreement_allowed_in_dicts(self):\n    rngkey = jax.random.key(0)\n\n    class Dummy(nn.Module):\n      xshape: tuple[int, ...]\n\n      def setup(self):\n        self.biases = {\n          # NOTE: Keys still must be strings. This is to make a possible\n          # future transition to automatically derived parameter names when\n          # assigned as a dict easier (like we currently have with\n          # submodules). See a bit of discussion here:\n          # https://github.com/google/flax/issues/705#issuecomment-738761853\n          str(i): self.param(f'bias_{i}', initializers.ones, self.xshape)\n          for i in range(4)\n        }\n\n      def __call__(self, x):\n        return x + self.biases['0']\n\n    x = jnp.array([1.0])\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    y = Dummy(x.shape, parent=scope)(x)\n    self.assertEqual(y, jnp.array([2.0]))\n\n  def test_submodule_var_collision_with_scope(self):\n    rngkey = jax.random.key(0)\n\n    class Dummy(nn.Module):\n      xshape: tuple[int, ...]\n\n      def setup(self):\n        self.bias = self.param('bias', initializers.ones, self.xshape)\n        self.bias = DummyModule()\n\n      def __call__(self, x):\n        return x + self.bias\n\n    x = jnp.array([1.0])\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n\n    with self.assertRaises(errors.NameInUseError):\n      unused_y = Dummy(x.shape, parent=scope)(x)\n\n  def test_submodule_var_collision_with_submodule(self):\n    rngkey = jax.random.key(0)\n\n    class Dummy(nn.Module):\n      xshape: tuple[int, ...]\n\n      def setup(self):\n        self.bias = self.param('bias', initializers.ones, self.xshape)\n\n      @compact\n      def __call__(self, x):\n        unused_bias = DummyModule(name='bias')\n        return x + self.bias\n\n    x = jnp.array([1.0])\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n\n    msg = 'Could not create submodule \"bias\" in Module Dummy: Name in use'\n    with self.assertRaisesRegex(errors.NameInUseError, msg):\n      unused_y = Dummy(x.shape, parent=scope)(x)\n\n  def test_submodule_var_collision_with_params(self):\n    rngkey = jax.random.key(0)\n\n    class Dummy(nn.Module):\n      xshape: tuple[int, ...]\n\n      def setup(self):\n        self.bias = DummyModule()\n\n      @compact\n      def __call__(self, x):\n        unused_bias = self.param('bias', initializers.ones, self.xshape)\n        return x + self.bias\n\n    x = jnp.array([1.0])\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n\n    msg = 'Could not create param \"bias\" in Module Dummy: Name in use'\n    with self.assertRaisesRegex(errors.NameInUseError, msg):\n      unused_y = Dummy(x.shape, parent=scope)(x)\n\n  def test_attr_empty_container(self):\n    class Foo(nn.Module):\n      bar: Mapping[str, Any]\n\n      @compact\n      def __call__(self):\n        pass\n\n    Foo({'a': ()}).apply({})\n\n  def test_multiple_compact_methods(self):\n    \"\"\"Test that multiple methods with the @compact decorator can be used.\n\n    NOTE: in the near future we might want to have compact methods reset the\n    autoname_cursor such that Dense would be reused in the second method.\n    \"\"\"\n\n    class MultipleCompactMethods(nn.Module):\n      @compact\n      def __call__(self, x):\n        x = nn.Dense(1)(x)\n        return self.method(x)\n\n      @compact\n      def method(self, x):\n        x = nn.Dense(1)(x)\n        return x\n\n    m = MultipleCompactMethods()\n    variables = m.init(random.key(0), jnp.ones((1, 1)))\n    params = variables['params']\n    self.assertIn('Dense_0', params)\n    self.assertIn('Dense_1', params)\n\n  def test_only_one_compact_method_subclass(self):\n    class Dummy(nn.Module):\n      @nn.compact\n      def __call__(self):\n        pass\n\n    class SubDummy(Dummy):\n      @nn.compact\n      def __call__(self):\n        super().__call__()\n\n    scope = Scope(variables={})\n\n    subdummy = SubDummy(parent=scope)\n    # Make sure the @compact annotation is valid on both base class and\n    # subclass, as long as its on the same method.\n    subdummy()\n\n  def test_forgotten_compact_annotation(self):\n    class Bar(nn.Module):\n      # user forgot to add @compact\n      def __call__(self, x):\n        return nn.Dense(1)(x)\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        bar = Bar()\n        x = bar(x)\n        x = bar(x)\n        return x\n\n    msg = (\n      r'Submodule Dense must be defined in `setup\\(\\)` or in a method '\n      'wrapped in `@compact`'\n    )\n    with self.assertRaisesRegex(errors.AssignSubModuleError, msg):\n      Foo().init(random.key(0), jnp.ones((1, 3)))\n\n  def test_forgotten_compact_annotation_with_explicit_parent(self):\n    class Bar(nn.Module):\n      def __call__(self, x):\n        return nn.Dense(1, parent=self)(x)\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        bar = Bar()\n        x = bar(x)\n        x = bar(x)\n        return x\n\n    msg = (\n      r'Submodule Dense must be defined in `setup\\(\\)` or in a method '\n      'wrapped in `@compact`'\n    )\n    with self.assertRaisesRegex(errors.AssignSubModuleError, msg):\n      Foo().init(random.key(0), jnp.ones((1, 3)))\n\n  def test_numpy_array_shape_class_args(self):\n    class MLP(nn.Module):\n      widths: Sequence[int]\n\n      @nn.compact\n      def __call__(self, x):\n        for width in self.widths[:-1]:\n          x = nn.relu(nn.Dense(width)(x))\n        return nn.Dense(self.widths[-1])(x)\n\n    test = MLP(np.array([3, 3], np.int32))\n    params = test.init({'params': random.key(42)}, jnp.ones((3, 3)))\n    _ = test.apply(params, jnp.ones((3, 3)))\n\n  def test_get_local_methods(self):\n    class Base:\n      @staticmethod\n      def bar(x):\n        return x\n\n      @classmethod\n      def baz(cls, x):\n        return x\n\n      def bleep(self, x):\n        return x\n\n    class Derived1(Base):\n      @staticmethod\n      def bar2(x):\n        return x\n\n      @classmethod\n      def baz2(cls, x):\n        return x\n\n      def bloop(self, x):\n        return x\n\n    class Derived2(Derived1):\n      pass\n\n    self.assertEqual(nn.module._get_local_method_names(Base), ('bleep',))\n    self.assertEqual(nn.module._get_local_method_names(Derived1), ('bloop',))\n    self.assertEqual(\n      nn.module._get_local_method_names(Derived1, exclude=('bloop',)), ()\n    )\n    self.assertEqual(nn.module._get_local_method_names(Derived2), ())\n\n  def test_inheritance_dataclass_attribs(self):\n    class Test(nn.Module):\n      bar: int\n\n      def __call__(self, x):\n        return x\n\n    class Test2(Test):\n      baz: int\n\n      def __call__(self, x):\n        return x\n\n    class Test3(Test):\n      baz: int\n\n      def __call__(self, x):\n        return x\n\n    class Test4(Test2):\n      def __call__(self, x):\n        return x\n\n    key = random.key(0)\n    x = jnp.ones((5,))\n    test1 = Test(bar=4)\n    test2 = Test2(bar=4, baz=2)\n    test3 = Test3(bar=4, baz=2)\n    test4 = Test4(bar=5, baz=3)\n    self.assertEqual(test1.init_with_output(key, x), (x, freeze({})))\n    self.assertEqual(test2.init_with_output(key, x), (x, freeze({})))\n    self.assertEqual(test3.init_with_output(key, x), (x, freeze({})))\n    self.assertEqual(test4.init_with_output(key, x), (x, freeze({})))\n    self.assertTrue(hasattr(test1, 'bar'))\n    self.assertTrue(hasattr(test1, 'name'))\n    self.assertTrue(hasattr(test1, 'parent'))\n    self.assertTrue(hasattr(test2, 'bar'))\n    self.assertTrue(hasattr(test2, 'baz'))\n    self.assertTrue(hasattr(test2, 'name'))\n    self.assertTrue(hasattr(test2, 'parent'))\n    self.assertTrue(hasattr(test3, 'bar'))\n    self.assertTrue(hasattr(test3, 'baz'))\n    self.assertTrue(hasattr(test3, 'name'))\n    self.assertTrue(hasattr(test3, 'parent'))\n    self.assertTrue(hasattr(test4, 'bar'))\n    self.assertTrue(hasattr(test4, 'baz'))\n    self.assertTrue(hasattr(test4, 'name'))\n    self.assertTrue(hasattr(test4, 'parent'))\n    self.assertEqual(\n      list(Test.__dataclass_fields__.keys()), ['bar', 'parent', 'name']\n    )\n    self.assertEqual(\n      list(Test2.__dataclass_fields__.keys()),\n      ['bar', 'baz', 'parent', 'name'],\n    )\n    self.assertEqual(\n      list(Test3.__dataclass_fields__.keys()),\n      ['bar', 'baz', 'parent', 'name'],\n    )\n    self.assertEqual(\n      list(Test4.__dataclass_fields__.keys()),\n      ['bar', 'baz', 'parent', 'name'],\n    )\n\n  def test_get_suffix_value_pairs(self):\n    for x in [(), [], {}, None, 0, set()]:\n      self.assertEqual(nn.module._get_suffix_value_pairs(x), [('', x)])\n    self.assertEqual(\n      nn.module._get_suffix_value_pairs({'a': 1, 'b': 2}),\n      [('_a', 1), ('_b', 2)],\n    )\n    self.assertEqual(\n      nn.module._get_suffix_value_pairs([1, 2, 3]),\n      [('_0', 1), ('_1', 2), ('_2', 3)],\n    )\n    x1 = [nn.Dense(10), nn.relu, nn.Dense(10)]\n    y1 = nn.module._get_suffix_value_pairs(x1)\n    self.assertEqual(y1, [('_0', x1[0]), ('_1', x1[1]), ('_2', x1[2])])\n    x2 = {'a': 1, 'b': {'c': nn.Dense(10), 'd': nn.relu}}\n    y2 = nn.module._get_suffix_value_pairs(x2)\n    self.assertEqual(\n      y2, [('_a', 1), ('_b_c', x2['b']['c']), ('_b_d', x2['b']['d'])]\n    )\n\n  def test_mixed_list_assignment_in_setup(self):\n    class Test(nn.Module):\n      def setup(self):\n        self.layers = [nn.Dense(10), nn.relu, nn.Dense(10)]\n\n      def __call__(self, x):\n        for lyr in self.layers:\n          x = lyr(x)\n        return x\n\n    x = random.uniform(random.key(0), (5, 5))\n    variables = Test().init(random.key(0), jnp.ones((5, 5)))\n    y = Test().apply(variables, x)\n    m0 = variables['params']['layers_0']['kernel']\n    m1 = variables['params']['layers_2']['kernel']\n    self.assertTrue(jnp.all(y == jnp.dot(nn.relu(jnp.dot(x, m0)), m1)))\n\n  def test_module_is_hashable(self):\n    module_a = nn.Dense(10)\n    module_a_2 = nn.Dense(10)\n    module_b = nn.Dense(5)\n    self.assertEqual(hash(module_a), hash(module_a_2))\n    self.assertNotEqual(hash(module_a), hash(module_b))\n\n  def test_module_custom_hash(self):\n    class Test(nn.Module):\n      x: int = 3\n      y: int = 5\n\n      def __hash__(self):\n        return 42 + self.x\n\n    module_a = Test(1, 2)\n    module_a_2 = Test(1, 5)\n    module_b = Test(2, 2)\n    self.assertEqual(hash(module_a), hash(module_a_2))\n    self.assertNotEqual(hash(module_a), hash(module_b))\n\n  def test_module_with_scope_is_not_hashable(self):\n    module_a = nn.Dense(10, parent=Scope({}))\n    msg = \"Can't call __hash__ on modules that hold variables.\"\n    with self.assertRaisesWithLiteralMatch(TypeError, msg):\n      hash(module_a)\n\n  def test_module_trace(self):\n    class MLP(nn.Module):\n      act: Callable = nn.relu\n      sizes: Sequence[int] = (3, 2)\n\n      @nn.compact\n      def __call__(self, x):\n        for size in self.sizes:\n          x = nn.Dense(size)(x)\n          x = self.act(x)\n        return repr(self)\n\n    mlp = MLP()\n    expected_trace = \"\"\"MLP(\n    # attributes\n    act = relu\n    sizes = (3, 2)\n    # children\n    Dense_0 = Dense(\n        # attributes\n        features = 3\n        use_bias = True\n        dtype = None\n        param_dtype = float32\n        precision = None\n        kernel_init = init\n        bias_init = zeros\n        promote_dtype = promote_dtype\n        dot_general = None\n        dot_general_cls = None\n    )\n    Dense_1 = Dense(\n        # attributes\n        features = 2\n        use_bias = True\n        dtype = None\n        param_dtype = float32\n        precision = None\n        kernel_init = init\n        bias_init = zeros\n        promote_dtype = promote_dtype\n        dot_general = None\n        dot_general_cls = None\n    )\n)\"\"\"\n    x = jnp.ones((1, 2))\n    trace, variables = mlp.init_with_output(random.key(0), x)\n    self.assertEqual(trace, expected_trace)\n    trace = mlp.apply(variables, x)\n    self.assertEqual(trace, expected_trace)\n\n  def test_default_params_rng_equivalence(self):\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x, add_dropout=False, add_noise=False):\n        x = nn.Dense(16)(x)\n        x = nn.Dropout(0.5)(x, deterministic=not add_dropout)\n        if add_noise:\n          x += jax.random.normal(self.make_rng('params'))\n        return x\n\n    model = Model()\n    key0, key1, key2 = jax.random.split(jax.random.key(0), 3)\n    x = jax.random.normal(key0, (10, 8))\n\n    with self.assertRaisesRegex(\n      ValueError,\n      'First argument passed to an init function should be a ``jax.PRNGKey``',\n    ):\n      model.init({'params': 'test'}, x)\n    with self.assertRaisesRegex(\n      errors.InvalidRngError,\n      'RNGs should be of shape \\\\(2,\\\\) or PRNGKey in Module Model, but rngs are: test',\n    ):\n      model.init('test', x)\n    # should not throw an error, since nn.Dropout will get an RNG key from the 'params' stream\n    model.init(key1, x, add_dropout=True)\n\n    v = model.init({'params': key1}, x)\n    v2 = model.init(key1, x)\n    jax.tree_util.tree_map(np.testing.assert_allclose, v, v2)\n\n    for add_dropout, add_noise in [[True, False], [False, True], [True, True]]:\n      out = model.apply(\n        v,\n        x,\n        add_dropout=add_dropout,\n        add_noise=add_noise,\n        rngs={'params': key2},\n      )\n      out2 = model.apply(\n        v, x, add_dropout=add_dropout, add_noise=add_noise, rngs=key2\n      )\n      np.testing.assert_allclose(out, out2)\n\n    with self.assertRaisesRegex(\n      ValueError,\n      'The ``rngs`` argument passed to an apply function should be a ``jax.PRNGKey``',\n    ):\n      model.apply(v, x, rngs={'params': 'test'})\n    with self.assertRaisesRegex(\n      errors.InvalidRngError,\n      'RNGs should be of shape \\\\(2,\\\\) or PRNGKey in Module Model, but rngs are: test',\n    ):\n      model.apply(v, x, rngs='test')\n\n  def test_module_apply_method(self):\n    class Foo(nn.Module):\n      not_callable: int = 1\n\n      @nn.compact\n      def __call__(self):\n        pass\n\n      def test(self):\n        pass\n\n    # We can use both instance and class methods in apply.\n    Foo().apply({}, method=Foo.test)\n    Foo().apply({}, method=Foo().test)\n\n    # We also use a function that is not in the provided Module, although it\n    # should have a first argument representing an instance of the Module (Foo\n    # in this case).\n    x = Foo().apply({}, method=lambda foo_instance: foo_instance)\n    self.assertEqual(type(x), type(Foo()))\n\n    # This is not allowed.\n    msg = 'Cannot call apply()'\n    with self.assertRaisesRegex(errors.ApplyModuleInvalidMethodError, msg):\n      Foo().apply({}, method=lambda: True)\n\n    # string method names are also allowed.\n    Foo().apply({}, method='test')\n    # test same for init.\n    Foo().init({}, method='test')\n\n    # non-existent attribute names will yield AttributeError.\n    with self.assertRaisesRegex(AttributeError, 'allowed_apply_fn'):\n      Foo().apply({}, method='allowed_apply_fn')\n      # test same for init.\n      Foo().init({}, method='allowed_apply_fn')\n\n    # attributes which are not callables yield TypeError.\n    with self.assertRaisesRegex(\n      TypeError, \"'Foo.not_callable' must be a callable\"\n    ):\n      Foo().apply({}, method='not_callable')\n      # test same for init.\n      Foo().init({}, method='not_callable')\n\n  def test_module_apply_method_submodule(self):\n    class Foo(nn.Module):\n      bar: nn.Module\n\n      @nn.compact\n      def __call__(self, x):\n        return self.bar(x)\n\n    foo = Foo(nn.Dense(3))\n    variables = foo.init(jax.random.PRNGKey(0), jnp.zeros(3))\n\n    foo.apply(variables, jnp.zeros(3), method='bar')\n\n  def test_call_unbound_compact_module_methods(self):\n    dense = Dense(3)\n    msg = r'Can\\'t call compact methods on unbound modules'\n    with self.assertRaisesRegex(errors.CallCompactUnboundModuleError, msg):\n      dense(jnp.ones((1,)))\n\n  def test_call_unbound_has_variable(self):\n    class EmptyModule(nn.Module):\n      def foo(self):\n        self.has_variable('bar', 'baz')\n\n    empty = EmptyModule()\n    with self.assertRaisesRegex(ValueError, 'variable.*unbound module'):\n      empty.foo()\n\n  def test_call_unbound_make_rng(self):\n    class EmptyModule(nn.Module):\n      def foo(self):\n        self.make_rng('bar')\n\n    empty = EmptyModule()\n    with self.assertRaisesRegex(ValueError, 'RNGs.*unbound module'):\n      empty.foo()\n\n  def test_call_unbound_variables(self):\n    class EmptyModule(nn.Module):\n      def foo(self):\n        self.variables\n\n    empty = EmptyModule()\n    with self.assertRaisesRegex(ValueError, 'variables.*unbound module'):\n      empty.foo()\n\n  def test_call_unbound_noncompact_module_methods(self):\n    class EmptyModule(nn.Module):\n      foo: int = 3\n\n      def bar(self):\n        return self.foo\n\n    empty = EmptyModule()\n    # It's fine to call methods of unbound methods that don't depend on\n    # attributes defined during `setup`.\n    self.assertEqual(empty.bar(), 3)\n\n  def test_call_unbound_noncompact_module_methods_depending_on_setup(self):\n    class EmptyModule(nn.Module):\n      def setup(self):\n        self.foo = 2\n\n      def bar(self):\n        return self.foo\n\n    empty = EmptyModule()\n    msg = r'\"EmptyModule\" object has no attribute \"foo\"'\n    with self.assertRaisesRegex(AttributeError, msg):\n      empty.bar()\n\n  def test_module_with_attrs(self):\n    class Foo(nn.Module):\n      bar: nn.Dense = dataclasses.field(init=False)\n\n      def setup(self):\n        self.bar = nn.Dense(3)\n\n      def __call__(self, x):\n        return self.bar(x)\n\n    foo = Foo()\n    x = jnp.ones((2,))\n    variables = foo.init(random.key(0), x)\n    self.assertEqual(variables['params']['bar']['kernel'].shape, (2, 3))\n\n  def test_noncompact_module_frozen(self):\n    class Foo(nn.Module):\n      def setup(self):\n        self.i = 1  # This is allowed (for assigning submodules).\n\n      def __call__(self):\n        self.i = 2  # This is not allowed.\n\n    msg = (\n      \"Can't set i=2 for Module of type Foo: Module instance is frozen \"\n      'outside of setup method.'\n    )\n    with self.assertRaisesRegex(errors.SetAttributeFrozenModuleError, msg):\n      Foo().init(random.key(0))\n\n  def test_compact_module_frozen(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self):\n        self.i = 2\n\n    msg = (\n      \"Can't set i=2 for Module of type Foo: Module instance is frozen \"\n      'outside of setup method.'\n    )\n    with self.assertRaisesRegex(errors.SetAttributeFrozenModuleError, msg):\n      Foo().init(random.key(0))\n\n  def test_submodule_frozen(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self):\n        dense = nn.Dense(10)\n        dense.features = 20  # <--- This is not allowed\n\n    msg = (\n      \"Can't set features=20 for Module of type Dense: Module instance \"\n      'is frozen outside of setup method.'\n    )\n    with self.assertRaisesRegex(errors.SetAttributeFrozenModuleError, msg):\n      Foo().init(random.key(0))\n\n  def test_module_call_not_implemented(self):\n    class Foo(nn.Module):\n      pass\n\n    msg = '\"Foo\" object has no attribute \"__call__\"'\n    with self.assertRaisesRegex(AttributeError, msg):\n      Foo().init(random.key(0))\n\n  def test_is_mutable_collection(self):\n    class EmptyModule(nn.Module):\n      def __call__(self):\n        return self.is_mutable_collection('test')\n\n    empty = EmptyModule()\n    self.assertTrue(empty.apply({}, mutable=['test'])[0])\n    self.assertFalse(empty.apply({}, mutable=False))\n\n  def test_module_lazy_getattr_setup(self):\n    class A(nn.Module):\n      def setup(self):\n        self.d = nn.Dense(2)\n\n      def __call__(self, x):\n        return self.d(x)\n\n    class B(nn.Module):\n      def setup(self):\n        self.a = A()\n\n      def __call__(self, x):\n        y1 = self.a.d(x)\n        y2 = self.a(x)\n        return y1, y2\n\n    key = random.key(0)\n    x = jnp.ones((2,))\n\n    (y1, y2), unused_vars = B().init_with_output(key, x)\n    np.testing.assert_array_equal(y1, y2)\n\n  def test_module_lazy_dir_setup(self):\n    class A(nn.Module):\n      def setup(self):\n        self.d = nn.Dense(2)\n\n      def __call__(self, x):\n        return self.d(x)\n\n    class B(nn.Module):\n      def setup(self):\n        self.a = A()\n\n      def __call__(self, x):\n        assert 'd' in dir(self.a)\n        y1 = self.a.d(x)\n        y2 = self.a(x)\n        return y1, y2\n\n    key = random.key(0)\n    x = jnp.ones((2,))\n    _ = B().init_with_output(key, x)\n\n  def test_module_unbound_getattr(self):\n    class A(nn.Module):\n      def setup(self):\n        b = B()\n        b.c  # B is unbound because it is not yet assigned to an attribute.\n        self.b = b\n\n      def __call__(self):\n        pass\n\n    class B(nn.Module):\n      def setup(self):\n        self.c = nn.Dense(2)\n\n    msg = '\"B\" object has no attribute \"c\"'\n    with self.assertRaisesRegex(AttributeError, msg):\n      A().init(random.key(0))\n\n  def test_unbound_setup_call(self):\n    setup_called = False\n\n    class A(nn.Module):\n      def setup(self):\n        nonlocal setup_called\n        setup_called = True\n\n      def test(self):\n        pass\n\n    A().test()\n    self.assertFalse(setup_called)\n\n  def test_module_pass_as_attr(self):\n    class A(nn.Module):\n      def setup(self):\n        self.b = B(nn.Dense(2))\n\n      def __call__(self, x):\n        return self.b(x)\n\n    class B(nn.Module):\n      foo: Any\n\n      def __call__(self, x):\n        return self.foo(x)\n\n    variables = A().init(random.key(0), jnp.ones((1,)))\n    var_shapes = jax.tree_util.tree_map(jnp.shape, variables)\n    ref_var_shapes = {\n      'params': {\n        'b': {\n          'foo': {\n            'bias': (2,),\n            'kernel': (1, 2),\n          }\n        },\n      },\n    }\n    self.assertTrue(tree_equals(var_shapes, ref_var_shapes))\n\n  def test_module_pass_in_closure(self):\n    a = nn.Dense(2)\n\n    class B(nn.Module):\n      def setup(self):\n        self.foo = a\n\n      def __call__(self, x):\n        return self.foo(x)\n\n    variables = B().init(random.key(0), jnp.ones((1,)))\n    var_shapes = jax.tree_util.tree_map(jnp.shape, variables)\n    ref_var_shapes = {\n      'params': {\n        'foo': {\n          'bias': (2,),\n          'kernel': (1, 2),\n        }\n      },\n    }\n    self.assertTrue(tree_equals(var_shapes, ref_var_shapes))\n    self.assertIsNone(a.name)\n\n  def test_toplevel_submodule_adoption(self):\n    class Encoder(nn.Module):\n      n_layers: int\n      ch: int\n\n      def setup(self):\n        self.layers = [nn.Dense(self.ch) for _ in range(self.n_layers)]\n\n      def __call__(self, x):\n        for layer in self.layers:\n          x = layer(x)\n          x = nn.relu(x)\n        return x\n\n    class Model(nn.Module):\n      encoder: nn.Module\n      n_out: int\n\n      def setup(self):\n        self.dense_out = nn.Dense(self.n_out)\n\n      def __call__(self, x):\n        x = self.encoder(x)\n        return self.dense_out(x)\n\n    # Define model.\n    encoder = Encoder(n_layers=1, ch=8)\n    model = Model(encoder=encoder, n_out=5)\n\n    # Initialize.\n    key = jax.random.key(0)\n    x = random.uniform(key, (4, 4))\n\n    variables = model.init(key, x)\n    y = model.apply(variables, x)\n    self.assertEqual(y.shape, (4, 5))\n\n    var_shapes = jax.tree_util.tree_map(jnp.shape, variables)\n    ref_var_shapes = {\n      'params': {\n        'dense_out': {\n          'bias': (5,),\n          'kernel': (8, 5),\n        },\n        'encoder': {\n          'layers_0': {\n            'bias': (8,),\n            'kernel': (4, 8),\n          },\n        },\n      },\n    }\n    self.assertTrue(tree_equals(var_shapes, ref_var_shapes))\n\n  def test_toplevel_submodule_adoption_pytree(self):\n    class A(nn.Module):\n      @nn.compact\n      def __call__(self, c, x):\n        counter = self.variable('counter', 'i', jnp.zeros, ())\n        counter.value += 1\n        x = nn.Dense(1)(x)\n        return c, x\n\n    class B(nn.Module):\n      A: Any\n\n      @nn.compact\n      def __call__(self, c, x):\n        return self.A['foo'](*self.A['bar'](c, x))\n\n    unused_a = A()\n    a_pytree = {'foo': A(), 'bar': A()}\n    b = B(a_pytree)\n\n    key = random.key(0)\n    x = jnp.ones((2, 2))\n\n    params = B(a_pytree).init(key, x, x)\n    unused_y, counters = b.apply(params, x, x, mutable='counter')\n    ref_counters = {\n      'counter': {\n        'A_bar': {\n          'i': jnp.array(2.0),\n        },\n        'A_foo': {\n          'i': jnp.array(2.0),\n        },\n      },\n    }\n    self.assertTrue(\n      jax.tree_util.tree_all(\n        jax.tree_util.tree_map(\n          lambda x, y: np.testing.assert_allclose(x, y, atol=1e-7),\n          counters,\n          ref_counters,\n        )\n      )\n    )\n\n  def test_toplevel_submodule_adoption_sharing(self):\n    dense = functools.partial(nn.Dense, use_bias=False)\n\n    class A(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return dense(2)(x)\n\n    class B(nn.Module):\n      a: nn.Module\n\n      @nn.compact\n      def __call__(self, x):\n        return dense(2)(x) + self.a(x)\n\n    class C(nn.Module):\n      a: nn.Module\n      b: nn.Module\n\n      @nn.compact\n      def __call__(self, x):\n        return dense(2)(x) + self.b(x) + self.a(x)\n\n    key = random.key(0)\n    x = jnp.ones((2, 2))\n    a = A()\n    b = B(a)\n    c = C(a, b)\n    p = c.init(key, x)\n    var_shapes = jax.tree_util.tree_map(jnp.shape, p)\n    ref_var_shapes = {\n      'params': {\n        'Dense_0': {\n          'kernel': (2, 2),\n        },\n        'a': {\n          'Dense_0': {\n            'kernel': (2, 2),\n          },\n        },\n        'b': {\n          'Dense_0': {\n            'kernel': (2, 2),\n          },\n        },\n      },\n    }\n    self.assertTrue(tree_equals(var_shapes, ref_var_shapes))\n\n  def test_toplevel_named_submodule_adoption(self):\n    dense = functools.partial(nn.Dense, use_bias=False)\n\n    class A(nn.Module):\n      def setup(self):\n        self.dense = dense(4)\n\n      def __call__(self, x):\n        return self.dense(x)\n\n    class B(nn.Module):\n      a: A\n\n      def setup(self):\n        self.proj = dense(6)\n\n      def __call__(self, x):\n        return self.proj(self.a(x))\n\n    a = A(name='foo')\n    b = B(a=a)\n    k = jax.random.key(0)\n    x = jnp.zeros((5, 5))\n    init_vars = b.init(k, x)\n    var_shapes = jax.tree_util.tree_map(jnp.shape, init_vars)\n    if config.flax_preserve_adopted_names:\n      ref_var_shapes = {\n        'params': {\n          'foo': {\n            'dense': {\n              'kernel': (5, 4),\n            },\n          },\n          'proj': {\n            'kernel': (4, 6),\n          },\n        },\n      }\n    else:\n      ref_var_shapes = {\n        'params': {\n          'a': {\n            'dense': {\n              'kernel': (5, 4),\n            },\n          },\n          'proj': {\n            'kernel': (4, 6),\n          },\n        },\n      }\n    self.assertTrue(tree_equals(var_shapes, ref_var_shapes))\n\n  def test_toplevel_submodule_pytree_adoption_sharing(self):\n    class A(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        counter = self.variable('counter', 'i', jnp.zeros, ())\n        counter.value += 1\n        x = nn.Dense(1)(x)\n        return x\n\n    class B(nn.Module):\n      A: Any\n\n      @nn.compact\n      def __call__(self, x):\n        return self.A['foo'](x) + self.A['bar'](x) + self.A['baz'](x)\n\n    key = random.key(0)\n    x = jnp.ones((2, 2))\n\n    a = A()\n    a_pytree = {'foo': a, 'bar': a, 'baz': a}\n    b = B(a_pytree)\n\n    params = b.init(key, x)\n    _, counters = b.apply(params, x, mutable='counter')\n    ref_counters = {\n      'counter': {\n        'A_bar': {\n          'i': jnp.array(6.0),\n        },\n      },\n    }\n    self.assertTrue(tree_equals(counters, ref_counters))\n\n  def test_inner_class_def(self):\n    class X(nn.Module):\n      class Hyper(struct.PyTreeNode):\n        a: int\n\n      hyper: Hyper\n\n      @nn.compact\n      def __call__(self, x):\n        return x + 1\n\n    self.assertIsInstance(X.Hyper(a=1), X.Hyper)\n\n  def test_sow(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x, **sow_args):\n        self.sow('intermediates', 'h', x, **sow_args)\n        self.sow('intermediates', 'h', 2 * x, **sow_args)\n        return 3 * x\n\n    variables = Foo().init(random.key(0), 1)\n    # During init we should not collect intermediates by default...\n    self.assertNotIn('intermediates', variables)\n    # ...unless we override mutable.\n    variables = Foo().init(random.key(0), 1, mutable=True)\n    self.assertEqual(variables, {'intermediates': {'h': (1, 2)}})\n\n    _, state = Foo().apply({}, 1, mutable=['intermediates'])\n    self.assertEqual(state, {'intermediates': {'h': (1, 2)}})\n    _, state = Foo().apply(\n      {},\n      1,\n      init_fn=lambda: 0,\n      reduce_fn=lambda a, b: a + b,\n      mutable=['intermediates'],\n    )\n    self.assertEqual(state, {'intermediates': {'h': 3}})\n    self.assertEqual(Foo().apply({}, 1), 3)\n\n  def test_capture_intermediates(self):\n    class Bar(nn.Module):\n      def test(self, x):\n        return x + 1\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return Bar().test(x) + 1\n\n    _, state = Foo().apply({}, 1, capture_intermediates=True)\n    self.assertEqual(state, {'intermediates': {'__call__': (3,)}})\n    fn = lambda mdl, _: isinstance(mdl, Bar)\n    _, state = Foo().apply({}, 1, capture_intermediates=fn)\n    self.assertEqual(state, {'intermediates': {'Bar_0': {'test': (2,)}}})\n\n  def test_perturb(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = nn.Dense(10)(x)\n        x = self.perturb('before_multiply', x)\n        x = 4 * x\n        x = self.perturb('after_multiply', x)\n        return x\n\n    def loss(params, perturbations, inputs, targets):\n      variables = {'params': params, 'perturbations': perturbations}\n      preds = Foo().apply(variables, inputs)\n      return jnp.square(preds - targets).mean()\n\n    x = jax.random.uniform(jax.random.key(1), shape=(10,))\n    y = jax.random.uniform(jax.random.key(2), shape=(10,))\n    variables = Foo().init(jax.random.key(0), x)\n    intm_grads = jax.grad(loss, argnums=1)(\n      variables['params'], variables['perturbations'], x, y\n    )\n    # activation * 4 so reverse gradient also * 4\n    self.assertTrue(\n      all(intm_grads['after_multiply'] * 4 == intm_grads['before_multiply'])\n    )\n\n  def test_perturb_setup(self):\n    class Foo(nn.Module):\n      def setup(self):\n        self.a = nn.Dense(10)\n\n      def __call__(self, x):\n        x = self.a(x)\n        x = self.perturb('before_multiply', x)\n        x = 4 * x\n        x = self.perturb('after_multiply', x)\n        return x\n\n    def loss(params, perturbations, inputs, targets):\n      variables = {'params': params, 'perturbations': perturbations}\n      preds = Foo().apply(variables, inputs)\n      return jnp.square(preds - targets).mean()\n\n    x = jax.random.uniform(jax.random.key(1), shape=(10,))\n    y = jax.random.uniform(jax.random.key(2), shape=(10,))\n    variables = Foo().init(jax.random.key(0), x)\n    intm_grads = jax.grad(loss, argnums=1)(\n      variables['params'], variables['perturbations'], x, y\n    )\n    # activation * 4 so reverse gradient also * 4\n    self.assertTrue(\n      all(intm_grads['after_multiply'] * 4 == intm_grads['before_multiply'])\n    )\n\n  def test_perturb_noop(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = nn.Dense(10)(x)\n        x = self.perturb('before_multiply', x)\n        x = 4 * x\n        x = self.perturb('after_multiply', x)\n        return x\n\n    x = jax.random.uniform(jax.random.key(1), shape=(10,))\n    module = Foo()\n    variables = module.init(jax.random.key(0), x)\n    params = variables['params']\n    perturbations = variables['perturbations']\n\n    # check no error if perturbations is not passed\n    module.apply({'params': params}, x)\n\n    # check errors if perturbations is passed but empty\n    with self.assertRaisesRegex(ValueError, 'Perturbation collection'):\n      module.apply({'params': params, 'perturbations': {}}, x)\n\n    # check no error if perturbations is passed and not empty\n    module.apply({'params': params, 'perturbations': perturbations}, x)\n\n  def test_functional_apply(self):\n    class Foo(nn.Module):\n      def setup(self):\n        self.a = nn.Dense(3)\n        self.b = nn.Dense(1)\n\n    def f(foo, x):\n      x = foo.a(x)\n      return foo.b(x)\n\n    foo = Foo()\n    x = jnp.ones((4,))\n    f_init = nn.init_with_output(f, foo)\n    f_apply = nn.apply(f, foo)\n    y1, variables = f_init(random.key(0), x)\n    y2 = f_apply(variables, x)\n    self.assertEqual(y1, y2)\n\n  def test_bind(self):\n    class Foo(nn.Module):\n      def setup(self):\n        self.a = nn.Dense(3)\n        self.b = nn.Dense(1)\n\n    def f(foo, x):\n      x = foo.a(x)\n      return foo.b(x)\n\n    foo = Foo()\n    x = jnp.ones((4,))\n    f_init = nn.init_with_output(f, foo)\n    y1, variables = f_init(random.key(0), x)\n    y2 = f(foo.bind(variables), x)\n    self.assertEqual(y1, y2)\n\n  def test_bind_stateful(self):\n    class Foo(nn.Module):\n      def setup(self):\n        self.a = nn.Dense(3)\n        self.bn = nn.BatchNorm()\n        self.b = nn.Dense(1)\n\n    def f(foo, x):\n      x = foo.a(x)\n      x = foo.bn(x, use_running_average=False)\n      return foo.b(x)\n\n    foo = Foo()\n    x = jnp.ones((4,))\n    f_init = nn.init_with_output(f, foo)\n    y1, variables = f_init(random.key(0), x)\n    foo_b = foo.bind(variables, mutable='batch_stats')\n    y2 = f(foo_b, x)\n    y3, new_state = nn.apply(f, foo, mutable='batch_stats')(variables, x)\n    self.assertEqual(y1, y2)\n    self.assertEqual(y2, y3)\n    bs_1 = new_state['batch_stats']\n    bs_2 = foo_b.variables['batch_stats']\n    for x, y in zip(\n      jax.tree_util.tree_leaves(bs_1), jax.tree_util.tree_leaves(bs_2)\n    ):\n      np.testing.assert_allclose(x, y)\n\n  def test_unbind(self):\n    class Foo(nn.Module):\n      def setup(self):\n        self.encoder = nn.Dense(4)\n        self.decoder = nn.Dense(2)\n\n      def __call__(self, x):\n        x = self.encoder(x)\n        return self.decoder(x)\n\n    foo = Foo()\n    x = jnp.ones((2,))\n\n    variables = foo.init(random.key(0), x)\n    encoder, encoder_vars = foo.bind(variables).encoder.unbind()\n    decoder, decoder_vars = foo.bind(variables).decoder.unbind()\n\n    self.assertIsInstance(encoder, nn.Dense)\n    self.assertEqual(encoder.features, 4)\n    self.assertIsInstance(decoder, nn.Dense)\n    self.assertEqual(decoder.features, 2)\n\n    self.assertTrue(\n        jax.tree_util.tree_all(\n            jax.tree_util.tree_map(\n                lambda v1, v2: (v1 == v2).all(),\n                variables['params']['encoder'],\n                encoder_vars['params'],\n            )\n        )\n    )\n    self.assertTrue(\n        jax.tree_util.tree_all(\n            jax.tree_util.tree_map(\n                lambda v1, v2: (v1 == v2).all(),\n                variables['params']['decoder'],\n                decoder_vars['params'],\n            )\n        )\n    )\n\n  def test_bind_unbind_equality(self):\n    class Foo(nn.Module):\n      sub_module: Any\n\n      @nn.compact\n      def __call__(self, x):\n        x = nn.Dense(2)(x)\n        return self.sub_module(x)\n\n    sub_module = Foo(nn.Dense(3))\n    module = Foo(sub_module)\n    x = jnp.ones((1, 2))\n    variables = module.init(jax.random.PRNGKey(0), x)\n\n    bound_module = module.bind(variables)\n    self.assertTrue((module.apply(variables, x) == bound_module(x)).all())\n    new_module, new_variables = bound_module.unbind()\n    self.assertTrue(\n        jax.tree_util.tree_all(\n            jax.tree_util.tree_map(\n                lambda v1, v2: (v1 == v2).all(), variables, new_variables\n            )\n        )\n    )\n    self.assertEqual(module, new_module)\n\n  def test_passing_mutable_variables(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return nn.Dense(2)(x)\n\n    x = jnp.ones((3,))\n    variables = Foo().init(random.key(0), x)\n    y = Foo().apply(variables, x)\n    self.assertEqual(y.shape, (2,))\n\n  def test_super_compact(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return nn.Dense(4)(x)\n\n    class Bar(Foo):\n      @nn.compact\n      def __call__(self, x):\n        y = super().__call__(x)\n        return nn.Dense(3)(y)\n\n    k = random.key(0)\n    x = jnp.ones((4, 7))\n\n    variables = Bar().init(k, x)\n    shapes = jax.tree_util.tree_map(np.shape, variables['params'])\n    self.assertEqual(\n      shapes,\n      {\n        'Dense_0': {'kernel': (7, 4), 'bias': (4,)},\n        'Dense_1': {'kernel': (4, 3), 'bias': (3,)},\n      },\n    )\n    y = Bar().apply(variables, x)\n    self.assertEqual(y.shape, (4, 3))\n\n  def test_super_setup(self):\n    class Foo(nn.Module):\n      def setup(self):\n        self.a = nn.Dense(4)\n\n    class Bar(Foo):\n      def setup(self):\n        super().setup()\n        self.b = nn.Dense(3)\n\n      def __call__(self, x):\n        y = self.a(x)\n        return self.b(y)\n\n    k = random.key(0)\n    x = jnp.ones((4, 7))\n\n    variables = Bar().init(k, x)\n    y = Bar().apply(variables, x)\n    self.assertEqual(y.shape, (4, 3))\n\n  def test_freeze_attr(self):\n    class Foo(NamedTuple):\n      a: int\n      b: int\n\n    self.assertEqual(nn.module._freeze_attr([1, 2]), (1, 2))\n    xs = nn.module._freeze_attr(Foo(1, 2))\n    self.assertEqual(xs, (1, 2))\n    self.assertEqual(\n      type(xs), Foo\n    )  # equality test for NamedTuple doesn't check class!\n\n  def test_generic_multiple_inheritance(self):\n    T = TypeVar('T')\n\n    class MyComponent(nn.Module, Generic[T]):\n      pass\n\n    class MyModule(nn.Module):\n      submodule: MyComponent[jnp.ndarray]\n\n    class MyComponent2(Generic[T], nn.Module):\n      pass\n\n    class MyModule2(nn.Module):\n      submodule: MyComponent2[jnp.ndarray]\n\n  def test_jit_rng_equivalance(self):\n    model = nn.fold_rngs(nn.Dense)(1, use_bias=False)\n    jit_model = nn.jit(nn.Dense)(1, use_bias=False)\n    param = model.init(random.key(0), np.ones((1, 1)))['params']['kernel']\n    param_2 = jit_model.init(random.key(0), np.ones((1, 1)))['params']['kernel']\n    self.assertEqual(param, param_2)\n\n  def test_rng_reuse_after_rewind(self):\n    class C(nn.Module):\n      @nn.compact\n      def __call__(self):\n        # Some module that has dropouts in it, in general,\n        # it does more than just dropout!\n        return self.make_rng('dropout')\n\n    class A(nn.Module):\n      @nn.compact\n      def __call__(self):\n        # Some module that has dropouts in it, in general,\n        # it does more than just dropout!\n        return C()()\n\n    class B(nn.Module):\n      @nn.compact\n      def __call__(self):\n        a = A()\n        x0 = a()\n        x1 = a()\n        return jnp.all(x0 == x1)\n\n    k = random.key(0)\n    rng_equals = B().apply({}, rngs={'dropout': k})\n    self.assertFalse(rng_equals)\n\n  def test_module_get_put_has_variable(self):\n    class A(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        self.put_variable('test_col', 'a', x)\n        assert self.has_variable('test_col', 'a')\n        return self.get_variable('test_col', 'a')\n\n    class B(nn.Module):\n      def __call__(self, x):\n        self.put_variable('test_col', 'a', x)\n        assert self.has_variable('test_col', 'a')\n        return self.get_variable('test_col', 'a')\n\n    class C(nn.Module):\n      def setup(self):\n        self.put_variable(\n          'test_col',\n          'a',\n          jnp.ones(\n            2,\n          ),\n        )\n        assert self.has_variable('test_col', 'a')\n\n      def __call__(self):\n        return self.get_variable('test_col', 'a')\n\n    x = jnp.ones((2,))\n\n    y, vs = A().apply({}, x, mutable=['test_col'])\n    np.testing.assert_array_equal(x, y)\n    np.testing.assert_array_equal(x, vs['test_col']['a'])\n\n    y, vs = B().apply({}, x, mutable=['test_col'])\n    np.testing.assert_array_equal(x, y)\n    np.testing.assert_array_equal(x, vs['test_col']['a'])\n\n    y, vs = C().apply({}, mutable=['test_col'])\n    np.testing.assert_array_equal(y, jnp.ones((2,)))\n    np.testing.assert_array_equal(y, vs['test_col']['a'])\n\n  def test_generic_module(self):\n    # See https://github.com/google/flax/issues/1899\n    T = TypeVar('T')\n\n    class C(nn.Module, Generic[T]):\n      def f(self, t: T) -> T:\n        return t\n\n    class D(nn.Module):\n      def setup(self):\n        unused_c = C[Any]()\n\n      def __call__(self) -> None:\n        pass\n\n    rngs = {}\n    D().init(rngs)\n\n  def test_modifying_attribs_in_post_init(self):\n    class Foo(nn.Module):\n      love: int = 99\n\n      def __post_init__(self):\n        self.hate = 100 - self.love\n        super().__post_init__()\n\n    foo = Foo()\n    self.assertEqual(foo.love, 99)\n    self.assertEqual(foo.hate, 1)\n\n    class Bar(nn.Module):\n      love: int = 99\n\n      def __post_init__(self):\n        self.love = 101\n        super().__post_init__()\n\n    bar = Bar()\n    self.assertEqual(bar.love, 101)\n\n  def test_has_rng(self):\n    class Foo(nn.Module):\n      def __call__(self):\n        return self.has_rng('bar')\n\n    foo = Foo()\n    with self.assertRaisesRegex(ValueError, 'RNGs.*unbound module'):\n      foo()\n    k = random.key(0)\n    self.assertTrue(foo.apply({}, rngs={'bar': k}))\n    self.assertFalse(foo.apply({}, rngs={'baz': k}))\n\n  def test_is_initializing(self):\n    class Foo(nn.Module):\n      def __call__(self):\n        return self.is_initializing()\n\n    foo = Foo()\n    k = random.key(0)\n    self.assertTrue(foo.init_with_output(k)[0])\n    self.assertFalse(foo.apply({}))\n\n  def test_throws_invalid_instance_module_error(self):\n    class B(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return x\n\n    k = random.key(0)\n    x = random.uniform(random.key(1), (2,))\n\n    with self.assertRaises(errors.InvalidInstanceModuleError):\n      B.init(k, x)  # B is module class, not B() a module instance\n    with self.assertRaises(errors.InvalidInstanceModuleError):\n      B.init_with_output(k, x)\n    with self.assertRaises(errors.InvalidInstanceModuleError):\n      B.apply(\n        {}, x\n      )  # similar issue w. apply called on class instead of instance.\n    with self.assertRaises(errors.InvalidInstanceModuleError):\n      B.bind(\n        {}, x\n      )  # similar issue w. apply called on class instead of instance.\n\n  def test_throws_incorrect_post_init_override_error(self):\n    class A(nn.Module):\n      x: float\n\n      def __post_init__(self):\n        self.x_square = self.x**2\n\n      @nn.compact\n      def __call__(self, input):\n        return input + 3\n\n    r = A(x=3)\n\n    with self.assertRaises(errors.IncorrectPostInitOverrideError):\n      r.init(jax.random.key(2), jnp.ones(3))\n\n  def test_deepcopy_unspecified_parent(self):\n    parent_parameter = inspect.signature(DummyModule).parameters['parent']\n    unspecified_parent = parent_parameter.default\n\n    self.assertIs(unspecified_parent, copy.copy(unspecified_parent))\n\n    self.assertIs(unspecified_parent, copy.deepcopy(unspecified_parent))\n\n  def test_type_hints(self):\n    class Network(nn.Module):\n      layers: int\n\n    type_hints = get_type_hints(Network)\n    self.assertEqual(type_hints['layers'], int)\n\n  def test_incorrect_property(self):\n    class Foo(nn.Module):\n      @property\n      def prop(self):\n        return self.non_existent\n\n      def __call__(self):\n        return self.prop\n\n    foo = Foo()\n    with self.assertRaisesRegex(\n      errors.DescriptorAttributeError, 'Trying to access a property that'\n    ):\n      foo.apply({})\n\n  def test_custom_descriptor(self):\n    class Descriptor:\n      def __get__(self, obj, objtype=None):\n        return 10\n\n    class Foo(nn.Module):\n      prop = Descriptor()\n\n      def __call__(self):\n        return self.prop\n\n    foo = Foo()\n    res = foo.apply({})\n    self.assertEqual(res, 10)\n\n  def test_custom_descriptor_error(self):\n    class Descriptor:\n      def __get__(self, obj, objtype=None):\n        return obj.non_existent\n\n    class Foo(nn.Module):\n      prop = Descriptor()\n\n      def __call__(self):\n        return self.prop\n\n    foo = Foo()\n    with self.assertRaisesRegex(\n      errors.DescriptorAttributeError, 'Trying to access a property that'\n    ):\n      foo.apply({})\n\n  def test_nested_external_modules(self):\n    class Baz(nn.Module):\n      a: int\n\n      def setup(self):\n        self.b = self.param('b', lambda k: 2)\n\n      def __call__(self, x):\n        return x + self.a * self.b\n\n    class Bar(nn.Module):\n      baz: Baz\n\n      def __call__(self, x):\n        return self.baz(x)\n\n    class Foo(nn.Module):\n      def setup(self):\n        self.bar = Bar(baz=Baz(a=1))\n\n      def __call__(self, x):\n        return self.bar.baz(x)\n\n    module = Foo()\n    y, variables = module.init_with_output(jax.random.key(0), 1)\n    self.assertEqual(y, 3)\n\n  def test_getattribute_triggers_setup(self):\n    class B(nn.Module):\n      def setup(self):\n        self.p1 = self.param('p1', lambda k: jnp.ones((2,)))\n\n      def fn1(self, x):\n        return self.p1 + x\n\n    class A(nn.Module):\n      b: nn.Module\n\n      def __call__(self, x):\n        return self.b.fn1(x)\n\n    a = A(b=B())\n    k = random.key(0)\n    x = jnp.zeros((2,))\n    vs = nn.init(lambda a, x: a(x), a)(k, x)\n    y = nn.apply(lambda a, x: a.b.fn1(x), a)(vs, x)\n    np.testing.assert_array_equal(y, jnp.ones((2,)))\n\n  def test_nested_sequential_in_call(self):\n    class Foo(nn.Module):\n      def setup(self):\n        self.seq = nn.Sequential([nn.Dense(10) for i in range(10)])\n\n      def __call__(self, x):\n        # try calling only the first layer\n        return self.seq.layers[0](x)\n\n    module = Foo()\n    variables = module.init(jax.random.key(0), jnp.ones((1, 10)))\n\n  def test_setup_called_bounded_submodules(self):\n    module = nn.Sequential(\n      [\n        nn.Sequential(\n          [\n            nn.Dense(2),\n            nn.relu,\n            nn.Dense(2),\n          ]\n        ),\n        nn.relu,\n        nn.Dense(2),\n      ]\n    )\n    x = jnp.ones((1, 3))\n    variables = module.init(jax.random.key(0), x)\n    bound_module = module.bind(variables)\n\n    self.assertIsNotNone(bound_module.layers[0].layers[0].scope)\n    self.assertIsNotNone(bound_module.layers[0].layers[2].scope)\n    self.assertIsNotNone(bound_module.layers[2].scope)\n\n  def test_call_bounded_toplevel_mutable(self):\n    class Bar(nn.Module):\n      a: int\n\n      def setup(self):\n        self.b = self.param('b', lambda k: 1)\n\n      def __call__(self, x):\n        return x + self.a * self.b\n\n    class Foo(nn.Module):\n      bars: Sequence[Bar]\n\n      def __call__(self, x):\n        for bar in self.bars:\n          x = bar(x)\n        return x\n\n    module = Foo(bars=[])\n    module.bars = [Bar(a=1)]\n\n    variables = module.init(jax.random.key(0), jnp.ones(()))\n    bound_module = module.bind(variables)\n\n    bar1 = bound_module.bars[0]\n    self.assertIsNotNone(bar1.scope)\n\n  def test_nested_init(self):\n    class Baz(nn.Module):\n      a: int\n\n      def setup(self):\n        self.b = self.param('b', lambda k: jnp.ones(()))\n\n      def __call__(self, x):\n        return x + self.a * self.b\n\n    class Bar(nn.Module):\n      baz: Baz\n\n      def setup(self):\n        a = 1\n\n      def __call__(self, x):\n        return self.baz(x)\n\n    class Foo(nn.Module):\n      def setup(self):\n        self.bar: Bar = Bar(baz=Baz(a=1))\n\n      def __call__(self, x):\n        # y = self.bar(x)\n        y, bar_vars = self.bar.init_with_output(jax.random.key(0), x)\n        return y, bar_vars\n\n    # create foo\n    module = Foo()\n\n    # run foo\n    (y, bar_vars), variables = module.init_with_output(\n      jax.random.key(0), jnp.ones(())\n    )\n\n    self.assertIn('params', bar_vars)\n\n  def test_nested_shared(self):\n    class Shared(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return nn.Dense(1)(x)\n\n    class Unshared(nn.Module):\n      shared: nn.Module\n\n      def __call__(self, x):\n        return self.shared(x)\n\n    class Super(nn.Module):\n      a: nn.Module\n      b: nn.Module\n\n      def run_a(self, x):\n        return self.a(x)\n\n      def run_b(self, x):\n        return self.b(x)\n\n      def __call__(self, x):\n        return self.a(x) + self.b(x)\n\n    sh = Shared()\n    a = Unshared(shared=sh)\n    b = Unshared(shared=sh)\n    module = Super(a=a, b=b)\n\n    rng = jax.random.key(0)\n    params = module.init(rng, jnp.ones(1))['params']\n\n    module.apply({'params': params}, jnp.ones(1))  # works as expected\n    module.apply(\n      {'params': params}, jnp.ones(1), method='run_a'\n    )  # works as expected\n    module.apply(\n      {'params': params}, jnp.ones(1), method='run_b'\n    )  # ScopeParamNotFoundError: Could not find parameter named \"kernel\" in scope \"/b/shared/Dense_0\"\n\n  def test_repr(self):\n    class Base1(nn.Module):\n      a: int\n\n    class Base2(nn.Module):\n      b: str\n\n    class Foo(Base2, Base1):\n      c: float\n\n    module = Foo(a=1, b='ok', c=3.0)\n    str_rep = repr(module)\n\n    self.assertIn('a = 1', str_rep)\n    self.assertIn(\"b = 'ok'\", str_rep)\n    self.assertIn('c = 3.0', str_rep)\n\n  def test_repr_should_not_cause_setup(self):\n    class MLP(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = nn.Dense(1)(x)\n        return repr(self)\n\n    class Foo(nn.Module):\n      a: float\n      b: MLP\n\n    scope = Scope({})\n    module = Foo(parent=scope, a=1, b=MLP(parent=scope))\n    str_rep = repr(module)\n    self.assertIn('a = 1', str_rep)\n\n    self.assertEqual(module._state.setup_called, nn.module.SetupState.NEW)\n    # repr() on a module should not cause inadvertent setup of submodules\n    # i.e. module.b._state.setup_called should remain nn.module.SetupState.NEW\n    # and not nn.module.SetupState.DONE\n    self.assertEqual(module.b._state.setup_called, nn.module.SetupState.NEW)\n\n  def test_kw_only(self):\n    def create_kw_layers():\n      class BaseLayer(nn.Module, kw_only=True):\n        base_multiplier: int | None = -1\n\n      class ChildLayer(BaseLayer):\n        child_multiplier: int  # Don't want to have to set a default argument!\n\n        def __call__(self, x):\n          return x * self.child_multiplier * self.base_multiplier\n\n      return BaseLayer, ChildLayer\n\n    if tuple(sys.version_info)[:3] < (3, 10, 0):\n      with self.assertRaisesRegex(TypeError, 'not available before Py 3.10'):\n        BaseLayer, ChildLayer = create_kw_layers()\n    else:\n      BaseLayer, ChildLayer = create_kw_layers()\n      with self.assertRaisesRegex(TypeError, 'positional argument'):\n        _ = BaseLayer(2)\n      # Like in Python dataclass, `kw_only` is not inherited, so ChildLayer can\n      # take positional arg. It takes BaseLayer's default kwargs though.\n      np.testing.assert_equal(ChildLayer(8)(np.ones(10)), -8 * np.ones(10))\n\n  def test_positional_cannot_be_kw_only(self):\n    class Foo(nn.Module):\n      a: int\n\n    Foo(1)  # ok\n    Foo(a=1)  # ok\n    with self.assertRaisesRegex(\n      TypeError, r'takes 2 positional arguments but 3 were'\n    ):\n      Foo(1, None)\n    Foo(a=1, parent=None)  # type: ignore[call-arg]\n\n  def test_module_path_empty(self):\n    rngkey = jax.random.key(0)\n    scope = Scope({}, {'params': rngkey}, mutable=['params'])\n    m1 = DummyModule(parent=scope)\n\n    self.assertEqual(m1.path, ())\n\n    scope = Scope({}, {'params': rngkey}, mutable=['params'], path=['root'])\n    m2 = DummyModule(parent=scope)\n\n    self.assertEqual(m2.path, ('root',))\n\n    m3 = DummyModule(parent=scope.rewound())\n\n    self.assertEqual(m3.path, ('root',))\n\n  def test_module_path_unbound_module_error(self):\n    m1 = DummyModule()\n    with self.assertRaisesRegex(ValueError, 'unbound module'):\n      _ = m1.path\n\n  def test_module_path_in_nested_module(self):\n    module_paths = []\n    debug_paths = []\n\n    class A(nn.Module):\n      def setup(self):\n        self.b1 = B()\n        self.b2 = B()\n        self.c1 = C()\n\n        module_paths.append(self.path)\n        debug_paths.append(self.scope.debug_path)\n\n      def __call__(self, x):\n        return self.b1(x) + self.b2(x) + self.c1(x)\n\n    class B(nn.Module):\n      def setup(self):\n        self.c1 = nn.remat(nn.remat(C))()\n        self.c2 = C()\n\n        module_paths.append(self.path)\n        debug_paths.append(self.scope.debug_path)\n\n      def __call__(self, x):\n        return self.c1(x) + self.c2(x)\n\n    class C(nn.Module):\n      def setup(self):\n        super().setup()\n        if self.scope.__class__.__name__ != 'TestScope':\n          module_paths.append(self.path)\n          debug_paths.append(self.scope.debug_path)\n\n      @nn.compact\n      def __call__(self, x):\n        return x\n\n    a = A()\n    k = random.key(0)\n    x = random.uniform(random.key(42), (2,))\n    _ = a.init(k, x)\n    expected_module_paths = [\n      (),\n      ('b1',),\n      ('b1', 'c1'),\n      ('b1', 'c2'),\n      ('b2',),\n      ('b2', 'c1'),\n      ('b2', 'c2'),\n      ('c1',),\n    ]\n    expected_debug_paths = [\n      (),\n      ('b1',),\n      ('b1', 'remat(remat(c1))'),\n      ('b1', 'c2'),\n      ('b2',),\n      ('b2', 'remat(remat(c1))'),\n      ('b2', 'c2'),\n      ('c1',),\n    ]\n\n    self.assertEqual(module_paths, expected_module_paths)\n    self.assertEqual(debug_paths, expected_debug_paths)\n\n  def test_intercept_methods(self):\n    mod = IdentityModule(parent=None)\n    x = jnp.ones([])\n    call_count = []\n\n    def add_one_interceptor(f, args, kwargs, context):\n      call_count.append(None)\n      self.assertLen(dataclasses.fields(context), 3)\n      self.assertIs(context.module, mod)\n      self.assertEqual(context.method_name, '__call__')\n      self.assertEqual(context.orig_method(3), 3)\n      self.assertEqual(args, (x,))\n      self.assertEmpty(kwargs)\n      y = f(*args, **kwargs)\n      return y + 1\n\n    y1 = mod(x)\n    with nn.intercept_methods(add_one_interceptor):\n      y2 = mod(x)\n    y3 = mod(x)\n\n    self.assertLen(call_count, 1)\n    self.assertEqual(y1, 1)\n    self.assertEqual(y2, 2)\n    self.assertEqual(y3, 1)\n\n  def test_intercept_methods_compact(self):\n    class CompactModule(nn.Module):\n      @compact\n      def __call__(self, x):\n        return nn.Dense(2)(x)\n\n    mod = CompactModule()\n    x = jnp.ones(shape=(1, 3))\n    variables = mod.init(jax.random.key(0), x)\n    call_modules = []\n\n    def log_interceptor(f, args, kwargs, context):\n      call_modules.append(context.module)\n      self.assertLen(dataclasses.fields(context), 3)\n      self.assertEqual(context.method_name, '__call__')\n      self.assertEqual(args, (x,))\n      self.assertEmpty(kwargs)\n      return f(*args, **kwargs)\n\n    with nn.intercept_methods(log_interceptor):\n      _ = mod.apply(variables, x)\n\n    self.assertLen(call_modules, 2)\n    self.assertIsInstance(call_modules[0], CompactModule)\n    self.assertIsInstance(call_modules[1], nn.Dense)\n\n  def test_intercept_methods_setup(self):\n    class SetupModule(nn.Module):\n      def setup(self):\n        self.layer = nn.Dense(2)\n\n      def __call__(self, x):\n        return self.layer(x)\n\n    mod = SetupModule()\n    x = jnp.ones(shape=(1, 3))\n    variables = mod.init(jax.random.key(0), x)\n    call_modules = []\n    log = []\n\n    def log_interceptor(f, args, kwargs, context):\n      call_modules.append(context.module)\n      log.append((context.method_name, args, kwargs))\n      return f(*args, **kwargs)\n\n    with nn.intercept_methods(log_interceptor):\n      _ = mod.apply(variables, x)\n\n    self.assertLen(call_modules, 3)\n    self.assertIsInstance(call_modules[0], SetupModule)\n    self.assertIsInstance(call_modules[1], SetupModule)\n    self.assertIsInstance(call_modules[2], nn.Dense)\n    self.assertEqual(\n      log, [('setup', (), {}), ('__call__', (x,), {}), ('__call__', (x,), {})]\n    )\n\n  def test_intercept_methods_calling_underlying_optional(self):\n    def do_nothing_interceptor(f, args, kwargs, context):\n      del f, context\n      self.assertEmpty(args)\n      self.assertEmpty(kwargs)\n\n    m = RaisesModule()\n    with nn.intercept_methods(do_nothing_interceptor):\n      m()\n\n    with self.assertRaises(AssertionError):\n      m()\n\n    with nn.intercept_methods(do_nothing_interceptor):\n      m()\n\n  def test_intercept_methods_run_in_lifo_order(self):\n    def op_interceptor(op):\n      def _interceptor(f, args, kwargs, context):\n        del context\n        y = f(*args, **kwargs)\n        return op(y)\n\n      return _interceptor\n\n    mod = IdentityModule(parent=None)\n    x = 7\n    with (\n      nn.intercept_methods(op_interceptor(lambda a: a + 1)),\n      nn.intercept_methods(op_interceptor(lambda a: a**2)),\n    ):\n      y = mod(x)\n\n    self.assertEqual(y, (x**2) + 1)\n\n    with (\n      nn.intercept_methods(op_interceptor(lambda a: a**2)),\n      nn.intercept_methods(op_interceptor(lambda a: a + 1)),\n    ):\n      y = mod(x)\n\n    self.assertEqual(y, (x + 1) ** 2)\n\n  def test_intercept_methods_subclasses(self):\n    class Foo(IdentityModule):\n      def __call__(self, x):  # pylint: disable=useless-parent-delegation\n        return super().__call__(x)\n\n    class Bar(Foo):\n      def __call__(self, x):  # pylint: disable=useless-parent-delegation\n        return super().__call__(x)\n\n    bar = Bar(parent=None)\n    x = jnp.ones([])\n    called = []\n\n    def record_interceptor(f, args, kwargs, context):\n      called.append(None)\n      self.assertIs(context.module, bar)\n      self.assertEqual(context.method_name, '__call__')\n      self.assertEqual(args, (x,))\n      self.assertEmpty(kwargs)\n      return f(*args, **kwargs)\n\n    with nn.intercept_methods(record_interceptor):\n      bar(x)\n\n    # Bar.__call__, Foo.__call__ and IdenityModule.__call__\n    self.assertLen(called, 3)\n\n  def test_intercept_methods_nested_module(self):\n    class Foo(nn.Module):\n      def __call__(self, x):\n        return x\n\n    class Bar(nn.Module):\n      sub: nn.Module\n\n      def __call__(self, x):\n        return self.sub(x)\n\n    foo = Foo()\n    bar = Bar(sub=foo)\n    x = jnp.ones([])\n    called = []\n\n    def record_interceptor(f, args, kwargs, context):\n      called.append(context.module)\n      self.assertEqual(context.method_name, '__call__')\n      self.assertEqual(args, (x,))\n      self.assertEmpty(kwargs)\n      return f(*args, **kwargs)\n\n    with nn.intercept_methods(record_interceptor):\n      bar(x)\n\n    # bar.__call__ and foo.__call__\n    self.assertLen(called, 2)\n    self.assertIs(called[0], bar)\n    self.assertIs(called[1], foo)\n\n  def test_cloudpickle_class(self):\n    import cloudpickle\n\n    class MyModule(nn.Module):\n      pass\n\n    a = MyModule()\n\n    UnpickledMyModule = cloudpickle.loads(cloudpickle.dumps(MyModule))\n    b = UnpickledMyModule()\n\n  def test_cloudpickle_module(self):\n    from cloudpickle import cloudpickle_fast\n\n    class NNModuleWithProperty(nn.Module):\n      a: int\n      b: str\n\n      @property\n      def my_property(self):\n        return self.b * self.a\n\n    m = NNModuleWithProperty(a=2, b='ok')\n\n    with TemporaryDirectory() as tmpdir:\n      filename = f'{tmpdir}/module.pkl'\n      with open(filename, 'wb') as f:\n        cloudpickle_fast.dump(m, f)\n\n      with open(filename, 'rb') as f:\n        obj_loaded = cloudpickle_fast.load(f)\n\n    self.assertEqual(obj_loaded.a, 2)\n    self.assertEqual(obj_loaded.b, 'ok')\n    self.assertEqual(obj_loaded.my_property, 'okok')\n\n  def test_module_paths(self):\n    class Bar(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = nn.Dense(3)(x)\n        x = nn.Dense(4)(x)\n        return x\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = Bar()(x)\n        x = nn.Dense(5)(x)\n        return x\n\n    x = jnp.ones((1, 2))\n    m = Foo()\n    module_paths = m.module_paths(random.key(0), x)\n\n    # assert all module are unbounded\n    for module in module_paths.values():\n      self.assertIsNone(module.scope)\n\n    # test paths\n    self.assertIn('', module_paths)\n    self.assertEqual(type(module_paths['']), Foo)\n    self.assertIn('Dense_0', module_paths)\n    self.assertEqual(type(module_paths['Dense_0']), nn.Dense)\n    self.assertIn('Bar_0', module_paths)\n    self.assertEqual(type(module_paths['Bar_0']), Bar)\n    self.assertIn('Bar_0/Dense_0', module_paths)\n    self.assertEqual(type(module_paths['Bar_0/Dense_0']), nn.Dense)\n    self.assertIn('Bar_0/Dense_1', module_paths)\n    self.assertEqual(type(module_paths['Bar_0/Dense_1']), nn.Dense)\n\n  def test_init_apply_default_rng(self):\n    class SubModel(nn.Module):\n      @nn.compact\n      def __call__(self, x, apply_dropout):\n        x = nn.Dense(8)(x)\n        x = nn.Dropout(0.8)(x, deterministic=not apply_dropout)\n        p = self.param(\n          'parameter', lambda key, shape: jax.random.normal(key, shape), x.shape\n        )\n        noise = jax.random.normal(self.make_rng('noise'), x.shape)\n        return x * p + noise\n\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x, apply_dropout):\n        x = nn.Dense(16)(x)\n        x = SubModel()(x, apply_dropout)\n        x = nn.Dropout(0.5)(x, deterministic=not apply_dropout)\n        v = self.variable(\n          'var_collection',\n          'variable',\n          lambda shape: jax.random.normal(self.make_rng('var_rng'), shape),\n          x.shape,\n        )\n        noise = jax.random.normal(self.make_rng('noise'), x.shape)\n        return x * v.value + noise\n\n    key0, key1, key2 = jax.random.split(jax.random.key(0), 3)\n    x = jax.random.normal(key0, (10, 4))\n    model = Model()\n\n    # test init equality\n    default_variables = model.init({'params': key1}, x, apply_dropout=False)\n    rngs = {'params': key1, 'var_rng': key1, 'noise': key1}\n    explicit_variables = model.init(rngs, x, apply_dropout=False)\n    self.assertTrue(\n        jax.tree_util.tree_all(\n            jax.tree_util.tree_map(\n                lambda v1, v2: (v1 == v2).all(),\n                default_variables,\n                explicit_variables,\n            )\n        )\n    )\n\n    # test init inequality\n    for rng_name in ('params', 'var_rng'):\n      rngs[rng_name] = key2\n      explicit_variables = model.init(rngs, x, apply_dropout=False)\n      self.assertFalse(\n          jax.tree_util.tree_all(\n              jax.tree_util.tree_map(\n                  lambda v1, v2: (v1 == v2).all(),\n                  default_variables,\n                  explicit_variables,\n              )\n          )\n      )\n      rngs[rng_name] = key1\n\n    # test apply equality\n    default_out = model.apply(\n      default_variables, x, apply_dropout=True, rngs={'params': key1}\n    )\n    rngs = {'dropout': key1, 'noise': key1}\n    explicit_out = model.apply(\n      default_variables, x, apply_dropout=True, rngs=rngs\n    )\n    np.testing.assert_allclose(default_out, explicit_out)\n\n    # test apply inequality\n    for rng_name in ('dropout', 'noise'):\n      rngs[rng_name] = key2\n      explicit_out = model.apply(\n        default_variables, x, apply_dropout=True, rngs=rngs\n      )\n      with self.assertRaises(AssertionError):\n        np.testing.assert_allclose(default_out, explicit_out, atol=1e-1)\n      rngs[rng_name] = key1\n\n  def test_default_make_rng(self):\n    class SubModel(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        noise = jax.random.normal(self.make_rng(), x.shape)\n        return x + noise\n\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = SubModel()(x)\n        noise = jax.random.normal(self.make_rng(), x.shape)\n        return x + noise\n\n    key0, key1 = jax.random.split(jax.random.key(0), 2)\n    x = jax.random.normal(key0, (10, 4))\n    default_out = Model().apply({}, x, rngs={'params': key1})\n\n    class SubModel(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        noise = jax.random.normal(self.make_rng('params'), x.shape)\n        return x + noise\n\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = SubModel()(x)\n        noise = jax.random.normal(self.make_rng('params'), x.shape)\n        return x + noise\n\n    explicit_out = Model().apply({}, x, rngs={'params': key1})\n    np.testing.assert_allclose(default_out, explicit_out)\n\n  def test_default_rng_error(self):\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return nn.Dense(2)(x)\n\n    model = Model()\n    with self.assertRaisesRegex(\n      errors.InvalidRngError, 'Dense_0 needs PRNG for \"params\"'\n    ):\n      model.init({'other_rng_stream': jax.random.key(0)}, jnp.ones((1, 3)))\n\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return x + jax.random.normal(self.make_rng(), x.shape)\n\n    model = Model()\n    with self.assertRaisesRegex(\n      errors.InvalidRngError, 'None needs PRNG for \"params\"'\n    ):\n      model.init({'other_rng_stream': jax.random.key(0)}, jnp.ones((1, 3)))\n\n  def test_compact_name_scope(self):\n    class Foo(nn.Module):\n      @nn.compact_name_scope\n      def up(self, x):\n        return nn.Dense(3)(x)\n\n      @nn.compact_name_scope\n      def down(self, x):\n        return nn.Dense(3)(x)\n\n      @nn.compact\n      def __call__(self, x):\n        return self.up(x) + self.down(x) + nn.Dense(3)(x)\n\n    m = Foo()\n    x = jnp.ones((1, 2))\n\n    self.assertEqual(set(m._compact_name_scope_methods), {'up', 'down'})\n\n    variables = m.init(random.key(0), x)\n    params = variables['params']\n\n    self.assertIn('Dense_0', params)\n    self.assertIn('down', params)\n    self.assertIn('up', params)\n    self.assertIn('Dense_0', params['down'])\n    self.assertIn('Dense_0', params['up'])\n\n    y = m.apply(variables, x)\n    y_up = m.apply(variables, x, method='up')\n    y_down = m.apply(variables, x, method='down')\n\n    assert y.shape == (1, 3)\n    assert y_up.shape == (1, 3)\n    assert y_down.shape == (1, 3)\n\n  def test_compact_name_scope_outside_compact(self):\n    class Foo(nn.Module):\n      @nn.compact_name_scope\n      def up(self, x):\n        return nn.Dense(3)(x)\n\n      @nn.compact_name_scope\n      def down(self, x):\n        return nn.Dense(3)(x)\n\n      def __call__(self, x):\n        return self.up(x) + self.down(x)\n\n    m = Foo()\n    x = jnp.ones((1, 2))\n\n    self.assertEqual(set(m._compact_name_scope_methods), {'up', 'down'})\n\n    variables = m.init(random.key(0), x)\n    params = variables['params']\n\n    self.assertIn('down', params)\n    self.assertIn('up', params)\n    self.assertIn('Dense_0', params['down'])\n    self.assertIn('Dense_0', params['up'])\n\n    y = m.apply(variables, x)\n    y_up = m.apply(variables, x, method='up')\n    y_down = m.apply(variables, x, method='down')\n\n    assert y.shape == (1, 3)\n    assert y_up.shape == (1, 3)\n    assert y_down.shape == (1, 3)\n\n\nclass LeakTests(absltest.TestCase):\n  def test_tracer_leaks(self):\n    model = nn.Sequential([nn.Dense(50)])\n\n    @jax.jit\n    @functools.partial(jax.vmap, in_axes=(0, None))\n    def sample_from_prior(rng, inp):\n      params = model.init(rng, np.zeros((10, 50)))\n      out = model.apply(params, inp)\n      del params\n      return out\n\n    # disable manual gc.collect call in jax leak checker\n    # so that we can test tracer leaks in ref-cycles.  This is a\n    # reasonable proxy for transiently leaked memory during\n    # eager execution.\n    with patch.object(gc, 'collect', return_value=0):\n      with jax.checking_leaks():\n        for i in range(5):\n          rngs = jax.random.split(jax.random.key(23), 100)\n          out = sample_from_prior(rngs, np.ones((4, 50)))\n          out.block_until_ready()\n          del out, rngs\n\n\nclass RelaxedNamingTests(absltest.TestCase):\n  def test_relaxed_adoption(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        p = self.param('p', nn.initializers.zeros, x.shape)\n        return x + p\n\n    class Bar(nn.Module):\n      sub: nn.Module\n\n      def __call__(self, x):\n        return self.sub(x)\n\n    with set_config('flax_preserve_adopted_names', True):\n      foo = Foo(name='foo')\n      bar = Bar(sub=foo)\n      k = random.key(0)\n      x = jnp.zeros((1,))\n      vs = bar.init(k, x)\n      self.assertTrue('foo' in vs['params'], 'relaxed naming failure')\n      y = bar.apply(vs, x)\n\n    with set_config('flax_preserve_adopted_names', False):\n      foo = Foo(name='foo')\n      bar = Bar(sub=foo)\n      k = random.key(0)\n      x = jnp.zeros((1,))\n      vs = bar.init(k, x)\n      self.assertTrue('sub' in vs['params'], 'old policy naming failure')\n      y = bar.apply(vs, x)\n\n  def test_class_optional_adoption_name_preservation(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        p = self.param('p', nn.initializers.zeros, x.shape)\n        return x + p\n\n    class Bar1(nn.Module):\n      sub: nn.Module\n      preserve_adopted_names = True\n\n      def __call__(self, x):\n        return self.sub(x)\n\n    class Bar2(nn.Module):\n      sub: nn.Module\n      preserve_adopted_names = False\n\n      def __call__(self, x):\n        return self.sub(x)\n\n    with set_config('flax_preserve_adopted_names', False):\n      foo = Foo(name='foo')\n      bar = Bar1(sub=foo)\n      k = random.key(0)\n      x = jnp.zeros((1,))\n      vs = bar.init(k, x)\n      self.assertTrue('foo' in vs['params'], 'adoption naming failure')\n      y = bar.apply(vs, x)\n\n    with set_config('flax_preserve_adopted_names', True):\n      foo = Foo(name='foo')\n      bar = Bar2(sub=foo)\n      k = random.key(0)\n      x = jnp.zeros((1,))\n      vs = bar.init(k, x)\n      self.assertTrue('sub' in vs['params'], 'adoption naming failure')\n      y = bar.apply(vs, x)\n\n  def test_nested_class_optional_adoption_name_preservation(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        p = self.param('p', nn.initializers.zeros, x.shape)\n        return x + p\n\n    class Bar(nn.Module):\n      sub: nn.Module\n      preserve_adopted_names = True\n\n      def __call__(self, x):\n        return self.sub(x)\n\n    class Baz(nn.Module):\n      sub: nn.Module\n      preserve_adopted_names = True\n\n      def __call__(self, x):\n        return self.sub(x)\n\n    with set_config('flax_preserve_adopted_names', False):\n      foo = Foo(name='foo')\n      bar = Bar(sub=foo, name='bar')\n      baz = Baz(sub=bar)\n      k = random.key(0)\n      x = jnp.zeros((1,))\n      vs = baz.init(k, x)\n      self.assertTrue('bar' in vs['params'], 'adoption naming failure')\n      self.assertTrue('foo' in vs['params']['bar'], 'adoption naming failure')\n      y = baz.apply(vs, x)\n\n  def test_relaxed_adoption_still_conflict_checks(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        p = self.param('p', nn.initializers.zeros, x.shape)\n        return x + p\n\n    class Bar(nn.Module):\n      sub1: nn.Module\n      sub2: nn.Module\n\n      def __call__(self, x):\n        return self.sub(x)\n\n    with set_config('flax_preserve_adopted_names', True):\n      foo1 = Foo(name='foo')\n      foo2 = Foo(name='foo')\n      bar = Bar(sub1=foo1, sub2=foo2)\n      k = random.key(0)\n      x = jnp.zeros((1,))\n      with self.assertRaises(errors.NameInUseError):\n        vs = bar.init(k, x)\n\n  def test_relaxed_adoption_unnamed_adoptee(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        p = self.param('p', nn.initializers.zeros, x.shape)\n        return x + p\n\n    class Bar(nn.Module):\n      sub: nn.Module\n\n      def __call__(self, x):\n        return self.sub(x)\n\n    with set_config('flax_preserve_adopted_names', True):\n      foo = Foo(name=None)\n      bar = Bar(sub=foo)\n      k = random.key(0)\n      x = jnp.zeros((1,))\n      vs = bar.init(k, x)\n      self.assertTrue('sub' in vs['params'], 'relaxed naming failure')\n      y = bar.apply(vs, x)\n\n    with set_config('flax_preserve_adopted_names', False):\n      foo = Foo(name='foo')\n      bar = Bar(sub=foo)\n      k = random.key(0)\n      x = jnp.zeros((1,))\n      vs = bar.init(k, x)\n      self.assertTrue('sub' in vs['params'], 'old policy naming failure')\n      y = bar.apply(vs, x)\n\n  def test_relaxed_python_conflict(self):\n    class Foo(nn.Module):\n      dummy = 0\n\n      @nn.compact\n      def __call__(self, x):\n        p = self.param('dummy', nn.initializers.zeros, x.shape)\n        return x + p\n\n    foo = Foo(name='foo')\n    k = random.key(0)\n    x = jnp.zeros((1,))\n    vs = foo.init(k, x)\n\n  def test_relaxed_intercollection_conflict(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        v1 = self.variable('col1', 'v', lambda x: jnp.zeros(x), x.shape)\n        v2 = self.variable('col2', 'v', lambda x: jnp.zeros(x), x.shape)\n        return x + v1.value + v2.value\n\n    foo = Foo(name='foo')\n    k = random.key(0)\n    x = jnp.zeros((1,))\n    vs = foo.init(k, x)\n\n  def test_relaxed_intercollection_conflict_set(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        v1 = self.variable('col1', 'v', lambda x: jnp.zeros(x), x.shape)\n        v2 = self.variable('col2', 'v', lambda x: jnp.zeros(x), x.shape)\n        v3 = self.variable('col1', 'v', lambda x: jnp.zeros(x), x.shape)\n        return x + v1.value + v2.value + v3.value\n\n    foo = Foo(name='foo')\n    k = random.key(0)\n    x = jnp.zeros((1,))\n    with self.assertRaises(errors.NameInUseError):\n      vs = foo.init(k, x)\n\n  def test_internal_deep_clone(self):\n    class Child(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        w = self.param('w', nn.initializers.zeros, (5, x.shape[1]))\n        return x @ w\n\n    class Parent(nn.Module):\n      num_layers: int\n      child_template: Child\n\n      @nn.compact\n      def __call__(self, x):\n        for i in range(self.num_layers):\n          x = self.child_template.clone(\n            parent=self, _deep_clone=True, name=None\n          )(x)\n        return x\n\n    model = Parent(num_layers=2, child_template=Child())\n    x = jnp.ones((32, 5))\n    variables = model.init(jax.random.key(0), x)\n    output = model.apply(variables, x)\n    self.assertTrue(\n      variables['params']['Child_0']['w'].shape\n      == variables['params']['Child_1']['w'].shape\n    )\n\n  def test_copy_method(self):\n    class Parent(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        child = nn.Dense(\n          2,\n        )\n        x = child(x)\n        x = child.copy()(x)\n        return x\n\n    model = Parent()\n    x = jnp.ones((2, 2))\n    variables = model.init(jax.random.key(0), x)\n    output = model.apply(variables, x)\n    self.assertTrue(\n      variables['params']['Dense_0']['kernel'].shape\n      == variables['params']['Dense_1']['kernel'].shape\n    )\n\n  def test_copy_from_template(self):\n    class Child(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        w = self.param('w', nn.initializers.zeros, (5, x.shape[1]))\n        return x @ w\n\n    class Parent(nn.Module):\n      num_layers: int\n      child_template: Child\n\n      @nn.compact\n      def __call__(self, x):\n        for i in range(self.num_layers):\n          x = self.child_template.copy()(x)\n        for i in range(self.num_layers):\n          x = self.child_template.copy(name=f'next_layer_{i}')(x)\n        return x\n\n    model = Parent(num_layers=2, child_template=Child())\n    x = jnp.ones((32, 5))\n    variables = model.init(jax.random.key(0), x)\n    output = model.apply(variables, x)\n    self.assertTrue(\n      variables['params']['Child_0']['w'].shape\n      == variables['params']['Child_1']['w'].shape\n    )\n    self.assertIn('Child_0', variables['params'])\n    self.assertIn('Child_1', variables['params'])\n    self.assertIn('next_layer_0', variables['params'])\n    self.assertIn('next_layer_1', variables['params'])\n    self.assertNotIn('child_template', variables['params'])\n\n  def test_nonstring_keys_in_dict_on_module(self):\n    class MyEnum(str, enum.Enum):\n      a = 'a'\n      b = 'b'\n\n    class MyModule(nn.Module):\n      config: dict[MyEnum, int]\n\n      def __call__(self, inputs):\n        return inputs\n\n    module = MyModule(config={MyEnum.a: 1, MyEnum.b: 2})\n    variables = module.init(jax.random.key(0), jnp.zeros([0]))\n\n\nclass FrozenDictTests(absltest.TestCase):\n  def test_frozendict_flag(self):\n    with set_config('flax_return_frozendict', True):\n      x = jnp.zeros((2, 3))\n      layer = nn.Dense(5)\n      params = layer.init(random.key(0), x)\n      self.assertTrue(isinstance(params, FrozenDict))\n\n    with set_config('flax_return_frozendict', False):\n      x = jnp.zeros((2, 3))\n      layer = nn.Dense(5)\n      params = layer.init(random.key(0), x)\n      self.assertTrue(isinstance(params, dict))\n\n\nclass ShareScopeTest(absltest.TestCase):\n  def test_basic(self):\n    class DenseLoRA(nn.Module):\n      inner: nn.Dense\n      rank: int\n\n      def setup(self):\n        nn.share_scope(self, self.inner)\n\n      @nn.compact\n      def __call__(self, x: jax.Array):\n        din, dout = x.shape[-1], self.inner.features\n        A = self.param('A', nn.zeros_init(), (din, self.rank))\n        B = self.param('B', nn.zeros_init(), (self.rank, dout))\n        return self.inner(x) + x @ A @ B\n\n    dense_lora = DenseLoRA(nn.Dense(10), rank=2)\n\n    params = dense_lora.init(random.key(0), jnp.ones((1, 5)))['params']\n\n    self.assertIn('kernel', params)\n    self.assertIn('bias', params)\n    self.assertIn('A', params)\n    self.assertIn('B', params)\n\n  def test_child_scope(self):\n    class DenseLoRA(nn.Module):\n      rank: int\n\n      def setup(self):\n        self.child = nn.Dense(10)\n        nn.share_scope(self, self.child)\n\n      @nn.compact\n      def __call__(self, x: jax.Array):\n        din, dout = x.shape[-1], self.child.features\n        A = self.param('A', nn.zeros_init(), (din, self.rank))\n        B = self.param('B', nn.zeros_init(), (self.rank, dout))\n        return self.child(x) + x @ A @ B\n\n    dense_lora = DenseLoRA(rank=2)\n\n    params = dense_lora.init(random.key(0), jnp.ones((1, 5)))['params']\n\n    self.assertIn('kernel', params)\n    self.assertIn('bias', params)\n    self.assertIn('A', params)\n    self.assertIn('B', params)\n\n  def test_in_compact(self):\n    class DenseLoRA(nn.Module):\n      rank: int\n\n      def setup(self):\n        self.child = nn.Dense(10)\n        nn.share_scope(self, self.child)\n\n      @nn.compact\n      def __call__(self, x: jax.Array):\n        din, dout = x.shape[-1], self.child.features\n        A = self.param('A', nn.zeros_init(), (din, self.rank))\n        B = self.param('B', nn.zeros_init(), (self.rank, dout))\n        return self.child(x) + x @ A @ B\n\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x: jax.Array):\n        return DenseLoRA(rank=2)(x)\n\n    model = Model()\n\n    params = model.init(random.key(0), jnp.ones((1, 5)))['params']\n\n    self.assertIn('kernel', params['DenseLoRA_0'])\n    self.assertIn('bias', params['DenseLoRA_0'])\n    self.assertIn('A', params['DenseLoRA_0'])\n    self.assertIn('B', params['DenseLoRA_0'])\n\n  def test_adopt_child_name(self):\n    class DenseLoRA(nn.Module):\n      inner: nn.Dense\n      rank: int\n\n      def setup(self):\n        nn.share_scope(self, self.inner)\n\n      @nn.compact\n      def __call__(self, x: jax.Array):\n        din, dout = x.shape[-1], self.inner.features\n        A = self.param('A', nn.zeros_init(), (din, self.rank))\n        B = self.param('B', nn.zeros_init(), (self.rank, dout))\n        return self.inner(x) + x @ A @ B\n\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x: jax.Array):\n        return DenseLoRA(nn.Dense(10), rank=2)(x)\n\n    model = Model()\n\n    params = model.init(random.key(0), jnp.ones((1, 5)))['params']\n\n    self.assertIn('kernel', params['Dense_0'])\n    self.assertIn('bias', params['Dense_0'])\n    self.assertIn('A', params['Dense_0'])\n    self.assertIn('B', params['Dense_0'])\n\n  def test_other_scope_is_none(self):\n    class DenseLoRA(nn.Module):\n      inner: nn.Dense\n      rank: int\n\n      def setup(self):\n        nn.share_scope(self, self.inner)\n\n      @nn.compact\n      def __call__(self, x: jax.Array):\n        din, dout = x.shape[-1], self.inner.features\n        A = self.param('A', nn.zeros_init(), (din, self.rank))\n        B = self.param('B', nn.zeros_init(), (self.rank, dout))\n        return self.inner(x) + x @ A @ B\n\n    class Model(nn.Module):\n      def setup(self):\n        # here Dense doesn't have a scope yet\n        self.dense_lora = DenseLoRA(nn.Dense(10), rank=2)\n\n      @nn.compact\n      def __call__(self, x: jax.Array):\n        return self.dense_lora(x)\n\n    model = Model()\n\n    params = model.init(random.key(0), jnp.ones((1, 5)))['params']\n\n    self.assertIn('kernel', params['dense_lora'])\n    self.assertIn('bias', params['dense_lora'])\n    self.assertIn('A', params['dense_lora'])\n    self.assertIn('B', params['dense_lora'])\n\n  def test_external_grandchild_scope_correct(self):\n    class GrandChild(nn.Module):\n      @nn.compact\n      def __call__(self):\n        return nn.Dense(50)(jnp.zeros(10))\n\n    class Child(nn.Module):\n      child: GrandChild\n\n      @nn.compact\n      def __call__(self, *args: Any, **kwargs: Any) -> Any:\n        return self.child(*args, **kwargs)\n\n    class Parent(nn.Module):\n      main_child: Child\n\n      def setup(self):\n        nn.share_scope(self, self.main_child)\n\n      @nn.compact\n      def __call__(self, *args: Any, **kwargs: Any) -> Any:\n        nn.Dense(10)(jnp.zeros(10))\n        r = self.main_child(*args, **kwargs)\n        return r\n\n    params = Parent(Child(GrandChild())).init(jax.random.key(0))\n    self.assertNotIn('main_child', params['params'])\n    self.assertIn('child', params['params'])\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/linen_recurrent_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Recurrent tests.\"\"\"\n\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom absl.testing import absltest\n\nfrom flax import linen as nn\nfrom flax.linen.recurrent import flip_sequences\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass RNNTest(absltest.TestCase):\n  def test_rnn_basic_forward(self):\n    batch_size = 10\n    seq_len = 40\n    channels_in = 5\n    channels_out = 15\n\n    rnn = nn.RNN(nn.LSTMCell(channels_out), return_carry=True)\n\n    xs = jnp.ones((batch_size, seq_len, channels_in))\n    variables = rnn.init(jax.random.key(0), xs)\n    ys: jnp.ndarray\n    carry, ys = rnn.apply(variables, xs)\n\n    self.assertEqual(ys.shape, (batch_size, seq_len, channels_out))\n\n    for layer_params in variables['params']['cell'].values():\n      if 'bias' in layer_params:\n        self.assertEqual(layer_params['bias'].shape, (channels_out,))\n      self.assertIn(\n        layer_params['kernel'].shape[0], [channels_in, channels_out]\n      )\n      self.assertEqual(layer_params['kernel'].shape[1], channels_out)\n\n  def test_rnn_multiple_batch_dims(self):\n    batch_dims = (10, 11)\n    seq_len = 40\n    channels_in = 5\n    channels_out = 15\n\n    rnn = nn.RNN(nn.LSTMCell(channels_out), return_carry=True)\n\n    xs = jnp.ones((*batch_dims, seq_len, channels_in))\n    variables = rnn.init(jax.random.key(0), xs)\n    ys: jnp.ndarray\n    carry, ys = rnn.apply(variables, xs)\n\n    self.assertEqual(ys.shape, (*batch_dims, seq_len, channels_out))\n\n    for layer_params in variables['params']['cell'].values():\n      if 'bias' in layer_params:\n        self.assertEqual(layer_params['bias'].shape, (channels_out,))\n      self.assertIn(\n        layer_params['kernel'].shape[0], [channels_in, channels_out]\n      )\n      self.assertEqual(layer_params['kernel'].shape[1], channels_out)\n\n  def test_rnn_unroll(self):\n    batch_size = 10\n    seq_len = 40\n    channels_in = 5\n    channels_out = 15\n\n    rnn = nn.RNN(nn.LSTMCell(channels_out), unroll=10, return_carry=True)\n\n    xs = jnp.ones((batch_size, seq_len, channels_in))\n    variables = rnn.init(jax.random.key(0), xs)\n    ys: jnp.ndarray\n    carry, ys = rnn.apply(variables, xs)\n\n    self.assertEqual(ys.shape, (batch_size, seq_len, channels_out))\n\n    for layer_params in variables['params']['cell'].values():\n      if 'bias' in layer_params:\n        self.assertEqual(layer_params['bias'].shape, (channels_out,))\n      self.assertIn(\n        layer_params['kernel'].shape[0], [channels_in, channels_out]\n      )\n      self.assertEqual(layer_params['kernel'].shape[1], channels_out)\n\n  def test_rnn_time_major(self):\n    seq_len = 40\n    batch_size = 10\n    channels_in = 5\n    channels_out = 15\n\n    rnn = nn.RNN(nn.LSTMCell(channels_out), time_major=True, return_carry=True)\n\n    xs = jnp.ones((seq_len, batch_size, channels_in))\n    variables = rnn.init(jax.random.key(0), xs)\n\n    ys: jnp.ndarray\n    carry, ys = rnn.apply(variables, xs)\n\n    # carry state should not be zeros after apply\n    for leaf in jax.tree_util.tree_leaves(carry):\n      assert not np.allclose(leaf, jnp.zeros_like(leaf))\n      self.assertEqual(leaf.shape, (batch_size, channels_out))\n\n    self.assertEqual(ys.shape, (seq_len, batch_size, channels_out))\n\n    for layer_params in variables['params']['cell'].values():\n      if 'bias' in layer_params:\n        self.assertEqual(layer_params['bias'].shape, (channels_out,))\n      self.assertIn(\n        layer_params['kernel'].shape[0], [channels_in, channels_out]\n      )\n      self.assertEqual(layer_params['kernel'].shape[1], channels_out)\n\n  def test_rnn_with_spatial_dimensions(self):\n    batch_size = 10\n    seq_len = 40\n    kernel_size = (3, 3)\n    image_size = (32, 32)\n    channels_in = 5\n    channels_out = 15\n\n    rnn = nn.RNN(\n      nn.ConvLSTMCell(channels_out, kernel_size),\n    )\n\n    xs = jnp.ones((batch_size, seq_len, *image_size, channels_in))\n    variables = rnn.init(jax.random.key(0), xs)\n\n    ys: jnp.ndarray\n    carry, ys = rnn.apply(variables, xs, return_carry=True)\n\n    # carry state should not be zeros after apply\n    for leaf in jax.tree_util.tree_leaves(carry):\n      assert not np.allclose(leaf, jnp.zeros_like(leaf))\n      self.assertEqual(leaf.shape[:-1], (batch_size, *image_size))\n      self.assertIn(leaf.shape[-1], [channels_in, channels_out])\n\n    self.assertEqual(ys.shape, (batch_size, seq_len, *image_size, channels_out))\n\n    for layer_params in variables['params']['cell'].values():\n      if 'bias' in layer_params:\n        self.assertEqual(layer_params['bias'].shape, (channels_out * 4,))\n      self.assertIn(\n        layer_params['kernel'].shape[2],\n        [channels_in, channels_out, channels_out * 4],\n      )\n      self.assertEqual(layer_params['kernel'].shape[3], channels_out * 4)\n\n  def test_numerical_equivalence(self):\n    batch_size = 3\n    seq_len = 4\n    channels_in = 5\n    channels_out = 6\n\n    rnn = nn.RNN(nn.LSTMCell(channels_out), return_carry=True)\n\n    xs = jnp.ones((batch_size, seq_len, channels_in))\n    ys: jnp.ndarray\n    (carry, ys), variables = rnn.init_with_output(jax.random.key(0), xs)\n\n    cell_carry = rnn.cell.initialize_carry(jax.random.key(0), xs[:, 0].shape)\n    cell_params = variables['params']['cell']\n\n    for i in range(seq_len):\n      cell_carry, y = rnn.cell.apply(\n        {'params': cell_params}, cell_carry, xs[:, i, :]\n      )\n      np.testing.assert_allclose(y, ys[:, i, :], rtol=1e-5)\n\n    np.testing.assert_allclose(cell_carry, carry, rtol=1e-5)\n\n  def test_numerical_equivalence_with_mask(self):\n    batch_size = 3\n    seq_len = 4\n    channels_in = 5\n    channels_out = 6\n\n    key = jax.random.key(0)\n    seq_lengths = jax.random.randint(\n      key, (batch_size,), minval=1, maxval=seq_len + 1\n    )\n\n    rnn = nn.RNN(nn.LSTMCell(channels_out), return_carry=True)\n\n    xs = jnp.ones((batch_size, seq_len, channels_in))\n    ys: jnp.ndarray\n    (carry, ys), variables = rnn.init_with_output(\n      jax.random.key(0), xs, seq_lengths=seq_lengths\n    )\n\n    cell_carry = rnn.cell.initialize_carry(jax.random.key(0), xs[:, 0].shape)\n    cell_params = variables['params']['cell']\n    carries = []\n\n    for i in range(seq_len):\n      cell_carry, y = rnn.cell.apply(\n        {'params': cell_params}, cell_carry, xs[:, i, :]\n      )\n      np.testing.assert_allclose(y, ys[:, i, :], rtol=1e-5)\n      carries.append(cell_carry)\n\n    for batch_idx, length in enumerate(seq_lengths):\n      t = int(length) - 1\n      for carries_t_, carry_ in zip(carries[t], carry):\n        np.testing.assert_allclose(\n          carries_t_[batch_idx], carry_[batch_idx], rtol=1e-5\n        )\n\n  def test_numerical_equivalence_single_batch(self):\n    batch_size = 3\n    seq_len = 4\n    channels_in = 5\n    channels_out = 6\n\n    rnn = nn.RNN(nn.LSTMCell(channels_out), return_carry=True)\n\n    xs = jnp.ones((batch_size, seq_len, channels_in))\n    ys: jnp.ndarray\n    (carry, ys), variables = rnn.init_with_output(jax.random.key(0), xs)\n\n    cell_params = variables['params']['cell']\n\n    for batch_idx in range(batch_size):\n      cell_carry = rnn.cell.initialize_carry(jax.random.key(0), xs[:1, 0].shape)\n\n      for i in range(seq_len):\n        cell_carry, y = rnn.cell.apply(\n          {'params': cell_params}, cell_carry, xs[batch_idx, i, :][None]\n        )\n        np.testing.assert_allclose(y[0], ys[batch_idx, i, :], rtol=1e-6)\n\n      carry_i = jax.tree_util.tree_map(\n          lambda x: x[batch_idx : batch_idx + 1], carry\n      )\n      np.testing.assert_allclose(cell_carry, carry_i, rtol=1e-6)\n\n  def test_numerical_equivalence_single_batch_nn_scan(self):\n    batch_size = 3\n    seq_len = 4\n    channels_in = 5\n    channels_out = 6\n\n    cell: nn.LSTMCell = nn.LSTMCell(channels_out)\n    rnn: nn.LSTMCell = nn.scan(\n      nn.LSTMCell,\n      in_axes=1,\n      out_axes=1,\n      variable_broadcast='params',\n      split_rngs={'params': False},\n    )(channels_out)\n\n    xs = jnp.ones((batch_size, seq_len, channels_in))\n    carry = rnn.initialize_carry(jax.random.key(0), xs[:, 0].shape)\n    ys: jnp.ndarray\n    (carry, ys), variables = rnn.init_with_output(jax.random.key(0), carry, xs)\n\n    cell_params = variables['params']\n\n    for batch_idx in range(batch_size):\n      cell_carry = cell.initialize_carry(jax.random.key(0), xs[:1, 0].shape)\n\n      for i in range(seq_len):\n        cell_carry, y = cell.apply(\n          {'params': cell_params},\n          cell_carry,\n          xs[batch_idx : batch_idx + 1, i, :],\n        )\n        np.testing.assert_allclose(y[0], ys[batch_idx, i, :], rtol=1e-5)\n\n      carry_i = jax.tree_util.tree_map(\n          lambda x: x[batch_idx : batch_idx + 1], carry\n      )\n      np.testing.assert_allclose(cell_carry, carry_i, rtol=1e-5)\n\n  def test_numerical_equivalence_single_batch_jax_scan(self):\n    batch_size = 3\n    seq_len = 4\n    channels_in = 5\n    channels_out = 6\n\n    xs = jax.random.uniform(\n      jax.random.key(0), (batch_size, seq_len, channels_in)\n    )\n    cell: nn.LSTMCell = nn.LSTMCell(channels_out)\n    carry = cell.initialize_carry(jax.random.key(0), xs[:, 0].shape)\n    variables = cell.init(jax.random.key(0), carry, xs[:, 0])\n    cell_params = variables['params']\n\n    def scan_fn(carry, x):\n      return cell.apply({'params': cell_params}, carry, x)\n\n    ys: jnp.ndarray\n    carry, ys = jax.lax.scan(scan_fn, carry, xs.swapaxes(0, 1))\n    ys = ys.swapaxes(0, 1)\n\n    cell_carry = cell.initialize_carry(jax.random.key(0), xs[:, 0].shape)\n\n    for i in range(seq_len):\n      cell_carry, y = cell.apply(\n        {'params': cell_params}, cell_carry, xs[:, i, :]\n      )\n      np.testing.assert_allclose(y, ys[:, i, :], rtol=1e-4)\n\n    np.testing.assert_allclose(cell_carry, carry, rtol=1e-4)\n\n  def test_reverse(self):\n    batch_size = 3\n    seq_len = 4\n    channels_in = 5\n    channels_out = 6\n\n    rnn = nn.RNN(nn.LSTMCell(channels_out), return_carry=True, reverse=True)\n\n    xs = jnp.ones((batch_size, seq_len, channels_in))\n    ys: jnp.ndarray\n    (carry, ys), variables = rnn.init_with_output(jax.random.key(0), xs)\n\n    cell_params = variables['params']['cell']\n\n    for batch_idx in range(batch_size):\n      cell_carry = rnn.cell.initialize_carry(jax.random.key(0), xs[:1, 0].shape)\n\n      for i in range(seq_len):\n        cell_carry, y = rnn.cell.apply(\n          {'params': cell_params},\n          cell_carry,\n          xs[batch_idx, seq_len - i - 1, :][None],\n        )\n        np.testing.assert_allclose(y[0], ys[batch_idx, i, :], rtol=1e-5)\n\n      np.testing.assert_allclose(\n          cell_carry,\n          jax.tree_util.tree_map(lambda x: x[batch_idx : batch_idx + 1], carry),\n          rtol=1e-5,\n      )\n\n  def test_reverse_but_keep_order(self):\n    batch_size = 3\n    seq_len = 4\n    channels_in = 5\n    channels_out = 6\n\n    rnn = nn.RNN(\n      nn.LSTMCell(channels_out),\n      return_carry=True,\n      reverse=True,\n      keep_order=True,\n    )\n\n    xs = jnp.ones((batch_size, seq_len, channels_in))\n    ys: jnp.ndarray\n    (carry, ys), variables = rnn.init_with_output(jax.random.key(0), xs)\n\n    cell_params = variables['params']['cell']\n\n    for batch_idx in range(batch_size):\n      cell_carry = rnn.cell.initialize_carry(jax.random.key(0), xs[:1, 0].shape)\n\n      for i in range(seq_len):\n        cell_carry, y = rnn.cell.apply(\n          {'params': cell_params},\n          cell_carry,\n          xs[batch_idx, seq_len - i - 1, :][None],\n        )\n        np.testing.assert_allclose(\n          y[0], ys[batch_idx, seq_len - i - 1, :], rtol=1e-5\n        )\n\n      np.testing.assert_allclose(\n          cell_carry,\n          jax.tree_util.tree_map(lambda x: x[batch_idx : batch_idx + 1], carry),\n          rtol=1e-5,\n      )\n\n  def test_flip_sequence(self):\n    x = jnp.arange(2 * 5).reshape((2, 5))\n    seq_lengths = jnp.array([4, 2])\n\n    flipped = flip_sequences(x, seq_lengths, num_batch_dims=1, time_major=False)\n\n    self.assertEqual(flipped.shape, (2, 5))\n    np.testing.assert_allclose(flipped[0, :4], [3, 2, 1, 0])\n    np.testing.assert_allclose(flipped[1, :2], [6, 5])\n\n  def test_flip_sequence_more_feature_dims(self):\n    x = jnp.arange(2 * 5 * 3).reshape((2, 5, 3))\n    seq_lengths = jnp.array([4, 2])\n\n    flipped = flip_sequences(x, seq_lengths, num_batch_dims=1, time_major=False)\n\n    self.assertEqual(flipped.shape, (2, 5, 3))\n    np.testing.assert_allclose(flipped[0, :4], x[0, :4][::-1])\n    np.testing.assert_allclose(flipped[1, :2], x[1, :2][::-1])\n\n  def test_flip_sequence_time_major(self):\n    x = jnp.arange(2 * 5).reshape((5, 2))\n    seq_lengths = jnp.array([4, 2])\n\n    flipped = flip_sequences(x, seq_lengths, num_batch_dims=1, time_major=True)\n\n    self.assertEqual(flipped.shape, (5, 2))\n    np.testing.assert_allclose(flipped[:4, 0], x[:4, 0][::-1])\n    np.testing.assert_allclose(flipped[:2, 1], x[:2, 1][::-1])\n\n  def test_flip_sequence_time_major_more_feature_dims(self):\n    x = jnp.arange(2 * 5 * 3).reshape((5, 2, 3))\n    seq_lengths = jnp.array([4, 2])\n\n    flipped = flip_sequences(x, seq_lengths, num_batch_dims=1, time_major=True)\n\n    self.assertEqual(flipped.shape, (5, 2, 3))\n    np.testing.assert_allclose(flipped[:4, 0], x[:4, 0][::-1])\n    np.testing.assert_allclose(flipped[:2, 1], x[:2, 1][::-1])\n\n  def test_basic_seq_lengths(self):\n    x = jnp.ones((2, 10, 6))\n    lstm = nn.RNN(nn.LSTMCell(265))\n    variables = lstm.init(jax.random.key(0), x)\n    y = lstm.apply(variables, x, seq_lengths=jnp.array([5, 5]))\n\n\nclass BidirectionalTest(absltest.TestCase):\n  def test_bidirectional(self):\n    batch_size = 3\n    seq_len = 4\n    channels_in = 5\n    channels_out = 6\n\n    bdirectional = nn.Bidirectional(\n      nn.RNN(nn.LSTMCell(channels_out)), nn.RNN(nn.LSTMCell(channels_out))\n    )\n\n    xs = jnp.ones((batch_size, seq_len, channels_in))\n    ys: jnp.ndarray\n    ys, variables = bdirectional.init_with_output(jax.random.key(0), xs)\n\n    self.assertEqual(ys.shape, (batch_size, seq_len, channels_out * 2))\n\n  def test_shared_cell(self):\n    batch_size = 3\n    seq_len = 4\n    channels_in = 5\n    channels_out = 6\n\n    cell = nn.LSTMCell(channels_out)\n    bdirectional = nn.Bidirectional(nn.RNN(cell), nn.RNN(cell))\n\n    xs = jnp.ones((batch_size, seq_len, channels_in))\n    ys: jnp.ndarray\n    ys, variables = bdirectional.init_with_output(jax.random.key(0), xs)\n\n    self.assertEqual(ys.shape, (batch_size, seq_len, channels_out * 2))\n\n  def test_custom_merge_fn(self):\n    batch_size = 3\n    seq_len = 4\n    channels_in = 5\n    channels_out = 6\n\n    bdirectional = nn.Bidirectional(\n      nn.RNN(nn.LSTMCell(channels_out)),\n      nn.RNN(nn.LSTMCell(channels_out)),\n      merge_fn=lambda x, y: x + y,\n    )\n\n    xs = jnp.ones((batch_size, seq_len, channels_in))\n    ys: jnp.ndarray\n    ys, variables = bdirectional.init_with_output(jax.random.key(0), xs)\n\n    self.assertEqual(ys.shape, (batch_size, seq_len, channels_out))\n\n  def test_return_carry(self):\n    batch_size = 3\n    seq_len = 4\n    channels_in = 5\n    channels_out = 6\n\n    bdirectional = nn.Bidirectional(\n      nn.RNN(nn.LSTMCell(channels_out)),\n      nn.RNN(nn.LSTMCell(channels_out)),\n      return_carry=True,\n    )\n\n    xs = jnp.ones((batch_size, seq_len, channels_in))\n    ys: jnp.ndarray\n    (carry, ys), variables = bdirectional.init_with_output(\n      jax.random.key(0), xs\n    )\n    carry_forward, carry_backward = carry\n\n    self.assertEqual(ys.shape, (batch_size, seq_len, channels_out * 2))\n    self.assertEqual(\n        jax.tree_util.tree_map(jnp.shape, carry_forward),\n        ((batch_size, channels_out), (batch_size, channels_out)),\n    )\n    self.assertEqual(\n        jax.tree_util.tree_map(jnp.shape, carry_backward),\n        ((batch_size, channels_out), (batch_size, channels_out)),\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/linen_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.linen.\"\"\"\n\nimport copy\nimport functools\nfrom typing import Any\n\nfrom absl.testing import absltest, parameterized\nfrom flax import ids\nfrom flax import linen as nn\nfrom flax.linen import fp8_ops\nfrom flax.training import train_state\nimport jax\nfrom jax import random\nimport jax.numpy as jnp\nimport numpy as np\nimport optax\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\ndef check_eq(xs, ys):\n  return jax.tree_util.tree_all(\n    jax.tree_util.tree_map(np.testing.assert_allclose, xs, ys)\n  )\n\n\nclass PoolTest(parameterized.TestCase):\n  def test_pool_custom_reduce(self):\n    x = jnp.full((1, 3, 3, 1), 2.0)\n    mul_reduce = lambda x, y: x * y\n    y = nn.pooling.pool(x, 1.0, mul_reduce, (2, 2), (1, 1), 'VALID')\n    np.testing.assert_allclose(y, np.full((1, 2, 2, 1), 2.0**4))\n\n  @parameterized.parameters(\n    {'count_include_pad': True}, {'count_include_pad': False}\n  )\n  def test_avg_pool(self, count_include_pad):\n    x = jnp.full((1, 3, 3, 1), 2.0)\n    pool = lambda x: nn.avg_pool(x, (2, 2), count_include_pad=count_include_pad)\n    y = pool(x)\n    np.testing.assert_allclose(y, np.full((1, 2, 2, 1), 2.0))\n    y_grad = jax.grad(lambda x: pool(x).sum())(x)\n    expected_grad = jnp.array(\n      [\n        [0.25, 0.5, 0.25],\n        [0.5, 1.0, 0.5],\n        [0.25, 0.5, 0.25],\n      ]\n    ).reshape((1, 3, 3, 1))\n    np.testing.assert_allclose(y_grad, expected_grad)\n\n  @parameterized.parameters(\n    {'count_include_pad': True}, {'count_include_pad': False}\n  )\n  def test_avg_pool_no_batch(self, count_include_pad):\n    x = jnp.full((3, 3, 1), 2.0)\n    pool = lambda x: nn.avg_pool(x, (2, 2), count_include_pad=count_include_pad)\n    y = pool(x)\n    np.testing.assert_allclose(y, np.full((2, 2, 1), 2.0))\n    y_grad = jax.grad(lambda x: pool(x).sum())(x)\n    expected_grad = jnp.array(\n      [\n        [0.25, 0.5, 0.25],\n        [0.5, 1.0, 0.5],\n        [0.25, 0.5, 0.25],\n      ]\n    ).reshape((3, 3, 1))\n    np.testing.assert_allclose(y_grad, expected_grad)\n\n  def test_max_pool(self):\n    x = jnp.arange(9).reshape((1, 3, 3, 1)).astype(jnp.float32)\n    pool = lambda x: nn.max_pool(x, (2, 2))\n    expected_y = jnp.array(\n      [\n        [4.0, 5.0],\n        [7.0, 8.0],\n      ]\n    ).reshape((1, 2, 2, 1))\n    y = pool(x)\n    np.testing.assert_allclose(y, expected_y)\n    y_grad = jax.grad(lambda x: pool(x).sum())(x)\n    expected_grad = jnp.array(\n      [\n        [0.0, 0.0, 0.0],\n        [0.0, 1.0, 1.0],\n        [0.0, 1.0, 1.0],\n      ]\n    ).reshape((1, 3, 3, 1))\n    np.testing.assert_allclose(y_grad, expected_grad)\n\n  @parameterized.parameters(\n    {'count_include_pad': True}, {'count_include_pad': False}\n  )\n  def test_avg_pool_padding_same(self, count_include_pad):\n    x = jnp.array([1.0, 2.0, 3.0, 4.0]).reshape((1, 2, 2, 1))\n    pool = lambda x: nn.avg_pool(\n      x, (2, 2), padding='SAME', count_include_pad=count_include_pad\n    )\n    y = pool(x)\n    if count_include_pad:\n      expected_y = jnp.array([10.0 / 4, 6.0 / 4, 7.0 / 4, 4.0 / 4]).reshape(\n        (1, 2, 2, 1)\n      )\n    else:\n      expected_y = jnp.array([10.0 / 4, 6.0 / 2, 7.0 / 2, 4.0 / 1]).reshape(\n        (1, 2, 2, 1)\n      )\n    np.testing.assert_allclose(y, expected_y)\n\n  def test_pooling_variable_batch_dims(self):\n    x = jnp.zeros((1, 8, 32, 32, 3), dtype=jnp.float32)\n    y = nn.max_pool(x, (2, 2), (2, 2))\n\n    assert y.shape == (1, 8, 16, 16, 3)\n\n  def test_pooling_no_batch_dims(self):\n    x = jnp.zeros((32, 32, 3), dtype=jnp.float32)\n    y = nn.max_pool(x, (2, 2), (2, 2))\n\n    assert y.shape == (16, 16, 3)\n\n\nclass NormalizationTest(parameterized.TestCase):\n  def test_layer_norm_mask(self):\n    key = random.key(0)\n    keys = random.split(key)\n    x = random.normal(keys[0], (3, 4, 5))\n    m = random.choice(keys[1], 2, x.shape).astype(bool)\n    m = m.at[..., :2].set(True)  # guarantee at least 2 elements\n    x = jnp.where(m, x, jnp.nan)\n\n    module = nn.LayerNorm()\n    y, w = module.init_with_output(key, x, mask=m)\n\n    z = y.mean(-1, where=m)\n    np.testing.assert_allclose(z, 0, atol=1e-4)\n\n    z = y.var(-1, where=m)\n    np.testing.assert_allclose(z, 1, atol=1e-4)\n\n  def test_rms_norm_mask(self):\n    key = random.key(0)\n    keys = random.split(key)\n    x = random.normal(keys[0], (3, 4, 5))\n    m = random.choice(keys[1], 2, x.shape).astype(bool)\n    m = m.at[..., :1].set(True)  # guarantee at least 1 element\n    x = jnp.where(m, x, jnp.nan)\n\n    module = nn.RMSNorm()\n    y, w = module.init_with_output(key, x, mask=m)\n\n    z = np.square(y).mean(-1, where=m)\n    np.testing.assert_allclose(z, 1, atol=1e-4)\n\n  def test_group_norm_mask(self):\n    key = random.key(0)\n    keys = random.split(key)\n    x = random.normal(keys[0], (13, 3, 5, 7 * 11))\n    m = random.choice(keys[1], 2, x.shape).astype(bool)\n    m = m.at[..., :2].set(True)  # guarantee at least 2 elements\n    x = jnp.where(m, x, jnp.nan)\n\n    module = nn.GroupNorm(7, use_bias=False, use_scale=False)\n    y, w = module.init_with_output(key, x, mask=m)\n\n    yr = y.reshape((13, 3, 5, 7, 11))\n    mr = m.reshape((13, 3, 5, 7, 11))\n\n    axes = list(range(1, x.ndim - 1)) + [-1]\n\n    z = yr.mean(axes, where=mr)\n    np.testing.assert_allclose(z, 0, atol=1e-4)\n\n    z = yr.var(axes, where=mr)\n    np.testing.assert_allclose(z, 1, atol=1e-4)\n\n  @parameterized.parameters({'test_mask': True}, {'test_mask': False})\n  def test_batch_norm(self, test_mask):\n    rng = random.key(0)\n    key1, key2, key3 = random.split(rng, 3)\n    x = random.normal(key1, (4, 3, 2))\n    if test_mask:\n      m = random.randint(\n        key2, (4, 3, 1), minval=0, maxval=2, dtype=jnp.int32\n      ).astype(jnp.bool_)\n      x = jnp.where(m, x, jnp.ones_like(x) * jnp.nan)\n    else:\n      m = None\n    model_cls = nn.BatchNorm(momentum=0.9, use_running_average=False)\n    y, initial_params = model_cls.init_with_output(key3, x, mask=m)\n\n    mean = y.mean((0, 1), where=m)\n    var = y.var((0, 1), where=m)\n    np.testing.assert_allclose(mean, np.array([0.0, 0.0]), atol=1e-4)\n    np.testing.assert_allclose(var, np.array([1.0, 1.0]), rtol=1e-4)\n    _, vars_out = model_cls.apply(\n      initial_params, x, mutable=['batch_stats'], mask=m\n    )\n\n    ema = vars_out['batch_stats']\n    np.testing.assert_allclose(\n      ema['mean'], 0.1 * x.mean((0, 1), keepdims=False, where=m), atol=1e-4\n    )\n    np.testing.assert_allclose(\n      ema['var'],\n      0.9 + 0.1 * x.var((0, 1), keepdims=False, where=m),\n      rtol=1e-4,\n    )\n\n  @parameterized.parameters({'test_mask': True}, {'test_mask': False})\n  def test_batch_norm_complex(self, test_mask):\n    rng = random.key(0)\n    key1, key2, key3 = random.split(rng, 3)\n    x = random.normal(key1, (4, 3, 2), dtype=jnp.complex64)\n    if test_mask:\n      m = random.randint(\n        key2, (4, 3, 1), minval=0, maxval=2, dtype=jnp.int32\n      ).astype(jnp.bool_)\n      x = jnp.where(m, x, jnp.ones_like(x) * jnp.nan)\n    else:\n      m = None\n    model_cls = nn.BatchNorm(\n      momentum=0.9, use_running_average=False, dtype=jnp.complex64\n    )\n    y, initial_params = model_cls.init_with_output(key3, x, mask=m)\n\n    mean = y.mean((0, 1), where=m)\n    var = y.var((0, 1), where=m)\n    np.testing.assert_allclose(mean, np.array([0.0, 0.0]), atol=1e-4)\n    np.testing.assert_allclose(var, np.array([1.0, 1.0]), rtol=1e-4)\n    self.assertEqual(mean.dtype, jnp.complex64)\n\n    _, vars_out = model_cls.apply(\n      initial_params, x, mutable=['batch_stats'], mask=m\n    )\n\n    ema = vars_out['batch_stats']\n    np.testing.assert_allclose(\n      ema['mean'], 0.1 * x.mean((0, 1), keepdims=False, where=m), atol=1e-4\n    )\n    np.testing.assert_allclose(\n      ema['var'],\n      0.9 + 0.1 * x.var((0, 1), keepdims=False, where=m),\n      rtol=1e-4,\n    )\n\n  @parameterized.parameters(\n    {'reduction_axes': -1},\n    {'reduction_axes': 1},\n    {'reduction_axes': (1, 2)},\n    {'reduction_axes': (0, 1, 2)},\n    {'reduction_axes': -1, 'use_fast_variance': False},\n  )\n  def test_layer_norm(self, reduction_axes, use_fast_variance=True):\n    rng = random.key(0)\n    key1, key2 = random.split(rng)\n    e = 1e-5\n    x = random.normal(key1, (2, 3, 4))\n    if not use_fast_variance:\n      x += 1e6  # This blows up fast variance, but should work otherwise.\n    model_cls = nn.LayerNorm(\n      use_bias=False,\n      use_scale=False,\n      epsilon=e,\n      reduction_axes=reduction_axes,\n      use_fast_variance=use_fast_variance,\n    )\n    y, _ = model_cls.init_with_output(key2, x)\n    self.assertEqual(x.dtype, y.dtype)\n    self.assertEqual(x.shape, y.shape)\n    y_one_liner = (\n      x - x.mean(axis=reduction_axes, keepdims=True)\n    ) * jax.lax.rsqrt(x.var(axis=reduction_axes, keepdims=True) + e)\n    np.testing.assert_allclose(y_one_liner, y, atol=1e-3, rtol=1e-3)\n\n  @parameterized.parameters(\n    {'reduction_axes': -1}, {'reduction_axes': 1}, {'reduction_axes': (1, 2)}\n  )\n  def test_rms_norm(self, reduction_axes):\n    rng = random.key(0)\n    key1, key2 = random.split(rng)\n    e = 1e-5\n    x = random.normal(key1, (2, 3, 4))\n    model_cls = nn.RMSNorm(\n      use_scale=False, epsilon=e, reduction_axes=reduction_axes\n    )\n    y, _ = model_cls.init_with_output(key2, x)\n    self.assertEqual(x.dtype, y.dtype)\n    self.assertEqual(x.shape, y.shape)\n    y_one_liner = x * jax.lax.rsqrt(\n      jnp.mean(jax.lax.square(x), axis=reduction_axes, keepdims=True) + e\n    )\n    np.testing.assert_allclose(y_one_liner, y, atol=1e-4)\n\n  def test_group_norm(self):\n    rng = random.key(0)\n    key1, key2 = random.split(rng)\n    e = 1e-5\n    x = random.normal(key1, (2, 5, 4, 4, 32))\n    model_cls = nn.GroupNorm(\n      num_groups=2, use_bias=False, use_scale=False, epsilon=e\n    )\n\n    y, _ = model_cls.init_with_output(key2, x)\n    self.assertEqual(x.dtype, y.dtype)\n    self.assertEqual(x.shape, y.shape)\n\n    x_gr = x.reshape([2, 5, 4, 4, 2, 16])\n    y_test = (\n      x_gr - x_gr.mean(axis=[1, 2, 3, 5], keepdims=True)\n    ) * jax.lax.rsqrt(x_gr.var(axis=[1, 2, 3, 5], keepdims=True) + e)\n    y_test = y_test.reshape([2, 5, 4, 4, 32])\n\n    np.testing.assert_allclose(y_test, y, atol=1e-4)\n\n  def test_group_norm_unbatched(self):\n    rng = random.key(0)\n    key1, key2 = random.split(rng)\n    e = 1e-5\n    x = random.normal(key1, (2, 5, 4, 4, 32))\n    model_cls = nn.GroupNorm(\n        num_groups=2,\n        use_bias=False,\n        use_scale=False,\n        epsilon=e,\n        reduction_axes=(0, 1, 3, 4),\n    )\n\n    y, _ = model_cls.init_with_output(key2, x)\n    self.assertEqual(x.dtype, y.dtype)\n    self.assertEqual(x.shape, y.shape)\n\n    x_gr = x.reshape([2, 5, 4, 4, 2, 16])\n    y_test = (\n        x_gr - x_gr.mean(axis=[0, 1, 3, 5], keepdims=True)\n    ) * jax.lax.rsqrt(x_gr.var(axis=[0, 1, 3, 5], keepdims=True) + e)\n    y_test = y_test.reshape([2, 5, 4, 4, 32])\n\n    np.testing.assert_allclose(y_test, y, atol=1e-4)\n\n  def test_group_norm_batched(self):\n    rng = random.key(0)\n    key1, key2 = random.split(rng)\n    e = 1e-5\n    x = random.normal(key1, (3, 4, 32))\n    model_cls = nn.GroupNorm(\n        num_groups=2,\n        use_bias=False,\n        use_scale=False,\n        epsilon=e,\n        reduction_axes=(-3, -2, -1),\n    )\n\n    y, _ = model_cls.init_with_output(key2, x)\n    self.assertEqual(x.dtype, y.dtype)\n    self.assertEqual(x.shape, y.shape)\n\n    x_stacked = jnp.stack([x] * 5)\n    y_stacked = model_cls.apply({}, x_stacked)\n\n    np.testing.assert_allclose(y, y_stacked[0, ...], atol=1e-4)\n\n    x_gr = x_stacked.reshape([5, 3, 4, 2, 16])\n    y_test = (x_gr - x_gr.mean(axis=[1, 2, 4], keepdims=True)) * jax.lax.rsqrt(\n        x_gr.var(axis=[1, 2, 4], keepdims=True) + e\n    )\n    y_test = y_test.reshape([5, 3, 4, 32])\n\n    np.testing.assert_allclose(y_test, y_stacked, atol=1e-4)\n\n  def test_group_norm_raises(self):\n    rng = random.key(0)\n    key1, key2 = random.split(rng)\n    e = 1e-5\n    x = random.normal(key1, (2, 5, 4, 4, 32))\n    model_cls = nn.GroupNorm(\n        num_groups=3, use_bias=False, use_scale=False, epsilon=e\n    )\n\n    with self.assertRaises(ValueError):\n      model_cls.init_with_output(key2, x)\n\n  def test_group_norm_raises_incorrect_reduction_axes(self):\n    rng = random.key(0)\n    key1, key2 = random.split(rng)\n    e = 1e-5\n    x = random.normal(key1, (2, 5, 4, 4, 32))\n    model_cls = nn.GroupNorm(\n        num_groups=3,\n        use_bias=False,\n        use_scale=False,\n        epsilon=e,\n        reduction_axes=(0, 1, 2, 3),\n    )\n\n    with self.assertRaises(ValueError):\n      model_cls.init_with_output(key2, x)\n\n  def test_batch_norm_multi_init(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        norm = nn.BatchNorm(\n          name='norm',\n          use_running_average=False,\n          axis_name='batch',\n        )\n        x = norm(x)\n        return x, norm(x)\n\n    key = random.key(0)\n    model = Foo()\n    x = random.normal(random.key(1), (2, 4))\n    (y1, y2), _ = model.init_with_output(key, x)\n    np.testing.assert_allclose(y1, y2, rtol=0.005)\n\n  @parameterized.parameters(\n    {'feature_axes': -1},\n    {'feature_axes': (1, 2)},\n    {'feature_axes': (1, 2, 3)},\n    {'feature_axes': -1, 'use_fast_variance': False},\n  )\n  def test_instance_norm(self, feature_axes, use_fast_variance=True):\n    rng = random.key(0)\n    key1, key2 = random.split(rng)\n    e = 1e-5\n    x = random.normal(key1, (2, 3, 4, 5))\n    if not use_fast_variance:\n      x += 1e4  # This blows up fast variance, but should work otherwise.\n    model_cls = nn.InstanceNorm(\n      use_bias=False,\n      use_scale=False,\n      epsilon=e,\n      feature_axes=feature_axes,\n      use_fast_variance=use_fast_variance,\n    )\n    y, _ = model_cls.init_with_output(key2, x)\n    self.assertEqual(x.dtype, y.dtype)\n    self.assertEqual(x.shape, y.shape)\n\n    canonicalized_feature_axes = [\n      i if i >= 0 else (x.ndim + i)\n      for i in (\n        feature_axes if isinstance(feature_axes, tuple) else (feature_axes,)\n      )\n    ]\n    reduction_axes = [\n      i for i in range(1, x.ndim) if i not in canonicalized_feature_axes\n    ]\n    y_one_liner = (\n      x - x.mean(axis=reduction_axes, keepdims=True)\n    ) * jax.lax.rsqrt(x.var(axis=reduction_axes, keepdims=True) + e)\n\n    np.testing.assert_allclose(y_one_liner, y, atol=1e-6)\n\n  @parameterized.parameters(\n    {'feature_axes': 0},\n    {'feature_axes': -4},\n    {'feature_axes': (0, 3)},\n    {'feature_axes': (2, -4)},\n  )\n  def test_instance_norm_raise_error(self, feature_axes):\n    with self.assertRaisesRegex(\n      ValueError,\n      'The channel axes cannot include the leading dimension '\n      'as this is assumed to be the batch axis.',\n    ):\n      x = jax.random.normal(jax.random.key(0), (2, 3, 4, 5))\n      layer = nn.InstanceNorm(feature_axes=feature_axes)\n      _ = layer.init(jax.random.key(1), x)\n\n  @parameterized.parameters(\n    {\n      'layer1': nn.LayerNorm(feature_axes=(1, 2)),\n      'layer2': nn.InstanceNorm(feature_axes=(1, 2)),\n    },\n    {\n      'layer1': nn.LayerNorm(reduction_axes=(1, 2), feature_axes=-1),\n      'layer2': nn.InstanceNorm(feature_axes=-1),\n    },\n    {\n      'layer1': nn.LayerNorm(\n        reduction_axes=-2,\n        feature_axes=(1, 3),\n        bias_init=nn.initializers.uniform(),\n        scale_init=nn.initializers.uniform(),\n      ),\n      'layer2': nn.InstanceNorm(\n        feature_axes=(1, -1),\n        bias_init=nn.initializers.uniform(),\n        scale_init=nn.initializers.uniform(),\n      ),\n    },\n    {\n      'layer1': nn.LayerNorm(\n        reduction_axes=(1, 2, 3),\n        bias_init=nn.initializers.uniform(),\n        scale_init=nn.initializers.uniform(),\n      ),\n      'layer2': nn.GroupNorm(\n        num_groups=1,\n        bias_init=nn.initializers.uniform(),\n        scale_init=nn.initializers.uniform()\n      ),\n    },\n    {\n      'layer1': nn.InstanceNorm(\n        bias_init=nn.initializers.uniform(),\n        scale_init=nn.initializers.uniform(),\n      ),\n      'layer2': nn.GroupNorm(\n        num_groups=None,\n        group_size=1,\n        bias_init=nn.initializers.uniform(),\n        scale_init=nn.initializers.uniform(),\n      ),\n    },\n  )\n  def test_normalization_equivalence(self, layer1, layer2):\n    x = jax.random.normal(jax.random.key(0), (2, 3, 4, 5))\n    layer1_variables = layer1.init(jax.random.key(1), x)\n    layer2_variables = layer2.init(jax.random.key(1), x)\n    self.assertTrue(\n        jax.tree_util.tree_all(\n            jax.tree_util.tree_map(\n                lambda v1, v2: (v1 == v2).all(),\n                layer1_variables,\n                layer2_variables,\n            )\n        )\n    )\n\n    layer1_y = layer1.apply(layer1_variables, x)\n    layer2_y = layer2.apply(layer2_variables, x)\n    np.testing.assert_allclose(layer1_y, layer2_y, atol=1e-7)\n\n  @parameterized.parameters(\n    {\n      'model_index': 0,\n      'key_paths': {'Dense_1/kernel/u', 'Dense_1/kernel/sigma'},\n    },\n    {\n      'model_index': 1,\n      'key_paths': {'Conv_0/kernel/u', 'Conv_0/kernel/sigma'},\n    },\n    {\n      'model_index': 2,\n      'key_paths': {\n        'MultiHeadDotProductAttention_0/key/bias/u',\n        'MultiHeadDotProductAttention_0/key/kernel/u',\n        'MultiHeadDotProductAttention_0/out/kernel/u',\n        'MultiHeadDotProductAttention_0/query/bias/u',\n        'MultiHeadDotProductAttention_0/query/kernel/u',\n        'MultiHeadDotProductAttention_0/value/bias/u',\n        'MultiHeadDotProductAttention_0/value/kernel/u',\n        'MultiHeadDotProductAttention_0/key/bias/sigma',\n        'MultiHeadDotProductAttention_0/key/kernel/sigma',\n        'MultiHeadDotProductAttention_0/out/kernel/sigma',\n        'MultiHeadDotProductAttention_0/query/bias/sigma',\n        'MultiHeadDotProductAttention_0/query/kernel/sigma',\n        'MultiHeadDotProductAttention_0/value/bias/sigma',\n        'MultiHeadDotProductAttention_0/value/kernel/sigma',\n      },\n    },\n  )\n  def test_spectral_norm_train(self, model_index, key_paths):\n    class FooDense(nn.Module):\n      @nn.compact\n      def __call__(self, x, train):\n        x = nn.Dense(8)(x)\n        x = nn.SpectralNorm(nn.Dense(6))(x, update_stats=train)\n        x = nn.Dense(4)(x)\n        return x\n\n    class FooConv(nn.Module):\n      @nn.compact\n      def __call__(self, x, train):\n        x = nn.Dense(9)(x)\n        x = x.reshape((1, 3, 3))\n        x = nn.SpectralNorm(nn.Conv(2, kernel_size=(2, 2)))(\n          x, update_stats=train\n        )\n        x = x.reshape(1, -1)\n        x = nn.Dense(4)(x)\n        return x\n\n    class FooAttention(nn.Module):\n      @nn.compact\n      def __call__(self, x, train):\n        a = nn.Dense(4)(x)\n        b = nn.Dense(4)(x)\n        x = nn.SpectralNorm(nn.attention.MultiHeadDotProductAttention(4))(\n          a, b, update_stats=train\n        )\n        x = nn.Dense(4)(x)\n        return x\n\n    key1, key2, key3 = random.split(random.PRNGKey(0), 3)\n    x = random.normal(key1, (1, 4))\n    y = random.normal(key2, (1, 4))\n\n    model_cls = (FooDense, FooConv, FooAttention)[model_index]\n    variables = model_cls().init(key3, x, train=False)\n    params, batch_stats = variables['params'], variables['batch_stats']\n    self.assertEqual(key_paths, batch_stats['SpectralNorm_0'].keys())\n\n    class TrainState(train_state.TrainState):\n      batch_stats: Any\n\n    state = TrainState.create(\n      apply_fn=model_cls().apply,\n      params=params,\n      batch_stats=batch_stats,\n      tx=optax.adam(1e-3),\n    )\n\n    @jax.jit\n    def train_step(state, batch):\n      def loss_fn(params):\n        logits, updates = state.apply_fn(\n          {'params': params, 'batch_stats': state.batch_stats},\n          x=batch['image'],\n          train=True,\n          mutable=['batch_stats'],\n        )\n        loss = jnp.mean(\n          optax.l2_loss(predictions=logits, targets=batch['label'])\n        )\n        return loss, updates\n\n      grad_fn = jax.value_and_grad(loss_fn, has_aux=True)\n      (loss, updates), grads = grad_fn(state.params)\n      state = state.apply_gradients(grads=grads)\n      state = state.replace(batch_stats=updates['batch_stats'])\n      return state, loss\n\n    prev_loss = float('inf')\n    for _ in range(10):\n      state, loss = train_step(state, {'image': x, 'label': y})\n      self.assertLess(loss, prev_loss)\n      prev_loss = loss\n\n  @parameterized.parameters(\n    {'n_steps': 1, 'update_stats': True, 'result': 4.0},\n    {'n_steps': 3, 'update_stats': True, 'result': 4.0},\n    {'n_steps': 10, 'update_stats': True, 'result': 4.0},\n    {'n_steps': 1, 'update_stats': False, 'result': 1.0},\n  )\n  def test_spectral_norm_sigma(self, n_steps, update_stats, result):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x, train):\n        x = nn.SpectralNorm(nn.Dense(8, use_bias=False), n_steps=n_steps)(\n          x, update_stats=train\n        )\n        return x\n\n    x = jnp.ones((1, 8))\n    model_cls = Foo()\n    variables = model_cls.init(random.PRNGKey(0), x, train=False)\n    params, batch_stats = variables['params'], variables['batch_stats']\n    params = jax.tree_util.tree_map(lambda x: 4 * jnp.eye(*x.shape), params)\n    _, updates = model_cls.apply(\n      {'params': params, 'batch_stats': batch_stats},\n      x=x,\n      train=update_stats,\n      mutable=True,\n    )\n    np.testing.assert_allclose(\n      updates['batch_stats']['SpectralNorm_0']['Dense_0/kernel/sigma'],\n      result,\n      atol=1e-3,\n    )\n\n  @parameterized.parameters(\n    {'error_on_non_matrix': True}, {'error_on_non_matrix': False}\n  )\n  def test_spectral_norm_3d_tensor(self, error_on_non_matrix):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x, train):\n        x = nn.SpectralNorm(\n          nn.DenseGeneral((3, 4), use_bias=False),\n          error_on_non_matrix=error_on_non_matrix,\n        )(x, update_stats=train)\n        return x\n\n    x = jnp.ones((1, 2))\n    model_cls = Foo()\n\n    if error_on_non_matrix:\n      with self.assertRaisesRegex(\n        ValueError, 'Input is 3D but error_on_non_matrix is True'\n      ):\n        _ = model_cls.init(random.PRNGKey(0), x, train=False)\n    else:\n      _ = model_cls.init(random.PRNGKey(0), x, train=False)\n\n  @parameterized.parameters(\n    {'feature_axes': -1, 'reduction_axes': 0, 'variable_filter': {'kernel'}},\n    {'feature_axes': 0, 'reduction_axes': 1, 'variable_filter': {'kernel'}},\n    {\n      'feature_axes': (0, 1),\n      'reduction_axes': (),\n      'variable_filter': {'kernel'},\n    },\n    {\n      'feature_axes': (),\n      'reduction_axes': (0, 1),\n      'variable_filter': {'kernel'},\n    },\n    {\n      'feature_axes': None,\n      'reduction_axes': (0, 1),\n      'variable_filter': {'kernel'},\n    },\n    {'feature_axes': 0, 'reduction_axes': (), 'variable_filter': {'bias'}},\n    {'feature_axes': (), 'reduction_axes': -1, 'variable_filter': {'bias'}},\n  )\n  def test_manual_weight_norm(\n    self, feature_axes, reduction_axes, variable_filter\n  ):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return nn.WeightNorm(\n          nn.Dense(2, bias_init=nn.initializers.normal()),\n          feature_axes=feature_axes,\n          variable_filter=variable_filter,\n        )(x)\n\n    key1, key2 = jax.random.split(jax.random.key(1))\n    x = jax.random.normal(key1, (1, 3))\n    module = Foo()\n    v = module.init(key2, x)\n    v = jax.tree_util.tree_map(lambda x: x + 0.5, v)\n    out = module.apply(v, x)\n\n    kernel = v['params']['Dense_0']['kernel']\n    if 'kernel' in variable_filter:\n      kernel /= jnp.sqrt(jnp.sum(kernel**2, axis=reduction_axes, keepdims=True))\n      kernel_scale = jnp.expand_dims(\n        v['params']['WeightNorm_0']['Dense_0/kernel/scale'],\n        axis=reduction_axes,\n      )\n    else:\n      kernel_scale = 1\n    bias = v['params']['Dense_0']['bias']\n    if 'bias' in variable_filter:\n      bias /= jnp.sqrt(jnp.sum(bias**2, axis=reduction_axes, keepdims=True))\n      bias_scale = jnp.expand_dims(\n        v['params']['WeightNorm_0']['Dense_0/bias/scale'], axis=reduction_axes\n      )\n    else:\n      bias_scale = 1\n    manual_out = jnp.dot(x, kernel_scale * kernel) + (\n      bias_scale * bias\n    ).reshape(1, -1)\n\n    self.assertTrue(jnp.allclose(out, manual_out))\n\n  @parameterized.parameters(\n    {\n      'variable_filters': ({}, None, {'kernel', 'bias'}, {'Bar'}),\n      'key_paths': {\n        'Bar_0/Baz_0/Dense_0/kernel/scale',\n        'Bar_0/Baz_0/Dense_0/bias/scale',\n        'Bar_0/Dense_0/kernel/scale',\n        'Bar_0/Dense_0/bias/scale',\n        'Bar_0/Baz_1/Dense_0/kernel/scale',\n        'Bar_0/Baz_1/Dense_0/bias/scale',\n        'Bar_0/Dense_1/kernel/scale',\n        'Bar_0/Dense_1/bias/scale',\n      },\n    },\n    {\n      'variable_filters': ({'kernel'},),\n      'key_paths': {\n        'Bar_0/Baz_0/Dense_0/kernel/scale',\n        'Bar_0/Dense_0/kernel/scale',\n        'Bar_0/Baz_1/Dense_0/kernel/scale',\n        'Bar_0/Dense_1/kernel/scale',\n      },\n    },\n    {\n      'variable_filters': ({'Baz', 'kernel'},),\n      'key_paths': {\n        'Bar_0/Baz_0/Dense_0/kernel/scale',\n        'Bar_0/Baz_0/Dense_0/bias/scale',\n        'Bar_0/Dense_0/kernel/scale',\n        'Bar_0/Baz_1/Dense_0/kernel/scale',\n        'Bar_0/Baz_1/Dense_0/bias/scale',\n        'Bar_0/Dense_1/kernel/scale',\n      },\n    },\n  )\n  def test_weight_norm_variable_filter(self, variable_filters, key_paths):\n    class Baz(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return nn.Dense(2)(x)\n\n    class Bar(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = Baz()(x)\n        x = nn.Dense(3)(x)\n        x = Baz()(x)\n        x = nn.Dense(3)(x)\n        return x\n\n    for variable_filter in variable_filters:\n\n      class Foo(nn.Module):\n        @nn.compact\n        def __call__(self, x):\n          return nn.WeightNorm(Bar(), variable_filter=variable_filter)(x)\n\n      v = Foo().init(jax.random.key(0), jnp.ones((1, 4)))\n      self.assertEqual(key_paths, v['params']['WeightNorm_0'].keys())\n\n  @parameterized.parameters(\n    {'model_index': 0, 'key_paths': {'Dense_1/kernel/scale'}},\n    {'model_index': 1, 'key_paths': {'Conv_0/kernel/scale'}},\n    {\n      'model_index': 2,\n      'key_paths': {\n        'MultiHeadDotProductAttention_0/key/kernel/scale',\n        'MultiHeadDotProductAttention_0/out/kernel/scale',\n        'MultiHeadDotProductAttention_0/query/kernel/scale',\n        'MultiHeadDotProductAttention_0/value/kernel/scale',\n      },\n    },\n  )\n  def test_weight_norm_train(self, model_index, key_paths):\n    class FooDense(nn.Module):\n      @nn.compact\n      def __call__(\n        self,\n        x,\n      ):\n        x = nn.Dense(8)(x)\n        x = nn.WeightNorm(nn.Dense(6))(x)\n        x = nn.Dense(4)(x)\n        return x\n\n    class FooConv(nn.Module):\n      @nn.compact\n      def __call__(\n        self,\n        x,\n      ):\n        x = nn.Dense(9)(x)\n        x = x.reshape((1, 3, 3))\n        x = nn.WeightNorm(nn.Conv(2, kernel_size=(2, 2)))(x)\n        x = x.reshape(1, -1)\n        x = nn.Dense(4)(x)\n        return x\n\n    class FooAttention(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        a = nn.Dense(4)(x)\n        b = nn.Dense(4)(x)\n        x = nn.WeightNorm(nn.attention.MultiHeadDotProductAttention(4))(a, b)\n        x = nn.Dense(4)(x)\n        return x\n\n    key1, key2, key3 = random.split(random.PRNGKey(0), 3)\n    x = random.normal(key1, (1, 4))\n    y = random.normal(key2, (1, 4))\n\n    model_cls = (FooDense, FooConv, FooAttention)[model_index]\n    params = model_cls().init(key3, x)['params']\n    self.assertEqual(key_paths, params['WeightNorm_0'].keys())\n\n    state = train_state.TrainState.create(\n      apply_fn=model_cls().apply,\n      params=params,\n      tx=optax.adam(1e-3),\n    )\n\n    @jax.jit\n    def train_step(state, batch):\n      def loss_fn(params):\n        logits = state.apply_fn(\n          {'params': params},\n          x=batch['image'],\n        )\n        loss = jnp.mean(\n          optax.l2_loss(predictions=logits, targets=batch['label'])\n        )\n        return loss\n\n      grad_fn = jax.value_and_grad(loss_fn)\n      loss, grads = grad_fn(state.params)\n      state = state.apply_gradients(grads=grads)\n      return state, loss\n\n    prev_loss = float('inf')\n    for _ in range(10):\n      state, loss = train_step(state, {'image': x, 'label': y})\n      self.assertLess(loss, prev_loss)\n      prev_loss = loss\n\n  def test_weight_norm_compatibility_with_partitioning(self):\n    replicated_module = nn.WeightNorm(nn.Dense(10))\n\n    @jax.jit\n    def _init_replicated(x):\n      return replicated_module.init(jax.random.key(0), x)\n\n    expected = _init_replicated(jnp.ones((10, 10)))\n\n    sharded_module = nn.WeightNorm(\n        nn.Dense(\n            10,\n            kernel_init=nn.with_partitioning(\n                nn.initializers.lecun_normal(), ('x',)\n            ),\n        )\n    )\n\n    @jax.jit\n    def _init_sharded(x):\n      return sharded_module.init(jax.random.key(0), x)\n\n    got = _init_sharded(jnp.ones((10, 10)))\n\n    def _strip_partitioning(x):\n      if isinstance(x, nn.Partitioned):\n        return x.value\n      return x\n\n    got = jax.tree.map(\n        _strip_partitioning,\n        got,\n        is_leaf=lambda x: isinstance(x, nn.Partitioned),\n    )\n\n    expected_scale = expected['params'].pop('layer_instance/kernel/scale')\n    # NOTE: `value` is from `nn.Partitioned.value`\n    got_scale = got['params'].pop('layer_instance/kernel/value/scale')\n    np.testing.assert_array_equal(got_scale, expected_scale)\n\n    # Compares the rest of PyTree nodes\n    jax.tree.map(lambda x, y: np.testing.assert_allclose(x, y), expected, got)\n\n\nclass StochasticTest(parameterized.TestCase):\n  def test_dropout(self):\n    rng = random.key(0)\n    key1, key2 = random.split(rng)\n    module = nn.Dropout(rate=0.5)\n    y1 = module.apply(\n      {}, jnp.ones((20, 20)), deterministic=False, rngs={'dropout': key1}\n    )\n    y2 = module.apply(\n      {}, jnp.ones((20, 20)), deterministic=False, rngs={'dropout': key2}\n    )\n    self.assertFalse(np.all(y1 == y2))\n\n    y1 = module.apply(\n      {}, jnp.ones((20, 20)), deterministic=True, rngs={'dropout': key1}\n    )\n    y2 = module.apply(\n      {}, jnp.ones((20, 20)), deterministic=True, rngs={'dropout': key2}\n    )\n    self.assertTrue(np.all(y1 == y2))\n\n  def test_dropout_rate_stats(self):\n    rootkey = random.key(0)\n    for rate in np.arange(0.1, 1.0, 0.1):\n      rootkey, subkey = random.split(rootkey)\n      module = nn.Dropout(rate=rate)\n      n_trials = 10\n      nonzero_counts = 0\n      for key in random.split(subkey, n_trials):\n        y = module.apply(\n          {}, jnp.ones((100, 100)), deterministic=False, rngs={'dropout': key}\n        )\n        nonzero_counts += np.sum(y > 0.0)\n      all_counts = np.prod((100, 100, n_trials))\n      frac = np.sum(nonzero_counts) / all_counts\n      keep_rate = 1.0 - rate\n      # just check within 4 sigma.\n      delta = 4 * np.sqrt(rate * keep_rate) / np.sqrt(all_counts)\n      self.assertTrue(keep_rate - delta < frac < keep_rate + delta)\n\n  def test_dropout_rate_limits(self):\n    rng = random.key(0)\n    key1, key2, key3 = random.split(rng, 3)\n    inputs = jnp.ones((20, 20))\n    d0 = nn.Dropout(rate=0.0)\n    y1 = d0.apply({}, inputs, deterministic=False, rngs={'dropout': key1})\n    np.testing.assert_array_equal(y1, inputs)\n    d1 = nn.Dropout(rate=1.0)\n    y2 = d1.apply({}, inputs, deterministic=False, rngs={'dropout': key2})\n    np.testing.assert_array_equal(y2, np.zeros_like(inputs))\n    # ensure gradient of rate==1.0 case is non-NaN\n    fn = lambda x, k: d1.apply({}, x, rngs={'dropout': k}, deterministic=False)\n    res = jax.grad(lambda x, k: jnp.sum(fn(x, k)))(inputs, key3)\n    self.assertFalse(np.isnan(res).any())\n\n  @parameterized.parameters(\n    {\n      'num_dims': 2,\n      'broadcast_dims': (1,),\n      'slice_fn': lambda out, i: out[i, :],\n      'summed_total': 2 * 10,\n    },\n    {\n      'num_dims': 2,\n      'broadcast_dims': (0,),\n      'slice_fn': lambda out, i: out[:, i],\n      'summed_total': 2 * 10,\n    },\n    {\n      'num_dims': 3,\n      'broadcast_dims': (1, 2),\n      'slice_fn': lambda out, i: out[i, :, :],\n      'summed_total': 2 * 10 * 10,\n    },\n    {\n      'num_dims': 3,\n      'broadcast_dims': (1,),\n      'slice_fn': lambda out, i, j: out[i, :, j],\n      'summed_total': 2 * 10,\n    },\n    {\n      'num_dims': 4,\n      'broadcast_dims': (0, 2, 3),\n      'slice_fn': lambda out, i: out[:, i, :, :],\n      'summed_total': 2 * 10 * 10 * 10,\n    },\n    {\n      'num_dims': 4,\n      'broadcast_dims': (0, 1),\n      'slice_fn': lambda out, i, j: out[:, :, i, j],\n      'summed_total': 2 * 10 * 10,\n    },\n    {\n      'num_dims': 4,\n      'broadcast_dims': (3,),\n      'slice_fn': lambda out, i, j, k: out[i, j, k, :],\n      'summed_total': 2 * 10,\n    },\n  )\n  def test_dropout_broadcast(\n    self, num_dims, broadcast_dims, slice_fn, summed_total\n  ):\n    module = nn.Dropout(\n      rate=0.5, broadcast_dims=broadcast_dims, deterministic=False\n    )\n    x = jnp.ones((10,) * num_dims)\n    out = module.apply({}, x, rngs={'dropout': random.key(0)})\n\n    for i in range(10):\n      if num_dims - len(broadcast_dims) >= 2:\n        for j in range(10):\n          if num_dims - len(broadcast_dims) >= 3:\n            for k in range(10):\n              self.assertTrue(slice_fn(out, i, j, k).sum() in (0, summed_total))\n          else:\n            self.assertTrue(slice_fn(out, i, j).sum() in (0, summed_total))\n      else:\n        self.assertTrue(slice_fn(out, i).sum() in (0, summed_total))\n\n  def test_dropout_manual_rng(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        key = self.make_rng('dropout')\n        x1 = nn.Dropout(rate=0.5, deterministic=False)(x, rng=key)\n        x2 = nn.Dropout(rate=0.5, deterministic=False)(x, rng=jax.random.clone(key))\n        return x1, x2\n\n    module = Foo()\n    x1, x2 = module.apply(\n      {}, jnp.ones((20, 20)), rngs={'dropout': random.key(0)}\n    )\n\n    np.testing.assert_array_equal(x1, x2)\n\n\n# TODO(flax-dev): add integration tests for RNN cells\nclass RecurrentTest(parameterized.TestCase):\n  def test_lstm(self):\n    lstm = nn.LSTMCell(features=4)\n    rng = random.key(0)\n    rng, key1, key2 = random.split(rng, 3)\n    x = random.normal(key1, (2, 3))\n    c0, h0 = lstm.initialize_carry(rng, x.shape)\n    self.assertEqual(c0.shape, (2, 4))\n    self.assertEqual(h0.shape, (2, 4))\n    (carry, y), initial_params = lstm.init_with_output(key2, (c0, h0), x)\n    self.assertEqual(carry[0].shape, (2, 4))\n    self.assertEqual(carry[1].shape, (2, 4))\n    np.testing.assert_allclose(y, carry[1])\n    param_shapes = jax.tree_util.tree_map(np.shape, initial_params['params'])\n    self.assertEqual(\n      param_shapes,\n      {\n        'ii': {'kernel': (3, 4)},\n        'if': {'kernel': (3, 4)},\n        'ig': {'kernel': (3, 4)},\n        'io': {'kernel': (3, 4)},\n        'hi': {'kernel': (4, 4), 'bias': (4,)},\n        'hf': {'kernel': (4, 4), 'bias': (4,)},\n        'hg': {'kernel': (4, 4), 'bias': (4,)},\n        'ho': {'kernel': (4, 4), 'bias': (4,)},\n      },\n    )\n\n  @parameterized.parameters(\n    {\n      'module_cls': nn.SimpleCell,\n      'expected_param_shapes': {\n        'i': {'kernel': (3, 4), 'bias': (4,)},\n        'h': {'kernel': (4, 4)},\n      },\n    },\n    {\n      'module_cls': nn.GRUCell,\n      'expected_param_shapes': {\n        'ir': {'kernel': (3, 4), 'bias': (4,)},\n        'iz': {'kernel': (3, 4), 'bias': (4,)},\n        'in': {'kernel': (3, 4), 'bias': (4,)},\n        'hr': {'kernel': (4, 4)},\n        'hz': {'kernel': (4, 4)},\n        'hn': {'kernel': (4, 4), 'bias': (4,)},\n      },\n    },\n    {\n      'module_cls': nn.MGUCell,\n      'expected_param_shapes': {\n        'if': {'kernel': (3, 4), 'bias': (4,)},\n        'in': {'kernel': (3, 4), 'bias': (4,)},\n        'hf': {'kernel': (4, 4)},\n        'hn': {'kernel': (4, 4), 'bias': (4,)},\n      },\n    },\n  )\n  def test_gated_units(self, module_cls, expected_param_shapes):\n    module = module_cls(features=4)\n    rng = random.key(0)\n    rng, key1, key2 = random.split(rng, 3)\n    x = random.normal(key1, (2, 3))\n    carry0 = module.initialize_carry(rng, x.shape)\n    self.assertEqual(carry0.shape, (2, 4))\n    (carry, y), initial_params = module.init_with_output(key2, carry0, x)\n    self.assertEqual(carry.shape, (2, 4))\n    np.testing.assert_allclose(y, carry)\n    param_shapes = jax.tree_util.tree_map(np.shape, initial_params['params'])\n    self.assertEqual(\n      param_shapes,\n      expected_param_shapes,\n    )\n    if module_cls == nn.MGUCell:\n      self.assertTrue(\n        (initial_params['params']['if']['bias'] == jnp.ones((4,))).all()\n      )\n      self.assertTrue(\n        (initial_params['params']['in']['bias'] == jnp.zeros((4,))).all()\n      )\n      self.assertTrue(\n        (initial_params['params']['hn']['bias'] == jnp.zeros((4,))).all()\n      )\n\n  @parameterized.parameters(\n    {'module_cls': nn.SimpleCell},\n    {'module_cls': nn.GRUCell},\n    {'module_cls': nn.MGUCell},\n  )\n  def test_complex_input_gated_units(self, module_cls):\n    module_instance = module_cls(features=4)\n    rng = random.key(0)\n    rng, key1, key2 = random.split(rng, 3)\n    x = random.normal(key1, (2, 3), dtype=jnp.complex64)\n    carry0 = module_instance.initialize_carry(rng, x.shape)\n    self.assertEqual(carry0.shape, (2, 4))\n    (carry, y), _ = module_instance.init_with_output(key2, carry0, x)\n    self.assertEqual(carry.dtype, jnp.complex64)\n    self.assertEqual(y.dtype, jnp.complex64)\n\n  def test_convlstm(self):\n    lstm = nn.ConvLSTMCell(features=6, kernel_size=(3, 3))\n    rng = random.key(0)\n    rng, key1, key2 = random.split(rng, 3)\n    x = random.normal(key1, (2, 4, 4, 3))\n    c0, h0 = lstm.initialize_carry(rng, x.shape)\n    self.assertEqual(c0.shape, (2, 4, 4, 6))\n    self.assertEqual(h0.shape, (2, 4, 4, 6))\n    (carry, y), initial_params = lstm.init_with_output(key2, (c0, h0), x)\n    self.assertEqual(carry[0].shape, (2, 4, 4, 6))\n    self.assertEqual(carry[1].shape, (2, 4, 4, 6))\n    np.testing.assert_allclose(y, carry[1])\n    param_shapes = jax.tree_util.tree_map(np.shape, initial_params['params'])\n    self.assertEqual(\n      param_shapes,\n      {\n        'hh': {'bias': (6 * 4,), 'kernel': (3, 3, 6, 6 * 4)},\n        'ih': {'bias': (6 * 4,), 'kernel': (3, 3, 3, 6 * 4)},\n      },\n    )\n\n  def test_optimized_lstm_cell_matches_regular(self):\n    # Create regular LSTMCell.\n    lstm = nn.LSTMCell(features=4)\n    rng = random.key(0)\n    rng, key1, key2 = random.split(rng, 3)\n    x = random.normal(key1, (2, 3))\n    c0, h0 = lstm.initialize_carry(rng, x.shape)\n    self.assertEqual(c0.shape, (2, 4))\n    self.assertEqual(h0.shape, (2, 4))\n    (_, y), lstm_params = lstm.init_with_output(key2, (c0, h0), x)\n\n    # Create OptimizedLSTMCell.\n    lstm_opt = nn.OptimizedLSTMCell(features=4)\n    rng = random.key(0)\n    rng, key1, key2 = random.split(rng, 3)\n    x = random.normal(key1, (2, 3))\n    c0, h0 = lstm_opt.initialize_carry(rng, x.shape)\n    self.assertEqual(c0.shape, (2, 4))\n    self.assertEqual(h0.shape, (2, 4))\n    (_, y_opt), lstm_opt_params = lstm_opt.init_with_output(key2, (c0, h0), x)\n\n    np.testing.assert_allclose(y, y_opt, rtol=1e-6)\n    check_eq(lstm_params, lstm_opt_params)\n\n  def test_mgu_reset_gate(self):\n    module = nn.MGUCell(features=4, reset_gate=False)\n    rng = random.key(0)\n    rng, key1, key2 = random.split(rng, 3)\n    x = random.normal(key1, (2, 3))\n    carry0 = module.initialize_carry(rng, x.shape)\n    (carry, y), v = module.init_with_output(key2, carry0, x)\n\n    self.assertIn('kernel', v['params']['hn'])\n    self.assertNotIn('bias', v['params']['hn'])\n\n    f = jax.nn.sigmoid(\n      jnp.dot(x, v['params']['if']['kernel'])\n      + v['params']['if']['bias'].reshape(1, -1)\n      + jnp.dot(carry0, v['params']['hf']['kernel'])\n    )\n    n = jax.nn.tanh(\n      jnp.dot(x, v['params']['in']['kernel'])\n      + v['params']['in']['bias'].reshape(1, -1)\n      + jnp.dot(carry0, v['params']['hn']['kernel'])\n    )\n    expected_out = (1 - f) * n + f * carry0\n    np.testing.assert_allclose(y, expected_out)\n\n\nclass IdsTest(absltest.TestCase):\n  def test_hashable(self):\n    id1 = ids.uuid()\n    id2 = ids.uuid()\n    self.assertEqual(id1, id1)\n    self.assertNotEqual(id1, id2)\n    self.assertNotEqual(hash(id1), hash(id2))\n    id1c = copy.copy(id1)\n    id1dc = copy.deepcopy(id1)\n    self.assertNotEqual(hash(id1), hash(id1c))\n    self.assertNotEqual(hash(id1), hash(id1dc))\n\ndef get_fp8_dtypes(fp8_genre):\n    assert fp8_genre in ('OCP', 'NANOO')\n    if fp8_genre == 'OCP':\n      e4m3_dtype = jnp.float8_e4m3fn\n      e5m2_dtype = jnp.float8_e5m2\n    else: # fp8_genre == 'NANOO'\n      e4m3_dtype = jnp.float8_e4m3fnuz\n      e5m2_dtype = jnp.float8_e5m2fnuz\n    return e4m3_dtype, e5m2_dtype\n\nclass Fp8Test(parameterized.TestCase):\n  @parameterized.parameters(\n    {'x_shape': (16, 32), 'y_shape': (32, 64), 'g_shape': (16, 64), 'eqn': 'mk,kn->mn'},\n    {'x_shape': (2, 3, 32), 'y_shape': (64, 32), 'g_shape': (2, 3, 64), 'eqn': '...k,nk->...n'},\n    {'x_shape': (2, 3, 64), 'y_shape': (64, 32), 'g_shape': (2, 3, 32), 'eqn': '...k,kn->...n'},\n  )\n  def test_fp8_einsum(self, x_shape, y_shape, g_shape, eqn):\n    rng, key1, key2, key3 = random.split(random.key(42), 4)\n    x = random.normal(key1, x_shape)\n    y = random.normal(key2, y_shape)\n    g = random.normal(key3, g_shape)\n    e4m3_dtype = jnp.float8_e4m3fn\n    e5m2_dtype = jnp.float8_e5m2\n    cast_to_representable = functools.partial(\n        fp8_ops.qdq,\n        scale=jnp.ones((1,)),\n        compute_dtype=jnp.float32,\n    )\n\n    x = cast_to_representable(x, e4m3_dtype)\n    y = cast_to_representable(y, e4m3_dtype)\n    g = cast_to_representable(g, e5m2_dtype)\n\n    p = nn.Fp8Einsum()\n    vars = p.init(rng, eqn, x, y)\n    def loss_fn(vars, x, y):\n      out = p.apply(vars, eqn, x, y)\n      return jnp.sum(out * g.astype(out.dtype))\n    step_fn = jax.value_and_grad(loss_fn, argnums=[1, 2])\n    out, grads = jax.jit(step_fn)(vars, x, y)\n\n    def loss_fn_ref(x, y):\n      out = jnp.einsum(eqn, x, y)\n      return jnp.sum(out * g.astype(out.dtype))\n    step_fn_ref = jax.value_and_grad(loss_fn_ref, argnums=[0, 1])\n    out_ref, grads_ref = jax.jit(step_fn_ref)(x, y)\n\n    np.testing.assert_allclose(out, out_ref, atol=1e-02, rtol=1e-02)\n    np.testing.assert_allclose(grads[0], grads_ref[0], atol=1e-02, rtol=1e-02)\n    np.testing.assert_allclose(grads[1], grads_ref[1], atol=1e-02, rtol=1e-02)\n\n\n  @parameterized.parameters(\n    {'fp8_genre': 'OCP'}, {'fp8_genre': 'NANOO'}\n  )\n  def test_fp8_dot_general_injection(self, fp8_genre):\n    # Used to cast the inputs to be representable in FP8, so that the difference\n    # of the results from the original gemm and fp8 gemm is small.\n    cast_to_representable = functools.partial(\n      fp8_ops.qdq,\n      scale=jnp.ones((1,)),\n      compute_dtype=jnp.float32,\n    )\n\n    e4m3_dtype, e5m2_dtype = get_fp8_dtypes(fp8_genre)\n\n    init_key, random_key = random.split(random.PRNGKey(seed=123), 2)\n    x = cast_to_representable(\n      random.uniform(random_key, (16, 32)), e4m3_dtype\n    )\n    dy = cast_to_representable(\n      random.uniform(random_key, (16, 64)), e5m2_dtype\n    )\n\n    quant_cls = nn.Fp8DotGeneral if fp8_genre == 'OCP' else nn.NANOOFp8DotGeneralOp\n\n    def run(fp8_injection, expected_shapes):\n      p = nn.DenseGeneral(features=64, name='dense')\n\n      if fp8_injection:\n        p.dot_general_cls = quant_cls\n\n      init_fn = jax.jit(p.init_with_output)\n      y, initial_vars = init_fn(init_key, x)\n      var_shapes = jax.tree_util.tree_map(jnp.shape, initial_vars)\n      self.assertEqual(var_shapes, expected_shapes)\n\n      def _train(variables, x):\n        y = p.apply(variables, x)\n        loss = y * dy\n        return jnp.mean(loss)\n\n      train_fn = jax.jit(jax.value_and_grad(_train, argnums=[0, 1]))\n      outputs, grads = train_fn(initial_vars, x)\n      return outputs, grads\n\n    expected_shapes_original = {\n      'params': {'kernel': (32, 64), 'bias': (64,)},\n    }\n\n    expected_shapes_new = {\n      'params': {'kernel': (32, 64), 'bias': (64,)},\n      fp8_ops.OVERWRITE_WITH_GRADIENT: {\n        f'{quant_cls.__name__}_0': {\n          'input_amax_history': (1024,),\n          'kernel_amax_history': (1024,),\n          'output_grad_amax_history': (1024,),\n          'input_scale': (1,),\n          'kernel_scale': (1,),\n          'output_grad_scale': (1,),\n        }\n      },\n    }\n    output1a, output1b = run(False, expected_shapes_original)\n    output2a, output2b = run(True, expected_shapes_new)\n    dw1, dw2 = output1b[0]['params']['kernel'], output2b[0]['params']['kernel']\n    dx1, dx2 = output1b[1], output2b[1]\n\n    np.testing.assert_allclose(output1a, output2a, atol=1e-02)\n    np.testing.assert_allclose(dw1, dw2, atol=1e-04)\n    np.testing.assert_allclose(dx1, dx2, atol=1e-04)\n\n  @parameterized.parameters(\n    {'fp8_genre': 'OCP'}, {'fp8_genre': 'NANOO'}\n  )\n  def test_fp8_train_state(self, fp8_genre):\n    key, init_key, random_key = random.split(random.PRNGKey(seed=123), 3)\n    x = random.uniform(random_key, (16, 16), dtype=jnp.float32)\n\n    quant_cls = nn.Fp8DotGeneral if fp8_genre == 'OCP' else nn.NANOOFp8DotGeneralOp\n    dense = nn.DenseGeneral(\n      features=32, use_bias=True, dot_general_cls=quant_cls\n    )\n\n    init_fn = jax.jit(dense.init)\n    variables = init_fn(init_key, x)\n    opt = optax.adam(learning_rate=0.1)\n    state = train_state.TrainState.create(\n      params=variables, tx=opt, apply_fn=dense.apply\n    )\n\n    def _roll_and_update(amax_h, update):\n      return jnp.roll(amax_h, shift=-1, axis=0).at[0].set(update)\n\n    def _train_loss(state, x, dy):\n      def loss_fn(vars):\n        y = state.apply_fn(vars, x)\n        loss = y * dy.astype(y.dtype)\n        return jnp.sum(loss)\n\n      grad_fn = jax.grad(loss_fn)\n      grads = grad_fn(state.params)\n      state = state.apply_gradients(grads=grads)\n      return state\n\n    train_fn = jax.jit(_train_loss)\n\n    scale_x, amax_history_x = jnp.ones(()), jnp.zeros((1024,))\n    scale_k, amax_history_k = jnp.ones(()), jnp.zeros((1024,))\n    scale_g, amax_history_g = jnp.ones(()), jnp.zeros((1024,))\n    e4m3_dtype, e5m2_dtype = get_fp8_dtypes(fp8_genre)\n    e4m3_max = jnp.finfo(e4m3_dtype).max.astype(jnp.float32)\n    e5m2_max = jnp.finfo(e5m2_dtype).max.astype(jnp.float32)\n\n    for _ in range(5):\n      key, random_key = random.split(key, 2)\n      x = random.normal(random_key, (16, 16), dtype=jnp.float32)\n      g = random.normal(random_key, (16, 32), dtype=jnp.float32)\n      k = state.params['params']['kernel']\n\n      # Manually compute the expected amax history and scaling factors.\n      amax_from_history_x = jnp.max(amax_history_x, axis=0)\n      amax_from_history_k = jnp.max(amax_history_k, axis=0)\n      amax_from_history_g = jnp.max(amax_history_g, axis=0)\n      scale_x = fp8_ops.compute_scale(amax_from_history_x, scale_x, e4m3_max)\n      scale_k = fp8_ops.compute_scale(amax_from_history_k, scale_k, e4m3_max)\n      scale_g = fp8_ops.compute_scale(amax_from_history_g, scale_g, e5m2_max)\n      amax_history_x = _roll_and_update(amax_history_x, jnp.max(jnp.abs(x)))\n      amax_history_k = _roll_and_update(amax_history_k, jnp.max(jnp.abs(k)))\n      amax_history_g = _roll_and_update(amax_history_g, jnp.max(jnp.abs(g)))\n\n      state = train_fn(state, x, g)\n\n      rtol, atol = 0.001, 0.001\n      fp8_vars = state.params[fp8_ops.OVERWRITE_WITH_GRADIENT][\n        f'{quant_cls.__name__}_0'\n      ]\n      np.testing.assert_allclose(\n        fp8_vars['input_amax_history'],\n        amax_history_x,\n        rtol=rtol,\n        atol=atol,\n      )\n      np.testing.assert_allclose(\n        fp8_vars['kernel_amax_history'],\n        amax_history_k,\n        rtol=rtol,\n        atol=atol,\n      )\n      np.testing.assert_allclose(\n        fp8_vars['output_grad_amax_history'],\n        amax_history_g,\n        rtol=rtol,\n        atol=atol,\n      )\n\n      np.testing.assert_allclose(fp8_vars['input_scale'][0], scale_x)\n      np.testing.assert_allclose(fp8_vars['kernel_scale'][0], scale_k)\n      np.testing.assert_allclose(fp8_vars['output_grad_scale'][0], scale_g)\n\n  @parameterized.parameters(\n          {'fp8_genre': 'OCP', 'use_jit': True},\n          {'fp8_genre': 'OCP', 'use_jit': False},\n          {'fp8_genre': 'NANOO', 'use_jit': True},\n          {'fp8_genre': 'NANOO', 'use_jit': False}\n  )\n  def test_fp8_meta_dtype(self, fp8_genre, use_jit):\n    if not use_jit and not fp8_ops.CAN_USE_EARRAY:\n      self.skipTest(\"TODO: requires newer jax that has earray\")\n    f32 = jnp.dtype('float32')\n    fmax32 = fp8_ops.fp32_max_grad\n    e4m3_dtype, _ = get_fp8_dtypes(fp8_genre)\n    e4m3_max = 448 if fp8_genre == 'OCP' else 240\n\n    # Create a scan loop with reused ah_f32 and sf_f32. So, the autograd will\n    # accumulate the grads of them. We expect the max op (rather than add op)\n    # for the accumulation by converting them to fmax32 dtype.\n    def outer(x, ah_f32, sf_f32):\n      ah_fmax32 = jax.lax.convert_element_type(ah_f32, fmax32)\n      sf_fmax32 = jax.lax.convert_element_type(sf_f32, fmax32)\n      array_x = jnp.array([x], f32)\n      def body_fun(carry, _):\n        carry = fp8_ops.in_qdq(f32, e4m3_dtype, carry, sf_fmax32, ah_fmax32)\n        return carry, None\n      array_x, _ = jax.lax.scan(body_fun, array_x, None, length=3)\n      return array_x[0]\n\n    outer_fn = jax.grad(outer, (0, 1, 2))\n    if use_jit:\n      outer_fn = jax.jit(outer_fn)\n    ah = jnp.array([0., 0., 0.], f32)\n    sf = jnp.array([1.], f32)\n    # 1st iteration\n    grads, new_ah, new_sf = outer_fn(2.0, ah, sf)\n    np.testing.assert_allclose(new_ah, [2., 0., 0.])\n    np.testing.assert_allclose(new_sf, [1.])\n    # 2nd iteration\n    grads, new_ah, new_sf = outer_fn(3., new_ah, new_sf)\n    np.testing.assert_allclose(new_ah, [3., 0., 2.])\n    np.testing.assert_allclose(new_sf, [2. / e4m3_max])\n    # 3rd iteration\n    grads, new_ah, new_sf = outer_fn(4., new_ah, new_sf)\n    np.testing.assert_allclose(new_ah, [4., 2., 3.])\n    np.testing.assert_allclose(new_sf, [3. / e4m3_max])\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/linen_transforms_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Transforms tests.\"\"\"\n\nfrom functools import partial\nimport operator\nfrom typing import Any\nfrom collections.abc import Callable, Sequence\nimport unittest\n\nfrom absl.testing import absltest, parameterized\nfrom flax import errors\nfrom flax import linen as nn\nfrom flax import serialization\nfrom flax import struct\nfrom flax.core import copy, freeze, AxisMetadata\nfrom flax.linen.transforms import _HashableProxy\nimport jax\nfrom jax import random\nimport jax.numpy as jnp\nimport numpy as np\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n# pylint: disable=attribute-defined-outside-init,unused-variable,g-wrong-blank-lines,g-bare-generic\n\n\ndef tree_equals(x, y):\n  return jax.tree_util.tree_all(jax.tree_util.tree_map(operator.eq, x, y))\n\n\ndef tree_allclose(x, y):\n  return jax.tree_util.tree_all(\n      jax.tree_util.tree_map(lambda x, y: np.all(np.isclose(x, y)), x, y)\n  )\n\n\nid_fn = lambda x: x\n\n\nclass TransformedMLP(nn.Module):\n  features: Sequence[int]\n  transform: Callable = id_fn\n\n  @nn.compact\n  def __call__(self, inputs):\n    x = inputs\n    for i, feat in enumerate(self.features):\n      # JIT the Module (it's __call__ fn by default.)\n      x = self.transform(nn.Dense)(feat, name=f'layers_{i}')(x)\n      if i != len(self.features) - 1:\n        x = nn.relu(x)\n    return x\n\n\ndef decorated_MLP(transform: Callable = id_fn):\n  class MLP(nn.Module):\n    features: Sequence[int]\n\n    @transform\n    @nn.compact\n    def __call__(self, inputs):\n      x = inputs\n      for i, feat in enumerate(self.features):\n        # JIT the Module (it's __call__ fn by default.)\n        x = nn.Dense(feat, name=f'layers_{i}')(x)\n        if i != len(self.features) - 1:\n          x = nn.relu(x)\n      return x\n\n  return MLP\n\n\nclass TransformTest(parameterized.TestCase):\n\n  def assert_keys_equal(self, key1, key2):\n    self.assertEqual(key1.dtype, key2.dtype)\n    np.testing.assert_array_equal(random.key_data(key1), random.key_data(key2))\n\n  def test_jit(self):\n    key1, key2 = random.split(random.key(3), 2)\n    x = random.uniform(key1, (4, 4))\n\n    normal_model = TransformedMLP(features=[3, 4, 5])\n    jit_model = TransformedMLP(features=[3, 4, 5], transform=nn.jit)\n    init_variables = normal_model.init(key2, x)\n    y1 = normal_model.apply(init_variables, x)\n    y2 = jit_model.apply(init_variables, x)\n\n    self.assertTrue(np.all(y1 == y2))\n\n  def test_jit_decorated(self):\n    key1, key2 = random.split(random.key(3), 2)\n    x = random.uniform(key1, (4, 4))\n\n    normal_model = decorated_MLP()(features=[3, 4, 5])\n    jit_model = decorated_MLP(nn.jit)(features=[3, 4, 5])\n    init_variables = normal_model.init(key2, x)\n    y1 = normal_model.apply(init_variables, x)\n    y2 = jit_model.apply(init_variables, x)\n\n    self.assertTrue(np.all(y1 == y2))\n\n  def test_jit_init_fn(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return nn.Dense(2)(x)\n\n      @nn.jit\n      def init_with_output(self, rngs, *args, **kwargs):\n        return super().init_with_output(rngs, *args, **kwargs)\n\n    Foo().init_with_output(random.key(0), jnp.ones((2, 3)))\n\n\n  def test_remat(self):\n    key1, key2 = random.split(random.key(3), 2)\n    x = random.uniform(key1, (4, 4))\n\n    normal_model = TransformedMLP(features=[3, 4, 5])\n    remat_model = TransformedMLP(features=[3, 4, 5], transform=nn.remat)\n    init_variables = normal_model.init(key2, x)\n    y1 = normal_model.apply(init_variables, x)\n    y2 = remat_model.apply(init_variables, x)\n\n    self.assertTrue(np.all(y1 == y2))\n\n  def test_remat_decorated(self):\n    key1, key2 = random.split(random.key(3), 2)\n    x = random.uniform(key1, (4, 4))\n\n    normal_model = decorated_MLP()(features=[3, 4, 5])\n    remat_model = decorated_MLP(nn.remat)(features=[3, 4, 5])\n    init_variables = normal_model.init(key2, x)\n    y1 = normal_model.apply(init_variables, x)\n    y2 = remat_model.apply(init_variables, x)\n\n    self.assertTrue(np.all(y1 == y2))\n\n  def test_remat_kwargs(self):\n    raise unittest.SkipTest('test breaks with grad')\n\n    class ConditionalReLU(nn.Module):\n\n      @nn.compact\n      def __call__(self, input, apply_relu: bool = False):\n        return nn.relu(input) if apply_relu else input\n\n    key = random.key(0)\n    x = jnp.ones((4, 4)) * -1\n    remat_model = nn.remat(ConditionalReLU)()\n    p = remat_model.init(key, x)\n    y = remat_model.apply(p, x, apply_relu=True)\n\n    self.assertTrue(np.all(y == jnp.zeros_like(x)))\n\n    # This next line crashes with a concretization error\n    _ = jax.grad(lambda x: remat_model.apply(p, x, apply_relu=True))(x)\n\n  def test_remat_static_argnums(self):\n    test = self\n\n    class Foo(nn.Module):\n      train_is_static: bool\n\n      @nn.compact\n      def __call__(self, inputs, train: bool):\n        if self.train_is_static:\n          test.assertTrue(isinstance(train, bool))\n        else:\n          test.assertTrue(isinstance(train, jnp.ndarray))\n\n        return nn.Dense(3, use_bias=False)(inputs)\n\n    # set train as a static argument\n    FooRemat = nn.remat(Foo, static_argnums=(2,))\n    foo = FooRemat(train_is_static=True)\n\n    x = jnp.empty((1, 2))\n    variables = foo.init(random.key(0), x, True)\n    y = foo.apply(variables, x, False)\n    self.assertEqual(y.shape, (1, 3))\n\n    # set train as a non-static arguments\n    FooRemat = nn.remat(Foo, static_argnums=())\n    foo = FooRemat(train_is_static=False)\n\n    variables = foo.init(random.key(0), x, True)\n    y = foo.apply(variables, x, False)\n    self.assertEqual(y.shape, (1, 3))\n\n  def test_remat_decorator_static_argnums(self):\n    test = self\n\n    class FooTrainStatic(nn.Module):\n\n      @partial(nn.remat, static_argnums=(2,))\n      @nn.compact\n      def __call__(self, inputs, train: bool):\n        test.assertTrue(isinstance(train, bool))\n\n        return nn.Dense(3, use_bias=False)(inputs)\n\n    # set train as a static argument\n    foo = FooTrainStatic()\n\n    x = jnp.empty((1, 2))\n    variables = foo.init(random.key(0), x, True)\n    y = foo.apply(variables, x, False)\n    self.assertEqual(y.shape, (1, 3))\n\n    class FooTrainDynamic(nn.Module):\n\n      @partial(nn.remat, static_argnums=())\n      @nn.compact\n      def __call__(self, inputs, train: bool):\n        test.assertTrue(isinstance(train, jnp.ndarray))\n\n        return nn.Dense(3, use_bias=False)(inputs)\n\n    # set train as a non-static arguments\n    foo = FooTrainDynamic()\n\n    variables = foo.init(random.key(0), x, True)\n    y = foo.apply(variables, x, False)\n    self.assertEqual(y.shape, (1, 3))\n\n  def test_vmap(self):\n    key1, key2, key3 = random.split(random.key(3), 3)\n    x = random.uniform(key1, (4, 4))\n    x2 = random.uniform(key2, (5, 4, 4))\n\n    def vmap(cls):\n      return nn.vmap(\n          cls,\n          in_axes=(0,),\n          variable_axes={'params': None},\n          split_rngs={'params': False},\n      )\n\n    normal_model = TransformedMLP(features=[3, 4, 5])\n    vmap_model = TransformedMLP(features=[3, 4, 5], transform=vmap)\n    init_variables = normal_model.init(key3, x)\n    # simulate vmap in python for comparison:\n    y1 = jnp.vstack([\n        normal_model.apply(init_variables, x2[i])[None, ...]\n        for i in np.arange(x2.shape[0])\n    ])\n    y2 = vmap_model.apply(init_variables, x2)\n    np.testing.assert_allclose(y1, y2, atol=1e-6)\n\n  def test_vmap_decorated(self):\n    key1, key2, key3 = random.split(random.key(3), 3)\n    x = random.uniform(key1, (4, 4))\n    x2 = random.uniform(key2, (5, 4, 4))\n\n    def vmap(fn):\n      return nn.vmap(\n          fn,\n          in_axes=(0,),\n          variable_axes={'params': None},\n          split_rngs={'params': False},\n      )\n\n    normal_model = decorated_MLP()(features=[3, 4, 5])\n    vmap_model = decorated_MLP(vmap)(features=[3, 4, 5])\n    init_variables = normal_model.init(key3, x)\n    # simulate vmap in python for comparison:\n    y1 = jnp.vstack([\n        normal_model.apply(init_variables, x2[i])[None, ...]\n        for i in np.arange(x2.shape[0])\n    ])\n    y2 = vmap_model.apply(init_variables, x2)\n    np.testing.assert_allclose(y1, y2, atol=1e-6)\n\n  def test_vmap_batchnorm(self):\n    key1, key2, key3 = random.split(random.key(3), 3)\n    x = random.uniform(key1, (4, 4))\n    x2 = random.uniform(key2, (5, 4, 4))\n\n    def vmap(cls):\n      return nn.vmap(\n          cls,\n          in_axes=(0,),\n          variable_axes={'params': None, 'batch_stats': None},\n          split_rngs={'params': False},\n          axis_name='batch',\n      )\n\n    class MlpBn(nn.Module):\n      axis_name: Any = None\n\n      @nn.compact\n      def __call__(self, x):\n        x = nn.Dense(3)(x)\n        x = nn.BatchNorm(axis_name=self.axis_name, use_running_average=False)(x)\n        return x\n\n    normal_model = MlpBn()\n    vmap_model = vmap(MlpBn)(axis_name='batch')\n    init_variables = normal_model.init(key3, x)\n    y1 = normal_model.apply(\n        init_variables, x2.reshape((-1, 4)), mutable=['batch_stats']\n    )[0]\n    y1 = y1.reshape((5, 4, 3))\n    y2 = vmap_model.apply(init_variables, x2, mutable=['batch_stats'])[0]\n    np.testing.assert_allclose(y1, y2, atol=1e-5)\n\n  def test_scan(self):\n    class SimpleScan(nn.Module):\n      features: int\n\n      @nn.compact\n      def __call__(self, c, xs):\n        LSTM = nn.scan(\n            nn.LSTMCell,\n            variable_broadcast='params',\n            split_rngs={'params': False},\n        )\n        return LSTM(self.features, name='lstm_cell')(c, xs)\n\n    key1, key2 = random.split(random.key(0), 2)\n    xs = random.uniform(key1, (5, 3, 2))\n    dummy_rng = random.key(0)\n    init_carry = nn.LSTMCell(2).initialize_carry(dummy_rng, xs[0].shape)\n    model = SimpleScan(2)\n    init_variables = model.init(key2, init_carry, xs)\n    # simulate scan in python for comparison:\n    c = init_carry\n    ys = []\n    lstmcell_variables = freeze(\n        {'params': init_variables['params']['lstm_cell']}\n    )\n    for i in range(xs.shape[0]):\n      c, y = nn.LSTMCell(2).apply(lstmcell_variables, c, xs[i])\n      ys.append(y[None, ...])\n    y1 = jnp.vstack(ys)\n\n    c2, y2 = model.apply(init_variables, init_carry, xs)\n    np.testing.assert_allclose(y1, y2, atol=1e-7)\n    np.testing.assert_allclose(c[0], c2[0], atol=1e-7)\n    np.testing.assert_allclose(c[1], c2[1], atol=1e-7)\n\n  def test_scan_decorated(self):\n    class SimpleScan(nn.Module):\n      features: int\n\n      @partial(\n          nn.scan,\n          variable_broadcast='params',\n          in_axes=(nn.broadcast, 0),\n          split_rngs={'params': False},\n      )\n      @nn.compact\n      def __call__(self, c, b, xs):\n        assert b.shape == (4,)\n        return nn.LSTMCell(self.features, name='lstm_cell')(c, xs)\n\n    key1, key2 = random.split(random.key(0), 2)\n    xs = random.uniform(key1, (4, 3, 2))\n    b = jnp.ones((4,))\n    dummy_rng = random.key(0)\n    init_carry = nn.LSTMCell(2).initialize_carry(dummy_rng, xs[0].shape)\n    model = SimpleScan(2)\n    init_variables = model.init(key2, init_carry, b, xs)\n    # simulate scan in python for comparison:\n    c = init_carry\n    ys = []\n    lstmcell_variables = freeze(\n        {'params': init_variables['params']['lstm_cell']}\n    )\n    for i in range(xs.shape[0]):\n      c, y = nn.LSTMCell(2).apply(lstmcell_variables, c, xs[i])\n      ys.append(y[None, ...])\n    y1 = jnp.vstack(ys)\n\n    c2, y2 = model.apply(init_variables, init_carry, b, xs)\n    np.testing.assert_allclose(y1, y2, atol=1e-7)\n    np.testing.assert_allclose(c[0], c2[0], atol=1e-7)\n    np.testing.assert_allclose(c[1], c2[1], atol=1e-7)\n\n  def test_scan_negative_axes(self):\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, _, x):\n        x = nn.Dense(4)(x)\n        return None, x\n\n    class Bar(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        _, x = nn.scan(\n            Foo,\n            variable_broadcast='params',\n            split_rngs=dict(params=False),\n            in_axes=1,\n            out_axes=-1,\n        )()(None, x)\n        return x\n\n    y, variables = Bar().init_with_output(\n        {'params': jax.random.PRNGKey(0)},\n        jax.random.normal(jax.random.PRNGKey(1), shape=[1, 2, 3]),\n    )\n    params = variables['params']\n\n    self.assertEqual(y.shape, (1, 4, 2))\n    self.assertEqual(params['ScanFoo_0']['Dense_0']['kernel'].shape, (3, 4))\n    self.assertEqual(params['ScanFoo_0']['Dense_0']['bias'].shape, (4,))\n\n  def test_multiscope_lifting_simple(self):\n    class Counter(nn.Module):\n\n      @nn.compact\n      def __call__(self):\n        v = self.variable('counter', 'foo', lambda: jnp.array([0]))\n        v.value += jnp.array([1])\n        return v.value\n\n    class Outer(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        cntr = nn.jit(Counter)(name='cntr')()\n        return x\n\n    class Inner(nn.Module):\n      outer_module: nn.Module\n\n      @nn.compact\n      def __call__(self, x):\n        return self.outer_module(x)\n\n    class Test(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        outer_dense = nn.jit(Outer)(name='outer')\n        # we share stateful outer module as arg to two different, transformed modules:\n        inner = nn.jit(Inner)(outer_dense, name='inner1')\n        inner2 = nn.jit(Inner)(outer_dense, name='inner2')\n        res = inner(x) + inner2(x)\n        return res\n\n    x = jnp.ones((10, 10))\n    rngs = random.key(0)\n    init_vars = Test(parent=None).init(rngs, x)\n    _, new_vars = Test(parent=None).apply(init_vars, x, mutable=['counter'])\n    self.assertEqual(\n        init_vars['counter']['outer']['cntr']['foo'], jnp.array([2], jnp.int32)\n    )\n    self.assertEqual(\n        new_vars['counter']['outer']['cntr']['foo'], jnp.array([4], jnp.int32)\n    )\n\n  def test_multiscope_lifting_simple_decorator(self):\n    class Counter(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self):\n        v = self.variable('counter', 'foo', lambda: jnp.array([0]))\n        v.value += jnp.array([1])\n        return v.value\n\n    class Outer(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        cntr = Counter(name='cntr')()\n        return x\n\n    class Inner(nn.Module):\n      outer_module: nn.Module\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        return self.outer_module(x)\n\n    class Test(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        outer_dense = Outer(name='outer')\n        # we share stateful outer module as arg to two different, transformed modules:\n        inner = Inner(outer_dense, name='inner1')\n        inner2 = Inner(outer_dense, name='inner2')\n        res = inner(x) + inner2(x)\n        return res\n\n    x = jnp.ones((1, 1))\n    rngs = random.key(0)\n    init_vars = Test(parent=None).init(rngs, x)\n    _, new_vars = Test(parent=None).apply(init_vars, x, mutable=['counter'])\n    self.assertEqual(\n        init_vars['counter']['outer']['cntr']['foo'], jnp.array([2], jnp.int32)\n    )\n    self.assertEqual(\n        new_vars['counter']['outer']['cntr']['foo'], jnp.array([4], jnp.int32)\n    )\n\n  def test_multiscope_lifting_argtree(self):\n    class Counter(nn.Module):\n\n      @nn.compact\n      def __call__(self):\n        v = self.variable('counter', 'foo', lambda: jnp.array([0]))\n        v.value += jnp.array([1])\n        return v.value\n\n    class Outer(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        cntr = nn.jit(Counter)(name='cntr')()\n        return x\n\n    class Inner(nn.Module):\n      outer_module: Sequence[nn.Module]\n\n      @nn.compact\n      def __call__(self, x):\n        return self.outer_module[0](x) + self.outer_module[1](x)\n\n    class Test(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        outer_dense1 = nn.jit(Outer)(name='outer1')\n        outer_dense2 = nn.jit(Outer)(name='outer2')\n        # we share stateful outer module as arg to two different, transformed modules:\n        inner1 = nn.jit(Inner)((outer_dense1, outer_dense2), name='inner1')\n        inner2 = nn.jit(Inner)((outer_dense1, outer_dense2), name='inner2')\n        res = inner1(x) + inner2(x)\n        return res\n\n    x = jnp.ones((1, 1))\n    rngs = random.key(0)\n    init_vars = Test(parent=None).init(rngs, x)\n    _, new_vars = Test(parent=None).apply(init_vars, x, mutable=['counter'])\n    self.assertEqual(\n        init_vars['counter']['outer1']['cntr']['foo'], jnp.array([2], jnp.int32)\n    )\n    self.assertEqual(\n        new_vars['counter']['outer1']['cntr']['foo'], jnp.array([4], jnp.int32)\n    )\n    self.assertEqual(\n        init_vars['counter']['outer2']['cntr']['foo'], jnp.array([2], jnp.int32)\n    )\n    self.assertEqual(\n        new_vars['counter']['outer2']['cntr']['foo'], jnp.array([4], jnp.int32)\n    )\n\n  def test_multiscope_lifting_argtree_decorator(self):\n    class Counter(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self):\n        v = self.variable('counter', 'foo', lambda: jnp.array([0]))\n        v.value += jnp.array([1])\n        return v.value\n\n    class Outer(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        cntr = nn.jit(Counter)(name='cntr')()\n        return x\n\n    class Inner(nn.Module):\n      outer_module: Sequence[nn.Module]\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        return self.outer_module[0](x) + self.outer_module[1](x)\n\n    class Test(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        outer_dense1 = Outer(name='outer1')\n        outer_dense2 = Outer(name='outer2')\n        # we share stateful outer module as arg to two different, transformed modules:\n        inner1 = Inner((outer_dense1, outer_dense2), name='inner1')\n        inner2 = Inner((outer_dense1, outer_dense2), name='inner2')\n        res = inner1(x) + inner2(x)\n        return res\n\n    x = jnp.ones((1, 1))\n    rngs = random.key(0)\n    init_vars = Test(parent=None).init(rngs, x)\n    _, new_vars = Test(parent=None).apply(init_vars, x, mutable=['counter'])\n    self.assertEqual(\n        init_vars['counter']['outer1']['cntr']['foo'], jnp.array([2], jnp.int32)\n    )\n    self.assertEqual(\n        new_vars['counter']['outer1']['cntr']['foo'], jnp.array([4], jnp.int32)\n    )\n    self.assertEqual(\n        init_vars['counter']['outer2']['cntr']['foo'], jnp.array([2], jnp.int32)\n    )\n    self.assertEqual(\n        new_vars['counter']['outer2']['cntr']['foo'], jnp.array([4], jnp.int32)\n    )\n\n  def test_multiscope_lifting_simple_decorator_w_jit(self):\n    # TODO: actually test jaxpr on a simpler module.\n    class Counter(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self):\n        v = self.variable('counter', 'foo', lambda: jnp.array([0]))\n        v.value += jnp.array([1])\n        return v.value\n\n    class Outer(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        cntr = Counter(name='cntr')()\n        return x\n\n    class Inner(nn.Module):\n      outer_module: nn.Module\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        return self.outer_module(x)\n\n    class Test(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        outer_dense = Outer(name='outer')\n        # we share stateful outer module as arg to two different, transformed modules:\n        inner = Inner(outer_dense, name='inner1')\n        inner2 = Inner(outer_dense, name='inner2')\n        res = inner(x) + inner2(x)\n        return res\n\n    x = jnp.ones((1, 1))\n    rngs = random.key(0)\n    init_vars = Test(parent=None).init(rngs, x)\n    _, new_vars = Test(parent=None).apply(init_vars, x, mutable=['counter'])\n    self.assertEqual(\n        init_vars['counter']['outer']['cntr']['foo'], jnp.array([2], jnp.int32)\n    )\n    self.assertEqual(\n        new_vars['counter']['outer']['cntr']['foo'], jnp.array([4], jnp.int32)\n    )\n\n  def test_vmapped_outer_module(self):\n    class Outer(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        return nn.Dense(5)(x)\n\n    class Inner(nn.Module):\n      outer_module: nn.Module\n\n      @partial(\n          nn.vmap,\n          in_axes=(0,),\n          variable_axes={'params': 0},\n          split_rngs={'params': True},\n      )\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        return self.outer_module(x)\n\n    class Test(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        outer_dense = Outer(name='outer')\n        inner = Inner(outer_dense, name='inner1')\n        inner2 = Inner(outer_dense, name='inner2')\n        res = inner(x) + inner2(x)\n        return res\n\n    x = jnp.ones((3, 1, 2))\n    rngs = random.key(0)\n    init_vars = Test(parent=None).init(rngs, x)\n    y = Test(parent=None).apply(init_vars, x)\n    self.assertEqual(\n        init_vars['params']['outer']['Dense_0']['kernel'].shape, (3, 2, 5)\n    )\n    self.assertEqual(\n        init_vars['params']['outer']['Dense_0']['bias'].shape, (3, 5)\n    )\n    self.assertEqual(y.shape, (3, 1, 5))\n\n  def test_module_transform_with_setup(self):\n    class Foo(nn.Module):\n\n      def setup(self):\n        self.test = self.param('test', nn.initializers.ones_init(), ())\n\n      def __call__(self, x):\n        return x * self.test\n\n    FooVmap = nn.vmap(\n        Foo,\n        in_axes=0,\n        out_axes=0,\n        variable_axes={'params': 0},\n        split_rngs={'params': True},\n    )\n    variables = FooVmap().init(random.key(0), jnp.ones((4,)))\n    self.assertEqual(variables['params']['test'].shape, (4,))\n\n  def test_nested_module_args_vmap(self):\n    class A(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        return nn.Dense(3)(x)\n\n    class B(nn.Module):\n      A: nn.Module\n\n      @nn.compact\n      def __call__(self, x):\n        return self.A(x)\n\n    class C(nn.Module):\n      B: nn.Module\n\n      @partial(\n          nn.vmap, variable_axes={'params': 0}, split_rngs={'params': True}\n      )\n      @nn.compact\n      def __call__(self, x):\n        return self.B(x)\n\n    class D(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        a = A()\n        b = B(a)\n        c = C(b)\n        return c(x)\n\n    key = random.key(0)\n    x = jnp.ones((10, 10))\n    p = D().init(key, x)\n\n    variable_shapes = jax.tree_util.tree_map(jnp.shape, p)\n    self.assertEqual(\n        variable_shapes['params']['A_0']['Dense_0']['kernel'], (10, 10, 3)\n    )\n    self.assertEqual(\n        variable_shapes['params']['A_0']['Dense_0']['bias'], (10, 3)\n    )\n\n  def test_nested_module_args_vmap_2(self):\n    class A(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        return nn.Dense(3)(x)\n\n    class B(nn.Module):\n      A: nn.Module\n\n      @nn.compact\n      def __call__(self, x):\n        return self.A(x)\n\n    class C(nn.Module):\n      A: nn.Module\n      B: nn.Module\n\n      @partial(\n          nn.vmap, variable_axes={'params': 0}, split_rngs={'params': True}\n      )\n      @nn.compact\n      def __call__(self, x):\n        return self.B(x) + self.A(x)\n\n    class D(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        a1 = A()\n        a2 = A()\n        b = B(a1)\n        c = C(a2, b)\n        return c(x)\n\n    key = random.key(0)\n    x = jnp.ones((10, 10))\n    p = D().init(key, x)\n\n    variable_shapes = jax.tree_util.tree_map(jnp.shape, p)\n    self.assertEqual(\n        variable_shapes['params']['A_0']['Dense_0']['kernel'], (10, 10, 3)\n    )\n    self.assertEqual(\n        variable_shapes['params']['A_0']['Dense_0']['bias'], (10, 3)\n    )\n    self.assertEqual(\n        variable_shapes['params']['A_1']['Dense_0']['kernel'], (10, 10, 3)\n    )\n    self.assertEqual(\n        variable_shapes['params']['A_1']['Dense_0']['bias'], (10, 3)\n    )\n\n  def test_nested_setup_calls_count(self):\n    D = 3\n    N = 4\n    setup_cntr = 0\n    call_cntr = 0\n\n    class Repeat(nn.Module):\n      mdl_def: Any\n\n      def setup(self):\n        self.lyrs = [self.mdl_def() for _ in range(N)]\n\n      @nn.remat  # we just use remat as a convenient test of transform logic\n      def __call__(self, x):\n        for lyr in self.lyrs:\n          lyr(x)\n        return x\n\n    class Counter(nn.Module):\n\n      def setup(self):\n        nonlocal setup_cntr\n        setup_cntr += 1\n        self.dense = nn.Dense(2, use_bias=False)\n\n      @nn.remat\n      def __call__(self, x):\n        nonlocal call_cntr\n        call_cntr += 1\n        return self.dense(x)\n\n    def nested_repeat(mdl):\n      for _ in range(D):\n        mdl = partial(Repeat, mdl)\n      return mdl()\n\n    _ = nested_repeat(Counter).init(random.key(0), jnp.ones((2,)))\n    # setup_cntr == 128 due to 1 call in Counter.setup by _validate_setup\n    # and 1 further \"real\" call.\n    self.assertEqual(setup_cntr, 128)\n    self.assertEqual(call_cntr, 64)\n\n  def test_multimethod_setup_calls(self):\n    cntr = 0\n\n    class A(nn.Module):\n\n      def setup(self):\n        nonlocal cntr\n        cntr += 1\n        self.d = nn.Dense(2)\n\n      @nn.remat\n      def foo(self, x):\n        return self.d(x)\n\n      @nn.remat\n      def bar(self, x):\n        return self.d(x)\n\n    class B(nn.Module):\n\n      def setup(self):\n        self.a = A()\n\n      def __call__(self, x):\n        y1 = self.a.foo(x)\n        y2 = self.a.bar(x)\n        return y1, y2\n\n    key = random.key(0)\n    x = jnp.ones((2,))\n    (y1, y2), _ = B().init_with_output(key, x)\n    np.testing.assert_array_equal(y1, y2)\n    # cntr == 4 due to:\n    # 1 call by _validate_setup\n    # 1 call for the setup() outside transform boundary\n    # and two further \"real\" calls in transform boundaries\n    self.assertEqual(cntr, 4)\n\n  def test_toplevel_submodule_adoption_transform(self):\n    class A(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        return nn.Dense(3)(x)\n\n    class B(nn.Module):\n      A: nn.Module\n\n      @nn.compact\n      def __call__(self, x):\n        return self.A(x)\n\n    class C(nn.Module):\n      A: nn.Module\n      B: nn.Module\n\n      @partial(\n          nn.vmap, variable_axes={'params': 0}, split_rngs={'params': True}\n      )\n      @nn.compact\n      def __call__(self, x):\n        return self.B(x) + self.A(x)\n\n    class Csimple(nn.Module):\n      A: nn.Module\n      B: nn.Module\n\n      @nn.compact\n      def __call__(self, x):\n        return self.B(x) + self.A(x)\n\n    class D(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        a1 = A()\n        a2 = A()\n        b = B(a1)\n        c = C(a2, b)\n        return c(x)\n\n    key = random.key(0)\n    x = jnp.ones((10, 10))\n    p1 = D().init(key, x)\n    y1 = D().apply(p1, x)\n\n    a1 = A()\n    a2 = A()\n    b = B(a1)\n    p2 = freeze({\n        'params': {\n            'A': p1['params']['A_0'],\n            'B': {\n                'A': p1['params']['A_1'],\n            },\n        }\n    })\n\n    # Test method wrapper transform.\n    y2 = C(a2, b).apply(p2, x)\n    np.testing.assert_allclose(y1, y2, atol=1e-7)\n    # Test class transform.\n    Ctrafo = nn.vmap(\n        Csimple, variable_axes={'params': 0}, split_rngs={'params': True}\n    )\n\n    y3 = Ctrafo(a2, b).apply(p2, x)\n    np.testing.assert_allclose(y1, y3, atol=1e-7)\n\n  def test_toplevel_submodule_adoption_pytree_transform(self):\n    class A(nn.Module):\n\n      @nn.compact\n      def __call__(self, c, x):\n        counter = self.variable('counter', 'i', jnp.zeros, ())\n        counter.value += 1\n        x = nn.Dense(1)(x)\n        return c, x\n\n    class B(nn.Module):\n      A: Any\n\n      @nn.compact\n      def __call__(self, c, x):\n        return self.A['foo'](*self.A['bar'](c, x))\n\n    a = A()\n    As = {'foo': A(), 'bar': A()}\n    b = nn.scan(\n        B,\n        in_axes=0,\n        variable_carry='counter',\n        variable_broadcast='params',\n        split_rngs={'params': False},\n    )(As)\n\n    key = random.key(0)\n    x = jnp.ones((10, 2))\n\n    p = B(As).init(key, x, x)\n    y, cntrs = b.apply(p, x, x, mutable='counter')\n    ref_cntrs = {\n        'counter': {\n            'A_bar': {\n                'i': jnp.array(11.0),\n            },\n            'A_foo': {\n                'i': jnp.array(11.0),\n            },\n        },\n    }\n    self.assertTrue(\n        jax.tree_util.tree_all(\n            jax.tree_util.tree_map(\n                lambda x, y: np.testing.assert_allclose(x, y, atol=1e-7),\n                cntrs,\n                ref_cntrs,\n            )\n        )\n    )\n\n  def test_partially_applied_module_constructor_transform(self):\n    k = random.key(0)\n    x = jnp.ones((3, 4, 4))\n    dense = partial(nn.Dense, use_bias=False)\n    vmap_dense = nn.vmap(\n        dense, variable_axes={'params': 0}, split_rngs={'params': True}\n    )(4)\n    init_vars = vmap_dense.init(k, x)\n    init_vars_shapes = jax.tree_util.tree_map(jnp.shape, init_vars)\n    ref_var_shapes = {\n        'params': {\n            'kernel': (3, 4, 4),\n        },\n    }\n    self.assertTrue(tree_equals(init_vars_shapes, ref_var_shapes))\n\n  def test_partial_module_method(self):\n    k = random.key(0)\n    x = jnp.ones((3, 4, 4))\n\n    class Foo(nn.Module):\n\n      @nn.compact\n      def inner(self, x):\n        return nn.Dense(2, use_bias=False)(x)\n\n      def __call__(self, x):\n        return nn.vmap(\n            partial(Foo.inner),\n            variable_axes={'params': 0},\n            split_rngs={'params': True},\n        )(self, x)\n\n    init_vars = Foo().init(k, x)\n    init_vars_shapes = jax.tree_util.tree_map(jnp.shape, init_vars)\n    ref_var_shapes = {\n        'params': {'Dense_0': {'kernel': (3, 4, 2)}},\n    }\n    self.assertTrue(tree_equals(init_vars_shapes, ref_var_shapes))\n\n  def test_variable_in_args_transform(self):\n    class Test(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        baz = self.variable('test', 'baz', jnp.zeros, x.shape)\n        y = self.mutate_variable_in_method(x, baz)\n        return y\n\n      @nn.jit\n      def mutate_variable_in_method(self, x, baz):\n        baz.value += x\n        return baz.value\n\n    k = random.key(0)\n    x = jnp.ones((1,))\n    variables = Test().init(k, x)\n    np.testing.assert_allclose(\n        variables['test']['baz'],\n        jnp.array([\n            1.0,\n        ]),\n        atol=1e-7,\n    )\n    y, variables = Test().apply(variables, x, mutable=['test'])\n    np.testing.assert_allclose(\n        variables['test']['baz'],\n        jnp.array([\n            2.0,\n        ]),\n        atol=1e-7,\n    )\n\n  def test_module_instance_in_args_transform(self):\n    class Inner(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        baz = self.variable('test', 'baz', jnp.zeros, x.shape)\n        baz.value += x\n        return baz.value\n\n    class Test(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        inner = Inner(name='inner')\n        y = self.call_instance_arg_in_method(x, inner)\n        return y\n\n      @nn.jit\n      def call_instance_arg_in_method(self, x, inner):\n        return inner(x)\n\n    k = random.key(0)\n    x = jnp.ones((1,))\n    variables = Test().init(k, x)\n    np.testing.assert_allclose(\n        variables['test']['inner']['baz'],\n        jnp.array([\n            1.0,\n        ]),\n        atol=1e-7,\n    )\n    y, variables = Test().apply(variables, x, mutable=['test'])\n    np.testing.assert_allclose(\n        variables['test']['inner']['baz'],\n        jnp.array([\n            2.0,\n        ]),\n        atol=1e-7,\n    )\n\n  def test_module_instance_in_args_transform_nested(self):\n    class Inner(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        baz = self.variable('test', 'baz', jnp.zeros, x.shape)\n        baz.value += x\n        return baz.value\n\n    class Outer(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, inner, x):\n        y = self.call_instance_arg_in_method(x, inner)\n        return y\n\n      @nn.jit\n      def call_instance_arg_in_method(self, x, inner):\n        return inner(x)\n\n    class Test(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        inner = Inner(name='inner')\n        outer = Outer(name='outer')\n        return outer(inner, x)\n\n    k = random.key(0)\n    x = jnp.ones((1,))\n    variables = Test().init(k, x)\n    np.testing.assert_allclose(\n        variables['test']['inner']['baz'],\n        jnp.array([\n            1.0,\n        ]),\n        atol=1e-7,\n    )\n    y, variables = Test().apply(variables, x, mutable=['test'])\n    np.testing.assert_allclose(\n        variables['test']['inner']['baz'],\n        jnp.array([\n            2.0,\n        ]),\n        atol=1e-7,\n    )\n\n  def test_nested_variable_passing(self):\n    class NestedVarUser(nn.Module):\n      somevar: nn.Variable\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        self.somevar.value += x\n        return x\n\n    class VarUser(nn.Module):\n      somevar: nn.Variable\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        return NestedVarUser(self.somevar)(x)\n\n    class VarPasser(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        baz = self.variable('test', 'baz', jnp.zeros, x.shape)\n        y = VarUser(baz)(x)\n        return y\n\n    k = random.key(0)\n    x = jnp.ones((1,))\n    variables = VarPasser().init(k, x)\n    np.testing.assert_allclose(\n        variables['test']['baz'],\n        jnp.array([\n            1.0,\n        ]),\n        atol=1e-7,\n    )\n    y, variables = VarPasser().apply(variables, x, mutable=['test'])\n    np.testing.assert_allclose(\n        variables['test']['baz'],\n        jnp.array([\n            2.0,\n        ]),\n        atol=1e-7,\n    )\n\n  def test_returned_module_warning(self):\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        return x\n\n    class Bar(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        f = self._helper()\n        return f(x)\n\n      @nn.jit\n      def _helper(self):\n        return Foo()\n\n    b = Bar()\n    with self.assertRaises(errors.TransformedMethodReturnValueError):\n      b.apply({}, jnp.ones(2))\n\n  def test_returned_variable_warning(self):\n    class Bar(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        f = self._helper()\n        return f(x)\n\n      @nn.jit\n      def _helper(self):\n        return nn.Variable(None, None, None, False)\n\n    b = Bar()\n    with self.assertRaises(errors.TransformedMethodReturnValueError):\n      b.apply({}, jnp.ones(2))\n\n  def test_nowrap(self):\n    class Bar(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        return self._helper(x)\n\n      @nn.nowrap\n      def _helper(self, x):\n        if len(nn.module._context.module_stack) > 2:  # pylint: disable=protected-access\n          raise ValueError('Module stack too deep.')\n        return x\n\n    b = Bar()\n    b.apply({}, jnp.ones(2))\n\n  def test_map_variables_tied_autoencoder(self):\n    def trans(variables):\n      return jax.tree_util.tree_map(lambda x: x.T, variables)\n\n    class TiedAutencoder(nn.Module):\n      features: int\n      latents: int\n\n      @nn.compact\n      def _call(self, x, decode):\n        def f(self):\n          return nn.Dense(\n              self.features if decode else self.latents, use_bias=False\n          )(x)\n\n        if decode:\n          map_fn = trans\n        else:\n          map_fn = lambda x: x\n        return nn.map_variables(f, 'params', map_fn, map_fn, mutable=True)(self)\n\n      def encode(self, x):\n        return self._call(x, False)\n\n      def decode(self, x):\n        return self._call(x, True)\n\n      def __call__(self, x):\n        return self.decode(self.encode(x))\n\n    x = jnp.ones((2, 4))\n    ae = TiedAutencoder(4, 5)\n    variables = ae.init(random.key(0), x)\n    param_shapes = jax.tree_util.tree_map(jnp.shape, variables['params'])\n    self.assertEqual(param_shapes, {'Dense_0': {'kernel': (4, 5)}})\n\n  def test_map_variables_bit_weights(self):\n    class BitWeights(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        def sign(x):\n          return jax.tree_util.tree_map(jnp.sign, x)\n\n        BitDense = nn.map_variables(nn.Dense, 'params', sign, init=True)\n        return BitDense(4)(x)\n\n    bw = BitWeights()\n    x = jnp.ones((2, 4))\n    y, variables = bw.init_with_output(random.key(0), x)\n    y_2 = bw.apply(variables, x)\n    np.testing.assert_allclose(y, y_2)\n\n  def test_remat_scan(self):\n    class BigModel(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        DenseStack = nn.remat_scan(nn.Dense, lengths=(100,))\n        return DenseStack(8, name='dense_stack')(x)\n\n    x = jnp.ones((2, 8))\n    model = BigModel()\n    variables = model.init(random.key(0), x)\n    param_shapes = jax.tree_util.tree_map(jnp.shape, variables['params'])\n    self.assertEqual(param_shapes['dense_stack']['kernel'], (100, 8, 8))\n    self.assertEqual(param_shapes['dense_stack']['bias'], (100, 8))\n    y = model.apply(variables, x)\n    self.assertEqual(y.shape, (2, 8))\n\n  def test_vjp(self):\n    class Bar(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, y):\n        p = self.param('test', nn.initializers.constant(0.5), ())\n        self.variable('state', 'counter', lambda: 0)\n        return p * x * y\n\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, y):\n        z, bwd = nn.vjp(Bar.__call__, Bar(), x, y)\n        return bwd(jnp.ones(z.shape))\n\n    x = jnp.array([1.0, 2.0, 3.0])\n    y = jnp.array([4.0, 5.0, 6.0])\n    params = Foo().init(random.key(0), x, y)\n    params_grad, x_grad, y_grad = Foo().apply(params, x, y)\n    self.assertEqual(\n        params_grad,\n        {\n            'params': nn.FrozenDict({'test': 32.0}),\n        },\n    )\n    np.testing.assert_allclose(x_grad, [2.0, 2.5, 3.0])\n    np.testing.assert_allclose(y_grad, [0.5, 1.0, 1.5])\n\n  def test_jvp(self):\n    class Bar(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        p = self.param('test', nn.initializers.zeros, ())\n        self.variable('state', 'counter', lambda: 0)\n        return p * x\n\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        bar = Bar()\n        vars_t = jax.tree_util.tree_map(\n            jnp.ones_like, bar.variables.get('params', {})\n        )\n        _, out_t = nn.jvp(\n            Bar.__call__, bar, (x,), (jnp.zeros_like(x),), {'params': vars_t}\n        )\n        return out_t\n\n    x = jnp.ones((3,))\n    params = Foo().init(random.key(0), x)\n    y_t = Foo().apply(params, x)\n    np.testing.assert_allclose(y_t, jnp.ones_like(x))\n\n  def test_complicated_alias_mutation(self):\n    class A(nn.Module):\n      b: nn.Module\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        return self.b(x)\n\n    class B(nn.Module):\n      c: nn.Module\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        y = C(name='outer_c')(x)\n        z = self.c(x)\n        return z\n\n    class C(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        initialized = self.has_variable('muts', 'v')\n        v = self.variable('muts', 'v', lambda: jnp.zeros_like(x))\n        if initialized:\n          v.value += x\n        return x\n\n    a = A(b=B(c=C()))\n    k = random.key(0)\n    x = jnp.ones((1,), jnp.float32)\n    vs = a.init(k, x)\n    y, vs_new = a.apply(\n        vs,\n        x,\n        mutable=[\n            'muts',\n        ],\n    )\n    np.testing.assert_array_equal(\n        vs_new['muts']['b']['c']['v'], jnp.array([1.0], jnp.float32)\n    )\n    np.testing.assert_array_equal(\n        vs_new['muts']['b']['outer_c']['v'], jnp.array([1.0], jnp.float32)\n    )\n\n  def test_custom_vjp(self):\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        def f(mdl, x):\n          return mdl(x)\n\n        def fwd(mdl, x):\n          return nn.vjp(f, mdl, x)\n\n        def bwd(vjp_fn, y_t):\n          params_t, input_t = vjp_fn(y_t)\n          params_t = jax.tree_util.tree_map(jnp.sign, params_t)\n          return params_t, input_t\n\n        sign_grad = nn.custom_vjp(f, forward_fn=fwd, backward_fn=bwd)\n        return sign_grad(nn.Dense(1), x).reshape(())\n\n    x = jnp.ones((2,))\n    variables = Foo().init(random.key(0), x)\n    grad = jax.grad(Foo().apply)(variables, x)\n    for grad_leaf in jax.tree_util.tree_leaves(grad):\n      self.assertTrue(jnp.all(jnp.abs(grad_leaf) == 1.0))\n\n  def test_transform_with_setup_and_methods_on_submodules(self):\n    # This is the archetypal example motivating the introduction of\n    # SetupState as a triple-enum to handle multiple setup() calls\n    # across transform boundaries and scope reuse.\n    class Foo(nn.Module):\n\n      def setup(self):\n        self.inner = nn.Dense(2)\n\n      def helper(self, x, m):\n        return m(x)\n\n      def __call__(self, x):\n        return self.helper(x, self.inner)\n\n    k = random.key(0)\n    x = jnp.ones((2,))\n    vs_foo = Foo().init(k, x)\n\n    class Bar(nn.Module):\n\n      def setup(self):\n        self.inner = nn.Dense(2)\n\n      @nn.jit\n      def helper(self, x, m):\n        return m(x)\n\n      @nn.jit\n      def __call__(self, x):\n        return self.helper(x, self.inner)\n\n    vs_bar = Bar().init(k, x)\n    self.assertTrue(\n        tree_equals(\n            jax.tree_util.tree_map(jnp.shape, vs_foo),\n            jax.tree_util.tree_map(jnp.shape, vs_bar),\n        )\n    )\n\n  def test_transform_methods_on_submodules_still_reserve_names(self):\n    class Foo(nn.Module):\n\n      @nn.jit\n      def helper(self, x, m):\n        conflicting_a = nn.Dense(2, name='a')\n        return m(x)\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        a = nn.Dense(2, name='a')\n        return self.helper(x, a)\n\n    k = random.key(0)\n    x = jnp.ones((2,))\n    with self.assertRaises(errors.NameInUseError):\n      vs = Foo().init(k, x)\n\n  def test_transform_setup_still_reserve_names(self):\n    class Identity(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        return x\n\n    class Test(nn.Module):\n\n      def setup(self):\n        self.sub = Identity()\n        self.sub = Identity()\n\n      @nn.jit\n      def __call__(self, x):\n        return x\n\n    k = random.key(0)\n    x = jnp.array([1.0])\n\n    with self.assertRaises(errors.NameInUseError):\n      y = Test().init(k, x)\n\n  def test_transform_with_setup_and_methods_on_submodule_pytrees(self):\n    class Foo(nn.Module):\n\n      def setup(self):\n        self.inners = [nn.Dense(2), nn.Dense(2)]\n\n      def helper(self, x, ms):\n        return ms[0](x) + ms[1](x)\n\n      @nn.fold_rngs\n      def __call__(self, x):\n        return self.helper(x, self.inners)\n\n    class JitFoo(nn.Module):\n\n      def setup(self):\n        self.inners = [nn.Dense(2), nn.Dense(2)]\n\n      def helper(self, x, ms):\n        return ms[0](x) + ms[1](x)\n\n      @nn.jit\n      def __call__(self, x):\n        return self.helper(x, self.inners)\n\n    k = random.key(0)\n    x = jnp.ones((2,))\n\n    vs_0 = Foo().init(k, x)\n    vs_1 = JitFoo().init(k, x)\n\n    self.assertTrue(tree_allclose(vs_0, vs_1))\n\n  def test_transform_setup_still_reserve_names_pytrees(self):\n    class Identity(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        return x\n\n    class Test(nn.Module):\n\n      def setup(self):\n        self.subs = [Identity(), Identity()]\n        self.subs = [Identity(), Identity()]\n\n      @nn.jit\n      def __call__(self, x):\n        return x\n\n    k = random.key(0)\n    x = jnp.array([1.0])\n\n    msg = r'Could not create submodule \"subs_0\".*'\n    with self.assertRaisesRegex(errors.NameInUseError, msg):\n      y = Test().init(k, x)\n\n  def test_scan_of_setup_parameter(self):\n    class Body(nn.Module):\n\n      def setup(self):\n        self.dense = nn.Dense(1)\n        self.p = self.param('p', lambda k: jnp.ones((1,)))\n\n      def __call__(self, x):\n        return self.dense(x) + self.p, None\n\n    scanbody = nn.scan(\n        Body, variable_axes={'params': 0}, split_rngs={'params': True}, length=2\n    )\n    k = random.key(0)\n    x = jnp.ones((1,))\n    vs = scanbody().init(k, x)\n    y = scanbody().apply(vs, x)\n\n  def test_multi_method_class_transform(self):\n    class Foo(nn.Module):\n\n      def setup(self):\n        self.dense0 = nn.Dense(2)\n        self.dense1 = nn.Dense(2)\n\n      def method_0(self, x):\n        return self.dense0(x), x\n\n      def method_1(self, x, y):\n        return self.dense1(x) + y, None\n\n    class Bar(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        ScanFoo = nn.scan(\n            Foo,\n            methods={\n                'method_0': dict(\n                    variable_axes={'params': 0},\n                    split_rngs={'params': True},\n                    in_axes=nn.broadcast,\n                    out_axes=0,\n                    length=3,\n                ),\n                'method_1': dict(\n                    variable_axes={'params': 0},\n                    split_rngs={'params': True},\n                    in_axes=0,\n                    length=3,\n                ),\n            },\n        )\n        sf = ScanFoo()\n        y, ys = sf.method_0(x)\n        z, _ = sf.method_1(y, ys)\n        return z\n\n    k = random.key(0)\n    x = random.uniform(random.key(1), (2, 2))\n    vs = Bar().init(k, x)\n    y = Bar().apply(vs, x)\n\n  def test_compact_aliasing_collision(self):\n    class Foo(nn.Module):\n      m1: nn.Module\n      m2: nn.Module\n\n      @nn.compact\n      def __call__(self, x):\n        x = self.m2(self.m1(x))\n        return x\n\n    class Bar(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        dense = nn.Dense(2)\n        x = nn.jit(Foo)(dense, dense)(x)\n        return x\n\n    k = random.key(0)\n    x = jnp.zeros((2, 2))\n    _ = Bar().init(k, x)\n\n  def test_compact_aliasing_collision_arg_and_attrib(self):\n    class Foo(nn.Module):\n      m1: nn.Module\n\n      @nn.compact\n      def __call__(self, x, m2):\n        x = m2(self.m1(x))\n        return x\n\n    class Bar(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        dense = nn.Dense(2)\n        x = nn.jit(Foo)(dense)(x, dense)\n        return x\n\n    k = random.key(0)\n    x = jnp.zeros((2, 2))\n    _ = Bar().init(k, x)\n\n  def test_jit_with_setup_helpers(self):\n    class Foo(nn.Module):\n\n      def setup(self):\n        self.a = nn.Dense(2)\n        self.setup_helper()\n\n      def setup_helper(self):\n        self.b = nn.Dense(2)\n\n      @nn.fold_rngs\n      def __call__(self, x):\n        return self.b(self.a(x))\n\n    class JitFoo(nn.Module):\n\n      def setup(self):\n        self.a = nn.Dense(2)\n        self.setup_helper()\n\n      def setup_helper(self):\n        self.b = nn.Dense(2)\n\n      @nn.jit\n      def __call__(self, x):\n        return self.b(self.a(x))\n\n    k = random.key(0)\n    x = jnp.ones((2, 2))\n    vs = JitFoo().init(k, x)\n    y0 = JitFoo().apply(vs, x)\n    vs = Foo().init(k, x)\n    y1 = Foo().apply(vs, x)\n    np.testing.assert_array_equal(y0, y1)\n\n  def test_jit_kwargs(self):\n    class Foo(nn.Module):\n\n      @nn.jit\n      def __call__(self, a: jax.Array, b: jax.Array):\n        return a + b\n\n    m = Foo()\n    y = m.apply({}, jnp.array(1.0), b=jnp.array(2.0))\n\n    np.testing.assert_array_equal(y, jnp.array(3.0))\n\n  def test_jit_static_argnames(self):\n    s = None\n\n    class Foo(nn.Module):\n\n      @partial(nn.jit, static_argnames=['b'])\n      def __call__(self, a: jax.Array, b: str):\n        nonlocal s\n        s = b\n        return a\n\n    m = Foo()\n    y = m.apply({}, jnp.array(1.0), b='hi')\n\n    self.assertEqual(s, 'hi')\n    np.testing.assert_array_equal(y, jnp.array(1.0))\n\n  def test_jit_and_sow(self):\n    class Inner(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        self.sow('intermediates', 'loss', jnp.sum(x))\n        return x + 1\n\n    class Outer(nn.Module):\n\n      def setup(self):\n        self.inner = Inner()\n\n      @nn.jit\n      def __call__(self, x):\n        return self.inner(x)\n\n    m = Outer()\n    x = jnp.ones((2, 2))\n    vs = m.init(random.key(0), x)\n    y, updates = m.apply(vs, x, mutable=['intermediates'])\n    np.testing.assert_array_equal(\n        updates['intermediates']['inner']['loss'], 4.0\n    )\n    np.testing.assert_array_equal(y, 2)\n\n  def test_fold_rngs(self):\n    class Foo(nn.Module):\n\n      def __call__(self, use_jit: bool):\n        def f(foo: Foo):\n          return foo.make_rng('params')\n\n        if use_jit:\n          key = nn.jit(f)(self)\n        else:\n          key = nn.fold_rngs(f)(self)\n\n        return key\n\n    foo = Foo()\n    key_jit = foo.apply({}, True, rngs={'params': random.key(0)})\n    key_fold_rngs = foo.apply({}, False, rngs={'params': random.key(0)})\n\n    self.assert_keys_equal(key_jit, key_fold_rngs)\n\n  def test_same_key(self):\n\n    class Block(nn.Module):\n\n      @nn.jit\n      @nn.compact\n      def __call__(self, carry, inputs):\n        # dump_rng_info(self)\n        key = self.make_rng('params')\n        # y = jax.random.uniform(self.make_rng('params'), (2,))\n        return carry, key\n\n    class Transformer(nn.Module):\n\n      @nn.compact\n      def __call__(self):\n        num_blocks = 10\n        carry, key = nn.scan(\n            Block,\n            variable_axes={'params': 0},\n            split_rngs={'params': True},\n            # length=num_blocks,\n        )()(None, jnp.arange(num_blocks))\n        return key\n\n    model = Transformer()\n    keys1, _ = model.init_with_output(jax.random.key(1))\n    keys2, _ = model.init_with_output(jax.random.key(1))\n    keys3, _ = model.init_with_output(jax.random.key(1))\n    keys4, _ = model.init_with_output(jax.random.key(1))\n\n    self.assert_keys_equal(keys1, keys2)\n    self.assert_keys_equal(keys2, keys3)\n    self.assert_keys_equal(keys2, keys3)\n\n  def test_jit_repr_hash(self):\n    n = 0\n\n    @partial(jax.jit, static_argnums=0)\n    def f(obj):\n      nonlocal n\n      n += 1\n      return None\n\n    f(_HashableProxy.from_module(nn.Dense(10)))\n    self.assertEqual(n, 1)\n    f(_HashableProxy.from_module(nn.Dense(10)))\n    self.assertEqual(n, 1)\n\n    f(_HashableProxy.from_module(nn.Dense(20)))\n    self.assertEqual(n, 2)\n    f(_HashableProxy.from_module(nn.Dense(20)))\n    self.assertEqual(n, 2)\n\n  def test_jit_reuse(self):\n    n = 0\n\n    class Foo(nn.Module):\n\n      @nn.jit\n      def __call__(self, x):\n        nonlocal n\n        n += 1\n        return x\n\n    x = jnp.array(1.0)\n    m = Foo()\n\n    self.assertEqual(n, 0)\n\n    y = m.apply({}, x)\n    self.assertEqual(n, 1)\n    y = m.apply({}, x)\n    self.assertEqual(n, 1)\n\n  def test_jit_recursive(self):\n    n = 0\n\n    class Foo(nn.Module):\n\n      @partial(nn.jit, static_argnames='recurse_once')\n      def __call__(self, x, *, recurse_once: bool = True):\n        nonlocal n\n        n += 1\n        if recurse_once:\n          x = self(x, recurse_once=False)\n        return x + 1\n\n    x = jnp.array(1.0)\n    m = Foo()\n\n    self.assertEqual(n, 0)\n\n    y = m.apply({}, x)\n    self.assertEqual(n, 2)\n    y = m.apply({}, x)\n    self.assertEqual(n, 2)\n\n  @parameterized.named_parameters(('class', True), ('method', False))\n  def test_jit_reuse_hash(self, jit_class: bool):\n    n = 0\n\n    class Foo(nn.Module):\n      key: int\n\n      def __call__(self, x):\n        nonlocal n\n        n += 1\n        return x\n\n    if jit_class:\n      Foo = nn.jit(Foo)\n    else:  # jit method\n      Foo.__call__ = nn.jit(Foo.__call__)\n\n    x = jnp.array(1.0)\n    self.assertEqual(n, 0)\n\n    y = Foo(1).apply({}, x)\n    self.assertEqual(n, 1)\n    y = Foo(1).apply({}, x)\n    self.assertEqual(n, 1)\n    y = Foo(2).apply({}, x)\n    self.assertEqual(n, 2)\n    y = Foo(2).apply({}, x)\n    self.assertEqual(n, 2)\n\n  @parameterized.named_parameters(('class', True), ('method', False))\n  def test_jit_reuse_submodules(self, jit_class: bool):\n    test = self\n    n = 0\n    key = None\n    name = None\n\n    class Foo(nn.Module):\n      key: int\n\n      def __call__(self, x):\n        nonlocal n, key, name\n        n += 1\n        key = self.key\n        name = self.name\n        return x\n\n    if jit_class:\n      Foo = nn.jit(Foo)\n    else:  # jit method\n      Foo.__call__ = nn.jit(Foo.__call__)\n\n    class Parent(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n\n        for i in range(3):\n          m = Foo(i)\n          y = m(x)\n          test.assertEqual(key, i)\n          test.assertEndsWith(name, f'Foo_{i}')\n          test.assertEqual(n, i + 1)\n\n    x = jnp.array(1.0)\n    self.assertEqual(n, 0)\n\n    y = Parent().apply({}, x)\n\n  @parameterized.named_parameters(('class', True), ('method', False))\n  def test_jit_stateful_submodules(self, jit_class: bool):\n    n = 0\n\n    class Foo(nn.Module):\n      key: int\n\n      @nn.compact\n      def __call__(self, x):\n        nonlocal n\n        n += 1\n        count = self.variable('counts', 'count', lambda: 0)\n        if not self.is_initializing():\n          count.value += 1\n        return x\n\n    if jit_class:\n      Foo = nn.jit(Foo)\n    else:  # jit method\n      Foo.__call__ = nn.jit(Foo.__call__)\n\n    class Parent(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        for _ in range(3):\n          m = Foo(0)\n          x = m(x)\n        return x\n\n    m = Parent()\n    x = jnp.array(1.0)\n    counts = m.init({}, x)['counts']\n    self.assertEqual(n, 1)\n\n    y, updates = m.apply({'counts': counts}, x, mutable=['counts'])\n    counts = updates['counts']\n    self.assertEqual(n, 2)\n\n    for count in jax.tree.leaves(counts):\n      self.assertEqual(count, 1)\n\n    y, updates = m.apply({'counts': counts}, x, mutable=['counts'])\n    counts = updates['counts']\n    self.assertEqual(n, 2)\n\n    for count in jax.tree.leaves(counts):\n      self.assertEqual(count, 2)\n\n  @parameterized.named_parameters(('class', True), ('method', False))\n  def test_jit_reuse_nested_submodules(self, jit_class: bool):\n    test = self\n    n = 0\n\n    class Foo(nn.Module):\n      key: int\n\n      def __call__(self, x):\n        return x\n\n    class Parent(nn.Module):\n      submodules: list[Foo]\n\n      @nn.compact\n      def __call__(self, x):\n        nonlocal n\n        n += 1\n        for i, m in enumerate(self.submodules):\n          x = m(x)\n\n        return x\n\n    if jit_class:\n      Parent = nn.jit(Parent)\n    else:  # jit method\n      Parent.__call__ = nn.jit(Parent.__call__)\n\n    x = jnp.array(1.0)\n    self.assertEqual(n, 0)\n\n    y = Parent([Foo(1), Foo(2)]).apply({}, x)\n    self.assertEqual(n, 1)\n    y = Parent([Foo(1), Foo(2)]).apply({}, x)\n    self.assertEqual(n, 1)\n    y = Parent([Foo(3), Foo(4)]).apply({}, x)\n    self.assertEqual(n, 2)\n    y = Parent([Foo(3), Foo(4)]).apply({}, x)\n    self.assertEqual(n, 2)\n\n  def test_jit_hashes_serializable_types(self):\n    class Node:\n\n      def __init__(self, a: int):\n        self.a = a\n\n      def __hash__(self):\n        # test object is not being passed as static\n        raise Exception('this should not be called')\n\n      def __eq__(self, __value, /):\n        raise Exception('this should not be called')\n\n    def to_dict(node: Node):\n      return {'a': node.a}\n\n    def from_dict(node: Node, d: dict[str, Any]):\n      node.a = d['a']\n      return node\n\n    serialization.register_serialization_state(Node, to_dict, from_dict)\n\n    try:\n      n = 0\n\n      class Foo(nn.Module):\n        node: Node\n\n        @nn.jit\n        @nn.compact\n        def __call__(self, x):\n          nonlocal n\n          n += 1\n          return self.node.a + nn.Dense(2)(x)\n\n      m = Foo(Node(1))\n      m.init_with_output(random.key(0), jnp.ones((2, 2)))\n      self.assertEqual(n, 1)\n      m.init_with_output(random.key(0), jnp.ones((2, 2)))\n      self.assertEqual(n, 1)\n    finally:\n      del serialization._STATE_DICT_REGISTRY[Node]\n\n  def test_while_loop(self):\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        key_zero = random.key(0)\n        key_zero = jnp.broadcast_to(key_zero, (2, *key_zero.shape))\n        self.param('inc', lambda _: 1)\n        self.put_variable('state', 'acc', 0)\n        self.put_variable('state', 'rng_params', key_zero)\n        self.put_variable('state', 'rng_loop', key_zero)\n\n        def cond_fn(mdl, c):\n          acc = mdl.get_variable('state', 'acc')\n          return acc < x\n\n        def body_fn(mdl, c):\n          i = mdl.get_variable('state', 'acc')\n          p_rng = mdl.make_rng('params')\n          l_rng = mdl.make_rng('loop')\n          mdl.put_variable(\n              'state',\n              'rng_params',\n              mdl.get_variable('state', 'rng_params').at[i].set(p_rng),\n          )\n          mdl.put_variable(\n              'state',\n              'rng_loop',\n              mdl.get_variable('state', 'rng_loop').at[i].set(l_rng),\n          )\n          inc = mdl.get_variable('params', 'inc')\n          mdl.put_variable('state', 'acc', i + inc)\n          return c\n\n        return nn.while_loop(\n            cond_fn,\n            body_fn,\n            self,\n            (),\n            carry_variables='state',\n            split_rngs={'params': False, 'loop': True},\n        )\n\n    x = 2\n    mdl = Foo()\n    _, vars = mdl.apply(\n        {},\n        x,\n        mutable=True,\n        rngs={'params': random.key(1), 'loop': random.key(2)},\n    )\n    self.assertEqual(vars['state']['acc'], x)\n    self.assertTrue(\n      jnp.equal(vars['state']['rng_params'][0], vars['state']['rng_params'][1])\n    )\n    with jax.debug_key_reuse(False):\n      self.assertFalse(\n        jnp.equal(\n          vars['state']['rng_loop'][0],\n          vars['state']['rng_loop'][1],\n        )\n      )\n\n  def test_while_loop_denylist_split_rngs(self):\n    def cond_fn(module, carry):\n      del module\n      return carry < 10\n\n    def body_fn(module, carry):\n      rng = module.make_rng('random')\n      return carry + 1\n\n    class Foo(nn.Module):\n\n      def setup(self):\n        pass\n\n    f = Foo().bind({}, rngs={'random': jax.random.PRNGKey(0)})\n    init = jnp.zeros(())\n\n    result = nn.while_loop(\n        cond_fn,\n        body_fn,\n        f,\n        init,\n        split_rngs={\n            'params': False,\n            nn.DenyList(('params',)): True,\n        },\n    )\n    np.testing.assert_array_equal(result, jnp.array(10.0))\n\n  def test_cond(self):\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, pred):\n        self.variable('state', 'true_count', lambda: 0)\n        self.variable('state', 'false_count', lambda: 0)\n\n        def true_fn(mdl, x):\n          mdl.variable('state', 'true_count').value += 1\n          return nn.Dense(2, name='dense')(x)\n\n        def false_fn(mdl, x):\n          mdl.variable('state', 'false_count').value += 1\n          return -nn.Dense(2, name='dense')(x)\n\n        return nn.cond(pred, true_fn, false_fn, self, x)\n\n  def test_switch(self):\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, pred):\n        self.variable('state', 'a_count', lambda: 0)\n        self.variable('state', 'b_count', lambda: 0)\n        self.variable('state', 'c_count', lambda: 0)\n\n        def a_fn(mdl, x):\n          mdl.variable('state', 'a_count').value += 1\n          return nn.Dense(2, name='dense')(x)\n\n        def b_fn(mdl, x):\n          mdl.variable('state', 'b_count').value += 1\n          return -nn.Dense(2, name='dense')(x)\n\n        def c_fn(mdl, x):\n          mdl.variable('state', 'c_count').value += 1\n          return nn.Dense(2, name='dense')(x)\n\n        return nn.switch(pred, [a_fn, b_fn, c_fn], self, x)\n\n    x = jnp.ones((1, 3))\n    foo = Foo()\n    y1, vars = foo.init_with_output(random.key(0), x, 0)\n    self.assertEqual(vars['state'], {'a_count': 1, 'b_count': 0, 'c_count': 0})\n    y2, updates = foo.apply(vars, x, 1, mutable='state')\n    vars = copy(vars, updates)\n    self.assertEqual(vars['state'], {'a_count': 1, 'b_count': 1, 'c_count': 0})\n    np.testing.assert_allclose(y1, -y2)\n    y3, updates = foo.apply(vars, x, 2, mutable='state')\n    vars = copy(vars, updates)\n    self.assertEqual(vars['state'], {'a_count': 1, 'b_count': 1, 'c_count': 1})\n    np.testing.assert_allclose(y1, y3)\n\n  def test_switch_multihead(self):\n    class Foo(nn.Module):\n\n      def setup(self) -> None:\n        self.heads = [\n            nn.Sequential([nn.Dense(10), nn.Dense(7), nn.Dense(5)]),\n            nn.Sequential([nn.Dense(11), nn.Dense(5)]),\n            nn.Dense(5),\n        ]\n\n      @nn.compact\n      def __call__(self, x, index):\n        def head_fn(i):\n          def fn(mdl, x):\n            mdl.variable('state', f'{i}_count', lambda: -1).value += 1\n            return mdl.heads[i](x)\n\n          return fn\n\n        branches = [head_fn(i) for i in range(len(self.heads))]\n\n        if self.is_mutable_collection('params'):\n          for branch in branches:\n            _ = branch(self, x)\n\n        return nn.switch(index, branches, self, x)\n\n    x = jnp.ones((1, 3))\n    foo = Foo()\n    y1, vars = foo.init_with_output(random.key(0), x, 0)\n    self.assertEqual(vars['state'], {'0_count': 1, '1_count': 0, '2_count': 0})\n    y2, updates = foo.apply(vars, x, 1, mutable='state')\n    vars = copy(vars, updates)\n    self.assertEqual(vars['state'], {'0_count': 1, '1_count': 1, '2_count': 0})\n    y3, updates = foo.apply(vars, x, 2, mutable='state')\n    vars = copy(vars, updates)\n    self.assertEqual(vars['state'], {'0_count': 1, '1_count': 1, '2_count': 1})\n\n    self.assertEqual(\n        vars['params']['heads_0']['layers_0']['kernel'].shape, (3, 10)\n    )\n    self.assertEqual(vars['params']['heads_0']['layers_0']['bias'].shape, (10,))\n    self.assertEqual(\n        vars['params']['heads_0']['layers_1']['kernel'].shape, (10, 7)\n    )\n    self.assertEqual(vars['params']['heads_0']['layers_1']['bias'].shape, (7,))\n    self.assertEqual(\n        vars['params']['heads_0']['layers_2']['kernel'].shape, (7, 5)\n    )\n    self.assertEqual(vars['params']['heads_0']['layers_2']['bias'].shape, (5,))\n\n    self.assertEqual(\n        vars['params']['heads_1']['layers_0']['kernel'].shape, (3, 11)\n    )\n    self.assertEqual(vars['params']['heads_1']['layers_0']['bias'].shape, (11,))\n    self.assertEqual(\n        vars['params']['heads_1']['layers_1']['kernel'].shape, (11, 5)\n    )\n    self.assertEqual(vars['params']['heads_1']['layers_1']['bias'].shape, (5,))\n\n    self.assertEqual(vars['params']['heads_2']['kernel'].shape, (3, 5))\n    self.assertEqual(vars['params']['heads_2']['bias'].shape, (5,))\n\n  def test_lift_instance_error(self):\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        return nn.checkpoint(nn.Dense(2))(x)\n\n    with self.assertRaises(errors.TransformTargetError):\n      Foo().init(random.key(0), jnp.zeros((2, 3)))\n\n  def test_scan_compact_count(self):\n    class Foo(nn.Module):\n      num_layers: int = 5\n\n      @nn.compact\n      def __call__(self, x):\n        def body_fn(mdl, x):\n          return nn.Dense(features=x.shape[-1])(x), ()\n\n        x, _ = nn.scan(\n            body_fn,\n            length=self.num_layers,\n            variable_axes={'params': 0},\n            split_rngs={'params': True},\n        )(self, x)\n        return x\n\n    m = Foo()\n    x = jnp.ones((3,))\n    v = m.init(jax.random.key(0), x)\n    self.assertEqual(v['params']['Dense_0']['kernel'].shape, (5, 3, 3))\n    m.apply(v, x)\n\n  def test_bound_methods_in_direct_transforms(self):\n    class CondModel(nn.Module):\n\n      def setup(self):\n        self.dense = nn.Dense(3)\n\n      def f1(self, arr):\n        arr = self.dense(arr)\n        return arr\n\n      def f2(self, arr):\n        _ = self.dense(arr)\n        return arr\n\n      def __call__(self, x):\n        return nn.cond(x.sum() > 0, self.f1, self.f2, self, x)\n\n    cond_model = CondModel()\n\n    output, init_params = jax.jit(cond_model.init_with_output)(\n        jax.random.key(0), x=jnp.ones(3)\n    )\n\n  def test_add_metadata_axis(self):\n    vars_copy = None\n\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x):\n        nonlocal vars_copy\n        kernel_init = nn.with_partitioning(\n            nn.initializers.lecun_normal(), ('foo', 'bar')\n        )\n        vars_copy = self.variables\n        return nn.Dense(\n            4, kernel_init=kernel_init, use_bias=False, name='dense'\n        )(x)\n\n    class Test(nn.Module):\n\n      @partial(\n          nn.add_metadata_axis,\n          variable_axes={'params': 0},\n          metadata_params={nn.PARTITION_NAME: 'baz'},\n      )\n      @nn.compact\n      def __call__(self, x):\n        return Foo(name='foo')(x)\n\n    k = random.key(0)\n    x = jnp.ones((4, 4), dtype=jnp.float32)\n    vs = Test().init(k, x)\n    y = Test().apply(vs, x)\n    outer_expect = jax.tree_util.tree_map(\n        jnp.shape,\n        freeze({\n            'params': {\n                'foo': {\n                    'dense': {\n                        'kernel': nn.Partitioned(\n                            jnp.ones((4, 4)), names=('baz', 'foo', 'bar')\n                        )\n                    }\n                }\n            }\n        }),\n    )\n    inner_expect = jax.tree_util.tree_map(\n        jnp.shape,\n        freeze({\n            'params': {\n                'dense': {\n                    'kernel': nn.Partitioned(\n                        jnp.ones((4, 4)), names=('foo', 'bar')\n                    )\n                }\n            }\n        }),\n    )\n    self.assertEqual(jax.tree_util.tree_map(jnp.shape, vs), outer_expect)\n    self.assertEqual(jax.tree_util.tree_map(jnp.shape, vars_copy), inner_expect)\n\n  def test_outer_setup_called_with_sharing_across_transforms(self):\n    class A(nn.Module):\n\n      def setup(self):\n        self.foo = self.param('foo', nn.initializers.zeros, (2, 2), jnp.float32)\n\n      def __call__(self, x):\n        return self.foo\n\n    class B(nn.Module):\n      a: Any\n\n      @nn.compact\n      def __call__(self, x):\n        return self.a(x)\n\n    class C(nn.Module):\n\n      def setup(self):\n        self.a = A()\n        self.b = nn.jit(B)(self.a)\n\n      def __call__(self, x):\n        b = self.b(x)\n        a = self.a(x)\n        return a + b\n\n    k = random.key(0)\n    x = random.randint(k, (2, 2), minval=0, maxval=10)\n    vs = C().init(k, x)\n    y = C().apply(vs, x)\n    outer_expect = jax.tree_util.tree_map(\n        jnp.shape, freeze({'params': {'a': {'foo': jnp.zeros((2, 2))}}})\n    )\n    self.assertEqual(jax.tree_util.tree_map(jnp.shape, vs), outer_expect)\n\n  def test_grad_simple(self):\n    class LearnScale(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, y):\n        p = self.param('scale', nn.initializers.ones_init(), ())\n        return jnp.sum(p * x * y)\n\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, y):\n        x_grad, y_grad = nn.grad(\n            lambda mdl, x, y: mdl(x, y), LearnScale(), x, y\n        )\n        return x_grad, y_grad\n\n    x = random.uniform(random.key(1), (4,))\n    y = random.uniform(random.key(2), (4,))\n    vs = Foo().init(random.key(0), x, y)\n\n    x_grad, y_grad = Foo().apply(vs, x, y)\n    self.assertTrue(tree_allclose(x_grad, y))\n    self.assertTrue(tree_allclose(y_grad, x))\n\n  def test_grad_simple_with_aux(self):\n    class LearnScale(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, y):\n        p = self.param('scale', nn.initializers.ones_init(), ())\n        return jnp.sum(p * x * y), p\n\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, y):\n        (x_grad, y_grad), aux = nn.grad(\n            lambda mdl, x, y: mdl(x, y), LearnScale(), x, y, has_aux=True\n        )\n        return aux, x_grad, y_grad\n\n    x = random.uniform(random.key(1), (4,))\n    y = random.uniform(random.key(2), (4,))\n    vs = Foo().init(random.key(0), x, y)\n\n    aux, x_grad, y_grad = Foo().apply(vs, x, y)\n    self.assertTrue(tree_allclose(x_grad, y))\n    self.assertTrue(tree_allclose(y_grad, x))\n    self.assertTrue(tree_allclose(aux, vs['params']['LearnScale_0']['scale']))\n\n  def test_value_and_grad_simple(self):\n    class LearnScale(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, y):\n        p = self.param('scale', nn.initializers.ones_init(), ())\n        return jnp.sum(p * x * y)\n\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, y):\n        z, (x_grad, y_grad) = nn.value_and_grad(\n            lambda mdl, x, y: mdl(x, y), LearnScale(), x, y\n        )\n        return z, x_grad, y_grad\n\n    x = random.uniform(random.key(1), (4,))\n    y = random.uniform(random.key(2), (4,))\n    vs = Foo().init(random.key(0), x, y)\n\n    z, x_grad, y_grad = Foo().apply(vs, x, y)\n    self.assertTrue(tree_allclose(x_grad, y))\n    self.assertTrue(tree_allclose(y_grad, x))\n\n  def test_value_and_grad_simple_with_aux(self):\n    class LearnScale(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, y):\n        p = self.param('scale', nn.initializers.ones_init(), ())\n        return jnp.sum(p * x * y), p\n\n    class Foo(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, y):\n        (z, aux), (x_grad, y_grad) = nn.value_and_grad(\n            lambda mdl, x, y: mdl(x, y), LearnScale(), x, y, has_aux=True\n        )\n        return z, aux, x_grad, y_grad\n\n    x = random.uniform(random.key(1), (4,))\n    y = random.uniform(random.key(2), (4,))\n    vs = Foo().init(random.key(0), x, y)\n\n    z, aux, x_grad, y_grad = Foo().apply(vs, x, y)\n    self.assertTrue(tree_allclose(x_grad, y))\n    self.assertTrue(tree_allclose(y_grad, x))\n    self.assertTrue(tree_allclose(aux, vs['params']['LearnScale_0']['scale']))\n\n  def test_value_and_grad_multiscope(self):\n    class Foo(nn.Module):\n      bar: nn.Module\n\n      @nn.compact\n      def __call__(self, x, y):\n        def fn(self, x, y):\n          qup = nn.Dense(y.shape[-1])\n          delta = y - self.bar(qup(x))\n          return jnp.sum(delta**2)\n\n        z, (x_grad, y_grad) = nn.value_and_grad(fn, self, x, y)\n        return z, x_grad, y_grad\n\n    class Baz(nn.Module):\n\n      @nn.compact\n      def __call__(self, x, y):\n        bar = nn.Dense(y.shape[-1])\n        return Foo(bar=bar)(x, y)\n\n    x = random.uniform(random.key(1), (4,))\n    y = random.uniform(random.key(2), (4,))\n    vs = Baz().init(random.key(0), x, y)\n    z, x_grad, y_grad = Baz().apply(vs, x, y)\n\n    def comparison_fn(x, y):\n      w1 = vs['params']['Foo_0']['Dense_0']['kernel']\n      w2 = vs['params']['Dense_0']['kernel']\n      delta = y - jnp.dot(jnp.dot(x, w1), w2)\n      return jnp.sum(delta**2)\n\n    self.assertTrue(tree_allclose(comparison_fn(x, y), z))\n    self.assertTrue(tree_allclose(jax.grad(comparison_fn, 0)(x, y), x_grad))\n    self.assertTrue(tree_allclose(jax.grad(comparison_fn, 1)(x, y), y_grad))\n\n  def test_value_and_grad_multiscope_adopted(self):\n    class Foo(nn.Module):\n      bar: nn.Module\n      qup: nn.Module\n\n      @nn.compact\n      def __call__(self, x, y):\n        def fn(self, x, y):\n          delta = y - self.bar(self.qup(x))\n          return jnp.sum(delta**2)\n\n        z, (x_grad, y_grad) = nn.value_and_grad(fn, self, x, y)\n        return z, x_grad, y_grad\n\n    x = random.uniform(random.key(1), (4,))\n    y = random.uniform(random.key(2), (4,))\n    vs = Foo(bar=nn.Dense(4), qup=nn.Dense(4)).init(random.key(0), x, y)\n    z, x_grad, y_grad = Foo(bar=nn.Dense(4), qup=nn.Dense(4)).apply(vs, x, y)\n\n    def comparison_fn(x, y):\n      w1 = vs['params']['qup']['kernel']\n      w2 = vs['params']['bar']['kernel']\n      delta = y - jnp.dot(jnp.dot(x, w1), w2)\n      return jnp.sum(delta**2)\n\n    self.assertTrue(tree_allclose(comparison_fn(x, y), z))\n    self.assertTrue(tree_allclose(jax.grad(comparison_fn, 0)(x, y), x_grad))\n    self.assertTrue(tree_allclose(jax.grad(comparison_fn, 1)(x, y), y_grad))\n\n  def test_vmap_add_remove_axis_transforms(self):\n    class BoxedData(struct.PyTreeNode, AxisMetadata):\n      value: Any\n      def unbox(self):\n        return self.value\n      def replace_boxed(self, val):\n        return self.replace(value=val)\n      def add_axis(self, index: int, params: dict[Any, Any]):\n        value = jnp.mean(self.value, axis=index)\n        return self.replace(value=value)\n      def remove_axis(self, index: int, params: dict[Any, Any]):\n        value_shape = list(self.value.shape)\n        value_shape.insert(index, params['axis_size'])\n        value = jnp.broadcast_to(self.value, value_shape)\n        return self.replace(value=value)\n\n    class Top(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        VFoo = nn.vmap(\n            Foo, in_axes=0, out_axes=0, variable_axes={'params':0, 'aux': 0},\n            metadata_params={'axis_size': x.shape[0]},\n        )\n        vfoo = VFoo(name=\"vfoo\")\n        y = vfoo(x)\n        y = vfoo(x)\n        assert vfoo.variables['aux']['v'].value.shape == ()\n        return y\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        if self.has_variable('aux', 'v'):\n          assert self.variables['aux']['v'].value.shape == ()\n        boxed_v = self.variable('aux', 'v', lambda: BoxedData(jnp.ones(())))\n        assert self.variables['aux']['v'].value.shape == ()\n        return x\n\n    vs = Top().init(random.key(0), jnp.ones((2,5)))\n    y = Top().apply(vs, jnp.ones((2, 5)))\n    assert vs['aux']['vfoo']['v'].value.shape == ()\n\n  def test_vjp_tracer_leak(self):\n    class LearnScale(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        p = self.param('scale', nn.initializers.zeros, ())\n        return p * x\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        y, bwd = nn.vjp(lambda mdl, x: mdl(x), LearnScale(), x)\n        params_grad, x_grad = bwd(jnp.ones(y.shape))\n        return y, params_grad, x_grad\n    key = jax.random.PRNGKey(0)\n    x = jnp.ones((2, 3))\n    foo = Foo()\n    with jax.check_tracer_leaks():\n      params = foo.init(key, x)\n      foo.apply(params, x)\n\n  @parameterized.named_parameters(\n      ('retracing scan', True), ('simple scan', False)\n  )\n  def test_jit_scan_retracing(self, retracing_scan: bool):\n    num_blocks = 4\n    num_patterns = 4\n    features = 4\n    trace_counts = [0, 0]\n\n    class Block(nn.Module):\n      def setup(self):\n        self.dense = nn.Dense(features, use_bias=False)\n      @nn.jit\n      def __call__(self, x):\n        nonlocal trace_counts\n        trace_counts[1] += 1\n        return self.dense(x)\n\n    class BlockSequence(nn.Module):\n      def setup(self):\n        self.blocks = [Block() for _ in range(num_blocks)]\n      @nn.jit\n      def __call__(self, carry, inputs):\n        nonlocal trace_counts\n        trace_counts[0] += 1\n        for block in self.blocks:\n          carry = block(carry)\n        return carry, inputs\n\n    class Transformer(nn.Module):\n      retracing_scan: bool = True\n      def setup(self):\n        self.scan = nn.scan(\n            BlockSequence,\n            variable_axes={'params': 0},\n            split_rngs={'params': False},\n            length=num_patterns,\n            check_constancy_invariants=retracing_scan,\n        )()\n      def __call__(self, inputs):\n        return self.scan(jnp.zeros_like(inputs), inputs)\n\n    model = Transformer(retracing_scan=retracing_scan)\n    _ = model.init(random.key(0), jnp.ones((num_patterns, features,)))\n    self.assertEqual(trace_counts[0], 2 if retracing_scan else 1)\n    self.assertEqual(trace_counts[1], 2 if retracing_scan else 1)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/partitioning_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.linen.partitioning.\"\"\"\n\nimport jax\nimport jax.numpy as jnp\nfrom absl.testing import absltest, parameterized\nfrom jax import random, sharding\nfrom jax.experimental import mesh_utils\n\nfrom flax import linen as nn\nfrom flax.core import freeze, unfreeze\nfrom flax.linen import partitioning\n\nmock = absltest.mock\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n# Testing constants.\nAXIS_RULES_1 = (('foo', 'data'), ('bar', 'model'), ('baz', None))\nAXIS_RULES_2 = (('foo', 'model'), ('bar', None), ('baz', 'data'))\n\n\nclass PartitioningTest(parameterized.TestCase):\n  def test_axis_rules(self):\n    self.assertEqual(nn.spmd.get_logical_axis_rules(), ())\n    partitioning.set_axis_rules(AXIS_RULES_1)\n    self.assertEqual(nn.spmd.get_logical_axis_rules(), AXIS_RULES_1)\n    self.assertEqual(partitioning.get_axis_rules(), AXIS_RULES_1)\n    partitioning.set_axis_rules(())\n\n  def test_axis_rules_context(self):\n    partitioning.set_axis_rules(AXIS_RULES_1)\n    self.assertEqual(partitioning.get_axis_rules(), AXIS_RULES_1)\n    with partitioning.axis_rules(AXIS_RULES_2):\n      self.assertEqual(partitioning.get_axis_rules(), AXIS_RULES_2)\n    self.assertEqual(partitioning.get_axis_rules(), AXIS_RULES_1)\n\n  def test_logical_to_mesh_axes_resolves_to_none_or_unconstrained(self):\n    unconstrained = jax.sharding.PartitionSpec.UNCONSTRAINED\n    rules = (\n        ('foo', None),\n        ('bad', None),\n        ('bar', unconstrained),\n        ('baz', unconstrained),\n    )\n    self.assertEqual(\n        partitioning.logical_to_mesh_axes(\n            ('foo', 'bad', 'bar', 'baz'), rules=rules\n        ),\n        (None, None, unconstrained, unconstrained),\n    )\n\n  def test_logical_to_mesh_axes(self):\n    axes_0 = ('foo', 'bar')\n    # direct rule assignment\n    self.assertEqual(\n      partitioning.logical_to_mesh_axes(axes_0, rules=AXIS_RULES_1),\n      ('data', 'model'),\n    )\n\n    # Repeated None and Unconstrained\n    unconstrained = jax.sharding.PartitionSpec.UNCONSTRAINED\n    axes_repeated = ('foo', unconstrained, unconstrained, None, None)\n    self.assertEqual(\n        partitioning.logical_to_mesh_axes(axes_repeated, rules=AXIS_RULES_1),\n        ('data', unconstrained, unconstrained, None, None),\n    )\n    # axis rules context\n    with partitioning.axis_rules(AXIS_RULES_1):\n      self.assertEqual(\n        partitioning.logical_to_mesh_axes(axes_0), ('data', 'model')\n      )\n      # nested context\n      with partitioning.axis_rules(AXIS_RULES_2):\n        self.assertEqual(\n          partitioning.logical_to_mesh_axes(axes_0), ('model', None)\n        )\n    # duplicated logical names\n    with partitioning.axis_rules(AXIS_RULES_1):\n      with self.assertRaises(ValueError):\n        partitioning.logical_to_mesh_axes(('foo', 'foo', 'baz'))\n\n  def test_logical_to_mesh_axes_priorities(self):\n    p_rules = (('foo', 'model'), ('bar', 'model'), ('baz', 'data'))\n    with partitioning.axis_rules(p_rules):\n      self.assertEqual(\n        partitioning.logical_to_mesh_axes(('foo', 'bar', 'baz')),\n        ('model', None, 'data'),\n      )\n      self.assertEqual(\n        partitioning.logical_to_mesh_axes(('bar', 'foo', 'baz')),\n        (None, 'model', 'data'),\n      )\n      self.assertEqual(\n        partitioning.logical_to_mesh_axes(('baz', 'bar', 'foo')),\n        ('data', None, 'model'),\n      )\n      self.assertEqual(\n        partitioning.logical_to_mesh_axes(('baz', 'bar', 'foo', 'unassigned')),\n        ('data', None, 'model', None),\n      )\n\n  @parameterized.parameters(\n      dict(\n          rules=(('a', ('model', 'data')), ('b', 'data')),\n          axes=('a', 'b'),\n          expected=(('model', 'data'), None),\n      ),\n      dict(\n          rules=(('a', ('model', 'replica')), ('b', 'data')),\n          axes=('a', 'b'),\n          expected=(('model', 'replica'), 'data'),\n      ),\n      dict(\n          rules=(('a', ('model', 'replica')), ('b', ('data', 'model'))),\n          axes=('a', 'b'),\n          expected=(('model', 'replica'), None),\n      ),\n      dict(\n          rules=(('a', ('model', 'replica')), ('b', 'model')),\n          axes=('a', 'b', 'c'),\n          expected=(('model', 'replica'), None, None),\n      ),\n      dict(rules=(), axes=('a', 'b', 'c'), expected=(None, None, None)),\n      dict(\n          rules=(('a', None), ('a', 'model')),\n          axes=('a', 'b'),\n          expected=(None, None),\n      ),\n      dict(\n          rules=(\n              ('baz', 'data'),\n              ('bar', None),\n              ('foo', 'model'),\n              ('foo', 'data'),\n          ),\n          axes=('baz', 'bar', 'foo'),\n          expected=('data', None, 'model'),\n      ),\n      dict(\n          rules=(('baz', 'data'), ('foo', ('model', 'emb'))),\n          axes=('baz', jax.sharding.PartitionSpec.UNCONSTRAINED, 'foo'),\n          expected=(\n              'data',\n              jax.sharding.PartitionSpec.UNCONSTRAINED,\n              ('model', 'emb'),\n          ),\n      ),\n  )\n  def test_logical_to_mesh_axes_cases(self, rules, axes, expected):\n    with partitioning.axis_rules(rules):\n      result = partitioning.logical_to_mesh_axes(axes)\n    self.assertEqual(result, expected)\n\n  @mock.patch('flax.linen.spmd._with_sharding_constraint')\n  def test_with_sharding_constraint(self, wsc_fn):\n    unconstrained = jax.sharding.PartitionSpec.UNCONSTRAINED\n    arr = jnp.ones((2, 2))\n    axes = ('foo', 'bar')\n    partitioning.set_axis_rules(())\n    _ = partitioning.with_sharding_constraint(arr, axes)\n    wsc_fn.assert_not_called()\n    with partitioning.axis_rules(AXIS_RULES_1):\n      _ = partitioning.with_sharding_constraint(arr, None)\n      wsc_fn.assert_not_called()\n      _ = partitioning.with_sharding_constraint(arr, axes)\n      wsc_fn.assert_called_with(\n          arr, jax.sharding.PartitionSpec('data', 'model'), mesh=None\n      )\n      _ = partitioning.with_sharding_constraint(arr, ('foo', unconstrained))\n      wsc_fn.assert_called_with(\n          arr, jax.sharding.PartitionSpec('data', unconstrained), mesh=None\n      )\n\n  @mock.patch('flax.linen.spmd._with_sharding_constraint')\n  def test_with_sharding_constraint_fallback(self, wsc_fn):\n    arr = jnp.ones((2, 2))\n    with partitioning.axis_rules(AXIS_RULES_1):\n      _ = partitioning.with_sharding_constraint(arr, ('foo', 'not_recognized'))\n      wsc_fn.assert_called_with(\n        arr, jax.sharding.PartitionSpec('data', None), mesh=None\n      )\n      wsc_fn.reset_mock()\n      _ = partitioning.with_sharding_constraint(\n        arr,\n        ('foo', 'not_recognized'),\n        fallback=partitioning.RulesFallback.AXIS_IS_UNSHARDED,\n      )\n      wsc_fn.assert_called_with(\n        arr, jax.sharding.PartitionSpec('data', None), mesh=None\n      )\n      wsc_fn.reset_mock()\n      with self.assertRaises(ValueError):\n        _ = partitioning.with_sharding_constraint(\n          arr,\n          ('foo', 'not_recognized'),\n          fallback=partitioning.RulesFallback.RAISE_ERROR,\n        )\n      wsc_fn.assert_not_called()\n      _ = partitioning.with_sharding_constraint(\n        arr,\n        ('foo', 'not_recognized'),\n        fallback=partitioning.RulesFallback.NO_CONSTRAINT,\n      )\n      wsc_fn.assert_not_called()\n\n  @parameterized.parameters(dict(axes_spec=None), dict(axes_spec=()))\n  def test_param_with_axes_no_axes(self, axes_spec):\n    class ParamTest(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        foo = partitioning.param_with_axes(\n          'foo',\n          lambda k, s, d: jnp.zeros(s, d),\n          (2, 2),\n          x.dtype,\n          axes=axes_spec,\n        )\n        return x + foo\n\n    k = random.key(0)\n    x = jnp.ones((2, 2))\n    _ = ParamTest().init(k, x)\n\n  def test_param_with_axes(self):\n    class ParamTest(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        foo = partitioning.param_with_axes(\n          'foo',\n          lambda k, s, d: jnp.zeros(s, d),\n          (2, 2),\n          x.dtype,\n          axes=('foo', 'bar'),\n        )\n        return x + foo\n\n    p_rules = (('foo', 'model'), ('bar', 'data'), ('baz', None))\n    k = random.key(0)\n    x = jnp.ones((2, 2))\n    with partitioning.axis_rules(p_rules):\n      variables = ParamTest().init(k, x)\n    self.assertIn('params', variables)\n    self.assertIn('params_axes', variables)\n    self.assertEqual(\n      variables['params_axes']['foo_axes'],\n      partitioning.AxisMetadata(names=('foo', 'bar')),\n    )\n    logical_axis_names = partitioning.get_axis_names(variables['params_axes'])\n    self.assertEqual(\n      logical_axis_names, {'foo': jax.sharding.PartitionSpec('foo', 'bar')}\n    )\n\n  def test_param_pytree_with_axes(self):\n    def init_fn(k, s, d):\n      del k\n      return {'a': jnp.zeros(s, d), 'b': (jnp.zeros(s, d), jnp.zeros(s, d))}\n\n    axes = {'a': ('foo', 'bar'), 'b': (('foo', 'bar'), ('bar', 'foo'))}\n\n    class ParamTest(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        foo = partitioning.param_with_axes(\n          'foo', init_fn, (2, 2), x.dtype, axes=axes\n        )\n        return x + foo['a']\n\n    p_rules = (('foo', 'model'), ('bar', 'data'), ('baz', None))\n    k = random.key(0)\n    x = jnp.ones((2, 2))\n    with partitioning.axis_rules(p_rules):\n      variables = ParamTest().init(k, x)\n    self.assertIn('params', variables)\n    self.assertIn('params_axes', variables)\n    self.assertEqual(\n      variables['params_axes']['foo_axes'],\n      partitioning.AxisMetadata(names=axes),\n    )\n    logical_axis_names = partitioning.get_axis_names(variables['params_axes'])\n    expected = freeze(\n      {\n        'foo': {\n          'a': jax.sharding.PartitionSpec('foo', 'bar'),\n          'b': (\n            jax.sharding.PartitionSpec('foo', 'bar'),\n            jax.sharding.PartitionSpec('bar', 'foo'),\n          ),\n        }\n      }\n    )\n    self.assertEqual(logical_axis_names, expected)\n\n  @parameterized.parameters(dict(axes_spec=None), dict(axes_spec=()))\n  def test_variable_with_axes_no_axes(self, axes_spec):\n    class VarTest(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        foo = partitioning.variable_with_axes(\n          'test', 'foo', jnp.zeros, (2, 2), x.dtype, axes=axes_spec\n        )\n        return x + foo.value\n\n    k = random.key(0)\n    x = jnp.ones((2, 2))\n    _ = VarTest().init(k, x)\n\n  def test_variable_with_empty_tuple_has_empty_axes(self):\n    class VarTest(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        foo = partitioning.variable_with_axes(\n          'test', 'foo', jnp.zeros, (2, 2), x.dtype, axes=()\n        )\n        return x + foo.value\n\n    k = random.key(0)\n    x = jnp.ones((2, 2))\n    variables = VarTest().init(k, x)\n    logical_axis_names = partitioning.get_axis_names(variables['test_axes'])\n    self.assertEqual(logical_axis_names, {'foo': jax.sharding.PartitionSpec()})\n\n  def test_variable_with_axes(self):\n    class VarTest(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        foo = partitioning.variable_with_axes(\n          'test', 'foo', jnp.zeros, (2, 2), x.dtype, axes=('foo', 'bar')\n        )\n        return x + foo.value\n\n    p_rules = (('foo', 'model'), ('bar', 'data'), ('baz', None))\n    k = random.key(0)\n    x = jnp.ones((2, 2))\n    with partitioning.axis_rules(p_rules):\n      variables = VarTest().init(k, x)\n    self.assertIn('test', variables)\n    self.assertIn('test_axes', variables)\n    self.assertEqual(\n      variables['test_axes']['foo_axes'],\n      partitioning.AxisMetadata(names=('foo', 'bar')),\n    )\n    logical_axis_names = partitioning.get_axis_names(variables['test_axes'])\n    self.assertEqual(\n      logical_axis_names, {'foo': jax.sharding.PartitionSpec('foo', 'bar')}\n    )\n\n  @mock.patch('flax.linen.partitioning._with_sharding_constraint')\n  def test_variable_with_axes_fallback(self, wsc_fn):\n    class VarTest(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        foo = partitioning.variable_with_axes(\n          'test',\n          'foo',\n          jnp.zeros,\n          (2, 2),\n          x.dtype,\n          axes=('foo', 'bar'),\n          fallback=partitioning.RulesFallback.NO_CONSTRAINT,\n        )\n        return x + foo.value\n\n    p_rules = (\n      # No rule for 'foo':\n      ('bar', 'data'),\n      ('baz', None),\n    )\n    k = random.key(0)\n    x = jnp.ones((2, 2))\n    with partitioning.axis_rules(p_rules):\n      variables = VarTest().init(k, x)\n\n    wsc_fn.assert_not_called()\n    self.assertIn('test', variables)\n    self.assertIn('test_axes', variables)\n    self.assertEqual(\n      variables['test_axes']['foo_axes'],\n      partitioning.AxisMetadata(names=('foo', 'bar')),\n    )\n    logical_axis_names = partitioning.get_axis_names(variables['test_axes'])\n    self.assertEqual(\n      logical_axis_names, {'foo': jax.sharding.PartitionSpec('foo', 'bar')}\n    )\n\n  def test_scan_with_axes(self):\n    # MLP Hparams\n    B, L, E = 8, 4, 32  # pylint: disable=invalid-name\n    # fake inputs\n    x = jnp.ones((B, E))\n    k = random.key(0)\n\n    class SinDot(nn.Module):\n      depth: int\n\n      @nn.compact\n      def __call__(self, x):\n        W1 = partitioning.param_with_axes(  # pylint: disable=invalid-name\n          'W1',\n          nn.initializers.xavier_normal(),\n          (x.shape[-1], self.depth),\n          axes=('emb', 'mlp'),\n        )\n        W2 = partitioning.param_with_axes(  # pylint: disable=invalid-name\n          'W2',\n          nn.initializers.xavier_normal(),\n          (self.depth, x.shape[-1]),\n          axes=('mlp', 'emb'),\n        )\n        y = jnp.dot(jnp.sin(jnp.dot(x, W1)), W2)\n        _ = partitioning.variable_with_axes(\n          'stats', 'y_st', lambda: y, axes=('batch', 'emb')\n        )\n        # scan expects a (carry, out) return signature.\n        return y, None\n\n    class Scanned(nn.Module):\n      num_layers: int\n      depth: int\n\n      @nn.compact\n      def __call__(self, x):\n        scanned_sindot = partitioning.scan_with_axes(\n          SinDot,\n          in_axes=(),\n          variable_axes={'params': 0, 'stats': 1},\n          split_rngs={'params': True},\n          axis_name='layer',\n          axes_collections=('params', 'stats'),\n          length=self.num_layers,\n        )(self.depth, name='scanned_layer')\n        y, _ = scanned_sindot(x)\n        # test calling again to test metadata compatibility across calls\n        _, _ = scanned_sindot(x)\n        return y\n\n    p_rules = (('emb', 'data'), ('mlp', 'model'), ('batch', 'data'))\n    with partitioning.axis_rules(p_rules):\n      variables = Scanned(L, E).init(k, x)\n\n      # Ensure that the module can be called when 'params_axes' is not mutable.\n      Scanned(L, E).apply(variables, x)\n    self.assertIn('params', variables)\n    self.assertIn('params_axes', variables)\n    self.assertIn('stats', variables)\n    self.assertIn('stats_axes', variables)\n    self.assertEqual(\n      variables['params_axes']['scanned_layer']['W1_axes'],\n      partitioning.AxisMetadata(names=('layer', 'emb', 'mlp')),\n    )\n    logical_axis_names = partitioning.get_axis_names(variables['params_axes'])\n    self.assertEqual(\n      logical_axis_names,\n      {\n        'scanned_layer': {\n          'W1': jax.sharding.PartitionSpec('layer', 'emb', 'mlp'),\n          'W2': jax.sharding.PartitionSpec('layer', 'mlp', 'emb'),\n        }\n      },\n    )\n    logical_axis_names = partitioning.get_axis_names(variables['stats_axes'])\n    self.assertEqual(\n      logical_axis_names,\n      {\n        'scanned_layer': {\n          'y_st': jax.sharding.PartitionSpec('batch', 'layer', 'emb')\n        }\n      },\n    )\n\n  def test_vmap_with_axes(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        return (\n          partitioning.param_with_axes(\n            'w', jax.nn.initializers.uniform(), [4, 3], axes=('out', 'in')\n          )\n          @ x\n        )\n\n    class Vmapped(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        FooVmapped = partitioning.vmap_with_axes(  # pylint: disable=invalid-name\n          Foo,\n          variable_axes={\n            'params': 1,\n          },\n          split_rngs={'params': True},\n          partitioning_axis_names={'params': 'vmap_axis'},\n        )\n        return FooVmapped(name='foo_vmapped')(x)\n\n    p_rules = (('out', None), ('in', 'data'), ('vmap_axis', 'model'))\n\n    # check that regular Food module is correct\n    with partitioning.axis_rules(p_rules):\n      variables = Foo().init(jax.random.key(0), jnp.array([1, 2, 3]))\n    variables = unfreeze(variables)\n    variables['params'] = jax.tree_util.tree_map(\n      lambda x: x.shape, variables['params']\n    )\n    self.assertDictEqual(\n      variables,\n      {\n        'params': {'w': (4, 3)},\n        'params_axes': {\n          'w_axes': partitioning.AxisMetadata(names=('out', 'in'))\n        },\n      },\n    )\n\n    # check that FooVmapped adds 'vmap_axis' to axis 1\n    with partitioning.axis_rules(p_rules):\n      variables = Vmapped().init(\n        jax.random.key(0), jnp.array([[1, 2, 3], [4, 5, 6]])\n      )\n    variables = unfreeze(variables)\n    variables['params'] = jax.tree_util.tree_map(\n      lambda x: x.shape, variables['params']\n    )\n    self.assertDictEqual(\n      variables,\n      {\n        'params': {'foo_vmapped': {'w': (4, 2, 3)}},\n        'params_axes': {\n          'foo_vmapped': {\n            'w_axes': partitioning.AxisMetadata(\n              names=('out', 'vmap_axis', 'in')\n            )\n          }\n        },\n      },\n    )\n\n  def test_logical_with_mesh_and_rules(self):\n    devices = mesh_utils.create_device_mesh((jax.local_device_count(), 1))\n    mesh = sharding.Mesh(devices, ('in', 'out'))\n    test = self\n    rules = (('a', 'in'), ('b', 'out'))\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        kernel_init = nn.with_logical_partitioning(\n          nn.initializers.ones_init(), ('a', 'b'), mesh=mesh, rules=rules\n        )\n        kernel = self.param('kernel', kernel_init, (x.shape[-1], 2))\n        kernel_box = self.get_variable('params', 'kernel')\n        test.assertIsInstance(kernel_box, nn.Partitioned)\n        test.assertEqual(kernel_box.names, ('a', 'b'))\n        return x @ kernel\n\n    @jax.jit\n    def create_state():\n      module = Foo()\n      variables = module.init(random.key(0), jnp.zeros((8, 4)))\n      logical_spec = nn.get_partition_spec(variables)\n      shardings = nn.logical_to_mesh_sharding(logical_spec, mesh, rules)\n      variables = jax.lax.with_sharding_constraint(variables, shardings)\n      return variables\n\n    variables = create_state()\n    self.assertEqual(variables['params']['kernel'].names, ('a', 'b'))\n    self.assertIs(variables['params']['kernel'].mesh, mesh)\n    self.assertEqual(variables['params']['kernel'].rules, rules)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/linen/summary_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport enum\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom absl.testing import absltest\nfrom jax import random\n\nfrom flax import linen as nn\nfrom flax import struct\nfrom flax.core.scope import Array\nfrom flax.linen import summary\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\nCONSOLE_TEST_KWARGS = dict(force_terminal=False, no_color=True, width=10_000)\n\n\ndef _get_shapes(pytree):\n  return jax.tree_util.tree_map(\n    lambda x: x.shape if hasattr(x, 'shape') else x, pytree\n  )\n\n\ndef _get_obj_repr_value(x):\n  if isinstance(x, summary._ObjectRepresentation):\n    return x.obj\n  return x\n\n\nclass ConvBlock(nn.Module):\n  features: int\n  kernel_size: list[int]\n  test_sow: bool\n\n  def setup(self) -> None:\n    self.conv = nn.Conv(self.features, self.kernel_size)\n    self.bn = nn.BatchNorm()\n    self.dropout = nn.Dropout(0.5)\n\n  def block_method(self, x: Array, training: bool) -> Array:\n    x = self.conv(x)\n\n    if self.test_sow:\n      self.sow('intermediates', 'INTERM', x)\n\n    x = self.bn(x, use_running_average=not training)\n    x = self.dropout(x, deterministic=not training)\n    x = nn.relu(x)\n    return x\n\n  def __call__(self, x: Array, training: bool) -> Array:\n    x = self.conv(x)\n\n    if self.test_sow:\n      self.sow('intermediates', 'INTERM', x)\n\n    x = self.bn(x, use_running_average=not training)\n    x = self.dropout(x, deterministic=not training)\n    x = nn.relu(x)\n    return x\n\n\nclass CNN(nn.Module):\n  test_sow: bool\n\n  def setup(self) -> None:\n    self.block1 = ConvBlock(32, [3, 3], test_sow=self.test_sow)\n    self.block2 = ConvBlock(64, [3, 3], test_sow=self.test_sow)\n    self.dense = nn.Dense(10)\n\n  def cnn_method(self, x: Array, training: bool) -> Array:\n    x = self.block1.block_method(x, training=training)\n    x = self.block2.block_method(x, training=training)\n    x = x.mean(axis=(1, 2))\n\n    if self.test_sow:\n      self.sow('intermediates', 'INTERM', x)\n\n    x = self.dense(x)\n\n    return x, dict(a=x, b=x + 1.0)\n\n  def __call__(self, x: Array, training: bool) -> Array:\n    x = self.block1.block_method(x, training=training)\n    x = self.block2.block_method(x, training=training)\n    x = x.mean(axis=(1, 2))\n\n    if self.test_sow:\n      self.sow('intermediates', 'INTERM', x)\n\n    x = self.dense(x)\n\n    return x, dict(a=x, b=x + 1.0)\n\n\nclass SummaryTest(absltest.TestCase):\n  def test_module_summary(self):\n    \"\"\"\n    This test creates a Table using `module_summary` and checks that it\n    matches the expected output given the CNN model defined in\n    `_get_tabulate_cnn`.\n    \"\"\"\n\n    batch_size = 32\n\n    x = jnp.ones((batch_size, 28, 28, 1))\n    module = CNN(test_sow=False)\n\n    table = summary._get_module_table(\n      module,\n      depth=None,\n      show_repeated=True,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    )(\n      {'dropout': random.key(0), 'params': random.key(1)},\n      x,\n      training=True,\n      mutable=True,\n    )\n    # get values for inputs and outputs from their _ValueRepresentation\n    for row in table:\n      row.inputs = jax.tree_util.tree_map(_get_obj_repr_value, row.inputs)\n      row.outputs = jax.tree_util.tree_map(_get_obj_repr_value, row.outputs)\n\n    # 10 rows = 1 CNN + 4 ConvBlock_0 + 4 ConvBlock_1 + 1 Dense_0\n    self.assertLen(table, 10)\n\n    # check paths\n    self.assertEqual(table[0].path, ())\n\n    self.assertEqual(table[1].path, ('block1',))\n    self.assertEqual(table[2].path, ('block1', 'conv'))\n    self.assertEqual(table[3].path, ('block1', 'bn'))\n    self.assertEqual(table[4].path, ('block1', 'dropout'))\n\n    self.assertEqual(table[5].path, ('block2',))\n    self.assertEqual(table[6].path, ('block2', 'conv'))\n    self.assertEqual(table[7].path, ('block2', 'bn'))\n    self.assertEqual(table[8].path, ('block2', 'dropout'))\n\n    self.assertEqual(table[9].path, ('dense',))\n\n    # check outputs shapes\n    self.assertEqual(\n      (table[0].inputs[0].shape, table[0].inputs[1]),\n      (x.shape, dict(training=True)),\n    )\n    self.assertEqual(\n      _get_shapes(table[0].outputs),\n      ((batch_size, 10), dict(a=(batch_size, 10), b=(batch_size, 10))),\n    )\n\n    self.assertEqual(\n      _get_shapes(table[1].inputs),\n      ((batch_size, 28, 28, 1), {'training': True}),\n    )\n    self.assertEqual(table[1].outputs.shape, (batch_size, 28, 28, 32))\n    self.assertEqual(table[2].inputs.shape, (batch_size, 28, 28, 1))\n    self.assertEqual(table[2].outputs.shape, (batch_size, 28, 28, 32))\n    self.assertEqual(\n      _get_shapes(table[3].inputs),\n      ((batch_size, 28, 28, 32), {'use_running_average': False}),\n    )\n    self.assertEqual(table[3].outputs.shape, (batch_size, 28, 28, 32))\n    self.assertEqual(\n      _get_shapes(table[4].inputs),\n      ((batch_size, 28, 28, 32), {'deterministic': False}),\n    )\n    self.assertEqual(table[4].outputs.shape, (batch_size, 28, 28, 32))\n\n    self.assertEqual(\n      _get_shapes(table[5].inputs),\n      ((batch_size, 28, 28, 32), {'training': True}),\n    )\n    self.assertEqual(table[5].outputs.shape, (batch_size, 28, 28, 64))\n    self.assertEqual(table[6].inputs.shape, (batch_size, 28, 28, 32))\n    self.assertEqual(table[6].outputs.shape, (batch_size, 28, 28, 64))\n    self.assertEqual(\n      _get_shapes(table[7].inputs),\n      ((batch_size, 28, 28, 64), {'use_running_average': False}),\n    )\n    self.assertEqual(table[7].outputs.shape, (batch_size, 28, 28, 64))\n    self.assertEqual(\n      _get_shapes(table[8].inputs),\n      ((batch_size, 28, 28, 64), {'deterministic': False}),\n    )\n    self.assertEqual(table[8].outputs.shape, (batch_size, 28, 28, 64))\n\n    self.assertEqual(table[9].inputs.shape, (batch_size, 64))\n    self.assertEqual(table[9].outputs.shape, (batch_size, 10))\n\n    # check no summary is performed\n    for row in table:\n      self.assertEqual(\n        row.module_variables,\n        row.counted_variables,\n      )\n\n    # Each module FLOPs >= sum of its submodule FLOPs.\n    # Can be greater due to ops like `nn.relu` not belonging to any submodule.\n    for r in table:\n      flops, vjp_flops = r.flops, r.vjp_flops\n      submodule_flops, submodule_vjp_flops = 0, 0\n      for s in table:\n        if len(s.path) == len(r.path) + 1 and s.path[: len(r.path)] == r.path:\n          submodule_flops += s.flops\n          submodule_vjp_flops += s.vjp_flops\n\n      self.assertGreaterEqual(flops, submodule_flops)\n      self.assertGreaterEqual(vjp_flops, submodule_vjp_flops)\n\n  def test_module_summary_with_depth(self):\n    \"\"\"\n    This test creates a Table using `module_summary` set the `depth` argument\n    to `1`, table should have fewer rows as a consequence.\n    \"\"\"\n    batch_size = 32\n\n    x = jnp.ones((batch_size, 28, 28, 1))\n    module = CNN(test_sow=False)\n\n    table = summary._get_module_table(\n      module,\n      depth=1,\n      show_repeated=True,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    )(\n      {'dropout': random.key(0), 'params': random.key(1)},\n      x,\n      training=True,\n      mutable=True,\n    )\n    # get values for inputs and outputs from their _ValueRepresentation\n\n    for row in table:\n      row.inputs = jax.tree_util.tree_map(_get_obj_repr_value, row.inputs)\n      row.outputs = jax.tree_util.tree_map(_get_obj_repr_value, row.outputs)\n\n    # 4 rows = 1 CNN + 1 ConvBlock_0 + 1 ConvBlock_1 + 1 Dense_0\n    self.assertLen(table, 4)\n\n    # check paths\n    self.assertEqual(table[0].path, ())\n\n    self.assertEqual(table[1].path, ('block1',))\n    self.assertEqual(table[2].path, ('block2',))\n    self.assertEqual(table[3].path, ('dense',))\n\n    # check outputs shapes\n    self.assertEqual(\n      (table[0].inputs[0].shape, table[0].inputs[1]),\n      (x.shape, dict(training=True)),\n    )\n    self.assertEqual(\n      _get_shapes(table[0].outputs),\n      ((batch_size, 10), dict(a=(batch_size, 10), b=(batch_size, 10))),\n    )\n\n    self.assertEqual(\n      _get_shapes(table[1].inputs),\n      ((batch_size, 28, 28, 1), {'training': True}),\n    )\n    self.assertEqual(table[1].outputs.shape, (batch_size, 28, 28, 32))\n\n    self.assertEqual(\n      _get_shapes(table[2].inputs),\n      ((batch_size, 28, 28, 32), {'training': True}),\n    )\n    self.assertEqual(table[2].outputs.shape, (batch_size, 28, 28, 64))\n\n    self.assertEqual(table[3].inputs.shape, (batch_size, 64))\n    self.assertEqual(table[3].outputs.shape, (batch_size, 10))\n\n    # check ConvBlock_0 and ConvBlock_1 are summarized\n    self.assertNotEqual(table[1].module_variables, table[1].counted_variables)\n    self.assertNotEqual(table[2].module_variables, table[2].counted_variables)\n\n    # check CNN and Dense_0 output are not summarized\n    self.assertEqual(table[0].module_variables, table[0].counted_variables)\n    self.assertEqual(table[3].module_variables, table[3].counted_variables)\n\n    # Top level FLOPs > sum of listed submodule FLOPs, since not all are listed.\n    self.assertGreater(table[0].flops, sum(r.flops for r in table[1:]))\n    self.assertGreater(table[0].vjp_flops, sum(r.vjp_flops for r in table[1:]))\n\n  def test_tabulate(self):\n    \"\"\"\n    This test creates a string representation of a Module using `Module.tabulate`\n    and checks that it matches the expected output given the CNN model defined\n    in `_get_tabulate_cnn`.\n    \"\"\"\n    batch_size = 32\n\n    x = jnp.ones((batch_size, 28, 28, 1))\n    module = CNN(test_sow=False)\n\n    module_repr = module.tabulate(\n      {'dropout': random.key(0), 'params': random.key(1)},\n      x,\n      training=True,\n      console_kwargs=CONSOLE_TEST_KWARGS,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    )\n\n    # NOTE: it's tricky to validate the content of lines\n    # because it seems to be shell-dependent, so we will\n    # just check lines that won't change between environments\n    lines = module_repr.split('\\n')\n\n    # check title\n    module_name = module.__class__.__name__\n    self.assertIn(f'{module_name} Summary', lines[1])\n\n    # check headers are correct\n    self.assertIn('path', lines[3])\n    self.assertIn('module', lines[3])\n    self.assertIn('inputs', lines[3])\n    self.assertIn('outputs', lines[3])\n    self.assertIn('params', lines[3])\n    self.assertIn('flops', lines[3])\n    self.assertIn('vjp_flops', lines[3])\n    self.assertIn('batch_stats', lines[3])\n\n    # collection counts\n    self.assertIn('Total', lines[-6])\n    self.assertIn('192', lines[-6])\n    self.assertIn('768 B', lines[-6])\n    self.assertIn('19,658', lines[-6])\n    self.assertIn('78.6 KB', lines[-6])\n\n    # total counts\n    self.assertIn('Total Parameters', lines[-3])\n    self.assertIn('19,850', lines[-3])\n    self.assertIn('79.4 KB', lines[-3])\n\n  def test_tabulate_with_sow(self):\n    batch_size = 32\n\n    x = jnp.ones((batch_size, 28, 28, 1))\n    module = CNN(test_sow=True)\n\n    module_repr = module.tabulate(\n      {'dropout': random.key(0), 'params': random.key(1)},\n      x,\n      training=True,\n      mutable=True,\n      console_kwargs=CONSOLE_TEST_KWARGS,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    )\n\n    self.assertIn('intermediates', module_repr)\n    self.assertIn('INTERM', module_repr)\n\n  def test_tabulate_with_method(self):\n    batch_size = 32\n\n    x = jnp.ones((batch_size, 28, 28, 1))\n    module = CNN(test_sow=False)\n\n    module_repr = module.tabulate(\n      {'dropout': random.key(0), 'params': random.key(1)},\n      x,\n      training=True,\n      method=CNN.cnn_method,\n      console_kwargs=CONSOLE_TEST_KWARGS,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    )\n\n    self.assertIn('(block_method)', module_repr)\n    self.assertIn('(cnn_method)', module_repr)\n\n  def test_tabulate_function(self):\n    \"\"\"\n    This test creates a string representation of a Module using\n    `Module.tabulate` and checks that it matches the expected output given the\n    CNN model defined in `_get_tabulate_cnn`.\n    \"\"\"\n    batch_size = 32\n\n    x = jnp.ones((batch_size, 28, 28, 1))\n    module = CNN(test_sow=False)\n\n    module_repr = nn.tabulate(\n      module,\n      {'dropout': random.key(0), 'params': random.key(1)},\n      console_kwargs=CONSOLE_TEST_KWARGS,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    )(x, training=True)\n\n    lines = module_repr.split('\\n')\n\n    # check title\n    module_name = module.__class__.__name__\n    self.assertIn(f'{module_name} Summary', lines[1])\n\n    # check headers are correct\n    self.assertIn('path', lines[3])\n    self.assertIn('module', lines[3])\n    self.assertIn('inputs', lines[3])\n    self.assertIn('outputs', lines[3])\n    self.assertIn('params', lines[3])\n    self.assertIn('flops', lines[3])\n    self.assertIn('batch_stats', lines[3])\n\n    # collection counts\n    self.assertIn('Total', lines[-6])\n    self.assertIn('192', lines[-6])\n    self.assertIn('768 B', lines[-6])\n    self.assertIn('19,658', lines[-6])\n    self.assertIn('78.6 KB', lines[-6])\n\n    # total counts\n    self.assertIn('Total Parameters', lines[-3])\n    self.assertIn('19,850', lines[-3])\n    self.assertIn('79.4 KB', lines[-3])\n\n  def test_lifted_transform(self):\n    class LSTM(nn.Module):\n      features: int\n\n      @nn.compact\n      def __call__(self, x):\n        carry = nn.LSTMCell(self.features).initialize_carry(\n          random.key(0), x[:, 0].shape\n        )\n        ScanLSTM = nn.scan(\n          nn.LSTMCell,\n          variable_broadcast='params',\n          split_rngs={'params': False},\n          in_axes=1,\n          out_axes=1,\n        )\n        return ScanLSTM(self.features, name='ScanLSTM')(carry, x)\n\n    lstm = LSTM(features=128)\n\n    with jax.check_tracer_leaks(True):\n      module_repr = lstm.tabulate(\n        random.key(0),\n        x=jnp.ones((32, 128, 64)),\n        console_kwargs=CONSOLE_TEST_KWARGS,\n        compute_flops=True,\n        compute_vjp_flops=True,\n      )\n\n    lines = module_repr.splitlines()\n\n    self.assertIn('LSTM', lines[5])\n    self.assertIn('ScanLSTM', lines[9])\n    self.assertIn('LSTMCell', lines[9])\n    self.assertIn('ScanLSTM/ii', lines[13])\n    self.assertIn('Dense', lines[13])\n\n  def test_lifted_transform_no_rename(self):\n    class LSTM(nn.Module):\n      features: int\n\n      @nn.compact\n      def __call__(self, x):\n        carry = nn.LSTMCell(self.features).initialize_carry(\n          random.key(0), x[:, 0].shape\n        )\n        ScanLSTM = nn.scan(\n          nn.LSTMCell,\n          variable_broadcast='params',\n          split_rngs={'params': False},\n          in_axes=1,\n          out_axes=1,\n        )\n        return ScanLSTM(self.features)(carry, x)\n\n    lstm = LSTM(features=128)\n\n    with jax.check_tracer_leaks(True):\n      module_repr = lstm.tabulate(\n        random.key(0),\n        x=jnp.ones((32, 128, 64)),\n        console_kwargs=CONSOLE_TEST_KWARGS,\n        compute_flops=True,\n        compute_vjp_flops=True,\n      )\n\n    lines = module_repr.splitlines()\n\n    self.assertIn('LSTM', lines[5])\n    self.assertIn('ScanLSTMCell_0', lines[9])\n    self.assertIn('LSTMCell', lines[9])\n    self.assertIn('ScanLSTMCell_0/ii', lines[13])\n    self.assertIn('Dense', lines[13])\n\n  def test_module_reuse(self):\n    class ConvBlock(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = nn.Conv(32, [3, 3])(x)\n        x = nn.BatchNorm(use_running_average=True)(x)\n        x = nn.Dropout(0.5, deterministic=True)(x)\n        x = nn.relu(x)\n        return x\n\n    class CNN(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        block = ConvBlock()\n        x = block(x)\n        x = block(x)\n        x = block(x)\n        return x\n\n    x = jnp.ones((4, 28, 28, 32))\n    module_repr = CNN().tabulate(\n      jax.random.key(0),\n      x=x,\n      show_repeated=True,\n      console_kwargs=CONSOLE_TEST_KWARGS,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    )\n    lines = module_repr.splitlines()\n\n    # first call\n    self.assertIn('ConvBlock_0/Conv_0', lines[9])\n    self.assertIn('bias', lines[9])\n    self.assertIn('ConvBlock_0/BatchNorm_0', lines[14])\n    self.assertIn('mean', lines[14])\n    self.assertIn('bias', lines[14])\n    self.assertIn('ConvBlock_0/Dropout_0', lines[19])\n\n    # second call\n    self.assertIn('ConvBlock_0/Conv_0', lines[23])\n    self.assertNotIn('bias', lines[23])\n    self.assertIn('ConvBlock_0/BatchNorm_0', lines[25])\n    self.assertNotIn('mean', lines[25])\n    self.assertNotIn('bias', lines[25])\n    self.assertIn('ConvBlock_0/Dropout_0', lines[27])\n\n    # third call\n    self.assertIn('ConvBlock_0/Conv_0', lines[31])\n    self.assertNotIn('bias', lines[31])\n    self.assertIn('ConvBlock_0/BatchNorm_0', lines[33])\n    self.assertNotIn('mean', lines[33])\n    self.assertNotIn('bias', lines[33])\n    self.assertIn('ConvBlock_0/Dropout_0', lines[35])\n\n    # Test that CNN FLOPs are 3x ConvBlock FLOPs.\n    args = ({'dropout': random.key(0), 'params': random.key(1)}, x)\n    cnn = summary._get_module_table(\n      CNN(),\n      depth=1,\n      show_repeated=True,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    )(*args, mutable=True)\n\n    block = summary._get_module_table(\n      ConvBlock(),\n      depth=1,\n      show_repeated=True,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    )(*args, mutable=True)\n\n    # Total forward/backward FLOPs equal to their sums of sub blocks.\n    self.assertEqual(cnn[0].flops, sum(r.flops for r in cnn[1:]))\n    self.assertEqual(cnn[0].vjp_flops, sum(r.vjp_flops for r in cnn[1:]))\n\n    # Each sub block has cost equal to ConvBlock instantiated separately.\n    for r in cnn[1:]:\n      self.assertEqual(r.flops, block[0].flops)\n      self.assertEqual(r.vjp_flops, block[0].vjp_flops)\n\n  def test_empty_input(self):\n    class EmptyInput(nn.Module):\n      @nn.compact\n      def __call__(self):\n        return 1\n\n    module = EmptyInput()\n    module_repr = module.tabulate(\n      {},\n      console_kwargs=CONSOLE_TEST_KWARGS,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    )\n    lines = module_repr.splitlines()\n\n    # 1 output and 0 forward / backward FLOPs.\n    self.assertRegex(\n      lines[5], r'│\\s*│\\s*EmptyInput\\s*│\\s*│\\s*1\\s*│\\s*0\\s*│\\s*0\\s*│'\n    )\n\n  def test_numpy_scalar(self):\n    class Submodule(nn.Module):\n      def __call__(self, x):\n        return x + 1\n\n    class EmptyInput(nn.Module):\n      @nn.compact\n      def __call__(self):\n        return Submodule()(x=np.pi)\n\n    module = EmptyInput()\n    module_repr = module.tabulate(\n      {},\n      console_kwargs=CONSOLE_TEST_KWARGS,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    )\n    lines = module_repr.splitlines()\n\n    self.assertIn('4.141592', lines[5])\n    self.assertIn('x: 3.141592', lines[7])\n    self.assertIn('4.141592', lines[7])\n\n    # 0 forward / backward FLOPs due to precomputed output values.\n    self.assertIn('│ 0     │ 0', lines[5])\n    self.assertIn('│ 0     │ 0', lines[7])\n\n  def test_partitioned_params(self):\n    class Classifier(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        hidden = nn.Dense(\n          features=1024,\n          kernel_init=nn.with_partitioning(\n            nn.initializers.lecun_normal(), (None, 'data')\n          ),\n          bias_init=nn.with_partitioning(nn.initializers.zeros, (None,)),\n          name='hidden',\n        )\n        x = x / 255.0\n        x = x.reshape((x.shape[0], -1))  # flatten\n        x = nn.relu(hidden(x))\n        x = nn.Dense(features=10, name='head')(x)\n        return x\n\n    module = Classifier()\n    lines = module.tabulate(\n      jax.random.key(0),\n      jnp.empty((1, 28, 28, 1)),\n      console_kwargs=CONSOLE_TEST_KWARGS,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    ).splitlines()\n    self.assertIn('P(None,)', lines[7])\n    self.assertIn('P(None, data)', lines[8])\n\n    # Per-layer forward FLOPs:\n    self.assertIn('1606656', lines[7])  # 1 * (28 * 28 * 1) * 1024 * 2 + 1024\n    self.assertIn('20490', lines[12])  #  1 * (1024       ) * 10   * 2 + 10\n\n    # Total forward FLOPs: input division + ReLU + two dense layers above.\n    # (1 * 28 * 28 * 1) + (1 * 1024) + 1606656 + 20490.\n    self.assertIn('1628954', lines[5])\n\n    # Per-layer backward FLOPs:\n\n    # [3x MMs: forward, input cotangent, weight cotangent] 1024 * 784 * 2 * 3\n    # + [forward bias addition] 1024\n    # + [`mutable=True`: weight and bias sizes] 1024 * 784 + 1024\n    self.assertIn('5621760', lines[7])\n\n    # [3x matmuls: forward, input cotangent, weight cotangent] 1024 * 10 * 2 * 3\n    # + [forward bias addition] 10\n    # + [`mutable=True`: weight and bias sizes] 1024 * 10 + 10\n    self.assertIn('71700', lines[12])\n\n    # Total backward FLOPs: input division + ReLU + two dense layers above.\n    # 2 * (1 * 28 * 28 * 1) + 3 * (1 * 1024) + 5621760 + 71700.\n    self.assertIn('5698100', lines[5])\n\n  def test_non_array_variables(self):\n    class Metadata(struct.PyTreeNode):\n      names: tuple = struct.field(pytree_node=False)\n\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self):\n        self.sow('foo', 'bar', Metadata(('baz', 'qux')))\n\n    module = Foo()\n    lines = module.tabulate(\n      {},\n      console_kwargs=CONSOLE_TEST_KWARGS,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    ).splitlines()\n    self.assertIn('names', lines[6])\n    self.assertIn('baz', lines[7])\n    self.assertIn('qux', lines[8])\n\n    # 0 forward and backward FLOPs.\n    self.assertIn('│ 0     │ 0', lines[5])\n\n  def test_tabulate_param_count_and_flops(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        h = nn.Dense(4)(x)\n        return nn.Dense(2)(h)\n\n    module = Foo()\n    rng = jax.random.key(0)\n    x = jnp.ones((16, 9))\n\n    rep = module.tabulate(\n      rng,\n      x,\n      console_kwargs=CONSOLE_TEST_KWARGS,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    )\n    lines = rep.splitlines()\n    self.assertIn('Total Parameters: 50', lines[-2])\n\n  def test_tabulate_enum(self):\n    class Net(nn.Module):\n      @nn.compact\n      def __call__(self, inputs):\n        x = inputs['x']\n        x = nn.Dense(features=2)(x)\n        return jnp.sum(x)\n\n    class InputEnum(str, enum.Enum):\n      x = 'x'\n\n    inputs = {InputEnum.x: jnp.ones((1, 1))}\n    # test args\n    lines = Net().tabulate(jax.random.key(0), inputs).split('\\n')\n    self.assertIn('x: \\x1b[2mfloat32\\x1b[0m[1,1]', lines[5])\n    # test kwargs\n    lines = Net().tabulate(jax.random.key(0), inputs=inputs).split('\\n')\n    self.assertIn('inputs:', lines[5])\n    self.assertIn('x: \\x1b[2mfloat32\\x1b[0m[1,1]', lines[6])\n\n  def test_tabulate_norm_wrapper(self):\n    class SubModel(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = nn.SpectralNorm(nn.Dense(5))(x, update_stats=False)\n        x = nn.Dense(6)(x)\n        x = nn.WeightNorm(nn.Dense(7))(x)\n        return x\n\n    class Model(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        x = nn.WeightNorm(nn.Dense(3))(x)\n        x = nn.Dense(4)(x)\n        x = SubModel()(x)\n        x = nn.Dense(8)(x)\n        x = nn.SpectralNorm(nn.Dense(9))(x, update_stats=False)\n        return x\n\n    x = jnp.ones((1, 2))\n    key = jax.random.key(0)\n    model = Model()\n\n    lines = model.tabulate(\n      key,\n      x,\n      console_kwargs=CONSOLE_TEST_KWARGS,\n      compute_flops=True,\n      compute_vjp_flops=True,\n    ).splitlines()\n\n    self.assertIn('Model', lines[5])\n    self.assertIn('WeightNorm_0', lines[7])\n    self.assertIn('Dense_0/kernel/scale', lines[7])\n    self.assertIn('Dense_0', lines[11])\n    self.assertIn('Dense_1', lines[16])\n    self.assertIn('SubModel_0', lines[21])\n    self.assertIn('SubModel_0/SpectralNorm_0', lines[23])\n    self.assertIn('Dense_0/kernel/sigma', lines[23])\n    self.assertIn('Dense_0/kernel/u', lines[24])\n    self.assertIn('SubModel_0/Dense_0', lines[28])\n    self.assertIn('SubModel_0/Dense_1', lines[33])\n    self.assertIn('SubModel_0/WeightNorm_0', lines[38])\n    self.assertIn('Dense_2/kernel/scale', lines[38])\n    self.assertIn('SubModel_0/Dense_2', lines[42])\n    self.assertIn('Dense_2', lines[47])\n    self.assertIn('SpectralNorm_0', lines[52])\n    self.assertIn('Dense_3/kernel/sigma', lines[52])\n    self.assertIn('Dense_3/kernel/u', lines[53])\n    self.assertIn('Dense_3', lines[57])\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/__init__.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "tests/nnx/bridge/module_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nfrom typing import Any\n\nfrom flax.linen.dtypes import promote_dtype\n\nos.environ['XLA_FLAGS'] = '--xla_force_host_platform_device_count=4'\n\nimport jax\nimport jax.numpy as jnp\nfrom absl.testing import absltest\nimport numpy as np\n\nfrom flax import linen as nn\nfrom flax import nnx\nfrom flax.nnx import bridge\nfrom flax.nnx.bridge.module import MODULE_CONTEXT\n\n\nclass TestBridgeModule(absltest.TestCase):\n  def test_update(self):\n    class Foo(bridge.Module):\n      a: int\n\n    foo = Foo(1)\n    state = {'b': {'c': nnx.Param(jnp.array(2))}}\n    nnx.update(foo, state)\n\n  def test_module_stack(self):\n    \"\"\"Test that apply set the module stack correctly.\"\"\"\n    test = self\n\n    class Foo(bridge.Module):\n      def setup(self):\n        current_ctx = MODULE_CONTEXT.module_stack[-1]\n        test.assertIs(current_ctx.module, self)\n        test.assertFalse(current_ctx.in_compact)\n\n      def __call__(self):\n        current_ctx = MODULE_CONTEXT.module_stack[-1]\n        test.assertIs(current_ctx.module, self)\n        test.assertFalse(current_ctx.in_compact)\n\n    foo = Foo()\n    foo.apply({})\n\n  def test_compact_basic(self):\n    test = self\n    class Linear(bridge.Module):\n      dout: int\n\n      @bridge.compact\n      def __call__(self, x):\n        w = self.param(\n          'w', nnx.initializers.uniform(), (x.shape[-1], self.dout)\n        )\n        b = self.param('b', nn.initializers.zeros_init(), (self.dout,))\n        return x @ w + b[None]\n\n    class Foo(bridge.Module):\n      dout: int\n\n      @bridge.compact\n      def __call__(self, x):\n        din = x.shape[-1]\n        self.linear = Linear(self.dout)\n        x = self.linear(x)\n\n        # NNX\n        graphdef, state = nnx.split(self)\n        test.assertIn('Linear_0', state)\n        test.assertIn('w', state['Linear_0'])\n        test.assertIn('b', state['Linear_0'])\n\n        return x\n\n    foo = Foo(5)\n    x = jnp.ones((3, 2))\n\n    self.assertIsInstance(foo, nnx.Module)\n\n    variables = foo.init(0, x)\n    params = variables['params']\n\n    self.assertIn('Linear_0', params)\n    self.assertIn('w', params['Linear_0'])\n    self.assertIn('b', params['Linear_0'])\n    self.assertEqual(params['Linear_0']['w'].shape, (2, 5))\n    self.assertEqual(params['Linear_0']['b'].shape, (5,))\n\n    y: jax.Array = foo.apply(variables, x)\n\n    self.assertEqual(y.shape, (3, 5))\n\n  def test_mutable_state(self):\n    class FooLinen(nn.Module):\n      @nn.compact\n      def __call__(self):\n        count = self.variable(\n          'counts', 'count', lambda: jnp.zeros((), jnp.int32)\n        )\n        count.value += 1\n\n    model_linen = FooLinen()\n    initial_vars_linen = model_linen.init({})\n    _, vars_linen = model_linen.apply(initial_vars_linen, mutable='counts')\n\n    class FooNNX(bridge.Module):\n      @bridge.compact\n      def __call__(self):\n        count = self.variable(\n          'counts', 'count', lambda: jnp.zeros((), jnp.int32)\n        )\n        count[...] += 1\n\n    model_nnx = FooNNX()\n\n    initial_vars_nnx = model_nnx.init({})\n    _, vars_nnx = model_nnx.apply(initial_vars_nnx, mutable='counts')\n\n    self.assertEqual(\n      initial_vars_linen['counts']['count'], initial_vars_nnx['counts']['count']\n    )\n    self.assertEqual(vars_linen['counts']['count'], vars_nnx['counts']['count'])\n\n  def test_compact_parent_none(self):\n    class Foo(bridge.Module):\n      pass\n\n    class Bar(bridge.Module):\n      @bridge.compact\n      def __call__(self):\n        return Foo().scope\n\n    bar = Bar()\n    scope = bar.apply({}, rngs=1)\n    self.assertIsNone(bar.scope)\n\n    self.assertEqual(scope.rngs.default.key[...], jax.random.key(1))\n    self.assertEqual(scope.rngs.default.count[...], 0)\n\n    class Baz(bridge.Module):\n      @bridge.compact\n      def __call__(self):\n        return Foo(parent=None).scope\n\n    baz = Baz()\n    scope = baz.apply({}, rngs=1)\n    self.assertIsNone(scope)\n\n  def test_dense_port(self):\n    class Dense(bridge.Module):\n      features: int\n      use_bias: bool = True\n      dtype: Any = None\n      param_dtype: Any = jnp.float32\n      precision: Any = None\n      kernel_init: Any = nnx.initializers.lecun_normal()\n      bias_init: Any = nnx.initializers.zeros_init()\n      # Deprecated. Will be removed.\n      dot_general: Any | None = None\n      dot_general_cls: Any = None\n\n      @bridge.compact\n      def __call__(self, inputs: jax.Array) -> jax.Array:\n        kernel = self.param(\n          'kernel',\n          self.kernel_init,\n          (jnp.shape(inputs)[-1], self.features),\n          self.param_dtype,\n        )\n        if self.use_bias:\n          bias = self.param(\n            'bias', self.bias_init, (self.features,), self.param_dtype\n          )\n        else:\n          bias = None\n        inputs, kernel, bias = promote_dtype(\n          inputs, kernel, bias, dtype=self.dtype\n        )\n\n        if self.dot_general_cls is not None:\n          dot_general = self.dot_general_cls()\n        elif self.dot_general is not None:\n          dot_general = self.dot_general\n        else:\n          dot_general = jax.lax.dot_general\n        y = dot_general(\n          inputs,\n          kernel,\n          (((inputs.ndim - 1,), (0,)), ((), ())),\n          precision=self.precision,\n        )\n        if bias is not None:\n          y += jnp.reshape(bias, (1,) * (y.ndim - 1) + (-1,))\n        return y\n\n    m = Dense(3)\n    x = jnp.ones((1, 10, 2))\n    y, variables = m.init_with_output(0, x)\n\n    self.assertEqual(y.shape, (1, 10, 3))\n    self.assertEqual(variables['params']['kernel'].shape, (2, 3))\n    self.assertEqual(variables['params']['bias'].shape, (3,))\n\n    y = m.apply(variables, x)\n\n    self.assertEqual(y.shape, (1, 10, 3))\n    self.assertEqual(variables['params']['kernel'].shape, (2, 3))\n    self.assertEqual(variables['params']['bias'].shape, (3,))\n\n    @jax.jit\n    def train_step(params, x, y):\n      def loss_fn(params):\n        y_pred = m.apply({'params': params}, x)\n        return jnp.mean((y - y_pred) ** 2)\n\n      grads = jax.grad(loss_fn)(params)\n\n      params = jax.tree.map(lambda p, g: p - 0.01 * g, params, grads)\n\n      return params\n\n    params = variables['params']\n    x = jnp.ones((1, 10, 2))\n    y = jnp.ones((1, 10, 3))\n\n    params = train_step(params, x, y)\n\n  def test_metadata(self):\n    class Linear(bridge.Module):\n      dout: int\n\n      @bridge.compact\n      def __call__(self, x):\n        w = self.param(\n          'w', bridge.with_partitioning(nnx.initializers.uniform(), ('in', 'out')),\n          (x.shape[-1], self.dout)\n        )\n        b = self.param('b', nnx.initializers.zeros_init(), (self.dout,))\n        return x @ w + b[None]\n\n    foo = Linear(6)\n    x = jnp.ones((4, 2))\n\n    mesh = jax.make_mesh(\n        (2, 2),\n        ('in', 'out'),\n        axis_types=(jax.sharding.AxisType.Auto,) * len(('in', 'out')),\n    )\n    with jax.set_mesh(mesh):\n      variables = foo.init(0, x)\n      y: jax.Array = foo.apply(variables, x)\n\n    params = variables['params']\n    self.assertIsInstance(params['w'], nn.Partitioned)\n    self.assertEqual(params['w'].value.shape, (2, 6))\n    self.assertEqual(params['w'].names, ('in', 'out'))\n    self.assertEqual(nn.get_partition_spec(variables)['params']['w'],\n                     jax.sharding.PartitionSpec('in', 'out'))\n    self.assertIsInstance(params['b'], jax.Array)\n    self.assertEqual(params['b'].shape, (6,))\n\n    self.assertEqual(y.shape, (4, 6))\n\n  def test_pure_nnx_submodule(self):\n    class NNXLayer(nnx.Module):\n      def __init__(self, dim, dropout, rngs):\n        self.linear = nnx.Linear(dim, dim, use_bias=False, rngs=rngs)\n        self.dropout = nnx.Dropout(dropout, rngs=rngs)\n        self.count = nnx.Intermediate(jnp.array([0.]))\n      def __call__(self, x):\n        # Required check to avoid state update in `init()`. Can this be avoided?\n        if not bridge.current_module().is_initializing():\n          self.count[...] += 1\n        x = self.linear(x)\n        x = self.dropout(x)\n        return x\n\n    class BridgeMLP(bridge.Module):\n      @bridge.compact\n      def __call__(self, x):\n        x = bridge.nnx_in_bridge_mdl(lambda r: NNXLayer(8, 0.3, rngs=r))(x)\n        x = bridge.nnx_in_bridge_mdl(\n          lambda r: NNXLayer(8, 0.3, rngs=r), name='another')(x)\n        return x\n\n    model = BridgeMLP()\n    x = jax.random.normal(jax.random.key(0), (4, 8))\n    variables = model.init(jax.random.key(1), x)\n    self.assertSameElements(variables['params'].keys(),\n                            ['NNXLayer_0', 'another'])\n    self.assertFalse(jnp.array_equal(\n      variables['params']['NNXLayer_0']['linear']['kernel'],\n      variables['params']['another']['linear']['kernel'], ))\n    self.assertEqual(variables['intermediates']['NNXLayer_0']['count'], 0)\n\n    k1, k2, k3 = jax.random.split(jax.random.key(0), 3)\n    y1 = model.apply(variables, x, rngs={'params': k1, 'dropout': k2})\n    y2 = model.apply(variables, x, rngs={'params': k1, 'dropout': k3})\n    assert not jnp.array_equal(y1, y2)\n\n    _, updates = model.apply(variables, x, rngs={'params': k1, 'dropout': k3},\n                             mutable=True)\n    self.assertEqual(updates['intermediates']['NNXLayer_0']['count'], 1)\n\n    class BridgeMLPSetup(bridge.Module):\n      def setup(self):\n        self.layer = bridge.nnx_in_bridge_mdl(\n          lambda r: NNXLayer(8, 0.3, rngs=r))\n      def __call__(self, x):\n        return self.layer(x)\n\n    model = BridgeMLPSetup()\n    variables = model.init(jax.random.key(1), x)\n    self.assertSameElements(variables['params'].keys(), ['layer'])\n    y1 = model.apply(variables, x, rngs={'params': k1, 'dropout': k2})\n    y2 = model.apply(variables, x, rngs={'params': k1, 'dropout': k3})\n    assert not jnp.array_equal(y1, y2)\n\n  def test_pure_nnx_submodule_modified_rng(self):\n    class FooStack(nnx.Module):\n      def __init__(self, in_dim, key):\n        keys = jax.random.split(key, in_dim)\n        self.rngs = nnx.Rngs(keys)\n      def __call__(self, x):\n        @nnx.vmap\n        def generate_weights(r):\n          return jax.random.normal(r.default(), (2,))\n        w = generate_weights(self.rngs)\n        return x @ w\n\n    class BridgeFoo(bridge.Module):\n      @bridge.compact\n      def __call__(self, x):\n        x = bridge.nnx_in_bridge_mdl(lambda r: FooStack(4, r.default()))(x)\n        return x\n\n    model = BridgeFoo()\n    v = model.init(jax.random.key(1), jnp.ones((1, 4)))\n    y = model.apply(v, jnp.ones((1, 4)), rngs=jax.random.key(1))\n\n  def test_linen_submodule(self):\n    class LinenLayer(nn.Module):\n      dim: int\n      dropout_rate: float\n      def setup(self):\n        self.linear = nn.Dense(self.dim, use_bias=False)\n        self.dropout = nn.Dropout(self.dropout_rate, deterministic=False)\n\n      def __call__(self, x):\n        if not self.is_initializing():\n          self.sow('intermediates', 'count', 1,\n                    init_fn=lambda: 0, reduce_fn=lambda a, b: a + b)\n        x = self.linear(x)\n        x = self.dropout(x)\n        return x\n\n    class BridgeMLP(bridge.Module):\n      @bridge.compact\n      def __call__(self, x):\n        x = bridge.linen_in_bridge_mdl(LinenLayer(8, 0.3))(x)\n        x = bridge.linen_in_bridge_mdl(LinenLayer(8, 0.3), name='another')(x)\n        return x\n\n    model = BridgeMLP()\n    x = jax.random.normal(jax.random.key(0), (4, 8))\n    variables = model.init(jax.random.key(1), x)\n    self.assertFalse(jnp.array_equal(\n      variables['params']['LinenLayer_0']['linear']['kernel'],\n      variables['params']['another']['linear']['kernel'], ))\n\n    k1, k2, k3 = jax.random.split(jax.random.key(0), 3)\n    y1 = model.apply(variables, x, rngs={'params': k1, 'dropout': k2})\n    y2 = model.apply(variables, x, rngs={'params': k1, 'dropout': k3})\n    assert not jnp.array_equal(y1, y2)\n\n    _, updates = model.apply(variables, x, rngs={'params': k1, 'dropout': k3},\n                             mutable=True)\n    self.assertEqual(updates['intermediates']['LinenLayer_0']['count'], 1)\n\n    class BridgeMLPSetup(bridge.Module):\n      def setup(self):\n        self.layer = bridge.linen_in_bridge_mdl(LinenLayer(8, 0.3))\n      def __call__(self, x):\n        return self.layer(x)\n\n    model = BridgeMLPSetup()\n    variables = model.init(jax.random.key(1), x)\n    self.assertSameElements(variables['params'].keys(), ['layer'])\n    y1 = model.apply(variables, x, rngs={'params': k1, 'dropout': k2})\n    y2 = model.apply(variables, x, rngs={'params': k1, 'dropout': k3})\n    assert not jnp.array_equal(y1, y2)\n\n  def test_name(self):\n    class Foo(bridge.Module):\n      name: str\n\n    class Bar(bridge.Module):\n      @bridge.compact\n      def __call__(self):\n        f = Foo(name='f')\n        assert f.name == 'f'\n        assert self.f == f\n\n    Bar().init()\n\n  def test_transforms(self):\n    class Dense(bridge.Module):\n      dout: int\n      @bridge.compact\n      def __call__(self, x: jax.Array) -> jax.Array:\n        return x @ self.param('w', nn.initializers.normal(),\n                              (x.shape[-1], self.dout))\n\n    class MLP(bridge.Module):\n      dim: int\n      num_layers: int\n      def setup(self):\n        @nnx.split_rngs(splits=self.num_layers)\n        @nnx.vmap(\n            in_axes=(nnx.StateAxes({nnx.RngState: 0, ...: None}),),\n            axis_size=self.num_layers,\n            transform_metadata={nnx.PARTITION_NAME: None},\n        )\n        def create_block(parent):\n          block = Dense(self.dim)\n          parent.block = block\n        create_block(self)\n\n      def __call__(self, x):\n        @nnx.split_rngs(splits=self.num_layers)\n        @nnx.scan(\n            in_axes=(0, nnx.Carry),\n            out_axes=nnx.Carry,\n            transform_metadata={nnx.PARTITION_NAME: None},\n        )\n        def forward_block(model, x):\n          return model(x)\n        x = forward_block(self.block, x)\n        return x\n\n    model = MLP(dim=32, num_layers=2)\n    x = jnp.ones((4, 32))\n    variables = model.init(jax.random.key(0), x)\n    y = model.apply(variables, x)\n    w = variables['params']['block']['w']\n    np.testing.assert_array_equal(y, x @ w[0] @ w[1])\n\n  def test_shared_modules(self):\n    class Dense(bridge.Module):\n      dout: int\n      @bridge.compact\n      def __call__(self, x):\n        return x @ self.param('w', nn.initializers.normal(),\n                              (x.shape[-1], self.dout))\n\n    class Bottom(bridge.Module):\n      def setup(self):\n        self.layer = Dense(4)\n\n      def __call__(self, x):\n        return self.layer(x)\n\n    class Top(bridge.Module):\n      def setup(self):\n        self.zzz = Bottom()\n        self.set_attr_priority('zzz', bridge.AttrPriority.HIGH)\n        self.aaa = self.zzz  # another reference\n        self.dense = self.aaa.layer  # and another reference\n\n      def forward(self, x):\n        return self.aaa(x)\n\n      def __call__(self, x):\n        forward = nnx.remat(self.__class__.forward)\n        return forward(self, x)\n\n    model = Top()\n    x = jnp.ones((4, 32))\n    params = model.init(jax.random.key(0), x)['params']\n    self.assertSameElements(['zzz'], params.keys())\n\n  def test_linen_layer_naming(self):\n    class Dense(bridge.Module):\n      dout: int\n      @bridge.compact\n      def __call__(self, x):\n        return x @ self.param('w', lambda _: jnp.ones((x.shape[-1], self.dout)))\n\n    class MLP(bridge.Module):\n      nlayers: int\n      def setup(self):\n        self.layers = [Dense(4, name=f'layer_{i}') for i in range(self.nlayers)]\n      def __call__(self, x):\n        for layer in self.layers:\n          x = layer(x)\n        return x\n\n    model = MLP(nlayers=3)\n    x = jnp.ones((2, 4))\n    params = model.init(jax.random.key(0), x)['params']\n    self.assertSameElements([f'layer_{i}' for i in range(3)], params.keys())\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/bridge/wrappers_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nos.environ['XLA_FLAGS'] = '--xla_force_host_platform_device_count=4'\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom absl.testing import absltest\n\nimport flax\nfrom flax import linen as nn\nfrom flax import nnx\nfrom flax.nnx import bridge\n\n\nclass TestCompatibility(absltest.TestCase):\n  def setUp(self):\n    super().setUp()\n    dim1 = max(jax.device_count() // 2, 1)\n    device_mesh = np.array(jax.devices()).reshape(dim1, jax.device_count() // dim1)\n    self.mesh = jax.sharding.Mesh(devices=device_mesh, axis_names=('in', 'out'))\n\n  def test_functional(self):\n    # Functional API for NNX Modules\n    functional = bridge.functional(nnx.Linear)(32, 64)\n    state = functional.init(rngs=nnx.Rngs(0))\n    x = jax.numpy.ones((1, 32))\n    y, updates = functional.apply(state)(x)\n\n  ##################\n  ### LinenToNNX ###\n  ##################\n\n  def test_linen_to_nnx(self):\n    ## Wrapper API for Linen Modules\n    linen_module = nn.Dense(features=64)\n    x = jax.numpy.ones((1, 32))\n    model = bridge.ToNNX(linen_module, rngs=nnx.Rngs(0)).lazy_init(x)  # like linen init\n    y = model(x)  # like linen apply\n    assert y.shape == (1, 64)\n    self.assertIsInstance(model.kernel, nnx.Variable)\n    # NNX automatically adds metadata box regardless of original Linen module.\n    linen_vars = {\n      'params': {'kernel': model.kernel[...], 'bias': model.bias[...]}\n    }\n    linen_y = linen_module.apply(linen_vars, x)\n    np.testing.assert_array_equal(y, linen_y)\n\n  def test_linen_to_nnx_submodule(self):\n    class NNXOuter(nnx.Module):\n      def __init__(self, dout: int, *, rngs: nnx.Rngs):\n        self.nn_dense1 = bridge.ToNNX(nn.Dense(dout, use_bias=False), rngs=rngs)\n        self.b = nnx.Param(jax.random.uniform(rngs.params(), (1, dout,)))\n        self.batchnorm = bridge.ToNNX(nn.BatchNorm(use_running_average=True), rngs=rngs)\n        self.rngs = rngs\n\n      def __call__(self, x):\n        x = self.nn_dense1(x) + self.b\n        return self.batchnorm(x)\n\n    x = jax.random.normal(jax.random.key(0), (2, 4))\n    model = NNXOuter(3, rngs=nnx.Rngs(0))\n    gdef_before_lazy_init, _ = nnx.split(model)\n    bridge.lazy_init(model, x)\n    gdef_full, state = nnx.split(model)\n    assert gdef_before_lazy_init != gdef_full\n    assert 'nn_dense1' in state\n    assert 'batchnorm' in state\n    assert 'kernel' in state['nn_dense1']\n    y = model(x)\n    k, b = state['nn_dense1']['kernel'][...], state['b'][...]\n    np.testing.assert_allclose(y, x @ k + b, rtol=1e-5)\n    assert gdef_full == nnx.graphdef(model)  # static data is stable now\n\n  def test_linen_to_nnx_noncall_method(self):\n    class Foo(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        b = self.param('b', nn.zeros_init(), (1, 3,))\n        return self.dot(x) + b\n\n      @nn.compact\n      def dot(self, x):\n        w = self.param('w', nn.initializers.lecun_normal(), (4, 3))\n        return x @ w\n\n      def rngs(self):\n        raise ValueError('This should not be called because ToNNX has .rngs')\n\n    x = jax.random.normal(jax.random.key(0), (2, 4))\n    model = bridge.ToNNX(Foo(), rngs=nnx.Rngs(0))\n    bridge.lazy_init(model.dot, x)\n    y = model.dot(x)\n    np.testing.assert_allclose(y, x @ nnx.state(model)['w'][...])\n    # lazy_init only initialized param w inside dot(), so calling __call__ should fail\n    with self.assertRaises(flax.errors.ScopeParamNotFoundError):\n      y = model(x)\n    assert isinstance(model.to_nnx__rngs, nnx.Rngs)\n\n  def test_linen_to_nnx_mutable(self):\n    class Foo(nn.Module):\n      def setup(self):\n        self.count = self.variable('counter', 'count', lambda: jnp.zeros((), jnp.int32))\n\n      def __call__(self, x):\n        if not self.is_initializing():\n          self.count.value += 1\n        return x\n\n    x = lambda: jnp.zeros((), jnp.int32)\n    model = bridge.ToNNX(Foo(), rngs=nnx.Rngs(0)).lazy_init(x)\n    self.assertEqual(nnx.state(model)['count'][...], 0)\n    y = model(x, mutable=True)\n    self.assertEqual(nnx.state(model)['count'][...], 1)\n\n  def test_linen_to_nnx_transform(self):\n    class NNXOuter(nnx.Module):\n      def __init__(self, dout: int, rngs: nnx.Rngs):\n        self.inner = nnx.bridge.ToNNX(nn.Dense(dout), rngs=rngs)\n        self.rngs = rngs\n\n      def __call__(self, x):\n\n        @nnx.split_rngs(splits=5)\n        @nnx.vmap(in_axes=(0, None), axis_size=5)\n        def vmap_fn(inner, x):\n          return inner(x)\n\n        return vmap_fn(self.inner, x)\n\n    x = jax.random.normal(jax.random.key(0), (2, 4))\n    model = NNXOuter(3, rngs=nnx.Rngs(0))\n    nnx.bridge.lazy_init(model, x)\n\n    self.assertEqual(model.inner.kernel.shape, (5, 4, 3))\n    self.assertEqual(model.inner.bias.shape, (5, 3))\n\n  def test_linen_to_nnx_metadata(self):\n    linen_module = nn.Dense(\n      features=64,\n      kernel_init=nn.with_partitioning(nn.initializers.lecun_normal(),\n                                       ('in', 'out')),\n      bias_init=nn.with_logical_partitioning(nn.initializers.zeros_init(),\n                                             ('out-alias',),\n                                             rules=(('out-alias', 'out'),)),\n      )\n    x = jax.numpy.ones((1, 32))\n    linen_vars = linen_module.init(jax.random.key(0), x)\n\n    @nnx.jit\n    def create_sharded_nnx_module(x):\n      model = bridge.lazy_init(bridge.ToNNX(linen_module, rngs=nnx.Rngs(0)), x)\n      state = nnx.state(model)\n      sharded_state = jax.lax.with_sharding_constraint(state, nnx.get_partition_spec(state))\n      nnx.update(model, sharded_state)\n      return model\n    with jax.set_mesh(self.mesh):\n      nnx_model = create_sharded_nnx_module(x)\n\n    # nn.Partitioned metadata boxes translated into valid nnx.Variable boxes.\n    self.assertIsInstance(linen_vars['params']['kernel'], nn.Partitioned)\n    self.assertIsInstance(linen_vars['params']['bias'], nn.LogicallyPartitioned)\n    self.assertIsInstance(nnx_model.kernel, nnx.Variable)\n    assert nnx_model.kernel.out_sharding == ('in', 'out')\n    assert nnx_model.kernel[...].sharding.is_equivalent_to(\n      jax.sharding.NamedSharding(\n        self.mesh, jax.sharding.PartitionSpec('in', 'out')\n      ),\n      ndim=2,\n    ), f'{nnx_model.kernel[...].sharding = }'\n\n    assert nnx_model.bias.out_sharding == ('out-alias',)\n    assert nnx_model.bias.sharding_rules == (('out-alias', 'out'),)\n    assert nnx_model.bias[...].sharding.is_equivalent_to(\n      jax.sharding.NamedSharding(self.mesh, jax.sharding.PartitionSpec('out')),\n      ndim=1,\n    )\n\n  def test_linen_to_nnx_state_structure_consistency(self):\n    class LinenInner(nn.Module):\n      dout: int\n      @nn.compact\n      def __call__(self, x):\n        w = self.param('w', nn.initializers.lecun_normal(), (x.shape[-1], self.dout))\n        return nn.Dropout(rate=0.5, deterministic=False)(x @ w)\n\n    class LinenMiddle(nn.Module):\n      dout: int\n      @nn.compact\n      def __call__(self, x):\n        dot = LinenInner(self.dout, name='dot')\n        b = self.variable('bias', 'b', nn.initializers.zeros_init(), None, (1, self.dout))\n        return dot(x) + b.value\n\n    @nnx.register_variable_name('bias')\n    class Bias(nnx.Variable):\n      pass\n\n    class NNXMiddle(nnx.Module):\n      def __init__(self, dout: int, *, rngs: nnx.Rngs):\n        self.dot = bridge.ToNNX(LinenInner(dout), rngs=rngs)\n        self.b = Bias(nnx.initializers.zeros_init()(rngs.params(), (1, dout)))\n      def __call__(self, x):\n        return self.dot(x) + self.b\n\n    x = jax.random.normal(jax.random.key(42), (2, 4))\n    from_top = bridge.lazy_init(\n      bridge.ToNNX(LinenMiddle(dout=3), rngs=nnx.Rngs(0, dropout=1)), x)\n    from_middle = bridge.lazy_init(\n      NNXMiddle(dout=3, rngs=nnx.Rngs(0, dropout=1)), x)\n\n    # Remove the NNX-module-local RNG states, which will be different\n    # because the NNX modules are on different level\n    def get_weights(model):\n      return nnx.split(model, nnx.RngCount, nnx.RngKey, ...)[3]\n    from_top_weights = get_weights(from_top)\n    from_middle_weights = get_weights(from_middle)\n\n    # Confirm the rest of the state has the same structure.\n    self.assertEqual(jax.tree.structure(from_top_weights),\n                     jax.tree.structure(from_middle_weights))\n\n  def test_adding_new_attributes(self):\n    class LinenModule(nn.Module):\n      @nn.compact\n      def __call__(self):\n        if self.is_initializing() and self.is_mutable_collection('cache'):\n          self.put_variable('cache', 'x', 0)\n        res = self.get_variable('cache', 'x')\n        return res\n\n    class NNXModule(nnx.Module):\n      def __init__(self):\n        self.module = nnx.bridge.ToNNX(LinenModule()).lazy_init()\n\n      def __call__(self):\n        result1 = self.module(mutable=['cache'])\n        assert result1 == 0\n        result2 = self.module()\n        assert result2 == 0, result2  # fails: result2 is None\n\n    module = NNXModule()\n    module()\n\n  ##################\n  ### NNXToLinen ###\n  ##################\n\n  def test_nnx_to_linen(self):\n    model = bridge.to_linen(nnx.Linear, 32, out_features=64)\n    x = jax.numpy.ones((1, 32))\n    y, variables = model.init_with_output(jax.random.key(0), x)\n    assert y.shape == (1, 64)\n    np.testing.assert_allclose(y, x @ variables['params']['kernel'])\n\n  def test_nnx_to_linen_multiple_rngs(self):\n    class NNXInner(nnx.Module):\n      def __init__(self, din, dout, rngs):\n        self.w = nnx.Param(nnx.initializers.lecun_normal()(rngs.params(), (din, dout)))\n        self.dropout = nnx.Dropout(rate=0.5, rngs=rngs)\n      def __call__(self, x):\n        return self.dropout(x @ self.w[...])\n\n    class LinenOuter(nn.Module):\n      @nn.compact\n      def __call__(self, x):\n        inner = bridge.to_linen(NNXInner, 4, 3)\n        return inner(x)\n\n    xkey, pkey, dkey1, dkey2 = jax.random.split(jax.random.key(0), 4)\n    x = jax.random.normal(xkey, (2, 4))\n    model = LinenOuter()\n    y1, var = model.init_with_output({'params': pkey, 'dropout': dkey1}, x)\n    y2 = model.apply(var, x, rngs={'dropout': dkey2})\n    assert not jnp.allclose(y1, y2)  # dropout keys are different\n\n  def test_nnx_to_linen_multiple_collections(self):\n    class NNXInner(nnx.Module):\n      def __init__(self, din, dout, rngs):\n        self.w = nnx.Param(nnx.initializers.lecun_normal()(rngs.params(), (din, dout)))\n        self.bn = nnx.BatchNorm(dout, use_running_average=False, rngs=rngs)\n        self.lora = nnx.LoRA(din, 3, dout, rngs=rngs)\n\n      def __call__(self, x):\n        return self.bn(x @ self.w[...]) + self.lora(x)\n\n    xkey, pkey, dkey = jax.random.split(jax.random.key(0), 3)\n    x = jax.random.normal(xkey, (2, 4))\n    model = bridge.to_linen(NNXInner, 4, 3)\n    var = model.init({'params': pkey, 'dropout': dkey}, x)\n    self.assertSameElements(var.keys(), ['LoRAParam', 'params', 'batch_stats'])\n    y = model.apply(var, x)\n    assert y.shape == (2, 3)\n\n  def test_nnx_to_linen_mutable(self):\n\n    @nnx.register_variable_name('Count', overwrite=True)\n    class Count(nnx.Variable):\n      pass\n\n    class Counter(nnx.Module):\n      def __init__(self):\n        self.count = Count(jnp.array(0))\n      def __call__(self):\n        self.count[...] += 1\n\n    model = bridge.ToLinen(Counter, skip_rng=True)\n    variables = model.init(jax.random.key(0))\n    assert variables['Count']['count'] == 0\n\n    _, updates = model.apply(variables, mutable='Count')\n    assert updates['Count']['count'] == 1\n    _ = model.apply(variables | updates)\n\n  def test_to_linen_method_call(self):\n    class Foo(nn.Module):\n      def setup(self):\n        self.embedding = nnx.bridge.to_linen(nnx.Embed, 2, 3)\n\n      def __call__(self, x):\n        return self.embedding(x)\n\n      def attend(self, x):\n        return self.embedding.attend(x)\n\n    module = Foo()\n    x = jnp.ones((1,), dtype=jnp.int32)\n    z = jnp.ones((1, 3))\n    y , params = module.init_with_output(jax.random.key(0), x)\n    assert y.shape == (1, 3)\n    assert params['params']['embedding']['embedding'].shape == (2, 3)\n    x_out = module.apply(params, z, method='attend')\n    assert x_out.shape == (1, 2)\n\n  def test_to_linen_nnx_method_arg(self):\n    module = nnx.bridge.to_linen(nnx.Embed, 2, 3)\n    x = jnp.ones((1,), dtype=jnp.int32)\n    z = jnp.ones((1, 3))\n    y , params = module.init_with_output(jax.random.key(0), x)\n    assert y.shape == (1, 3)\n    assert params['params']['embedding'].shape == (2, 3)\n    x_out = module.apply(params, z, nnx_method='attend')\n    assert x_out.shape == (1, 2)\n\n\n\n  def test_nnx_to_linen_mutated_static_data(self):\n\n    @nnx.register_variable_name('Count', overwrite=True)\n    class Count(nnx.Variable):\n      pass\n\n    class Counter(nnx.Module):\n      def __init__(self):\n        self.count = Count(jnp.array(0))\n      def __call__(self):\n        self.count[...] += 1\n        self.count_nonzero = nnx.Intermediate(jnp.array(1))\n\n    model = bridge.ToLinen(Counter, skip_rng=True)\n    variables = model.init(jax.random.key(0))\n    assert variables['Count']['count'] == 0\n\n    _, updates = model.apply(variables, mutable=['Count', 'intermediates'])\n    assert updates['Count']['count'] == 1\n    assert updates['intermediates']['count_nonzero'] == 1\n    del updates['intermediates']\n    _ = model.apply(variables | updates)\n\n  def test_nnx_to_linen_transforms(self):\n    class LinenOuter(nn.Module):\n      dout: int\n      @nn.compact\n      def __call__(self, x):\n        inner = nn.vmap(\n            bridge.ToLinen,\n            variable_axes={'params': 0},\n            split_rngs={'params': True},\n        )(nnx.Linear, args=(x.shape[-1], self.dout))\n        return inner(x)\n\n    xkey, pkey, _ = jax.random.split(jax.random.key(0), 3)\n    x = jax.random.normal(xkey, (2, 4))\n    model = LinenOuter(dout=3)\n    y, var = model.init_with_output(pkey, x)\n    k = var['params']['VmapToLinen_0']['kernel']\n    assert k.shape == (2, 4, 3)\n    np.testing.assert_allclose(y, jnp.einsum('ab,abc->ac', x, k))\n\n  def test_nnx_to_linen_metadata(self):\n    model = bridge.to_linen(\n      nnx.Linear, 32, 64,\n      kernel_init=nnx.with_partitioning(nnx.initializers.lecun_normal(), ('in', 'out')))\n    x = jax.numpy.ones((1, 32))\n    with jax.set_mesh(self.mesh):\n      y, variables = model.init_with_output(jax.random.key(0), x)\n      pspec_tree = nn.get_partition_spec(variables)\n    assert y.shape == (1, 64)\n    self.assertIsInstance(variables['params']['kernel'], nnx.bridge.NNXMeta)\n    assert variables['params']['kernel'].metadata['out_sharding'] == ('in', 'out')\n    self.assertEqual(pspec_tree['params']['kernel'],\n                     jax.sharding.PartitionSpec('in', 'out'))\n    np.testing.assert_allclose(y, x @ variables['params']['kernel'].value)\n\n  def test_nnx_to_linen_metadata_transform(self):\n    # TODO: add support and testing after axis add/remove in transform is fixed.\n    pass\n\n  def test_nnx_to_linen_pytree_structure_consistency(self):\n    class NNXInner(nnx.Module):\n      def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n        self.w = nnx.Param(nnx.initializers.lecun_normal()(rngs.params(), (din, dout)))\n        self.dropout = nnx.Dropout(rate=0.5, rngs=rngs)\n      def __call__(self, x):\n        return self.dropout(x @ self.w)\n\n    @nnx.register_variable_name('bias', overwrite=True)\n    class Bias(nnx.Variable):\n      pass\n\n    class NNXMiddle(nnx.Module):\n      def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n        self.dot = NNXInner(din, dout, rngs=rngs)\n        self.b = Bias(nnx.initializers.zeros_init()(rngs.params(), (1, dout)))\n      def __call__(self, x):\n        return self.dot(x) + self.b\n\n    class LinenMiddle(nn.Module):\n      dout: int\n      @nn.compact\n      def __call__(self, x):\n        dot = bridge.to_linen(NNXInner, x.shape[-1], self.dout, name='dot')\n        b = self.variable('bias', 'b', nn.initializers.zeros_init(), None, (1, self.dout))\n        return dot(x) + b.value\n\n    x = jax.random.normal(jax.random.key(42), (2, 4))\n    keys = {'params': jax.random.key(0), 'dropout': jax.random.key(1)}\n    from_top = bridge.to_linen(NNXMiddle, din=4, dout=3).init(keys, x)\n    from_middle = LinenMiddle(dout=3).init(keys, x)\n\n    # Remove the NNX-module-local RNG states, which will be different\n    # because the NNX modules are on different level\n    def get_weights(variables):\n      non_rngs = {}\n      for kp, v in flax.traverse_util.flatten_dict(variables).items():\n        if 'rngs' not in kp:\n          non_rngs[kp] = v\n      return flax.traverse_util.unflatten_dict(non_rngs)\n    from_top_weights = get_weights(from_top)\n    from_middle_weights = get_weights(from_middle)\n\n    # Confirm the rest of the state has the same structure.\n    self.assertEqual(jax.tree.structure(from_top_weights),\n                     jax.tree.structure(from_middle_weights))\n\n  ############################\n  ### Hybrid mix-and-match ###\n  ############################\n\n  def test_nnx_linen_nnx(self):\n    class NNXInner(nnx.Module):\n      def __init__(self, din, dout, dropout_rate, rngs):\n        self.w = nnx.Param(\n          nnx.with_partitioning(nnx.initializers.lecun_normal(), sharding=('in', 'out')\n                                )(rngs.params(), (din, dout)))\n        self.dropout = nnx.Dropout(rate=dropout_rate, rngs=rngs)\n      def __call__(self, x):\n        return self.dropout(x @ self.w)\n\n    class LinenMiddle(nn.Module):\n      dout: int\n      dropout_rate: float\n      @nn.compact\n      def __call__(self, x):\n        dot = bridge.to_linen(NNXInner, x.shape[-1], self.dout, self.dropout_rate, name='dot')\n        logical_init = nn.with_logical_partitioning(\n          nn.initializers.lecun_normal(), ('out-alias',), rules=(('out-alias', 'out'),))\n        b = self.param('b', logical_init, (2, self.dout))\n        return dot(x) + b\n\n    class NNXOuter(nnx.Module):\n      def __init__(self, dout: int, dropout_rate: float, *, rngs: nnx.Rngs):\n        self.inner = bridge.ToNNX(LinenMiddle(dout, dropout_rate), rngs=rngs)\n        self.rngs = rngs\n      def __call__(self, x):\n        return self.inner(x)\n\n    x = jax.random.normal(jax.random.key(0), (2, 4))\n\n    # Test the RNG\n    with jax.set_mesh(self.mesh):\n      model = bridge.lazy_init(NNXOuter(dout=6, dropout_rate=0.5,\n                                        rngs=nnx.Rngs(default=1, dropout=2)), x)\n      nnx.reseed(model, dropout=2)\n      y1, y2 = model(x), model(x)\n      # The dropout key of lowest NNX level still changes over stateful calls\n      assert not jnp.allclose(y1, y2)\n      # Another reseed resets the RNG key back\n      nnx.reseed(model, dropout=2)\n      np.testing.assert_array_equal(y1, model(x))\n\n    # Test the param value with disabled dropout\n    with jax.set_mesh(self.mesh):\n      model = bridge.lazy_init(NNXOuter(dout=6, dropout_rate=0.,\n                                        rngs=nnx.Rngs(default=1, dropout=2)), x)\n      w, b = model.inner.dot['w'], model.inner.b\n      np.testing.assert_allclose(model(x), x @ w + b)\n    self.assertIsInstance(w, nnx.Param)\n    assert hasattr(w, 'out_sharding') and w.out_sharding == ('in', 'out')\n    assert hasattr(b, 'out_sharding') and b.out_sharding == ('out-alias', )\n\n  def test_linen_nnx_linen(self):\n    # TODO: add when we can safely `lazy_init` the NNX module inside `ToLinen` without\n    # messing up the stateful part of the NNX module.\n    pass\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/containers_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nfrom flax import nnx\nfrom absl.testing import absltest\nimport jax.numpy as jnp\n\n\nclass TestContainers(absltest.TestCase):\n  def test_unbox(self):\n    x = nnx.Param(\n      jnp.array(1),\n      on_get_value=lambda c, x: x + 3,  # type: ignore\n    )\n\n    assert x[...] == 4\n\n  def test_on_set_value(self):\n    x = nnx.Param(\n      jnp.array(1),  # type: ignore\n      on_set_value=lambda c, x: x + 7,  # type: ignore\n    )\n    x[...] = 5\n\n    assert x.get_raw_value() == 12\n\n  def test_module_unbox(self):\n    class Foo(nnx.Module):\n      def __init__(self) -> None:\n        self.x = nnx.Param(1, on_get_value=lambda c, x: x + 3)\n\n    module = Foo()\n\n    assert module.x.get_value() == 4\n    assert vars(module)['x'].get_raw_value() == 1\n\n  def test_module_box(self):\n    class Foo(nnx.Module):\n      def __init__(self) -> None:\n        self.x = nnx.Param(\n          jnp.array(1),\n          on_set_value=lambda c, x: x + 7,  # type: ignore\n        )\n\n    module = Foo()\n    module.x[...] = 5\n\n    assert module.x[...] == 12\n    assert vars(module)['x'][...] == 12\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/filters_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom absl.testing import absltest\n\nfrom flax import nnx\n\n\nclass TestFilters(absltest.TestCase):\n  def test_path_contains(self):\n    class Model(nnx.Module):\n      def __init__(self, rngs):\n        self.backbone1 = nnx.Linear(2, 3, rngs=rngs)\n        self.backbone2 = nnx.Linear(3, 3, rngs=rngs)\n        self.head = nnx.Linear(3, 10, rngs=rngs)\n\n    model = Model(nnx.Rngs(0))\n\n    head_state = nnx.state(model, nnx.PathContains('head'))\n    backbones_state = nnx.state(model, nnx.PathContains('backbone', exact=False))\n\n    self.assertIn('head', head_state)\n    self.assertNotIn('backbone', head_state)\n    self.assertIn('backbone1', backbones_state)\n    self.assertIn('backbone2', backbones_state)\n    self.assertNotIn('head', backbones_state)\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/graph_utils_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport dataclasses\nfrom functools import partial\nfrom threading import Thread\nfrom typing import Any\n\nfrom absl.testing import absltest, parameterized\nimport numpy as np\nfrom flax import linen, nnx, struct\nimport jax\nimport jax.numpy as jnp\n\n\nclass StatefulLinear(nnx.Module):\n  def __init__(self, din, dout, rngs):\n    self.w = nnx.Param(jax.random.uniform(rngs(), (din, dout)))\n    self.b = nnx.Param(jnp.zeros((dout,)))\n    self.count = nnx.Variable(jnp.array(0, dtype=jnp.uint32))\n\n  def increment(self):\n    self.count[...] += 1\n\n  def __call__(self, x):\n    self.count[...] += 1\n    return x @ self.w + self.b[None]\n\n\nclass TestGraphUtils(parameterized.TestCase):\n  def test_flatten(self):\n    a = {'a': 1, 'b': nnx.Param(2)}\n    g = [a, 3, a, nnx.Param(4)]\n\n    refmap = nnx.graphlib.RefMap()\n    graphdef, flat_state = nnx.graphlib.flatten(g, ref_index=refmap, graph=True)\n\n    assert flat_state[0][1].get_value() == 2\n    assert flat_state[1][1].get_value() == 4\n\n    assert len(refmap) == 2  # 2 Variables\n    assert a['b'] in refmap\n    assert g[3] in refmap\n\n  def test_flatten_no_paths(self):\n    a = {'a': 1, 'b': nnx.Param(jnp.array(2))}\n    g = [a, 3, a, nnx.Param(jnp.array(4))]\n\n    refmap = nnx.graphlib.RefMap()\n    graphdef, flat_state = nnx.graphlib.flatten(\n      g, ref_index=refmap, with_paths=False, graph=True\n    )\n\n    assert flat_state[0][...] == 2\n    assert flat_state[1][...] == 4\n\n    assert len(refmap) == 2  # 2 Variables\n    assert a['b'] in refmap\n    assert g[3] in refmap\n\n  def test_unflatten(self):\n    a = nnx.Dict(a=1, b=nnx.Param(2))\n    g = nnx.List([a, 3, a, nnx.Param(4)])\n\n    graphdef, state = nnx.split(g)\n    g = nnx.merge(graphdef, state)\n\n    assert g[0] is g[2]\n\n  @parameterized.parameters(True, False)\n  def test_flatten_unflatten_unkown_leaves(self, graph):\n    x = jnp.array(1.0)\n    graphdef, flat_state = nnx.graphlib.flatten(x, graph=graph)\n\n    self.assertIs(flat_state[0][1], x)\n\n    x1 = nnx.merge(graphdef, flat_state)\n    self.assertIs(x1, x)\n\n  @parameterized.parameters(True, False)\n  def test_split_merge_unkown_leaves(self, graph):\n    x = jnp.array(1.0)\n    graphdef, state = nnx.graphlib.split(x, graph=graph)\n\n    self.assertIs(state, x)\n\n    x1 = nnx.merge(graphdef, state)\n    self.assertIs(x1, x)\n\n  @parameterized.parameters(True, False)\n  def test_split_merge_unkown_leaves_with_filters(self, graph):\n    x = jnp.array(1.0)\n    graphdef, state, rest = nnx.graphlib.split(x, jax.Array, ..., graph=graph)\n\n    self.assertIs(state, x)\n\n    x1 = nnx.merge(graphdef, state, rest)\n    self.assertIs(x1, x)\n\n  def test_unflatten_pure_dict(self):\n    a = nnx.Dict(a=1, b=nnx.Param(2))\n    g = nnx.List([a, 3, a, nnx.Param(4)])\n\n    graphdef, state = nnx.split(g)\n    pure_state = nnx.to_pure_dict(state)\n\n    g = nnx.merge(graphdef, pure_state)\n\n    assert g[0] is g[2]\n\n  def test_unflatten_pytree(self):\n    a = {'a': 1, 'b': nnx.Param(2)}\n    g = [a, 3, a, nnx.Param(4)]\n\n    graphdef, state = nnx.split(g)\n    g = nnx.merge(graphdef, state)\n\n    assert g[0] is not g[2]\n\n  def test_unflatten_empty(self):\n    a = nnx.Dict({'a': 1, 'b': nnx.Param(2)})\n    g = nnx.List([a, 3, a, nnx.Param(4)])\n\n    graphdef, state = nnx.split(g)\n\n    with self.assertRaisesRegex(ValueError, 'Incorrect number of leaves'):\n      nnx.graphlib.unflatten(graphdef, nnx.State({}))\n\n  def test_unflatten_return_variables(self):\n    a = nnx.Dict({'a': 1, 'b': nnx.Param(2)})\n    g = nnx.List([a, 3, a, nnx.Param(4)])\n\n    graphdef, state = nnx.graphlib.flatten(\n      g, with_paths=True, graph=True\n    )\n\n    self.assertLen(state, 2)\n    self.assertIsInstance(state, nnx.graphlib.FlatState)\n    self.assertIsInstance(state[0][1], nnx.Param)\n    self.assertIsInstance(state[1][1], nnx.Param)\n\n  def test_update_dynamic(self):\n    a = {'a': 1, 'b': nnx.Param(jnp.array(2))}\n    g = [a, 3, a, nnx.Param(jnp.array(4))]\n\n    graphdef, state = nnx.split(g)\n\n    state[0]['b'][...] = 3\n    nnx.update(g, state)\n\n    assert g[0]['b'][...] == 3\n    assert g[2]['b'][...] == 3\n\n  def test_update_from_pure_dict(self):\n    a = {'a': 1, 'b': nnx.Param(jnp.array(2))}\n    g = [a, 3, a, nnx.Param(jnp.array(4))]\n\n    graphdef, state = nnx.split(g)\n    pure_state = nnx.to_pure_dict(state)\n\n    pure_state[0]['b'] = jnp.array(3)\n    nnx.update(g, pure_state)\n\n    assert g[0]['b'][...] == 3\n    assert g[2]['b'][...] == 3\n\n  @parameterized.parameters(True, False)\n  def test_module_list(self, graph):\n    rngs = nnx.Rngs(0)\n    ls = [\n      nnx.Linear(2, 2, rngs=rngs),\n      nnx.BatchNorm(2, rngs=rngs),\n    ]\n\n    graphdef, state = nnx.split(ls, graph=graph)\n\n    assert state[0]['kernel'].shape == (2, 2)\n    assert state[0]['bias'].shape == (2,)\n    assert state[1]['scale'].shape == (2,)\n    assert state[1]['bias'].shape == (2,)\n    assert state[1]['mean'].shape == (2,)\n    assert state[1]['var'].shape == (2,)\n\n  def test_shared_variables(self):\n    v = nnx.Param(1)\n    g = [v, v]\n\n    graphdef, state = nnx.split(g)\n\n    assert len(nnx.to_flat_state(state)) == 1\n\n    g2 = nnx.merge(graphdef, state)\n\n    assert g2[0] is g2[1]\n\n  def test_tied_weights(self):\n    class Foo(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs) -> None:\n        self.bar = nnx.Linear(2, 2, rngs=rngs)\n        self.baz = nnx.Linear(2, 2, rngs=rngs)\n\n        # tie the weights\n        self.baz.kernel = self.bar.kernel\n\n    node = Foo(rngs=nnx.Rngs(0))\n    graphdef, state = nnx.split(node)\n\n    assert len(nnx.to_flat_state(state)) == 3  # 2 bias + 1 kernel\n\n    node2 = nnx.merge(graphdef, state)\n\n    assert node2.bar.kernel is node2.baz.kernel\n\n  def test_tied_weights_example(self):\n    class LinearTranspose(nnx.Module):\n      def __init__(self, dout: int, din: int, *, rngs: nnx.Rngs) -> None:\n        self.kernel = nnx.Param(\n          nnx.initializers.lecun_normal()(rngs(), (dout, din))\n        )\n\n      def __call__(self, x):\n        return x @ self.kernel.T\n\n    class Encoder(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs) -> None:\n        self.embed = nnx.Embed(10, 2, rngs=rngs)\n        ...\n        self.linear_out = LinearTranspose(10, 2, rngs=rngs)\n\n        # tie the weights\n        self.linear_out.kernel = self.embed.embedding\n\n      def __call__(self, x):\n        x = self.embed(x)\n        ...\n        return self.linear_out(x)\n\n    model = Encoder(rngs=nnx.Rngs(0))\n    graphdef, state = nnx.split(model)\n\n    assert len(nnx.to_flat_state(state)) == 1\n\n    x = jax.random.randint(jax.random.key(0), (2,), 0, 10)\n    y = model(x)\n\n    assert y.shape == (2, 10)\n\n  def test_state_variables_shared_with_graph(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = nnx.Param(jnp.array(1))\n\n    m = Foo()\n    graphdef, state = nnx.split(m)\n\n    assert isinstance(m.a, nnx.Param)\n    assert isinstance(state['a'], nnx.Param)\n    assert m.a is state['a']\n    assert m.a[...] == state['a'][...]\n\n    m2 = nnx.merge(graphdef, state)\n\n    assert isinstance(m2.a, nnx.Param)\n    assert isinstance(state['a'], nnx.Param)\n    assert m2.a is state['a']\n    assert m2.a[...] == state['a'][...]\n\n  def test_shared_state_variables_shared_with_graph(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        p = nnx.Param(jnp.array(1))\n        self.a = p\n        self.b = p\n\n    m = Foo()\n    graphdef, state = nnx.split(m)\n\n    assert isinstance(m.a, nnx.Param)\n    assert isinstance(m.b, nnx.Param)\n    assert isinstance(state['a'], nnx.Param)\n    assert 'b' not in state\n    assert m.a is state['a']\n    assert m.b is state['a']\n    assert m.a[...] == state['a'][...]\n    assert m.b[...] == state['a'][...]\n\n    m2 = nnx.merge(graphdef, state)\n\n    assert isinstance(m2.a, nnx.Param)\n    assert isinstance(m2.b, nnx.Param)\n    assert isinstance(state['a'], nnx.Param)\n    assert m2.a is state['a']\n    assert m2.b is state['a']\n    assert m2.a[...] == state['a'][...]\n    assert m2.b[...] == state['a'][...]\n    assert m2.a is m2.b\n\n  def test_pytree_flatten(self):\n    @struct.dataclass\n    class Tree:\n      a: int\n      b: str = struct.field(pytree_node=False)\n\n    p = Tree(1, 'a')\n\n    leaves, treedef = nnx.graphlib._flatten_pytree(p)\n    fields = dict(leaves)\n\n    assert 'a' in fields\n    assert 'b' not in fields\n    assert fields['a'] == 1\n\n    p2 = nnx.graphlib._unflatten_pytree(leaves, treedef)\n\n    assert isinstance(p2, Tree)\n    assert p2.a == 1\n\n  def test_pytree_node(self):\n    @struct.dataclass\n    class Tree:\n      a: nnx.Param[int]\n      b: str = struct.field(pytree_node=False)\n\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.tree = nnx.data(Tree(nnx.Param(1), 'a'))\n\n    m = Foo()\n\n    graphdef, state = nnx.split(m)\n\n    assert 'tree' in state\n    assert 'a' in state['tree']\n\n    m2 = nnx.merge(graphdef, state)\n\n    assert isinstance(m2.tree, Tree)\n    assert m2.tree.a.get_value() == 1\n    assert m2.tree.b == 'a'\n    assert m2.tree.a is m.tree.a\n    assert m2.tree is not m.tree\n\n  def test_cached_unflatten(self):\n    class Foo(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.a = nnx.Linear(2, 2, rngs=rngs)\n        self.b = nnx.BatchNorm(2, rngs=rngs)\n\n    def f(m: Foo):\n      m.a, m.b = m.b, m.a  # type: ignore\n\n    m = Foo(rngs=nnx.Rngs(0))\n    a = m.a\n    b = m.b\n\n    ref_out_idx_out = nnx.graphlib.RefMap()\n    graphdef: nnx.graphlib.GraphDef[Foo]\n    graphdef, state = nnx.graphlib.flatten(m, ref_index=ref_out_idx_out, graph=True)\n    state = state.to_nested_state()\n\n    @partial(jax.jit, static_argnums=(0,))\n    def f_pure(graphdef: nnx.graphlib.GraphDef[Foo], state):\n      idx_out_ref_in = nnx.graphlib.IndexMap()\n      m = nnx.graphlib.unflatten(graphdef, state, index_ref=idx_out_ref_in)\n      ref_in_idx_out = nnx.graphlib.RefMap.from_indexmap(idx_out_ref_in)\n      f(m)\n      ref_in_idx_in = nnx.graphlib.RefMap()\n      graphdef, state = nnx.graphlib.flatten(\n        m, ref_index=ref_in_idx_in, ref_outer_index=ref_in_idx_out, graph=True\n      )\n      state = state.to_nested_state()\n      return state, graphdef\n\n    state, graphdef_out = f_pure(graphdef, state)\n    idx_out_ref_out = nnx.graphlib.IndexMap.from_refmap(ref_out_idx_out)\n    m2 = nnx.graphlib.unflatten(\n      graphdef_out, state, outer_index_outer_ref=idx_out_ref_out\n    )\n    assert m2 is m\n    assert m2.a is b\n    assert m2.b is a\n\n  def test_cached_unflatten_swap_variables(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = nnx.Param(1)\n        self.b = nnx.Param(2)\n\n    def f(m: Foo):\n      m.a, m.b = m.b, m.a\n\n    m = Foo()\n    a = m.a\n    b = m.b\n\n    ref_out_idx_out = nnx.graphlib.RefMap()\n    graphdef: nnx.graphlib.GraphDef[Foo]\n    graphdef, state = nnx.graphlib.flatten(m, ref_index=ref_out_idx_out, graph=True)\n    idx_out_ref_out = {v: k for k, v in ref_out_idx_out.items()}\n    state = state.to_nested_state()\n\n    @partial(jax.jit, static_argnums=(0,))\n    def f_pure(graphdef: nnx.graphlib.GraphDef[Foo], state):\n      idx_out_ref_in = nnx.graphlib.IndexMap()\n      m = nnx.graphlib.unflatten(graphdef, state, index_ref=idx_out_ref_in)\n      ref_in_idx_out = nnx.graphlib.RefMap.from_indexmap(idx_out_ref_in)\n      f(m)\n      ref_in_idx_in = nnx.graphlib.RefMap()\n      graphdef, state = nnx.graphlib.flatten(\n        m, ref_index=ref_in_idx_in, ref_outer_index=ref_in_idx_out, graph=True\n      )\n      state = state.to_nested_state()\n      return state, graphdef\n\n    state, graphdef = f_pure(graphdef, state)\n    m2 = nnx.graphlib.unflatten(\n      graphdef, state, outer_index_outer_ref=idx_out_ref_out\n    )\n    assert m2 is m\n    assert m2.a is b\n    assert m2.b is a\n\n  def test_cached_unflatten_add_self_reference(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.ref = nnx.data(None)\n\n    def f(m: Foo):\n      m.ref = m\n\n    m = Foo()\n\n    ref_out_idx_out = nnx.graphlib.RefMap()\n    graphdef: nnx.graphlib.GraphDef[Foo]\n    graphdef, state = nnx.graphlib.flatten(m, ref_index=ref_out_idx_out, graph=True)\n    idx_out_ref_out = nnx.graphlib.IndexMap.from_refmap(ref_out_idx_out)\n    state = state.to_nested_state()\n\n    @partial(jax.jit, static_argnums=(0,))\n    def f_pure(graphdef: nnx.graphlib.GraphDef[Foo], state):\n      idx_out_ref_in = nnx.graphlib.IndexMap()\n      m = nnx.graphlib.unflatten(graphdef, state, index_ref=idx_out_ref_in)\n      ref_in_idx_out = nnx.graphlib.RefMap.from_indexmap(idx_out_ref_in)\n      f(m)\n      ref_in_idx_in = nnx.graphlib.RefMap()\n      graphdef, state = nnx.graphlib.flatten(\n        m, ref_index=ref_in_idx_in, ref_outer_index=ref_in_idx_out, graph=True\n      )\n      state = state.to_nested_state()\n      return state, graphdef\n\n    state, graphdef_out = f_pure(graphdef, state)\n    m2 = nnx.graphlib.unflatten(\n      graphdef_out, state, outer_index_outer_ref=idx_out_ref_out\n    )\n    assert m2 is m\n    assert m2.ref is m2\n\n  def test_call_jit_update(self):\n    class Counter(nnx.Module):\n      def __init__(self):\n        self.count = nnx.Param(jnp.zeros(()))\n\n      def inc(self):\n        self.count[...] += 1\n        return 1\n\n    graph_state = nnx.split(Counter())\n\n    @jax.jit\n    def update(graph_state: nnx.PureState[Counter]):\n      out, graph_state = nnx.call(graph_state).inc()\n      self.assertEqual(out, 1)\n      return graph_state\n\n    graph_state = update(graph_state)\n    graph_state = update(graph_state)\n\n    counter = nnx.merge(*graph_state)\n\n    self.assertEqual(counter.count[...], 2)\n\n  def test_stateful_linear(self):\n    linear = StatefulLinear(3, 2, nnx.Rngs(0))\n    linear_state = nnx.split(linear)\n\n    @jax.jit\n    def forward(x, pure_linear: nnx.PureState[StatefulLinear]):\n      y, pure_linear = nnx.call(pure_linear)(x)\n      return y, pure_linear\n\n    x = jnp.ones((1, 3))\n    y, linear_state = forward(x, linear_state)\n    y, linear_state = forward(x, linear_state)\n\n    self.assertEqual(linear.count[...], 0)\n    new_linear = nnx.merge(*linear_state)\n    self.assertEqual(new_linear.count[...], 2)\n\n  def test_getitem(self):\n    rngs = nnx.Rngs(0)\n    nodes = dict(\n      a=StatefulLinear(3, 2, rngs),\n      b=StatefulLinear(2, 1, rngs),\n    )\n    node_state = nnx.split(nodes)\n    _, node_state = nnx.call(node_state)['b'].increment()\n\n    nodes = nnx.merge(*node_state)\n\n    self.assertEqual(nodes['a'].count[...], 0)\n    self.assertEqual(nodes['b'].count[...], 1)\n\n  def test_object_state_propagation(self):\n    test = self\n\n    class Foo(nnx.Module):\n      def __call__(self):\n        test.assertTrue(self._pytree__state.initializing)\n        self = nnx.merge(*nnx.split(self))\n        test.assertTrue(self._pytree__state.initializing)\n\n    module = Foo()\n    nnx.bridge.lazy_init(module)\n\n  def test_object_state_propagation_nested(self):\n    class NNXOuter(nnx.Module):\n      def __init__(self, dout: int, rngs: nnx.Rngs):\n        self.inner = nnx.bridge.ToNNX(linen.Dense(dout), rngs=rngs)\n        self.rngs = rngs\n\n      def __call__(self, x):\n        @nnx.split_rngs(splits=5)\n        @nnx.vmap(in_axes=(0, None), axis_size=5)\n        def vmap_fn(inner, x):\n          return inner(x)\n\n        return vmap_fn(self.inner, x)\n\n    x = jax.random.normal(jax.random.key(0), (2, 4))\n    model = NNXOuter(3, rngs=nnx.Rngs(0))\n    nnx.bridge.lazy_init(model, x)\n\n    self.assertEqual(model.inner.kernel.shape, (5, 4, 3))\n    self.assertEqual(model.inner.bias.shape, (5, 3))\n\n  def test_split_merge_context(self):\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    with nnx.graphlib.split_context() as ctx:\n      graphdef1, state1 = ctx.split(m)\n      graphdef2, state2 = ctx.split(m)\n\n    self.assertFalse(hasattr(ctx, 'ref_index'))\n    self.assertFalse(hasattr(ctx, 'ctxtag'))\n    self.assertIsInstance(graphdef1.nodes[0], nnx.graphlib.NodeDef)\n    self.assertIsInstance(graphdef2.nodes[0], nnx.graphlib.NodeRef)\n    self.assertLen(nnx.to_flat_state(state1), 2)\n    self.assertLen(nnx.to_flat_state(state2), 0)\n\n    with nnx.graphlib.merge_context() as ctx:\n      m1 = ctx.merge(graphdef1, state1)\n      m2 = ctx.merge(graphdef2, state2)\n\n    self.assertIs(m1, m2)\n    self.assertFalse(hasattr(ctx, 'index_ref'))\n    self.assertFalse(hasattr(ctx, 'ctxtag'))\n\n  def test_split_merge_context_example(self):\n    m1 = nnx.Dict({})\n    with nnx.update_context('example'):\n      with nnx.split_context('example') as ctx:\n        graphdef, state = ctx.split(m1)\n\n      @jax.jit\n      def f(graphdef, state):\n        with nnx.merge_context('example', True) as ctx:\n          m2 = ctx.merge(graphdef, state)\n        m2.a = 1\n        m2.ref = m2  # create a reference cycle\n        with nnx.split_context('example') as ctx:\n          return ctx.split(m2)\n\n      graphdef_out, state_out = f(graphdef, state)\n      with nnx.merge_context('example', False) as ctx:\n        m3 = ctx.merge(graphdef_out, state_out)\n\n  def test_split_merge_context_nested(self):\n    m2 = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    m1 = nnx.Sequential(m2)\n    with nnx.graphlib.split_context() as ctx:\n      graphdef1, state1 = ctx.split(m1)\n      graphdef2, state2 = ctx.split(m2)\n\n    self.assertIsInstance(graphdef1.nodes[0], nnx.graphlib.NodeDef)\n    self.assertIsInstance(graphdef2.nodes[0], nnx.graphlib.NodeRef)\n    self.assertLen(nnx.to_flat_state(state1), 2)\n    self.assertLen(nnx.to_flat_state(state2), 0)\n\n    with nnx.graphlib.merge_context() as ctx:\n      m1 = ctx.merge(graphdef1, state1)\n      m2 = ctx.merge(graphdef2, state2)\n\n    self.assertIs(m2, m1.layers[0])\n    self.assertFalse(hasattr(ctx, 'index_ref'))\n    self.assertFalse(hasattr(ctx, 'ctxtag'))\n\n  def test_split_merge_update_context(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = nnx.Param(1)\n        self.b = nnx.data(2)\n\n    m = Foo()\n    ctxtag = 'test'\n\n    with nnx.update_context(ctxtag):\n      with nnx.graphlib.split_context(ctxtag) as ctx:\n        graphdef1, state1 = ctx.split(m)\n        graphdef2, state2 = ctx.split(m)\n\n      self.assertFalse(hasattr(ctx, 'ref_index'))\n      self.assertFalse(hasattr(ctx, 'ctxtag'))\n      self.assertIsInstance(graphdef1.nodes[0], nnx.graphlib.NodeDef)\n      self.assertIsInstance(graphdef2.nodes[0], nnx.graphlib.NodeRef)\n      self.assertLen(nnx.to_flat_state(state1), 2)\n      self.assertLen(nnx.to_flat_state(state2), 0)\n\n      @jax.jit\n      def f(graphdef1, state1, graphdef2, state2):\n        with nnx.graphlib.merge_context(ctxtag, True) as ctx:\n          m1 = ctx.merge(graphdef1, state1)\n          m2 = ctx.merge(graphdef2, state2)\n\n        self.assertIs(m1, m2)\n        self.assertFalse(hasattr(ctx, 'index_ref'))\n        self.assertFalse(hasattr(ctx, 'ctxtag'))\n\n        # swap a and b\n        m1.a, m1.b = m1.b, m1.a\n\n        with nnx.graphlib.split_context(ctxtag) as ctx:\n          graphdef1, state1 = ctx.split(m1)\n          graphdef2, state2 = ctx.split(m2)\n\n        return graphdef1, state1, graphdef2, state2\n\n      graphdef1, state1, graphdef2, state2 = f(\n        graphdef1, state1, graphdef2, state2\n      )\n\n      with nnx.graphlib.merge_context(ctxtag, False) as ctx:\n        m1_out = ctx.merge(graphdef1, state1)\n        m2_out = ctx.merge(graphdef2, state2)\n\n      self.assertIs(m, m1_out)\n      self.assertIs(m, m2_out)\n      self.assertEqual(m.a, 2)\n      self.assertEqual(m.b[...], 1)  # type: ignore\n\n      self.assertFalse(hasattr(ctx, 'index_ref'))\n      self.assertFalse(hasattr(ctx, 'ctxtag'))\n\n  def test_to_tree_simple(self):\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    impure_tree = (m, 1, {'b': m})\n\n    pure_tree = nnx.to_tree(impure_tree)\n\n    t1 = pure_tree[0]\n    t2 = pure_tree[2]['b']\n\n    self.assertEqual(pure_tree[1], 1)\n    self.assertIsInstance(t1, nnx.NodeStates)\n    assert isinstance(t1, nnx.NodeStates)\n    self.assertIsInstance(t2, nnx.NodeStates)\n    assert isinstance(t2, nnx.NodeStates)\n    self.assertIsInstance(t1.graphdef.nodes[0], nnx.graphlib.NodeDef)\n    self.assertIsInstance(t2.graphdef.nodes[0], nnx.graphlib.NodeRef)\n    self.assertLen(nnx.to_flat_state(t1.states[0]), 2)\n    self.assertLen(nnx.to_flat_state(t2.states[0]), 0)\n\n    impure_tree2 = nnx.from_tree(pure_tree)\n\n    m1_out = impure_tree2[0]\n    m2_out = impure_tree2[2]['b']\n\n    self.assertIs(m1_out, m2_out)\n    self.assertEqual(impure_tree2[1], 1)\n\n  def test_to_tree_update_context(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = nnx.Param(1)\n        self.b = nnx.data(2)\n\n    m = Foo()\n    impure_tree = (m, 1, {'b': m})\n    ctxtag = 'test'\n\n    with nnx.update_context(ctxtag):\n      pure_tree = nnx.to_tree(impure_tree, ctxtag=ctxtag)\n\n      t1 = pure_tree[0]\n      t2 = pure_tree[2]['b']\n\n      self.assertEqual(pure_tree[1], 1)\n      self.assertIsInstance(t1, nnx.NodeStates)\n      assert isinstance(t1, nnx.NodeStates)\n      self.assertIsInstance(t2, nnx.NodeStates)\n      assert isinstance(t2, nnx.NodeStates)\n      self.assertIsInstance(t1.graphdef.nodes[0], nnx.graphlib.NodeDef)\n      self.assertIsInstance(t2.graphdef.nodes[0], nnx.graphlib.NodeRef)\n      self.assertLen(nnx.to_flat_state(t1.states[0]), 2)\n      self.assertLen(nnx.to_flat_state(t2.states[0]), 0)\n\n      @jax.jit\n      def f(pure_tree):\n        impure_tree2 = nnx.from_tree(pure_tree, ctxtag=ctxtag, is_inner=True)\n        m1_out = impure_tree2[0]\n        m2_out = impure_tree2[2]['b']\n\n        self.assertIs(m1_out, m2_out)\n        # self.assertEqual(impure_tree2[1], 1)\n\n        # swap a and b\n        m1_out.a, m1_out.b = m1_out.b, m1_out.a\n\n        pure_tree2 = nnx.to_tree(impure_tree2, ctxtag=ctxtag)\n\n        t1 = pure_tree2[0]\n        t2 = pure_tree2[2]['b']\n\n        # self.assertEqual(pure_tree2[1], 1)\n        self.assertIsInstance(t1, nnx.NodeStates)\n        assert isinstance(t1, nnx.NodeStates)\n        self.assertIsInstance(t2, nnx.NodeStates)\n        assert isinstance(t2, nnx.NodeStates)\n        self.assertIsInstance(t1.graphdef.nodes[0], nnx.graphlib.NodeDef)\n        self.assertIsInstance(t2.graphdef.nodes[0], nnx.graphlib.NodeRef)\n        self.assertLen(nnx.to_flat_state(t1.states[0]), 2)\n        self.assertLen(nnx.to_flat_state(t2.states[0]), 0)\n\n        return pure_tree2\n\n      pure_tree2 = f(pure_tree)\n\n      impure_tree2 = nnx.from_tree(pure_tree2, ctxtag=ctxtag, is_inner=False)\n\n      m1_out = impure_tree2[0]\n      m2_out = impure_tree2[2]['b']\n\n      self.assertIs(m, m1_out)\n      self.assertIs(m, m2_out)\n      self.assertEqual(m.a, 2)\n      self.assertEqual(m.b[...], 1)  # type: ignore\n      self.assertEqual(impure_tree2[1], 1)\n\n  def test_graph_flatten_with_data_wrapper(self):\n    class Foo(nnx.Pytree):\n      def __init__(self, data, static):\n        self.data = nnx.data(data)\n        self.static = nnx.static(static)\n\n    tree = Foo(1, 2)\n    state = nnx.state(tree)\n\n    self.assertIn('data', state)\n    self.assertIsInstance(state['data'], int)\n    self.assertEqual(state['data'], 1)\n    self.assertNotIn('static', state)\n\n  def test_to_tree_consistent_prefix(self):\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    impure_tree = (m, 1, {'b': m})\n    prefix = (0, None, 0)\n    pure_tree = nnx.to_tree(impure_tree, prefix=prefix)\n\n    prefix = (0, None, 1)\n    with self.assertRaisesRegex(ValueError, 'Inconsistent aliasing detected'):\n      nnx.to_tree(impure_tree, prefix=prefix)\n\n  def test_simple_vmap(self):\n    @dataclasses.dataclass(frozen=True)\n    class StateAxes:\n      params: Any\n      batch_stats: Any\n\n    class Foo(nnx.Module):\n      def __init__(self, a, b):\n        self.a = nnx.Param(a)\n        self.b = nnx.BatchStat(b)\n\n    ctxtag = 'test'\n    with nnx.update_context(ctxtag):\n      m1 = Foo(a=jnp.array(0), b=jnp.arange(5))\n      m2 = Foo(a=jnp.array(1), b=jnp.array(2))\n\n      args = (m1, m2, {'b': m1})\n      m1_axes = StateAxes(None, 0)\n      in_axes = (m1_axes, None, {'b': m1_axes})\n      jax_in_axes = jax.tree.map(\n        lambda x: nnx.NodeStates.from_prefixes((x.params, x.batch_stats))\n        if isinstance(x, StateAxes)\n        else x,\n        in_axes,\n      )\n      out_axes = 0\n\n      def split_fn(ctx: nnx.SplitContext, path, prefix, x):\n        if isinstance(prefix, StateAxes):\n          return nnx.NodeStates.from_split(\n            *ctx.split(x, nnx.Param, nnx.BatchStat)\n          )\n        return nnx.NodeStates.from_split(*ctx.split(x))\n\n      pure_args = nnx.to_tree(\n        args, ctxtag=ctxtag, prefix=in_axes, split_fn=split_fn\n      )\n\n      @partial(jax.vmap, in_axes=jax_in_axes, out_axes=(jax_in_axes, out_axes))\n      def f(*pure_args):\n        args = nnx.from_tree(pure_args, ctxtag=ctxtag, is_inner=True)\n\n        y = 0\n\n        self.assertIs(args[0], args[2]['b'])\n        for path, m in nnx.iter_graph(args):\n          if isinstance(m, Foo):\n            self.assertEqual(m.a.shape, ())\n            self.assertEqual(m.b.shape, ())\n            y += m.a + m.b\n\n        args_out = nnx.extract.clear_non_graph_nodes(args)\n\n        pure_args_out, y = nnx.to_tree(\n          (args_out, y),\n          prefix=(in_axes, out_axes),\n          ctxtag=ctxtag,\n          split_fn=split_fn,\n        )\n        return pure_args_out, y\n\n      pure_args_out, y = f(*pure_args)\n\n      args_out, y = nnx.from_tree(\n        (pure_args_out, y), ctxtag=ctxtag, is_inner=False\n      )\n\n    self.assertEqual(y.shape, (5,))\n    self.assertGreater(y.sum(), 5)\n    self.assertIs(m1, args_out[0])\n    self.assertIs(m1, args_out[2]['b'])\n    self.assertIs(m2, args_out[1])\n\n  @parameterized.parameters(True, False)\n  def test_split_variable(self, graph):\n    v = nnx.Param(1)\n    graphdef, state = nnx.split(v, graph=graph)\n\n    expected_type = nnx.graphlib.VariableDef if graph else nnx.graphlib.TreeNodeDef\n    self.assertIsInstance(graphdef.nodes[0], expected_type)\n    self.assertIsInstance(state, nnx.Variable)\n\n    v2 = nnx.merge(graphdef, state)\n    self.assertIsInstance(v2, nnx.Param)\n\n  @parameterized.parameters(True, False)\n  def test_split_filter_variable(self, graph):\n    v = nnx.Param(1)\n    graphdef, batch_stats, params, rest = nnx.split(\n      v, nnx.BatchStat, nnx.Param, ..., graph=graph\n    )\n\n    expected_type = nnx.graphlib.VariableDef if graph else nnx.graphlib.TreeNodeDef\n    self.assertIsInstance(graphdef.nodes[0], expected_type)\n    self.assertIsInstance(params, nnx.Variable)\n    self.assertIsInstance(batch_stats, nnx.State)\n    self.assertEmpty(batch_stats)\n    self.assertIsInstance(rest, nnx.State)\n    self.assertEmpty(rest)\n\n    v2 = nnx.merge(graphdef, batch_stats, params, rest)\n    self.assertIsInstance(v2, nnx.Param)\n\n  @parameterized.parameters(True, False)\n  def test_split_update_variable(self, graph):\n    v = nnx.Param(jnp.array(1))\n    graphdef, state = nnx.split(v, graph=graph)\n\n    expected_type = nnx.graphlib.VariableDef if graph else nnx.graphlib.TreeNodeDef\n    self.assertIsInstance(graphdef.nodes[0], expected_type)\n    self.assertIsInstance(state, nnx.Variable)\n\n    state[...] = 2\n    nnx.update(v, state)\n\n    self.assertEqual(v[...], 2)\n\n  @parameterized.parameters(True, False)\n  def test_split_update_filter_variable(self, graph):\n    v = nnx.Param(jnp.array(1))\n    graphdef, batch_stats, params, rest = nnx.split(\n      v, nnx.BatchStat, nnx.Param, ..., graph=graph\n    )\n\n    expected_type = nnx.graphlib.VariableDef if graph else nnx.graphlib.TreeNodeDef\n    self.assertIsInstance(graphdef.nodes[0], expected_type)\n    self.assertIsInstance(params, nnx.Variable)\n    self.assertIsInstance(batch_stats, nnx.State)\n    self.assertEmpty(batch_stats)\n    self.assertIsInstance(rest, nnx.State)\n    self.assertEmpty(rest)\n\n    params[...] = 2\n    nnx.update(v, batch_stats, params, rest)\n\n    self.assertEqual(v[...], 2)\n\n  @parameterized.parameters(\n    (lambda: nnx.Param(1),),\n    (lambda: 42,),\n    (lambda: jnp.array([1, 2, 3]),),\n  )\n  def test_split_leaf(self, leaf_fn):\n    leaf = leaf_fn()\n    graphdef, state = nnx.split(leaf)\n\n    out = nnx.merge(graphdef, state)\n    self.assertIs(out, state)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_jit_variable(self, graph, graph_updates):\n    v = nnx.Param(1)\n\n    @nnx.jit(graph=graph, graph_updates=graph_updates)\n    def f(v):\n      v[...] += 1\n\n    f(v)\n\n    np.testing.assert_allclose(v[...], 2)\n\n  def test_jit_pytree_of_variables(self):\n    v1 = nnx.Param(jnp.array(1))\n    v2 = nnx.Param(jnp.array(2))\n    vs = [v1, v1, v2]\n\n    @nnx.jit\n    def f(vs):\n      self.assertIs(vs[0], vs[1])\n      self.assertIsNot(vs[0], vs[2])\n      vs[0][...] += 10\n\n    f(vs)\n\n    self.assertIs(vs[0], vs[1])\n    self.assertIsNot(vs[0], vs[2])\n    np.testing.assert_allclose(vs[0][...], 11)\n    np.testing.assert_allclose(vs[2][...], 2)\n\n  def test_variable_reference_in_module(self):\n    class Foo(nnx.Module):\n      def __init__(self, var):\n        self.var = var\n\n    var = nnx.Param(1)\n    foo = Foo(var)\n\n    @nnx.jit\n    def increment_var(var, foo):\n      self.assertIs(var, foo.var)\n      var[...] += 1\n\n    increment_var(var, foo)\n    self.assertEqual(foo.var[...], 2)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_variables_example(self, graph, graph_updates):\n    def stateful_linear_init(din: int, dout: int, rngs: nnx.Rngs):\n      w = nnx.Param(jax.random.normal(rngs(), (din, dout)))\n      b = nnx.Param(jnp.zeros((dout,)))\n      count = nnx.Variable(jnp.array(0))\n      return w, b, count\n\n    rngs = nnx.Rngs(0)\n    w, b, count = stateful_linear_init(2, 3, rngs=rngs)\n\n    @nnx.jit(graph=graph, graph_updates=graph_updates)\n    def stateful_linear(w, b, count, x):\n      count[...] += 1\n      return x @ w + b[None]\n\n    x = jax.random.normal(rngs(), (1, 2))\n    y = stateful_linear(w, b, count, x)\n    self.assertEqual(count[...], 1)\n\n    y = stateful_linear(w, b, count, x)\n    self.assertEqual(count[...], 2)\n    self.assertEqual(y.shape, (1, 3))\n\n  @parameterized.parameters(True, False)\n  def test_array_attributes(self, graph):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = jnp.array(1)\n        self.b = 'yes'\n\n    m = Foo()\n\n    graphdef, state = nnx.split(m, graph=graph)\n\n    self.assertLen(state, 1)\n    self.assertIsInstance(state['a'], jax.Array)\n\n    m2 = nnx.merge(graphdef, state)\n\n    self.assertIsInstance(m2.a, jax.Array)\n    self.assertEqual(m2.a, 1)\n    self.assertEqual(m2.b, 'yes')\n\n  def test_transform_array_attributes(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = jnp.array(1)\n        self.b = 'yes'\n\n    m = Foo()\n\n    @nnx.jit\n    def f(m):\n      m.a += 1\n      self.assertEqual(m.b, 'yes')\n\n    f(m)\n\n    self.assertEqual(m.a, 2)\n\n  def test_data_after_init(self):\n    test = self\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.ls = []\n        self.ls.append(jnp.array(1))\n\n    with self.assertRaisesRegex(\n        ValueError, 'Found unexpected data on value of type'\n    ):\n      m = Foo()\n\n  def test_update_dict(self):\n    node = {\n      'a': {\n        'b': 1,\n        'c': nnx.Param(jnp.array(2)),\n        'd': 3,\n      },\n    }\n\n    updates = {\n      'a': {\n        'b': 4,\n        'c': jnp.array(10),\n      },\n    }\n\n    nnx.update(node, updates)\n\n    self.assertEqual(node['a']['b'], 4)\n    self.assertEqual(node['a']['c'][...], 10)\n    self.assertEqual(node['a']['d'], 3)\n\n  def test_pop_dict(self):\n    node = {\n      'a': {\n        'b': jnp.array(1),\n        'c': nnx.Param(jnp.array(2)),\n        'd': jnp.array(3.0),\n      },\n    }\n    lt_2 = lambda _, x: x < 2\n    popped = nnx.pop(node, (nnx.Param, lt_2))\n\n    self.assertEqual(popped['a']['b'], 1)\n    self.assertEqual(popped['a']['c'][...], 2)\n    self.assertEqual(node['a']['d'], 3.0)\n    self.assertLen(jax.tree.leaves(node), 1)\n    self.assertLen(jax.tree.leaves(popped), 2)\n\n  def test_iter_graph(self):\n    arr0 = jnp.zeros(1)\n    arr1 = jnp.zeros(1)\n    var0 = nnx.Variable(jnp.zeros(1))\n    var1 = nnx.Variable(jnp.zeros(1))\n\n    child = nnx.Module()\n    child.a = var0\n    child.b = arr0\n    child.c = var1\n    child.d = var0\n    child.e = arr1\n    child.f = arr0\n\n    root = nnx.Module()\n    root.a = child\n    root.b = var1\n    root.c = arr1\n    root.d = var1\n    root.e = child\n    root.f = var0\n    root.g = arr1\n\n    nodes = [node for _, node in nnx.iter_graph(root)]\n    count = lambda e: sum(node is e for node in nodes)\n\n    # All internal nodes must be visited exactly once.\n    self.assertEqual(count(var0), 1)\n    self.assertEqual(count(var1), 1)\n    self.assertEqual(count(child), 1)\n    self.assertEqual(count(root), 1)\n\n    # Arrays must not be deduplicated.\n    self.assertEqual(count(arr0), 2)\n    self.assertEqual(count(arr1), 3)\n\n    # Nodes must be yielded in DFS order.\n    expected = [var0, arr0, var1, arr1, arr0, child, arr1, arr1, root]\n    unique = (arr0, arr1, var0, var1, child, root)\n    index = lambda e: next(node is e for node in unique)\n    actual = [node for node in nodes if any(node is e for e in unique)]\n    self.assertEqual(list(map(index, actual)), list(map(index, expected)))\n\n  def test_cached_partial_docstring_example(self):\n    import optax\n\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    optimizer = nnx.Optimizer(model, optax.adamw(1e-3), wrt=nnx.Param)\n\n    @nnx.jit\n    def train_step(model, optimizer, x, y):\n      def loss_fn(model):\n        return jnp.mean((model(x) - y) ** 2)\n\n      loss, grads = nnx.value_and_grad(loss_fn)(model)\n      optimizer.update(model, grads)\n      return loss\n\n    cached_train_step = nnx.cached_partial(train_step, model, optimizer)\n\n    for step in range(2):\n      x, y = jnp.ones((10, 2)), jnp.ones((10, 3))\n      loss = cached_train_step(x, y)\n      self.assertIsInstance(loss, jax.Array)\n\n  def test_find_duplicates(self):\n    class SharedModules(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.a = nnx.Linear(1, 1, rngs=rngs)\n        self.b = nnx.Linear(1, 1, rngs=rngs)\n        self.c = self.a  # shared Module\n\n    model = SharedModules(nnx.Rngs(0))\n    duplicates = nnx.find_duplicates(model)\n\n    self.assertLen(duplicates, 1)\n    self.assertEqual(duplicates[0], [('a',), ('c',)])\n\n  def test_resursive_map(self):\n    class Foo(nnx.Pytree):\n      def __init__(self, d):\n        self.d = d\n\n    foo1 = Foo(10)\n    foo2 = Foo(20)\n    bar = [foo1, foo2, foo1]\n    n = 0\n\n    def inc_d(path, node):\n      nonlocal n\n      if isinstance(node, Foo):\n        n += 1\n        node.d += 1\n      return node\n\n    bar2 = nnx.recursive_map(inc_d, bar)\n    self.assertIs(bar2[0], bar2[2])\n    self.assertEqual(bar2[0].d, 11)\n    self.assertEqual(bar2[1].d, 21)\n    self.assertEqual(n, 2)\n\n  def test_resursive_map_replace(self):\n    class Foo(nnx.Pytree):\n      def __init__(self, d):\n        self.d = d\n\n    foo1 = Foo(10)\n    foo2 = Foo(20)\n    bar = [foo1, foo2, foo1]\n    n = 0\n\n    def swap(path, node):\n      nonlocal n\n      if isinstance(node, Foo):\n        n += 1\n        node = Foo(-node.d)\n      return node\n\n    bar2 = nnx.recursive_map(swap, bar)\n    self.assertIs(bar2[0], bar2[2])\n    self.assertEqual(bar2[0].d, -10)\n    self.assertEqual(bar2[1].d, -20)\n    self.assertEqual(n, 2)\n\n  @parameterized.parameters(True, False)\n  def test_recursive_map_with_list(self, graph):\n    rngs = nnx.Rngs(0)\n    model = nnx.Sequential(nnx.Linear(2, 3, rngs=rngs), nnx.relu, nnx.Linear(3, 4, rngs=rngs))\n\n    def add_rank2_lora(_, node):\n      if isinstance(node, nnx.Linear):\n        return nnx.LoRA(node.in_features, 2, node.out_features, base_module=node, rngs=rngs)\n      return node\n\n    self.assertEqual(len(nnx.recursive_map(add_rank2_lora, model, graph=graph).layers), 3)\n\n  def test_graphdef_hash_with_sequential(self):\n    rngs = nnx.Rngs(0)\n    net = nnx.Sequential(\n        nnx.Linear(2, 1, rngs=rngs),\n    )\n    hash(nnx.graphdef(net))\n\n  @nnx.set_graph_mode(False)\n  def test_split_graph_error(self):\n    v = nnx.Variable(jnp.array(1.0))\n    with self.assertRaisesRegex(\n      ValueError, 'found at paths'\n    ):\n      graphdef, state = nnx.split((v, v))\n\nclass SimpleModule(nnx.Module):\n  pass\n\n\nclass TestThreading(parameterized.TestCase):\n  def test_threading(self):\n    x = SimpleModule()\n\n    class MyThread(Thread):\n      def run(self) -> None:\n        nnx.graphlib.split(x)\n\n    thread = MyThread()\n    thread.start()\n    thread.join()\n\n\nclass TestTreeFlatten(parameterized.TestCase):\n  def test_tree_flatten_unflatten(self):\n    a = {'a': 1, 'b': nnx.Param(jnp.array(2))}\n    b = {'a': 5, 'b': nnx.Param(jnp.array(6))}\n    g = [a, 3, b, nnx.Param(jnp.array(4))]\n\n    graphdef, flat_state = nnx.graphlib.flatten(g, graph=False)\n\n    self.assertIsInstance(graphdef.nodes[0], nnx.graphlib.TreeNodeDef)\n\n    g2 = nnx.graphlib.unflatten(graphdef, flat_state)\n    self.assertIsInstance(g2, list)\n    self.assertLen(g2, 4)\n    self.assertEqual(g2[0]['a'], 1)\n    self.assertEqual(g2[1], 3)\n    self.assertIsInstance(g2[0]['b'], nnx.Param)\n    self.assertIsInstance(g2[3], nnx.Param)\n    self.assertEqual(g2[0]['b'][...], 2)\n    self.assertEqual(g2[3][...], 4)\n\n  def test_tree_flatten_no_paths(self):\n    a = {'a': 1, 'b': nnx.Param(jnp.array(2))}\n    b = {'a': 5, 'b': nnx.Param(jnp.array(6))}\n    g = [a, 3, b, nnx.Param(jnp.array(4))]\n\n    graphdef, leaves = nnx.graphlib.flatten(g, with_paths=False, graph=False)\n    self.assertIsInstance(graphdef.nodes[0], nnx.graphlib.TreeNodeDef)\n    self.assertIsInstance(leaves, list)\n\n  def test_tree_split_merge(self):\n    a = {'a': 1, 'b': nnx.Param(jnp.array(2))}\n    b = {'a': 5, 'b': nnx.Param(jnp.array(6))}\n    g = [a, 3, b, nnx.Param(jnp.array(4))]\n\n    graphdef, state = nnx.split(g, graph=False)\n    g2 = nnx.merge(graphdef, state)\n\n    self.assertIsInstance(g2, list)\n    self.assertEqual(g2[0]['a'], 1)\n    self.assertEqual(g2[1], 3)\n    self.assertIsInstance(g2[0]['b'], nnx.Param)\n    self.assertEqual(g2[0]['b'][...], 2)\n    self.assertEqual(g2[3][...], 4)\n\n  def test_tree_split_merge_module(self):\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    graphdef, state = nnx.split(m, graph=False)\n    self.assertIsInstance(graphdef.nodes[0], nnx.graphlib.TreeNodeDef)\n\n    m2 = nnx.merge(graphdef, state)\n    self.assertIsInstance(m2, nnx.Linear)\n    self.assertEqual(m2.kernel.shape, (2, 3))\n    self.assertEqual(m2.bias.shape, (3,))\n\n  def test_tree_shared_variables_raises(self):\n    v = nnx.Param(jnp.array(1))\n    g = [v, v]\n\n    with self.assertRaises(ValueError):\n      nnx.split(g, graph=False)\n\n  def test_tree_shared_refs_raises(self):\n    ref = jax.new_ref(jnp.array(1.0))\n    g = [ref, ref]\n\n    with self.assertRaises(ValueError):\n      nnx.split(g, graph=False)\n\n  def test_tree_shared_variables_state_raises(self):\n    v = nnx.Param(jnp.array(1))\n    g = [v, v]\n\n    with self.assertRaises(ValueError):\n      nnx.state(g, graph=False)\n\n  def test_tree_shared_variables_graphdef_raises(self):\n    v = nnx.Param(jnp.array(1))\n    g = [v, v]\n\n    with self.assertRaises(ValueError):\n      nnx.graphdef(g, graph=False)\n\n  def test_tree_shared_variables_clone_raises(self):\n    v = nnx.Param(jnp.array(1))\n    g = [v, v]\n\n    with self.assertRaises(ValueError):\n      nnx.clone(g, graph=False)\n\n  def test_tree_flatten_unflatten_ordering(self):\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    graphdef, state = nnx.split(m, graph=False)\n\n    tree_nodedef = graphdef.nodes[0]\n    self.assertIsInstance(tree_nodedef, nnx.graphlib.TreeNodeDef)\n    paths = [p for p, _ in tree_nodedef.path_index]\n    self.assertEqual(paths, sorted(paths))\n\n    m2 = nnx.merge(graphdef, state)\n    np.testing.assert_array_equal(m.kernel[...], m2.kernel[...])\n    np.testing.assert_array_equal(m.bias[...], m2.bias[...])\n\n  def test_tree_flatten_dict(self):\n    g = {'z': nnx.Param(jnp.array(1)), 'a': jnp.array(2)}\n    graphdef, state = nnx.split(g, graph=False)\n    g2 = nnx.merge(graphdef, state)\n    self.assertEqual(g2['z'][...], 1)\n    np.testing.assert_array_equal(g2['a'], jnp.array(2))\n\n  def test_tree_flatten_tuple(self):\n    g = (nnx.Param(jnp.array(1)), jnp.array(2), 3)\n    graphdef, state = nnx.split(g, graph=False)\n    g2 = nnx.merge(graphdef, state)\n    self.assertIsInstance(g2, tuple)\n    self.assertEqual(g2[0][...], 1)\n    np.testing.assert_array_equal(g2[1], jnp.array(2))\n    self.assertEqual(g2[2], 3)\n\n  def test_tree_flatten_namedtuple(self):\n    import collections\n    Point = collections.namedtuple('Point', ['y', 'x'])\n    g = Point(\n      y=nnx.Param(jnp.array(1.0)),\n      x=nnx.Param(jnp.array(2.0)),\n    )\n    graphdef, flat_state = nnx.graphlib.flatten(g, graph=False)\n    self.assertLen(flat_state, 2)\n    path, value = flat_state[0]\n    self.assertEqual(path, ('x',))\n    self.assertEqual(value, 2.0)\n    path, value = flat_state[1]\n    self.assertEqual(path, ('y',))\n    self.assertEqual(value, 1.0)\n\n    g2 = nnx.graphlib.unflatten(graphdef, flat_state)\n    self.assertIsInstance(g2, Point)\n    self.assertEqual(g2.y[...], 1.0)\n    self.assertEqual(g2.x[...], 2.0)\n\n  def test_tree_flatten_registered_dataclass(self):\n    @jax.tree_util.register_dataclass\n    @dataclasses.dataclass\n    class MyData:\n      z_param: Any\n      a_value: Any\n\n    g = MyData(\n      z_param=nnx.Param(jnp.array(10.0)),\n      a_value=jnp.array(20.0),\n    )\n    graphdef, flat_state = nnx.graphlib.flatten(g, graph=False)\n    self.assertLen(flat_state, 2)\n    path, value = flat_state[0]\n    self.assertEqual(path, ('a_value',))\n    np.testing.assert_array_equal(value, 20.0)\n    path, value = flat_state[1]\n    self.assertEqual(path, ('z_param',))\n    self.assertEqual(value, 10.0)\n\n    g2 = nnx.graphlib.unflatten(graphdef, flat_state)\n    self.assertIsInstance(g2, MyData)\n    self.assertEqual(g2.z_param[...], 10.0)\n    np.testing.assert_array_equal(g2.a_value, jnp.array(20.0))\n\n  def test_tree_flatten_nested_mixed(self):\n    g = {\n      'b': [nnx.Param(jnp.array(1)), jnp.array(2)],\n      'a': (nnx.Param(jnp.array(3)), 4),\n    }\n    graphdef, flat_state = nnx.graphlib.flatten(g, graph=False)\n    self.assertLen(flat_state, 4)\n    path, value = flat_state[0]\n    self.assertEqual(path, ('a', 0))\n    self.assertEqual(value, 3)\n    path, value = flat_state[1]\n    self.assertEqual(path, ('a', 1))\n    self.assertEqual(value, 4)\n    path, value = flat_state[2]\n    self.assertEqual(path, ('b', 0))\n    self.assertEqual(value, 1)\n    path, value = flat_state[3]\n    self.assertEqual(path, ('b', 1))\n    np.testing.assert_array_equal(value, 2)\n\n    g2 = nnx.graphlib.unflatten(graphdef, flat_state)\n    self.assertIsInstance(g2, dict)\n    self.assertEqual(g2['b'][0][...], 1)\n    np.testing.assert_array_equal(g2['b'][1], jnp.array(2))\n    self.assertEqual(g2['a'][0][...], 3)\n    self.assertEqual(g2['a'][1], 4)\n\n  @parameterized.parameters(True, False)\n  def test_iter_graph(self, graph):\n    var0 = nnx.Variable(jnp.zeros(1))\n    var1 = nnx.Variable(jnp.zeros(1))\n    arr0 = jnp.zeros(1)\n\n    child = nnx.Module()\n    child.a = var0\n    child.b = arr0\n    child.c = var1\n\n    root = nnx.Module()\n    root.x = child\n    root.y = jnp.ones(2)\n\n    node_ids = [id(node) for _, node in nnx.iter_graph(root, graph=graph)]\n    self.assertIn(id(var0), node_ids)\n    self.assertIn(id(var1), node_ids)\n    self.assertIn(id(child), node_ids)\n    self.assertIn(id(root), node_ids)\n\n  def test_iter_graph_tree_mode_shared_variable_raises(self):\n    var = nnx.Variable(jnp.zeros(1))\n    root = nnx.Module()\n    root.a = var\n    root.b = var\n\n    with self.assertRaisesRegex(\n      ValueError, 'found at paths'\n    ):\n      list(nnx.iter_graph(root, graph=False))\n\n  def test_iter_graph_tree_mode_cycle_raises(self):\n    a = nnx.List([1])\n    b = nnx.List([2, a])\n    a.append(b)\n\n    with self.assertRaisesRegex(\n      ValueError, 'found at paths'\n    ):\n      list(nnx.iter_graph(a, graph=False))\n\n  @parameterized.parameters(True, False)\n  def test_iter_modules(self, graph):\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    modules = list(nnx.iter_modules(model, graph=graph))\n    self.assertLen(modules, 1)\n    path, m = modules[0]\n    self.assertEqual(path, ())\n    self.assertIs(m, model)\n\n  @parameterized.parameters(True, False)\n  def test_iter_modules_nested(self, graph):\n    class Block(nnx.Module):\n      def __init__(self, rngs):\n        self.linear = nnx.Linear(2, 3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5)\n\n    model = Block(nnx.Rngs(0))\n    modules = list(nnx.iter_modules(model, graph=graph))\n    module_types = [type(m).__name__ for _, m in modules]\n    self.assertIn('Block', module_types)\n    self.assertIn('Linear', module_types)\n    self.assertIn('Dropout', module_types)\n    self.assertLen(modules, 3)\n\n  def test_recursive_map_tree_mode(self):\n    class Foo(nnx.Pytree):\n      def __init__(self, d):\n        self.d = d\n\n    foo1 = Foo(10)\n    foo2 = Foo(20)\n    bar = [foo1, foo2]\n    n = 0\n\n    def inc_d(path, node):\n      nonlocal n\n      if isinstance(node, Foo):\n        n += 1\n        node.d += 1\n      return node\n\n    bar2 = nnx.recursive_map(inc_d, bar, graph=False)\n    self.assertEqual(bar2[0].d, 11)\n    self.assertEqual(bar2[1].d, 21)\n    self.assertEqual(n, 2)\n\n  def test_recursive_map_tree_mode_replace(self):\n    class Foo(nnx.Pytree):\n      def __init__(self, d):\n        self.d = d\n\n    foo1 = Foo(10)\n    foo2 = Foo(20)\n    bar = [foo1, foo2]\n    n = 0\n\n    def swap(path, node):\n      nonlocal n\n      if isinstance(node, Foo):\n        n += 1\n        node = Foo(-node.d)\n      return node\n\n    bar2 = nnx.recursive_map(swap, bar, graph=False)\n    self.assertEqual(bar2[0].d, -10)\n    self.assertEqual(bar2[1].d, -20)\n    self.assertEqual(n, 2)\n\n  def test_recursive_map_tree_mode_with_list(self):\n    rngs = nnx.Rngs(0)\n    model = nnx.Sequential(\n      nnx.Linear(2, 3, rngs=rngs), nnx.relu, nnx.Linear(3, 4, rngs=rngs)\n    )\n\n    def add_rank2_lora(_, node):\n      if isinstance(node, nnx.Linear):\n        return nnx.LoRA(\n          node.in_features, 2, node.out_features,\n          base_module=node, rngs=rngs,\n        )\n      return node\n\n    result = nnx.recursive_map(add_rank2_lora, model, graph=False)\n    self.assertLen(result.layers, 3)\n\n  def test_recursive_map_tree_mode_shared_variable_raises(self):\n    v = nnx.Param(jnp.array(1))\n    g = [v, v]\n\n    with self.assertRaisesRegex(\n      ValueError, 'found at paths'\n    ):\n      nnx.recursive_map(lambda path, node: node, g, graph=False)\n\n  def test_recursive_map_tree_mode_cycle_raises(self):\n    a = nnx.List([1])\n    b = nnx.List([2, a])\n    a.append(b)\n\n    with self.assertRaisesRegex(\n      ValueError, 'found at paths'\n    ):\n      nnx.recursive_map(lambda path, node: node, a, graph=False)\n\n  def test_check_valid_pytree_flatten(self):\n    class NotAPytree(nnx.Pytree, pytree=False):\n      def __init__(self):\n        self.x = 1\n\n    node = [NotAPytree()]\n    with self.assertRaisesRegex(\n      ValueError, \"pytree=False.*Found at path\"\n    ):\n      nnx.graphlib.flatten(node, graph=False)\n\n  def test_check_valid_pytree_iter_graph(self):\n    class NotAPytree(nnx.Pytree, pytree=False):\n      def __init__(self):\n        self.x = 1\n\n    node = nnx.List([NotAPytree()])\n    with self.assertRaisesRegex(\n      ValueError, \"pytree=False.*Found at path\"\n    ):\n      list(nnx.iter_graph(node, graph=False))\n\n  def test_check_valid_pytree_iter_children(self):\n    class NotAPytree(nnx.Pytree, pytree=False):\n      def __init__(self):\n        self.x = 1\n\n    node = NotAPytree()\n    with self.assertRaisesRegex(\n      ValueError, \"pytree=False\"\n    ):\n      list(nnx.iter_children(node, graph=False))\n\n  def test_check_valid_pytree_recursive_map(self):\n    class NotAPytree(nnx.Pytree, pytree=False):\n      def __init__(self):\n        self.x = 1\n\n    node = nnx.List([NotAPytree()])\n    with self.assertRaisesRegex(\n      ValueError, \"pytree=False.*Found at path\"\n    ):\n      nnx.recursive_map(lambda path, node: node, node, graph=False)\n\n  @parameterized.parameters(True, False)\n  def test_map(self, graph):\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    new_model = nnx.map(lambda path, x: jnp.zeros_like(x), model, graph=graph)\n\n    self.assertTrue(hasattr(new_model, 'kernel'))\n    self.assertTrue(hasattr(new_model, 'bias'))\n    np.testing.assert_array_equal(new_model.kernel, jnp.zeros((2, 3)))\n    np.testing.assert_array_equal(new_model.bias, jnp.zeros((3,)))\n\n  def test_map_with_path(self):\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    paths_seen = []\n\n    def record_path(path, x):\n      paths_seen.append(path)\n      return x\n\n    nnx.map(record_path, model)\n    self.assertLen(paths_seen, 2)\n    path_last_parts = sorted(p[-1] for p in paths_seen)\n    self.assertEqual(path_last_parts, ['bias', 'kernel'])\n\n  def test_map_nested(self):\n    class Model(nnx.Module):\n      def __init__(self, rngs):\n        self.linear = nnx.Linear(2, 3, rngs=rngs)\n\n    model = Model(rngs=nnx.Rngs(0))\n    new_model = nnx.map(lambda path, x: jnp.ones_like(x), model)\n\n    self.assertTrue(hasattr(new_model, 'linear'))\n    np.testing.assert_array_equal(new_model.linear.kernel, jnp.ones((2, 3)))\n    np.testing.assert_array_equal(new_model.linear.bias, jnp.ones((3,)))\n\n  def test_map_replace(self):\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    new_model = nnx.map(\n      lambda path, v: v.replace(jnp.zeros_like(v)), model\n    )\n\n    self.assertTrue(hasattr(new_model, 'kernel'))\n    self.assertTrue(hasattr(new_model, 'bias'))\n    self.assertIsInstance(new_model.kernel, nnx.Param)\n    np.testing.assert_array_equal(new_model.kernel[...], jnp.zeros((2, 3)))\n    np.testing.assert_array_equal(new_model.bias[...], jnp.zeros((3,)))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/helpers_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax\nimport jax.numpy as jnp\nimport optax\n\nfrom absl.testing import absltest\nimport numpy as np\n\nfrom flax import linen\nfrom flax import nnx\n\n\nclass TrainState(nnx.TrainState):\n  batch_stats: nnx.State\n\n\nclass TestHelpers(absltest.TestCase):\n  def test_train_state(self):\n    m = nnx.Dict(a=nnx.Param(1), b=nnx.BatchStat(2))\n\n    graphdef, params, batch_stats = nnx.split(m, nnx.Param, nnx.BatchStat)\n\n    state = TrainState.create(\n      graphdef,\n      params=params,\n      tx=optax.sgd(1.0),\n      batch_stats=batch_stats,\n    )\n\n    leaves = jax.tree_util.tree_leaves(state)\n\n  def test_train_state_methods(self):\n    class Foo(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(2, 4, rngs=rngs)\n        self.batch_norm = nnx.BatchNorm(4, rngs=rngs)\n\n      def __call__(self, x: jax.Array, train: bool) -> jax.Array:\n        x = self.linear(x)\n        x = self.batch_norm(x, use_running_average=not train)\n        return x\n\n    module = Foo(rngs=nnx.Rngs(0))\n    graphdef, params, batch_stats = nnx.split(module, nnx.Param, nnx.BatchStat)\n\n    state = TrainState.create(\n      graphdef,\n      params=params,\n      tx=optax.sgd(1.0),\n      batch_stats=batch_stats,\n    )\n\n    x = jax.numpy.ones((1, 2))\n    y, _updates = state.apply('params', 'batch_stats')(x, train=True)\n\n    assert y.shape == (1, 4)\n\n    # fake gradient\n    grads = jax.tree.map(jnp.ones_like, state.params)\n    # test apply_gradients\n    state = state.apply_gradients(grads)\n\n  def test_nnx_linen_sequential_equivalence(self):\n    key1, key2 = jax.random.split(jax.random.key(0), 2)\n    rngs = nnx.Rngs(0)\n    x = jax.random.uniform(key1, (3, 1, 5))\n\n    model_nnx = nnx.Sequential(\n      nnx.Linear(5, 4, rngs=rngs), nnx.Linear(4, 2, rngs=rngs)\n    )\n    model = linen.Sequential([linen.Dense(4), linen.Dense(2)])\n\n    variables = model.init(key2, x)\n    for layer_index in range(2):\n      for param in ('kernel', 'bias'):\n        variables['params'][f'layers_{layer_index}'][param] = getattr(\n          model_nnx.layers[layer_index], param\n        )[...]\n    out_nnx = model_nnx(x)\n    out = model.apply(variables, x)\n    np.testing.assert_array_equal(out, out_nnx)\n\n    variables = model.init(key2, x)\n    for layer_index in range(2):\n      for param in ('kernel', 'bias'):\n        getattr(model_nnx.layers[layer_index], param)[...] = variables[\n          'params'\n        ][f'layers_{layer_index}'][param]\n    out_nnx = model_nnx(x)\n    out = model.apply(variables, x)\n    np.testing.assert_array_equal(out, out_nnx)\n\n  def test_nnx_empty_sequential_is_identity(self):\n    iden = nnx.Sequential()\n    assert iden(12) == 12\n    assert iden(12, 23) == (12, 23)\n    assert iden() is None\n    assert iden(k=2) == {'k': 2}\n\n  def test_dict_mutable_mapping(self):\n    d = nnx.Dict({'a': 1, 'b': 2})\n    self.assertEqual(d['a'], 1)\n    self.assertEqual(d['b'], 2)\n    self.assertEqual(len(d), 2)\n\n    d['c'] = 3\n    self.assertEqual(d['c'], 3)\n    self.assertEqual(len(d), 3)\n\n    del d['a']\n    self.assertEqual(len(d), 2)\n    with self.assertRaises(KeyError):\n      _ = d['a']\n\n    self.assertSetEqual(set(d), {'b', 'c'})\n\n  def test_dict_setdefault(self):\n    d = nnx.Dict({'a': 1, 'b': 2})\n    self.assertEqual(d.setdefault('a', 10), 1)\n    self.assertEqual(d['a'], 1)\n\n    self.assertEqual(d.setdefault('c', 3), 3)\n    self.assertEqual(d['c'], 3)\n    self.assertEqual(len(d), 3)\n\n  def test_dict_contains(self):\n    d = nnx.Dict({'a': 1, 'b': 2})\n    self.assertIn('a', d)\n    self.assertIn('b', d)\n    self.assertNotIn('c', d)\n\n    d['c'] = 3\n    self.assertIn('c', d)\n\n    del d['a']\n    self.assertNotIn('a', d)\n\n  def test_list_mutable_sequence(self):\n    l = nnx.List([1, 2, 3])\n    self.assertEqual(len(l), 3)\n    self.assertEqual(l[0], 1)\n    self.assertEqual(l[1], 2)\n    self.assertEqual(l[2], 3)\n\n    l.append(4)\n    self.assertEqual(len(l), 4)\n    self.assertEqual(l[3], 4)\n\n    l.insert(1, 5)\n    self.assertEqual(len(l), 5)\n    self.assertEqual(l[0], 1)\n    self.assertEqual(l[1], 5)\n    self.assertEqual(l[2], 2)\n    self.assertEqual(l[3], 3)\n    self.assertEqual(l[4], 4)\n\n    del l[2]\n    self.assertEqual(len(l), 4)\n    self.assertEqual(l[0], 1)\n    self.assertEqual(l[1], 5)\n    self.assertEqual(l[2], 3)\n    self.assertEqual(l[3], 4)\n\n    l[1:3] = [6, 7]\n    self.assertEqual(l[1], 6)\n    self.assertEqual(l[2], 7)\n\n    self.assertEqual(l[1:3], [6, 7])\n\n  def test_list_fori_loop(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.layers = nnx.List([\n            nnx.Linear(1, 1, rngs=nnx.Rngs(0)),\n            nnx.Linear(1, 1, rngs=nnx.Rngs(0)),\n        ])\n\n    def batch_loop_body(i, carry):\n      return carry\n\n    net = Foo()\n    jax.lax.fori_loop(0, 2, batch_loop_body, net)\n\n  def test_list_pytree_default_behavior(self):\n    ls = nnx.List([jnp.array(1), jnp.array(2), jnp.array(3)])\n    leaves = jax.tree_util.tree_leaves(ls)\n    self.assertLen(leaves, 3)\n    np.testing.assert_array_equal(leaves[0], jnp.array(1))\n    np.testing.assert_array_equal(leaves[1], jnp.array(2))\n    np.testing.assert_array_equal(leaves[2], jnp.array(3))\n\n  def test_list_pytree_static_elements(self):\n    ls = nnx.List([nnx.static(10), nnx.static(20), nnx.static(30)])\n    leaves = jax.tree_util.tree_leaves(ls)\n    self.assertEmpty(leaves)\n\n  def test_list_pytree_data_elements(self):\n    ls = nnx.List([nnx.data(1), nnx.data(2), nnx.data(3)])\n    leaves = jax.tree_util.tree_leaves(ls)\n    self.assertLen(leaves, 3)\n    self.assertEqual(leaves[0], 1)\n    self.assertEqual(leaves[1], 2)\n    self.assertEqual(leaves[2], 3)\n\n  def test_list_pytree_mixed_static_data(self):\n    ls = nnx.List([\n        nnx.data(jnp.array(1)),\n        nnx.static(100),\n        nnx.data(jnp.array(2)),\n        nnx.static(200),\n    ])\n    leaves = jax.tree_util.tree_leaves(ls)\n    self.assertLen(leaves, 2)\n    np.testing.assert_array_equal(leaves[0], jnp.array(1))\n    np.testing.assert_array_equal(leaves[1], jnp.array(2))\n\n  def test_list_pytree_flatten_unflatten(self):\n    ls = nnx.List([nnx.data(10), nnx.static('hello'), nnx.data(20)])\n    leaves, treedef = jax.tree_util.tree_flatten(ls)\n    self.assertLen(leaves, 2)\n    self.assertEqual(leaves[0], 10)\n    self.assertEqual(leaves[1], 20)\n\n    new_leaves = [x * 2 for x in leaves]\n    new_ls = jax.tree_util.tree_unflatten(treedef, new_leaves)\n    self.assertEqual(new_ls[0], 20)\n    self.assertEqual(new_ls[1], 'hello')\n    self.assertEqual(new_ls[2], 40)\n\n  def test_list_pytree_jit(self):\n    ls = nnx.List([nnx.data(jnp.array(1.0)), nnx.static(999)])\n\n    @jax.jit\n    def double(ls):\n      return jax.tree.map(lambda x: x * 2, ls)\n\n    result = double(ls)\n    np.testing.assert_array_equal(result[0], jnp.array(2.0))\n    self.assertEqual(result[1], 999)\n\n\nif __name__ == '__main__':\n  absltest.main()\n\n"
  },
  {
    "path": "tests/nnx/ids_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport copy\n\nfrom absl.testing import absltest\nfrom flax.nnx import ids\n\n\nclass TestIds(absltest.TestCase):\n  def test_hashable(self):\n    id1 = ids.uuid()\n    id2 = ids.uuid()\n    assert id1 == id1\n    assert id1 != id2\n    assert hash(id1) != hash(id2)\n    id1c = copy.copy(id1)\n    id1dc = copy.deepcopy(id1)\n    assert hash(id1) != hash(id1c)\n    assert hash(id1) != hash(id1dc)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/integration_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport tempfile\nimport typing as tp\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nimport orbax.checkpoint as ocp\nimport optax\n\nfrom flax import nnx\n\nA = tp.TypeVar('A')\n\n\nclass TestIntegration(parameterized.TestCase):\n\n  @parameterized.parameters(True, False)\n  def test_basic_view_example(self, graph_mode):\n    class Model(nnx.Module):\n\n      def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dmid, rngs=rngs)\n        self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n        self.dropout = nnx.Dropout(0.2, rngs=rngs)\n        self.linear_out = nnx.Linear(dmid, dout, rngs=rngs)\n\n      def __call__(self, x):\n        x = nnx.relu(self.dropout(self.bn(self.linear(x))))\n        return self.linear_out(x)\n\n    model = Model(2, 64, 3, rngs=nnx.Rngs(0))  # eager initialization\n    train_model = nnx.view(\n        model, deterministic=False, use_running_average=False\n    )\n    eval_model = nnx.view(\n        model, deterministic=True, use_running_average=True\n    )\n    optimizer = nnx.Optimizer(train_model, optax.adam(1e-3), wrt=nnx.Param)\n\n    self.assertEqual(train_model.dropout.deterministic, False)\n    self.assertEqual(train_model.bn.use_running_average, False)\n    self.assertEqual(eval_model.dropout.deterministic, True)\n    self.assertEqual(eval_model.bn.use_running_average, True)\n    self.assertIs(train_model.dropout.rngs.count, eval_model.dropout.rngs.count)\n\n    @nnx.jit(graph=graph_mode)  # automatic state management for JAX transforms\n    def train_step(model, optimizer, x, y):\n      def loss_fn(model):\n        y_pred = model(x)\n        return jnp.mean((y_pred - y) ** 2)\n\n      loss, grads = nnx.value_and_grad(loss_fn)(model)\n      optimizer.update(model, grads)  # in-place updates\n\n      return loss\n\n    @nnx.jit(graph=graph_mode)\n    def eval_step(model, x, y):\n      y_pred = model(x)\n      return jnp.mean((y_pred - y) ** 2)\n\n    x = jax.random.normal(jax.random.key(0), (8, 2))\n    y = jax.random.normal(jax.random.key(1), (8, 3))\n\n    train_step(train_model, optimizer, x, y)\n    self.assertEqual(train_model.dropout.rngs.count[...], 1)\n    eval_step(eval_model, x, y)\n    self.assertEqual(train_model.dropout.rngs.count[...], 1)\n\n  def test_shared_modules(self):\n    class Block(nnx.Module):\n      def __init__(self, linear: nnx.Linear, *, rngs):\n        self.linear = linear\n        self.bn = nnx.BatchNorm(2, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.linear(x)\n        x = self.bn(x)\n        return nnx.relu(x)\n\n    class Model(nnx.Module):\n      def __init__(self, *, rngs):\n        shared = nnx.Linear(2, 2, rngs=rngs)\n        self.block1 = Block(shared, rngs=rngs)\n        self.block2 = Block(shared, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.block1(x)\n        x = self.block2(x)\n        return x\n\n    @nnx.jit\n    def train_step(model: Model, x, y):\n      @nnx.grad\n      def loss_fn(model: Model):\n        y_pred = model(x)\n        return jnp.mean((y - y_pred) ** 2)\n\n      grads = loss_fn(model)\n      nnx.update(\n        model,\n        jax.tree.map(\n          lambda w, g: w - 0.1 * g, nnx.state(model, nnx.Param), grads\n        ),\n      )\n\n    model = Model(rngs=nnx.Rngs(0))\n\n    x = np.random.uniform(size=(4, 2))\n    y = np.random.uniform(size=(4, 2))\n    model.set_attributes(use_running_average=False, graph=True)\n\n    for _i in range(3):\n      train_step(model, x, y)\n\n    assert model.block1.linear is model.block2.linear\n    assert model.block1.linear.bias is not None\n    assert model.block1.bn is not model.block2.bn\n\n  def test_shared_modules_view(self):\n    class Block(nnx.Module):\n      def __init__(self, linear: nnx.Linear, *, rngs):\n        self.linear = linear\n        self.bn = nnx.BatchNorm(2, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.linear(x)\n        x = self.bn(x)\n        return nnx.relu(x)\n\n    class Model(nnx.Module):\n      def __init__(self, *, rngs):\n        shared = nnx.Linear(2, 2, rngs=rngs)\n        self.block1 = Block(shared, rngs=rngs)\n        self.block2 = Block(shared, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.block1(x)\n        x = self.block2(x)\n        return x\n\n    @nnx.jit\n    def train_step(model: Model, x, y):\n      @nnx.grad\n      def loss_fn(model: Model):\n        y_pred = model(x)\n        return jnp.mean((y - y_pred) ** 2)\n\n      grads = loss_fn(model)\n      nnx.update(\n        model,\n        jax.tree.map(\n          lambda w, g: w - 0.1 * g, nnx.state(model, nnx.Param), grads\n        ),\n      )\n\n    model = Model(rngs=nnx.Rngs(0))\n\n    x = np.random.uniform(size=(4, 2))\n    y = np.random.uniform(size=(4, 2))\n    new_model = nnx.view(model, use_running_average=False)\n\n    for _i in range(3):\n      train_step(model, x, y)\n\n    assert new_model.block1.linear is new_model.block2.linear\n    assert new_model.block1.linear.bias is not None\n    assert new_model.block1.bn is not new_model.block2.bn\n\n  def test_shared_modules_pure(self):\n    class Block(nnx.Module):\n      def __init__(self, linear: nnx.Linear, *, rngs: nnx.Rngs):\n        self.linear = linear\n        self.bn = nnx.BatchNorm(2, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.linear(x)\n        x = self.bn(x)\n        return nnx.relu(x)\n\n    class Model(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        shared = nnx.Linear(2, 2, rngs=rngs)\n        self.block1 = Block(shared, rngs=rngs)\n        self.block2 = Block(shared, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.block1(x)\n        x = self.block2(x)\n        return x\n\n    @jax.jit\n    def train_step(state: nnx.State, graphdef: nnx.GraphDef[Model], x, y):\n      model = nnx.merge(graphdef, state)\n      model.set_attributes(use_running_average=False, graph=True)\n\n      @nnx.grad\n      def loss_fn(model: Model):\n        y_pred = model(x)\n        return jnp.mean((y - y_pred) ** 2)\n\n      grads = loss_fn(model)\n      nnx.update(\n        model,\n        jax.tree.map(\n          lambda w, g: w - 0.1 * g, nnx.state(model, nnx.Param), grads\n        ),\n      )\n\n      return nnx.split(model)\n\n    graphdef: nnx.GraphDef[Model]\n    graphdef, state = nnx.split(Model(rngs=nnx.Rngs(0)))\n\n    x = np.random.uniform(size=(4, 2))\n    y = np.random.uniform(size=(4, 2))\n\n    for _i in range(3):\n      graphdef, state = train_step(state, graphdef, x, y)\n\n    model = nnx.merge(graphdef, state)\n\n    assert model.block1.linear.bias is not None\n    assert model.block2.linear.bias is not None\n    assert model.block1.linear.kernel is model.block2.linear.kernel\n    assert model.block1.linear.bias is model.block2.linear.bias\n    assert model.block1.bn is not model.block2.bn\n\n  def test_shared_modules_pure_view(self):\n    class Block(nnx.Module):\n      def __init__(self, linear: nnx.Linear, *, rngs: nnx.Rngs):\n        self.linear = linear\n        self.bn = nnx.BatchNorm(2, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.linear(x)\n        x = self.bn(x)\n        return nnx.relu(x)\n\n    class Model(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        shared = nnx.Linear(2, 2, rngs=rngs)\n        self.block1 = Block(shared, rngs=rngs)\n        self.block2 = Block(shared, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.block1(x)\n        x = self.block2(x)\n        return x\n\n    @jax.jit\n    def train_step(state: nnx.State, graphdef: nnx.GraphDef[Model], x, y):\n      model = nnx.merge(graphdef, state)\n      new_model = nnx.view(model, use_running_average=False, graph=True)\n\n      @nnx.grad\n      def loss_fn(model: Model):\n        y_pred = model(x)\n        return jnp.mean((y - y_pred) ** 2)\n\n      grads = loss_fn(new_model)\n      nnx.update(\n        new_model,\n        jax.tree.map(\n          lambda w, g: w - 0.1 * g, nnx.state(new_model, nnx.Param), grads\n        ),\n      )\n\n      return nnx.split(new_model)\n\n    graphdef: nnx.GraphDef[Model]\n    graphdef, state = nnx.split(Model(rngs=nnx.Rngs(0)))\n\n    x = np.random.uniform(size=(4, 2))\n    y = np.random.uniform(size=(4, 2))\n\n    for _ in range(3):\n      graphdef, state = train_step(state, graphdef, x, y)\n\n    model = nnx.merge(graphdef, state)\n\n    assert model.block1.linear.bias is not None\n    assert model.block2.linear.bias is not None\n    assert model.block1.linear.kernel is model.block2.linear.kernel\n    assert model.block1.linear.bias is model.block2.linear.bias\n    assert model.block1.bn is not model.block2.bn\n\n  @parameterized.parameters(True, False)\n  def test_stateful_example(self, graph_mode):\n    class State(nnx.Variable[A]):\n      pass\n\n    class Linear(nnx.Module):\n      def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n        key = rngs.params()\n        self.w = nnx.Param(jax.random.uniform(key, (din, dout)))\n        self.b = nnx.Param(jnp.zeros((dout,)))\n        self.count = State(jnp.array(0))\n\n      def __call__(self, x):\n        self.count[...] += 1\n        return x @ self.w + self.b[None]\n\n    model = Linear(din=12, dout=2, rngs=nnx.Rngs(0))\n    # forward pass\n    x = jnp.ones((8, 12))\n    y = model(x)\n    assert model.count[...] == 1\n\n    @nnx.jit(graph=graph_mode)\n    def train_step(model, x, y):\n      def loss_fn(model):\n        y_pred = model(x)\n        return jax.numpy.mean((y_pred - y) ** 2)\n\n      # compute gradient\n      grads: nnx.State = nnx.grad(loss_fn)(model)\n      # SGD update\n      nnx.update(\n        model,\n        jax.tree.map(\n          lambda w, g: w - 0.1 * g, nnx.state(model, nnx.Param), grads\n        ),\n      )\n\n    # execute the training step\n    train_step(model, x, y)\n    assert model.count[...] == 2\n\n  def test_functional_example(self):\n    class Count(nnx.Variable[A]):\n      pass\n\n    class Linear(nnx.Module):\n      def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n        key = rngs.params()\n        self.w = nnx.Param(jax.random.uniform(key, (din, dout)))\n        self.b = nnx.Param(jnp.zeros((dout,)))\n        self.count = Count(jnp.array(0))\n\n      def __call__(self, x):\n        self.count[...] += 1\n        return x @ self.w + self.b[None]\n\n    model = Linear(din=12, dout=2, rngs=nnx.Rngs(0))\n    # forward pass\n    x = jnp.ones((8, 12))\n    y = model(x)\n    assert model.count[...] == 1\n\n    graphdef, params, counts = nnx.split(model, nnx.Param, Count)\n\n    @jax.jit\n    def train_step(params, counts, x, y):\n      def loss_fn(params):\n        model = nnx.merge(graphdef, params, counts, copy=True)\n        loss = jax.numpy.mean((model(x) - y) ** 2)\n        return loss, nnx.state(model, Count)\n\n      # compute gradient\n      grads, counts = jax.grad(loss_fn, has_aux=True)(params)\n      # SGD update\n      params = jax.tree.map(lambda w, g: w - 0.1 * g, params, grads)\n\n      return params, counts\n\n    # execute the training step\n    params, counts = train_step(params, counts, x, y)\n    model = nnx.merge(graphdef, params, counts)\n    assert model.count[...] == 2\n\n  def test_intermediates_example(self):\n    class Linear(nnx.Module):\n      def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n        key = rngs.params()\n        self.w = nnx.Param(jax.random.uniform(key, (din, dout)))\n        self.b = nnx.Param(jnp.zeros((dout,)))\n\n      def __call__(self, x):\n        y = x @ self.w + self.b[None]\n        self.y = nnx.Intermediate(y)\n        return y\n\n    model = Linear(12, 2, rngs=nnx.Rngs(0))\n\n    y = model(jnp.ones((8, 12)))\n\n    intermediates = nnx.pop(model, nnx.Intermediate)\n\n    assert 'y' in intermediates\n\n  def test_intermediates_example_functional(self):\n    class Linear(nnx.Module):\n      def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n        key = rngs.params()\n        self.w = nnx.Param(jax.random.uniform(key, (din, dout)))\n        self.b = nnx.Param(jnp.zeros((dout,)))\n\n      def __call__(self, x):\n        y = x @ self.w + self.b[None]\n        self.y = nnx.Intermediate(y)\n        return y\n\n    model = Linear(12, 2, rngs=nnx.Rngs(0))\n\n    graphdef, state = nnx.split(model)\n\n    y, (_, state) = graphdef.apply(state)(jnp.ones((8, 12)))\n\n    intermediates, state = nnx.split_state(state, nnx.Intermediate, ...)\n\n    assert 'y' in intermediates\n\n  def test_replace_by_pure_dict(self):\n    class MLPs(nnx.Module):\n      def __init__(self, dim, rngs: nnx.Rngs):\n        self.layers = nnx.List()\n        for _ in range(4):\n          self.layers.append(nnx.Linear(dim, dim, rngs=rngs, use_bias=False))\n\n      def __call__(self, x):\n        for layer in self.layers:\n          x = layer(x)\n        return x\n\n    model = MLPs(4, rngs=nnx.Rngs(0))\n    x = jax.random.normal(jax.random.key(42), (3, 4))\n    assert model(x).shape == (3, 4)\n\n    _, state = nnx.split(model)\n    pure_dict_state = nnx.to_pure_dict(state)\n    nnx.display(pure_dict_state)\n\n    with tempfile.TemporaryDirectory() as tmpdir:\n      ckpt_dir = ocp.test_utils.erase_and_create_empty(\n        tmpdir + '/my-checkpoints/'\n      )\n      checkpointer = ocp.StandardCheckpointer()\n      # checkpointer.save(ckpt_dir / 'state', state)\n      checkpointer.save(ckpt_dir / 'pure_dict', pure_dict_state)\n\n      # Restore as a pure dictionary.\n      restored_pure_dict = checkpointer.restore(ckpt_dir / 'pure_dict')\n      restored_pure_dict = nnx.statelib.restore_int_paths(restored_pure_dict)\n\n      model = nnx.eval_shape(lambda: MLPs(4, rngs=nnx.Rngs(0)))\n      nnx.update(model, restored_pure_dict)\n      assert model(x).shape == (3, 4)  # The model still works!\n\n  @nnx.var_defaults(hijax=True)\n  def test_example_mutable_arrays(self):\n    class Model(nnx.Module):\n      def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dmid, rngs=rngs)\n        self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n        self.dropout = nnx.Dropout(0.2, rngs=rngs)\n        self.linear_out = nnx.Linear(dmid, dout, rngs=rngs)\n\n      def __call__(self, x):\n        x = nnx.relu(self.dropout(self.bn(self.linear(x))))\n        return self.linear_out(x)\n\n    model = Model(2, 64, 3, rngs=nnx.Rngs(0))  # eager initialization\n    optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\n\n    @jax.jit  # automatic state management for JAX transforms\n    def train_step(x, y):\n      graphdef, params, nondiff = nnx.split(model, nnx.Param, ...)\n      def loss_fn(params):\n        model =  nnx.merge(graphdef, params, nondiff)\n        return ((model(x) - y) ** 2).mean()  # call methods directly\n\n      loss, grads = jax.value_and_grad(loss_fn)(\n        nnx.vars_as(params, hijax=False)\n      )\n      optimizer.update(model, grads)  # in-place updates\n\n      return loss\n\n    x = jax.random.normal(jax.random.key(0), (8, 2))\n    y = jax.random.normal(jax.random.key(1), (8, 3))\n\n    train_step(x, y)\n\n  def test_tree_mode_train_step(self):\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\n\n    @nnx.jit(graph=False)\n    def train_step(model, optimizer, x, y):\n      def loss_fn(model):\n        y_pred = model(x)\n        return jnp.mean((y_pred - y) ** 2)\n\n      loss, grads = nnx.value_and_grad(loss_fn, graph=False)(model)\n      optimizer.update(model, grads)\n      return loss\n\n    x = jax.random.normal(jax.random.key(0), (4, 2))\n    y = jax.random.normal(jax.random.key(1), (4, 3))\n\n    loss0 = train_step(model, optimizer, x, y)\n    loss1 = train_step(model, optimizer, x, y)\n    self.assertLess(loss1, loss0)\n\n  def test_tree_mode_multi_module(self):\n    class Block(nnx.Module):\n      def __init__(self, din, dout, *, rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.bn = nnx.BatchNorm(dout, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.linear(x)\n        x = self.bn(x)\n        return nnx.relu(x)\n\n    class Model(nnx.Module):\n      def __init__(self, *, rngs):\n        self.block1 = Block(2, 2, rngs=rngs)\n        self.block2 = Block(2, 2, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.block1(x)\n        x = self.block2(x)\n        return x\n\n    @nnx.jit(graph=False)\n    def train_step(model: Model, x, y):\n      @nnx.grad(graph=False)\n      def loss_fn(model: Model):\n        y_pred = model(x)\n        return jnp.mean((y - y_pred) ** 2)\n\n      grads = loss_fn(model)\n      model = jax.tree.map(\n        lambda w, g: w - 0.1 * g, model, grads,\n      )\n      return model\n\n    model = Model(rngs=nnx.Rngs(0))\n\n    x = np.random.uniform(size=(4, 2))\n    y = np.random.uniform(size=(4, 2))\n    model.set_attributes(use_running_average=False)\n\n    for _i in range(3):\n      model = train_step(model, x, y)\n\n  def test_tree_mode_stateful(self):\n    class State(nnx.Variable[A]):\n      pass\n\n    class Linear(nnx.Module):\n      def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n        key = rngs.params()\n        self.w = nnx.Param(jax.random.uniform(key, (din, dout)))\n        self.b = nnx.Param(jnp.zeros((dout,)))\n        self.count = State(jnp.array(0))\n\n      def __call__(self, x):\n        self.count[...] = self.count[...] + 1\n        return x @ self.w + self.b[None]\n\n    model = Linear(din=12, dout=2, rngs=nnx.Rngs(0))\n    x = jnp.ones((8, 12))\n    y = model(x)\n    assert model.count[...] == 1\n\n    @nnx.jit(graph=False)\n    def train_step(model, x, y):\n      def loss_fn(model):\n        y_pred = model(x)\n        return jax.numpy.mean((y_pred - y) ** 2)\n\n      grads = nnx.grad(loss_fn, graph=False, allow_int=True)(model)\n      params = jax.tree.map(\n        lambda w, g: w - 0.1 * g,\n        nnx.state(model, nnx.Param),\n        nnx.state(grads, nnx.Param),\n      )\n      nnx.update(model, params)\n\n    train_step(model, x, y)\n    assert model.count[...] == 2\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/metrics_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom flax import nnx\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\n\n\nclass TestMetrics(parameterized.TestCase):\n\n  def test_split_merge(self):\n    logits = jnp.array(\n      [[-1.0, 1.0], [-1.0, 1.0], [-1.0, 1.0], [1.0, -1.0], [1.0, -1.0]]\n    )\n    labels = jnp.array([1, 1, 1, 1, 1])\n    logits2 = jnp.array(\n      [[-1.0, 1.0], [-1.0, 1.0], [-1.0, 1.0], [1.0, -1.0], [1.0, -1.0]]\n    )\n    labels2 = jnp.array([1, 1, 1, 1, 0])\n\n    accuracy = nnx.metrics.Accuracy()\n    accuracy.update(logits=logits, labels=labels)\n    graphdef, state = accuracy.split()\n    accuracy = nnx.merge(graphdef, state)\n    self.assertEqual(accuracy.compute(), 0.6)\n    accuracy.update(logits=logits2, labels=labels2)\n    self.assertEqual(accuracy.compute(), 0.7)\n\n  def test_welford(self):\n    values = jax.random.normal(jax.random.key(0), (5, 2))\n\n    welford = nnx.metrics.Welford()\n    welford.update(values=values)\n    graphdef, state = welford.split()\n    welford = nnx.merge(graphdef, state)\n    expected = nnx.metrics.Statistics(\n        mean=values.mean(),\n        standard_deviation=values.std(),\n        standard_error_of_mean=values.std() / jnp.sqrt(values.size),\n    )\n    computed = welford.compute()\n    self.assertAlmostEqual(computed.mean, expected.mean, )\n    self.assertAlmostEqual(computed.standard_deviation, expected.standard_deviation)\n    self.assertAlmostEqual(\n        computed.standard_error_of_mean, expected.standard_error_of_mean\n    )\n\n  def test_welford_large(self):\n    values = jax.random.normal(jax.random.key(0), (5, 2)) + 1e16\n\n    welford = nnx.metrics.Welford()\n    welford.update(values=values)\n    graphdef, state = welford.split()\n    welford = nnx.merge(graphdef, state)\n    expected = nnx.metrics.Statistics(\n        mean=values.mean(),\n        standard_deviation=values.std(),\n        standard_error_of_mean=values.std() / jnp.sqrt(values.size),\n    )\n    computed = welford.compute()\n    self.assertAlmostEqual(computed.mean, expected.mean)\n    self.assertAlmostEqual(computed.standard_deviation, expected.standard_deviation)\n    self.assertAlmostEqual(\n        computed.standard_error_of_mean, expected.standard_error_of_mean\n    )\n\n  def test_welford_many(self):\n    values = jax.random.normal(jax.random.key(0), (50_000,))\n\n    welford = nnx.metrics.Welford()\n    welford.update(values=values)\n    graphdef, state = welford.split()\n    welford = nnx.merge(graphdef, state)\n    computed = welford.compute()\n    self.assertAlmostEqual(\n        computed.mean, 0.0, delta=3 * computed.standard_error_of_mean\n    )\n    self.assertAlmostEqual(computed.standard_deviation, 1.0, places=2)\n\n  @parameterized.product(with_mask=[True, False])\n  def test_multimetric(self, with_mask):\n    logits = jnp.array(\n      [[-1.0, 1.0], [-1.0, 1.0], [-1.0, 1.0], [1.0, -1.0], [1.0, -1.0]]\n    )\n    labels = jnp.array([1, 1, 0, 1, 0])\n    mask = labels > 0 if with_mask else None\n    logits2 = jnp.array(\n      [[-1.0, 1.0], [-1.0, 1.0], [-1.0, 1.0], [1.0, -1.0], [1.0, -1.0]]\n    )\n    labels2 = jnp.array([0, 1, 1, 1, 1])\n    mask2 = labels2 > 0 if with_mask else None\n    batch_loss = jnp.array([1, 2, 3, 4])\n    batch_loss2 = jnp.array([3, 2, 1, 0])\n    loss_mask2 = batch_loss2 > 1 if with_mask else None\n\n    metrics = nnx.MultiMetric(\n        accuracy=nnx.metrics.Accuracy(), loss=nnx.metrics.Average()\n    )\n    values = metrics.compute()\n    self.assertTrue(jnp.isnan(values['accuracy']))\n    self.assertTrue(jnp.isnan(values['loss']))\n\n    metrics.update(logits=logits, labels=labels, values=batch_loss, mask={\"accuracy\": mask})\n    values = metrics.compute()\n    self.assertEqual(values['accuracy'], 0.6 if not with_mask else 2 / 3)\n    self.assertEqual(values['loss'], 2.5)\n\n    metrics.update(logits=logits2, labels=labels2, values=batch_loss2, mask={\"accuracy\": mask2, \"loss\": loss_mask2})\n    values = metrics.compute()\n    self.assertEqual(values['accuracy'], 0.5 if not with_mask else (2 + 2) / (3 + 4))\n    self.assertEqual(values['loss'], 2.0 if not with_mask else 15 / 6)\n\n    metrics.reset()\n    values = metrics.compute()\n    self.assertTrue(jnp.isnan(values['accuracy']))\n    self.assertTrue(jnp.isnan(values['loss']))\n\n  @parameterized.product(with_mask=[True, False])\n  def test_multimetric_with_custom_metric(self, with_mask):\n    class CustomAccuracy(nnx.metrics.Accuracy):\n      # we use unused values arg is on purpose instead of using **kwargs\n      # to reproduce the error if mask arg is injected to the custom metric without mask arg\n      def update(self, y_preds, y_true, values):\n        super().update(logits=y_preds, labels=y_true)\n\n    logits = jnp.array(\n      [[-1.0, 1.0], [-1.0, 1.0], [-1.0, 1.0], [1.0, -1.0], [1.0, -1.0]]\n    )\n    labels = jnp.array([1, 1, 0, 1, 0])\n    batch_loss = jnp.array([1, 2, 3, 4])\n    mask = batch_loss > 1 if with_mask else None\n\n    metrics = nnx.MultiMetric(\n        accuracy=CustomAccuracy(), loss=nnx.metrics.Average()\n    )\n    metrics.update(y_preds=logits, y_true=labels, values=batch_loss, mask={\"loss\": mask})\n    values = metrics.compute()\n    self.assertEqual(values['accuracy'], 0.6)\n    self.assertEqual(values['loss'], 3.0 if with_mask else 2.5)\n\n  def test_binary_classification_accuracy(self):\n    logits = jnp.array([0.4, 0.7, 0.2, 0.6])\n    labels = jnp.array([0, 1, 1, 1])\n    logits2 = jnp.array([0.1, 0.9, 0.8, 0.3])\n    labels2 = jnp.array([0, 1, 1, 0])\n    accuracy = nnx.metrics.Accuracy(threshold=0.5)\n    accuracy.update(logits=logits, labels=labels)\n    self.assertEqual(accuracy.compute(), 0.75)\n    accuracy.update(logits=logits2, labels=labels2)\n    self.assertEqual(accuracy.compute(), 0.875)\n\n  @parameterized.parameters(\n    {\n      'logits': np.array([[[0.0, 0.0], [0.0, 0.0]], [[0.0, 0.0], [0.0, 0.0]]]),\n      'labels': np.array([0, 0, 0, 0]),\n      'threshold': None,\n      'error_msg': 'For multi-class classification'\n    },\n    {\n      'logits': np.array([0.0, 0.0, 0.0, 0.0]),\n      'labels': np.array([[0, 0], [0, 0]]),\n      'threshold': 0.5,\n      'error_msg': 'For binary classification'\n    }\n  )\n  def test_accuracy_dims(self, logits, labels, threshold, error_msg):\n    accuracy = nnx.metrics.Accuracy(threshold=threshold)\n    with self.assertRaisesRegex(ValueError, error_msg):\n      accuracy.update(logits=logits, labels=labels)\n\n  @parameterized.product(\n    with_mask=[True, False],\n    scalar_values=[True, False],\n  )\n  def test_average(self, with_mask, scalar_values):\n    average = nnx.metrics.Average()\n    value1 = 2 if scalar_values else jnp.arange(5 * 3, dtype=jnp.int32).reshape(5, 3)\n    value2 = 3 if scalar_values else jnp.arange(5 * 3 * 4, dtype=jnp.float32).reshape(5, 3, 4)\n    if with_mask:\n      list_masks = [\n        jnp.ones(5) if scalar_values else jnp.where(value1 > 4, 1.0, 0.0),\n        jnp.ones(5) if scalar_values else value2 > 10,\n      ]\n    else:\n      list_masks = [None, None]\n\n    list_values = [value1, value2]\n\n    for mask, values in zip(list_masks, list_values):\n      if with_mask and scalar_values:\n        with self.assertRaisesRegex(ValueError, \"should be a jax array\"):\n          average.update(mask=mask, values=values)\n      else:\n          average.update(mask=mask, values=values)\n\n    if with_mask and scalar_values:\n      return\n\n    self.assertEqual(\n      average.count,\n      (\n        len(list_values) if scalar_values\n        else sum(m.sum() for m in list_masks) if with_mask\n        else sum(v.size for v in list_values)\n      )\n    )\n    self.assertEqual(\n      average.total,\n      (\n        sum(list_values) if scalar_values\n        else sum((v * m).sum() for m, v in zip(list_masks, list_values)) if with_mask\n        else sum(v.sum() for v in list_values)\n      )\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/module_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom copy import deepcopy\nimport dataclasses\nimport pickle\nimport tempfile\nfrom typing import TypeVar\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nimport cloudpickle\nfrom flax import errors, nnx\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nimport flax\n\nA = TypeVar('A')\n\nfrom contextlib import contextmanager\n\n@contextmanager\ndef set_graph_mode(mode):\n  old_mode = flax.config._read('nnx_graph_mode')\n  try:\n    flax.config.update('nnx_graph_mode', mode)\n    yield\n  finally:\n    flax.config.update('nnx_graph_mode', old_mode)\n\n\nclass PytreeTest(absltest.TestCase):\n  def test_pytree(self):\n    class Foo(nnx.Pytree):\n      def __init__(self, a, b):\n        self.a = nnx.data(a)\n        self.b = nnx.static(b)\n\n    foo = Foo(a=1, b=2)\n\n    self.assertEqual(jax.tree.leaves(foo), [1])\n\n  def test_sequential_map(self):\n    model = nnx.Sequential(nnx.Linear(2,8, rngs=nnx.Rngs(0)))\n    jax.tree.map(lambda x: x + 1, model) # shouldn't error\n\n  def test_sequential_has_leaves(self):\n    model = nnx.Sequential(nnx.Linear(2,8, rngs=nnx.Rngs(0)))\n    self.assertLen(jax.tree.leaves(model), 2)\n\n  def test_consistent_attrs(self):\n    class Foo(nnx.Pytree):\n      def __init__(self, a, b, c):\n        self.a = nnx.data(a)\n        self.b = nnx.static(b)\n        self.c = c\n\n    foo = Foo(a=1, b=2, c=jnp.array(3))\n    self.assertLen(jax.tree.leaves(foo), 2)\n\n    foo.a = 3\n    self.assertLen(jax.tree.leaves(foo), 2)\n\n    foo.a = nnx.static(3)\n    self.assertLen(jax.tree.leaves(foo), 1)\n\n    foo.b = 4  # ok\n    self.assertLen(jax.tree.leaves(foo), 1)\n\n    foo.b = nnx.data(4)\n    self.assertLen(jax.tree.leaves(foo), 2)\n\n    foo.c = jnp.array(5)  # ok\n    self.assertLen(jax.tree.leaves(foo), 2)\n\n    foo.c = nnx.static(5)\n    self.assertLen(jax.tree.leaves(foo), 1)\n\n    with self.assertRaisesRegex(\n        ValueError,\n        'Found data on value of type',\n    ):\n      foo.a = ['hi', jnp.array(6)]\n\n    with self.assertRaisesRegex(\n        ValueError,\n        'Found data in value of type',\n    ):\n      foo.b = nnx.static(jnp.array(4))\n\n  def test_assing_pytree_with_data(self):\n    class Foo(nnx.Pytree):\n      pass\n\n    foo = Foo()\n    with self.assertRaisesRegex(\n        ValueError,\n        'Found data on value of type',\n    ):\n      foo.a = [nnx.Variable(1)]\n\n  def test_consistent_attrs_frozen_dataclass(self):\n    @nnx.dataclass\n    class Foo(nnx.Pytree):\n      a: int = nnx.data()\n      b: int = nnx.static()\n      c: jax.Array\n\n    foo = Foo(a=1, b=2, c=jnp.array(3))\n    self.assertLen(jax.tree.leaves(foo), 2)\n\n  def test_consistent_attrs_dataclass_annotations(self):\n    @dataclasses.dataclass\n    class Foo(nnx.Pytree):\n      a: nnx.Data[int]\n      b: nnx.Static[int]\n      c: jax.Array\n\n    foo = Foo(a=1, b=2, c=jnp.array(3))\n    self.assertLen(jax.tree.leaves(foo), 2)\n\n    foo.a = 3\n    self.assertLen(jax.tree.leaves(foo), 2)\n\n    foo.a = nnx.static(3)\n    self.assertLen(jax.tree.leaves(foo), 1)\n\n    foo.b = 4  # ok\n    self.assertLen(jax.tree.leaves(foo), 1)\n\n    foo.b = nnx.data(4)\n    self.assertLen(jax.tree.leaves(foo), 2)\n\n    foo.c = jnp.array(5)  # ok\n    self.assertLen(jax.tree.leaves(foo), 2)\n\n    foo.c = nnx.static(5)\n    self.assertLen(jax.tree.leaves(foo), 1)\n\n    with self.assertRaisesRegex(\n        ValueError,\n        'Found data on value of type',\n    ):\n      foo.a = ['hi', jnp.array(6)]\n\n    with self.assertRaisesRegex(\n        ValueError,\n        'Found data in value of type',\n    ):\n      foo.b = nnx.static(jnp.array(4))\n\n  def test_explicit_dont_change(self):\n    class Foo(nnx.Pytree):\n      def __init__(self):\n        self.b = nnx.data(2)\n\n    foo = Foo()\n    self.assertEqual(jax.tree.leaves(foo), [2])\n    foo.b = \"hello\"\n    self.assertEqual(jax.tree.leaves(foo), [\"hello\"])\n\n  def test_no_data_in_static(self):\n    class Foo(nnx.Pytree):\n      def __init__(self):\n        self.a = nnx.static(jnp.array(1))\n\n    with self.assertRaisesRegex(\n        ValueError,\n        'Found data in value of type',\n    ):\n      foo = Foo()\n\nclass TestCapture(parameterized.TestCase):\n\n  def test_vmap(self):\n\n    class Foo(nnx.Module):\n      def __init__(self, dim):\n        self.w = nnx.Param(jax.random.normal(jax.random.key(0), dim))\n\n      def __call__(self, x):\n        x = self.perturb('grad_of_x', x)\n        y = jnp.dot(x, self.w)\n        self.sow(nnx.Intermediate, 'y', y)\n        return y\n\n      def pre_run(self, x):\n        graphdef, intms, params = nnx.split(model, nnx.Intermediate, nnx.Param)\n        def run(intms, params, x):\n          return nnx.merge(graphdef, intms, params)(x)\n        nnx.vmap(run, in_axes=(0, None, 0))(intms, params, x)\n\n    @nnx.jit\n    def train_step(model, perturbations, x):\n      def loss_grad(model, perturbations, x):\n        def loss(model, perturbations, x):\n          loss, interms = nnx.capture(model, nnx.Intermediate, init=perturbations)(x)\n          return loss, interms\n        (grads, perturb_grads), sowed = nnx.grad(loss, argnums=(0, 1), has_aux=True)(model, perturbations, x)\n        return grads, nnx.merge_state(perturb_grads, sowed)\n      return nnx.vmap(loss_grad, in_axes=(None, 0, 0))(model, perturbations,x)\n\n    model, x = Foo(4), jnp.ones((3, 4))\n\n    pre_run_capture = nnx.capture(model.pre_run, nnx.Perturbation)\n    _, perturbations = pre_run_capture(x)\n    _, intermediates = train_step(model, perturbations, x)\n    np.testing.assert_allclose(intermediates['grad_of_x'].get_value(),\n      jnp.broadcast_to(model.w.get_value()[None, :], (3, 4)))\n    self.assertEqual(intermediates['y'].get_value()[0].shape, (3,))\n\n\n  @parameterized.parameters(True, False)\n  def test_fwd_bwd(self, graph_mode):\n    with set_graph_mode(graph_mode):\n\n        class Foo(nnx.Module):\n          @nnx.jit\n          def __call__(self, x):\n            x = self.perturb('grad_of_x', x)\n            y = 3 * x\n            self.sow(nnx.Intermediate, 'y', y)\n            return y\n\n        model = Foo()\n\n        @nnx.jit\n        def train_step(model, perturbations, x):\n          def loss(model, perturbations, x):\n            loss, interms = nnx.capture(model, nnx.Intermediate, init=perturbations)(x)\n            return loss, interms\n          (grads, perturb_grads), sowed = nnx.grad(loss, argnums=(0, 1), has_aux=True)(model, perturbations, x)\n          return grads, nnx.merge_state(perturb_grads, sowed)\n\n        x = 1.0\n\n        forward = nnx.capture(model, nnx.Perturbation)\n        _, perturbations = forward(x)\n        grads, intermediates = train_step(model, perturbations, x)\n        self.assertEqual(intermediates['grad_of_x'], 3)\n        self.assertEqual(intermediates['y'][0], 3)\n\n  @parameterized.parameters(True, False)\n  def test_nested_modules(self, graph_mode):\n    with set_graph_mode(graph_mode):\n\n      class Foo(nnx.Module):\n        def __call__(self, x):\n          x = self.perturb('grad_of_x', x)\n          y = 3 * x\n          self.sow(nnx.Intermediate, 'y', y)\n          return y\n      class Bar(nnx.Module):\n        def __init__(self):\n          self.foos = nnx.data([Foo() for _ in range(3)])\n        def __call__(self, x):\n          for block in self.foos:\n            x = block(x)\n          return x\n\n      model = Bar()\n\n      @nnx.jit\n      def train_step(model, perturbations, x):\n        def loss(model, perturbations, x):\n          loss, interms = nnx.capture(model, nnx.Intermediate, init=perturbations)(x)\n          return loss, interms\n        (grads, perturb_grads), sowed = nnx.grad(loss, argnums=(0, 1), has_aux=True)(model, perturbations, x)\n        return grads, nnx.merge_state(perturb_grads, sowed)\n\n      x = 1.0\n\n      forward = nnx.capture(model, nnx.Perturbation)\n      _, perturbations = forward(x)\n      _, intermediates = train_step(model, perturbations, x)\n      for i in range(3):\n        self.assertEqual(intermediates['foos'][i]['grad_of_x'], 3**(3-i))\n        self.assertEqual(intermediates['foos'][i]['y'][0], 3**(i+1))\n\n  def test_method_outputs_single_module(self):\n    class Foo(nnx.Module):\n      def __init__(self, dim):\n        self.w = nnx.Param(jax.random.normal(jax.random.key(0), (dim, dim)))\n      def __call__(self, x):\n        return x @ self.w\n      def helper(self, x):\n        return jnp.sin(x)\n      def run(self, x):\n        y = self(x)\n        z = self.helper(y)\n        return (y, z)\n\n    model = Foo(8)\n    x = jnp.ones((4, 8))\n\n    run_with_capture = nnx.capture(\n      model.run,\n      nnx.Intermediate, method_outputs=nnx.Intermediate\n    )\n    (y, z), intms = run_with_capture(x)\n\n    self.assertIn('__call__', intms)\n    self.assertIn('helper', intms)\n    np.testing.assert_allclose(intms['__call__'][0], y)\n    np.testing.assert_allclose(intms['helper'][0], z)\n\n  def test_method_outputs_nested_modules(self):\n    class Inner(nnx.Module):\n      def __init__(self, dim, rngs):\n        self.w = nnx.Param(jax.random.normal(rngs.params(), (dim, dim)))\n      def __call__(self, x):\n        return x @ self.w\n      def process(self, x):\n        return jnp.sin(x)\n\n    class Outer(nnx.Module):\n      def __init__(self, rngs):\n        self.inner1 = Inner(8, rngs)\n        self.inner2 = Inner(8, rngs)\n      def __call__(self, x):\n        x = self.inner1(x)\n        x = self.inner2.process(x)\n        return x\n\n    model = Outer(nnx.Rngs(0))\n    x = jnp.ones((4, 8))\n\n    forward = nnx.capture(\n      model,\n      nnx.Intermediate, method_outputs=nnx.Intermediate\n    )\n    y, intms = forward(x)\n\n    self.assertIn('__call__', intms)\n    self.assertIn('inner1', intms)\n    self.assertIn('process', intms['inner2'])\n    self.assertEqual(intms['inner1']['__call__'][0].shape, (4, 8))\n    self.assertEqual(intms['inner2']['process'][0].shape, (4, 8))\n\n  def test_method_outputs_mixed_with_sow(self):\n    class Foo(nnx.Module):\n      def __init__(self, dim):\n        self.w = nnx.Param(jax.random.normal(jax.random.key(0), (dim, dim)))\n      def __call__(self, x):\n        x = x @ self.w\n        self.sow(nnx.Intermediate, 'intermediate', x)\n        return jnp.sin(x)\n\n    model = Foo(8)\n    x = jnp.ones((4, 8))\n\n    forward = nnx.capture(\n      model,\n      nnx.Intermediate, method_outputs=nnx.Intermediate\n    )\n    y, intms = forward(x)\n\n    self.assertIn('__call__', intms)\n    self.assertIn('intermediate', intms)\n    np.testing.assert_allclose(intms['__call__'][0], y)\n    np.testing.assert_allclose(jnp.sin(intms['intermediate'][0]), y)\n\nclass SowMod(nnx.Module):\n    def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(4, 4, rngs=rngs)\n\n    def __call__(self, x):\n        y = self.linear(x)\n        self.sow(nnx.Intermediate, \"my_summary\", y.mean())\n        return y * 2\n\nclass TestModule(parameterized.TestCase):\n  def test_has_module_state(self):\n    class Foo(nnx.Module): ...\n\n    foo = Foo()\n\n    assert hasattr(foo, '_pytree__state')\n\n  def test_trace_level(self):\n    m = nnx.Dict(a=nnx.Param(1))\n\n    @jax.jit\n    def f():\n      with self.assertRaisesRegex(\n        errors.TraceContextError,\n        \"Cannot mutate 'Dict' from different trace level\",\n      ):\n        m.a = 2\n\n    f()\n\n  def test_tree_map(self):\n    m = nnx.Dict(a=nnx.Param(1))\n\n    graphdef, state = nnx.split(m)\n\n    state = jax.tree.map(lambda x: x + 1, state)\n\n  def test_split_2(self):\n    m = nnx.Dict(a=nnx.Param(1))\n\n    graphdef, empty, some = nnx.split(m, None, ...)\n\n    some = jax.tree.map(lambda x: x + 1, some)\n\n  def test_split_merge(self):\n    m = nnx.Dict(a=nnx.Param(1))\n\n    @jax.jit\n    def g(graphdef: nnx.GraphDef[nnx.Dict], state: nnx.State):\n      m = nnx.merge(graphdef, state)\n      m.a = 2\n      return nnx.split(m)\n\n    graphdef, state = g(*nnx.split(m))\n    m2 = nnx.merge(graphdef, state)\n\n    assert m2.a == 2\n\n  def test_call(self):\n    class Foo(nnx.Module):\n      def __init__(self, c: float, *, rngs: nnx.Rngs):\n        key = rngs.params()\n        self.w = nnx.Param(jax.random.uniform(key, ()))\n        self.c = c\n\n      def __call__(self, x, *, rngs: nnx.Rngs):\n        return self.w * x + rngs.e.normal(()) + self.c\n\n    foo = Foo(c=1.0, rngs=nnx.Rngs(0))\n\n    y = foo(x=2.0, rngs=nnx.Rngs(e=1))\n\n    assert isinstance(y, jax.Array)\n\n  def test_shared_module(self):\n    m1 = nnx.Dict(a=nnx.Param(1), b=nnx.Param(2))\n    m2 = nnx.Dict(x=m1, y=m1, z=nnx.Param(3))\n\n    m3 = nnx.merge(*nnx.split(m2))\n\n    assert m3['x'] is m3['y']\n    assert m3['x']['a'] is m3['y']['a']\n    assert m3['x']['b'] is m3['y']['b']\n\n  def test_module_graph(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = nnx.Param(1)\n        self.sub = self\n\n    m = Foo()\n\n    graphdef, state = nnx.split(m)\n    assert len(state) == 1\n\n    m2 = nnx.merge(graphdef, state)\n    assert m2 is m2.sub\n\n  def test_deref_through_jit(self):\n    r1 = nnx.Variable(1)\n    r2 = nnx.Variable(2)\n\n    m = m0 = nnx.Dict({'a': nnx.List([r1, r2]), 'b': r1})\n\n    @jax.jit\n    def f(graphdef: nnx.GraphDef[nnx.Dict], state: nnx.State):\n      m = nnx.merge(graphdef, state)\n\n      assert m['a'][0] is m['b']\n      assert m['a'][1] is not m['b']\n\n      return nnx.split(m)\n\n    graphdef, state = f(*nnx.split(m))\n    m = nnx.merge(graphdef, state)\n\n    assert m['a'][0] is m['b']\n    assert m['a'][1] is not m['b']\n\n    # compare with original\n    assert m['a'][0] is not m0['a'][0]\n    assert m['a'][1] is not m0['a'][1]\n    assert m['b'] is not m0['b']\n\n  def test_cross_barrier(self):\n    m = nnx.Dict(a=nnx.Param(jnp.array(1)))\n\n    @jax.jit\n    def g(graphdef: nnx.GraphDef[nnx.Dict], state: nnx.State):\n      m = nnx.merge(graphdef, state)\n      m.a[...] += 1\n      return nnx.split(m)\n\n    graphdef, state = g(*nnx.split(m))\n    m2 = nnx.merge(graphdef, state)\n    assert m2 is not m\n    assert m.a[...] == 1\n    assert m2.a[...] == 2\n\n  def test_no_rejit(self):\n    n = 0\n    m = nnx.Dict(a=nnx.Param(jnp.array(1)))\n\n    @jax.jit\n    def g(state_and_def):\n      nonlocal n\n      n += 1\n      m = nnx.merge(*state_and_def)\n      m.a[...] += 1\n      return nnx.split(m)\n\n    m2 = nnx.merge(*g(nnx.split(m)))\n\n    assert n == 1\n    assert m2 is not m\n    assert m.a[...] == 1\n    assert m2.a[...] == 2\n\n    g(nnx.split(m))\n    assert n == 1\n\n    g(nnx.split(m2))\n    assert n == 1\n\n    m2.b = nnx.Param(10)\n    g(nnx.split(m2))\n\n    assert n == 2\n\n  def test_deref_number_of_fields(self):\n    r1 = nnx.Variable(1)\n    r2 = nnx.Variable(2)\n    v1 = 3\n    m = nnx.Dict(\n      {\n        'a': nnx.List([r1, r2, v1]),\n        'b': nnx.Dict({'c': r1, 'd': r2}),\n      }\n    )\n\n    graphdef, p = nnx.split(m)\n    assert len(nnx.to_flat_state(p)) == 2\n    assert len(jax.tree_util.tree_leaves(p)) == 2\n\n  def test_clone(self):\n    m = nnx.Dict(\n      a=nnx.List([nnx.Param(1), nnx.Param(2), 3]),\n      b=nnx.Dict(c=nnx.Param(1), d=nnx.Param(2)),\n    )\n\n    m2 = nnx.clone(m)\n\n    assert m is not m2\n    assert m2.a[0].get_value() == m2.b.c.get_value()\n    assert m2.a[1].get_value() == m2.b.d.get_value()\n\n    assert m.a[0].get_value() == m2.a[0].get_value()\n    assert m.a[1].get_value() == m2.a[1].get_value()\n    assert m.b.c.get_value() == m2.b.c.get_value()\n    assert m.b.d.get_value() == m2.b.d.get_value()\n\n  def test_sow_existing_non_variable_field(self):\n    class Foo(nnx.Module):\n      def __init__(self) -> None:\n        self.y = 10\n\n      def __call__(self, x):\n        y = x + 1\n        self.sow(nnx.Intermediate, 'y', y)\n        return y\n\n    m = Foo()\n\n    with self.assertRaisesRegex(ValueError, 'to be a Variable, got'):\n      m(2)\n\n  def test_sow_wrong_collection(self):\n    class Foo(nnx.Module):\n      def __init__(self) -> None:\n        self.y = nnx.Param(10)\n\n      def __call__(self, x):\n        y = x + 1\n        self.sow(nnx.Intermediate, 'y', y)\n        return y\n\n    m = Foo()\n\n    with self.assertRaisesRegex(ValueError, 'to be of type'):\n      m(2)\n\n  def test_sow_pop(self):\n    x = jnp.ones((2, 4))\n    model = SowMod(nnx.Rngs(42))\n    out, intermediates = nnx.capture(model, nnx.Intermediate)(x)\n    attr_names = set(model._pytree__nodes)\n    assert 'my_summary' not in attr_names\n\n  def test_cached_partial(self):\n    model = SowMod(nnx.Rngs(42))\n    x = jnp.ones((2, 4))\n\n    @nnx.jit\n    def train_step(model, x):\n      out, intermediates = nnx.capture(model, nnx.Intermediate)(x)\n      return out, intermediates\n\n    train_step_fn = nnx.cached_partial(train_step, model)\n    train_step_fn(x)\n\n  def test_update_static_state_submodules(self):\n    class Bar(nnx.Module):\n      def __init__(self) -> None:\n        self.x = 1\n\n      def add_field(self):\n        self.y = 2\n\n    class Foo(nnx.Module):\n      def __init__(self) -> None:\n        self.a = Bar()\n        self.b = self.a\n\n    m1 = Foo()\n    with nnx.update_context('test'):\n      with nnx.split_context('test') as ctx:\n        graphdef, state = ctx.split(m1)\n      with nnx.merge_context('test', inner=True) as ctx:\n        m2 = ctx.merge(graphdef, state)\n      m2.a.add_field()\n      with nnx.split_context('test') as ctx:\n        new_graphdef, state = ctx.split(m2)\n\n      with nnx.merge_context('test', inner=False) as ctx:\n        m3 = ctx.merge(new_graphdef, state)\n\n    assert m3 is m1\n    assert m1.a.x == 1\n    assert m1.a.y == 2\n    assert m1.b.x == 1\n    assert m1.b.y == 2\n\n  def test_update_new_submodule(self):\n    class Bar(nnx.Module):\n      def __init__(self) -> None:\n        self.x = 1\n\n    class Foo(nnx.Module):\n      def __init__(self) -> None:\n        self.a = Bar()\n\n      def add_module(self):\n        self.b = Bar()\n\n    m1 = Foo()\n    with nnx.update_context('test'):\n      with nnx.split_context('test') as ctx:\n        graphdef, state = ctx.split(m1)\n      with nnx.merge_context('test', inner=True) as ctx:\n        m2 = ctx.merge(graphdef, state)\n      m2.add_module()\n      with nnx.split_context('test') as ctx:\n        new_graphdef, state = ctx.split(m2)\n\n      with nnx.merge_context('test', inner=False) as ctx:\n        m3 = ctx.merge(new_graphdef, state)\n\n    assert m3 is m1\n    assert m1.a.x == 1\n    assert m1.b.x == 1\n\n  def test_update_update_submodule(self):\n    class Bar(nnx.Module):\n      def __init__(self) -> None:\n        self.x = 1\n\n    class Foo(nnx.Module):\n      def __init__(self) -> None:\n        self.a = Bar()\n        self.b = self.a\n\n    m1 = Foo()\n    with nnx.update_context('test'):\n      with nnx.split_context('test') as ctx:\n        graphdef, state = ctx.split(m1)\n      with nnx.merge_context('test', inner=True) as ctx:\n        m2 = ctx.merge(graphdef, state)\n      m2.a.x = 2\n      with nnx.split_context('test') as ctx:\n        new_graphdef, state = ctx.split(m2)\n      with nnx.merge_context('test', inner=False) as ctx:\n        m3 = ctx.merge(new_graphdef, state)\n\n    assert m3 is m1\n    assert m1.a.x == 2\n    assert m1.b.x == 2\n\n  def test_update_add_shared(self):\n    class Bar(nnx.Module):\n      def __init__(self) -> None:\n        self.x = 1\n\n    class Foo(nnx.Module):\n      def __init__(self) -> None:\n        self.a = Bar()\n        self.b = self.a\n\n      def add_submodule(self):\n        self.c = self.a\n\n    m1 = Foo()\n    with nnx.update_context('test'):\n      with nnx.split_context('test') as ctx:\n        graphdef, state = ctx.split(m1)\n      with nnx.merge_context('test', inner=True) as ctx:\n        m2 = ctx.merge(graphdef, state)\n      m2.add_submodule()\n      with nnx.split_context('test') as ctx:\n        new_graphdef, state = ctx.split(m2)\n      with nnx.merge_context('test', inner=False) as ctx:\n        m3 = ctx.merge(new_graphdef, state)\n\n    assert m3 is m1\n    assert hasattr(m1, 'c')\n\n  def test_create_abstract(self):\n    linear = nnx.eval_shape(lambda: nnx.Linear(2, 3, rngs=nnx.Rngs(0)))\n\n    assert linear.kernel.get_value() == jax.ShapeDtypeStruct((2, 3), jnp.float32)\n    assert linear.bias.get_value() == jax.ShapeDtypeStruct((3,), jnp.float32)\n\n  def test_create_abstract_stateful(self):\n    linear = nnx.eval_shape(lambda: nnx.Dropout(0.5, rngs=nnx.Rngs(0)))\n\n    assert linear.rngs.key.get_value() == jax.ShapeDtypeStruct(\n      (), jax.random.key(0).dtype\n    )\n\n  def test_partial_init(self):\n    linear = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    state = nnx.state(linear)\n\n    del state['bias']\n\n    @nnx.jit\n    def partial_init(state: nnx.State):\n      m = nnx.Linear(\n        2, 3, bias_init=nnx.initializers.ones_init(), rngs=nnx.Rngs(1)\n      )\n      nnx.update(m, state)\n      return m\n\n    linear2 = partial_init(state)\n\n    np.testing.assert_allclose(linear.kernel[...], linear2.kernel[...])\n    np.testing.assert_allclose(linear.bias[...], 0)\n    np.testing.assert_allclose(linear2.bias[...], 1)\n\n  def test_deepcopy(self):\n    class Foo(nnx.Module):\n      def __init__(self) -> None:\n        self.a = nnx.Param(jnp.array(1))\n        self.b = [1, 2, 3]\n        self.c = nnx.Param(jnp.array([1.0]))\n        self.self = self\n\n    m1 = Foo()\n    m2 = deepcopy(m1)\n\n    assert m1.a[...] == m2.a[...]\n    assert vars(m1)['a'] is not vars(m2)['a']\n    assert m1.b is not m2.b\n    assert m1.c is not m2.c\n    assert m1.self is m1\n\n  def test_set_attributes(self):\n    class Block(nnx.Module):\n      def __init__(self, din, dout, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False)\n        self.batch_norm = nnx.BatchNorm(\n          10, use_running_average=False, rngs=rngs\n        )\n\n    block = Block(2, 5, rngs=nnx.Rngs(0))\n    assert block.dropout.deterministic == False\n    assert block.batch_norm.use_running_average == False\n\n    block.set_attributes(deterministic=True, use_running_average=True)\n    assert block.dropout.deterministic == True\n    assert block.batch_norm.use_running_average == True\n\n    block = Block(2, 5, rngs=nnx.Rngs(0))\n    block.set_attributes(nnx.Dropout, deterministic=True)\n    # Only the dropout will be modified\n    assert block.dropout.deterministic == True\n    assert block.batch_norm.use_running_average == False\n\n  def test_set_attribute_error(self):\n    class Block(nnx.Module):\n      def __init__(self, din, dout, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False)\n        self.batch_norm = nnx.BatchNorm(\n          10, use_running_average=False, rngs=rngs\n        )\n\n    block = Block(2, 5, rngs=nnx.Rngs(0))\n\n    with self.assertRaisesRegex(\n        ValueError,\n        (\n            'Could not find at least one instance of the following attributes:'\n            \" \\\\['unknown'\\\\]\"\n        ),\n    ):\n      block.set_attributes(\n        deterministic=True, use_running_average=True, unknown=True\n      )\n\n    block.set_attributes(\n      deterministic=True,\n      use_running_average=True,\n      unknown=True,\n      raise_if_not_found=False,\n    )\n\n  @parameterized.parameters(True, False)\n  def test_view(self, graph):\n    class Block(nnx.Module):\n      def __init__(self, din, dout, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False, rngs=rngs)\n        self.batch_norm = nnx.BatchNorm(\n          10, use_running_average=False, rngs=rngs\n        )\n\n    block = Block(2, 5, rngs=nnx.Rngs(0))\n    assert block.dropout.deterministic == False\n    assert block.batch_norm.use_running_average == False\n\n    new_block = nnx.view(block, deterministic=True, use_running_average=True, graph=graph)\n    assert new_block.dropout.deterministic == True\n    assert new_block.batch_norm.use_running_average == True\n    assert new_block.linear.kernel is block.linear.kernel\n\n    block = Block(2, 5, rngs=nnx.Rngs(0))\n    new_block = nnx.view(block, only=nnx.Dropout, deterministic=True, graph=graph)\n    assert new_block.dropout.deterministic == True\n    assert new_block.batch_norm.use_running_average == False\n\n  @parameterized.parameters(True, False)\n  def test_with_attributes(self, graph):\n    class Block(nnx.Module):\n      def __init__(self, din, dout, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False)\n        self.batch_norm = nnx.BatchNorm(\n          10, use_running_average=False, rngs=rngs\n        )\n\n    block = Block(2, 5, rngs=nnx.Rngs(0))\n    assert block.dropout.deterministic == False\n    assert block.batch_norm.use_running_average == False\n\n    new_block = nnx.with_attributes(\n      block, deterministic=True, use_running_average=True, graph=graph\n    )\n    assert new_block.dropout.deterministic == True\n    assert new_block.batch_norm.use_running_average == True\n    assert new_block.linear.kernel is block.linear.kernel\n    assert block.dropout.deterministic == False\n    assert block.batch_norm.use_running_average == False\n\n  @parameterized.parameters(True, False)\n  def test_with_attributes_filter(self, graph):\n    class Block(nnx.Module):\n      def __init__(self, din, dout, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False)\n        self.batch_norm = nnx.BatchNorm(\n          10, use_running_average=False, rngs=rngs\n        )\n\n    block = Block(2, 5, rngs=nnx.Rngs(0))\n    new_block = nnx.with_attributes(\n      block, only=nnx.Dropout, deterministic=True, graph=graph\n    )\n    assert new_block.dropout.deterministic == True\n    assert new_block.batch_norm.use_running_average == False\n\n  @parameterized.parameters(True, False)\n  def test_with_attributes_error(self, graph):\n    class Block(nnx.Module):\n      def __init__(self, din, dout, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False)\n        self.batch_norm = nnx.BatchNorm(\n          10, use_running_average=False, rngs=rngs\n        )\n\n    block = Block(2, 5, rngs=nnx.Rngs(0))\n    with self.assertRaisesRegex(\n        ValueError,\n        (\n            'Could not find at least one instance of the following attributes:'\n            \" \\\\['unknown'\\\\]\"\n        ),\n    ):\n      nnx.with_attributes(\n        block, deterministic=True, use_running_average=True,\n        unknown=True, graph=graph,\n      )\n\n    new_block = nnx.with_attributes(\n      block, deterministic=True, use_running_average=True,\n      unknown=True, raise_if_not_found=False, graph=graph,\n    )\n    assert new_block.dropout.deterministic == True\n    assert new_block.batch_norm.use_running_average == True\n\n  @parameterized.parameters(True, False)\n  def test_view_error(self, graph):\n    class Block(nnx.Module):\n      def __init__(self, din, dout, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False, rngs=rngs)\n        self.batch_norm = nnx.BatchNorm(\n          10, use_running_average=False, rngs=rngs\n        )\n    block = Block(2, 5, rngs=nnx.Rngs(0))\n\n    with self.assertRaisesRegex(\n        ValueError,\n        (\n            \"Unused keys found in nnx.view: \\\\['unknown'\\\\]\"\n        ),\n    ):\n      nnx.view(block, deterministic=True, use_running_average=True, unknown=True, graph=graph)\n\n  def test_cloud_pickle(self):\n    import platform\n    if platform.python_version().startswith('3.11'):\n      self.skipTest(\"Cloudpickle cannot pickle PRNGKeyArray on python 3.11\")\n    class Model(nnx.Module):\n      def __init__(self, din, dmid, dout, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dmid, rngs=rngs)\n        self.bn = nnx.BatchNorm(dmid, rngs=rngs)\n        self.dropout = nnx.Dropout(0.1, rngs=rngs)\n        self.linear_out = nnx.Linear(dmid, dout, rngs=rngs)\n\n      def __call__(self, x):\n        x = nnx.relu(self.dropout(self.bn(self.linear(x))))\n        return self.linear_out(x)\n\n    model = Model(2, 64, 3, rngs=nnx.Rngs(0))  # eager initialization\n    model.eval()\n\n    y1 = model(jnp.ones((5, 2)))\n    with tempfile.TemporaryDirectory() as tmpdir:\n      path = f'{tmpdir}/model.pkl'\n      with open(path, 'wb') as f:\n        cloudpickle.dump(model, f)\n        del model\n      with open(path, 'rb') as f:\n        model = pickle.load(f)\n\n    self.assertIsInstance(model, Model)\n    y2 = model(jnp.ones((5, 2)))\n    np.testing.assert_allclose(y1, y2)\n\n  def test_repr(self):\n    class Block(nnx.Module):\n      def __init__(self, din, dout, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.bn = nnx.BatchNorm(dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.2, rngs=rngs)\n\n      def __call__(self, x):\n        return nnx.relu(self.dropout(self.bn(self.linear(x))))\n\n    class Foo(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.block1 = Block(32, 128, rngs=rngs)\n        self.block2 = Block(128, 10, rngs=rngs)\n\n      def __call__(self, x):\n        return self.block2(self.block1(x))\n\n    obj = Foo(nnx.Rngs(0))\n\n    leaves = nnx.to_flat_state(nnx.state(obj)).leaves\n\n    expected_total = sum(int(np.prod(x.shape)) for x in leaves)\n    expected_total_params = sum(\n      int(np.prod(x.shape)) for x in leaves if isinstance(x, nnx.Param)\n    )\n    expected_total_batch_stats = sum(\n      int(np.prod(x.shape)) for x in leaves if isinstance(x, nnx.BatchStat)\n    )\n    expected_total_rng_states = sum(\n      int(np.prod(x.shape)) for x in leaves if isinstance(x, nnx.RngState)\n    )\n\n    foo_repr = repr(obj).replace(',', '').splitlines()\n\n    self.assertIn(str(expected_total), foo_repr[0])\n    self.assertIn(str(expected_total_params), foo_repr[0])\n    self.assertIn(str(expected_total_batch_stats), foo_repr[0])\n    self.assertIn(str(expected_total_rng_states), foo_repr[0])\n\n  @parameterized.parameters(True, False)\n  def test_view_info(self, graph):\n    class Block(nnx.Module):\n      def __init__(self, din, dout, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.bn = nnx.BatchNorm(dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.2, rngs=rngs)\n\n      def __call__(self, x):\n        return nnx.relu(self.dropout(self.bn(self.linear(x))))\n\n    class Foo(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.block1 = Block(32, 128, rngs=rngs)\n        self.block2 = Block(128, 10, rngs=rngs)\n\n      def __call__(self, x):\n        return self.block2(self.block1(x))\n\n    obj = Foo(rngs=nnx.Rngs(0))\n    info_str = nnx.view_info(obj, graph=graph)\n    self.assertEqual(info_str.count(\"BatchNorm:\"), 1)\n    self.assertEqual(info_str.count(\"Dropout:\"), 1)\n\n  @parameterized.parameters(True, False)\n  def test_view_info_with_filter(self, graph):\n    class Block(nnx.Module):\n      def __init__(self, din, dout, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.bn = nnx.BatchNorm(dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.2, rngs=rngs)\n\n      def __call__(self, x):\n        return nnx.relu(self.dropout(self.bn(self.linear(x))))\n\n    obj = Block(4, 8, rngs=nnx.Rngs(0))\n    info_str = nnx.view_info(obj, only=nnx.Dropout, graph=graph)\n    self.assertIn(\"Dropout:\", info_str)\n    self.assertNotIn(\"BatchNorm:\", info_str)\n\n    info_str = nnx.view_info(obj, only=nnx.MultiHeadAttention, graph=graph)\n    self.assertEmpty(info_str)\n\n  @parameterized.parameters(True, False)\n  def test_view_info_with_custom_set_mode(self, graph):\n    class Block(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        pass\n\n      def __call__(self, x):\n        return x\n\n      def set_view(self, arg1: bool | None = None, arg2: int | None = None, **kwargs) -> dict:\n        \"\"\"Example set_view docstring. This follows Google style docstrings.\n\n        Args:\n          arg1: The first argument.\n          arg2: The second argument.\n            This has two lines.\n        \"\"\"\n        return kwargs\n\n    obj = Block(rngs=nnx.Rngs(0))\n    info_str = nnx.view_info(obj, graph=graph)\n    self.assertEqual(f\"{obj.__class__.__qualname__}:\\n  arg1: bool | None = None\\n    The first argument.\\n  arg2: int | None = None\\n    The second argument.\\n    This has two lines.\", info_str)\n\nclass TestModuleDataclass(absltest.TestCase):\n  def test_basic(self):\n\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      a: int\n      b: nnx.Variable[int]\n      c: nnx.Param[int]\n      d: nnx.Variable[int]\n      e: nnx.Variable[int]\n      f: int\n\n    m = Foo(\n      a=1,  # graphdef\n      b=nnx.Variable(2),  # node\n      c=nnx.Param(3),  # param\n      d=nnx.Variable(4),  # var\n      e=nnx.BatchStat(5),  # var\n      f=6,  # graphdef int\n    )\n\n    graphdef, state = nnx.split(m)\n\n    assert len(state) == 4\n    assert state['b'].get_value() == 2\n    assert isinstance(state['b'], nnx.Variable)\n    assert state['c'].get_value() == 3\n    assert isinstance(state['c'], nnx.Param)\n    assert state['d'].get_value() == 4\n    assert isinstance(state['d'], nnx.Variable)\n    assert state['e'].get_value() == 5\n    assert isinstance(state['e'], nnx.BatchStat)\n\n  def test_field_specifiers(self):\n    @nnx.dataclass\n    class Foo(nnx.Pytree):\n      a: int = nnx.static()\n      b: jax.Array = nnx.data()\n\n    m = Foo(a=1, b=jnp.array(2))\n\n    leaves = jax.tree.leaves(m)\n    assert len(leaves) == 1\n    assert leaves[0] == jnp.array(2)\n\n  def test_field_specifiers_forced(self):\n    @nnx.dataclass\n    class Bar(nnx.Pytree):\n      a: int = nnx.data()\n\n    m = Bar(a=1)\n\n    leaves = jax.tree.leaves(m)\n    assert len(leaves) == 1\n    assert leaves[0] == 1\n\n  def test_field_specifiers_with_defaults(self):\n    @nnx.dataclass\n    class Bar(nnx.Pytree):\n      a: int = nnx.data(default=3)\n\n    m = Bar()\n\n    leaves = jax.tree.leaves(m)\n    assert len(leaves) == 1\n    assert leaves[0] == 3\n\n  def test_field_specifiers_array_in_static(self):\n    @nnx.dataclass\n    class Bar(nnx.Pytree):\n      a: jax.Array = nnx.static()\n\n    with self.assertRaisesRegex(\n      ValueError,\n      'Found unexpected data on value of type',\n    ):\n      m = Bar(a=jnp.array(3))\n\n  def test_variable_in_static_list(self):\n    @nnx.dataclass\n    class Foo(nnx.Module):\n      filters: list\n\n    with self.assertRaisesRegex(\n      ValueError,\n      'Found data on value of type',\n    ):\n      Foo([nnx.Variable(1)])\n\n  def test_module_in_static_list(self):\n    class Bar(nnx.Module):\n      pass\n\n    @nnx.dataclass\n    class Foo(nnx.Module):\n      filters: list\n\n    with self.assertRaisesRegex(\n      ValueError,\n      'Found data on value of type',\n    ):\n      Foo([Bar()])\n\n  def test_post_init(self):\n\n    @dataclasses.dataclass\n    class DFoo(nnx.Module):\n      din: int\n      dout: int\n      rngs: nnx.Rngs\n\n      def __post_init__(self):\n        self.bar = nnx.Linear(self.din, self.dout, rngs=self.rngs)\n\n      def __call__(self, x):\n        return self.bar(x)\n\n    m = DFoo(1, 1, rngs=nnx.Rngs(0))\n\n    assert hasattr(m, 'bar')\n\n\nclass TestModuleDef(parameterized.TestCase):\n  def test_apply(self):\n    class Foo(nnx.Module):\n      def __init__(self, c: float, *, rngs: nnx.Rngs):\n        self.w = nnx.Param(jax.random.uniform(rngs.params(), ()))\n        self.c = c\n\n      def __call__(self, x, *, rngs: nnx.Rngs):\n        return self.w * x + rngs.e.normal(()) + self.c\n\n    rngs = nnx.Rngs(0)\n    foo = Foo(c=1.0, rngs=rngs)\n\n    graphdef, states = nnx.split(foo)\n\n    assert isinstance(states, nnx.State)\n    assert isinstance(states['w'], nnx.Param)\n\n    y, _updates = graphdef.apply(states)(x=2.0, rngs=nnx.Rngs(e=1))\n\n    assert isinstance(y, jax.Array)\n\n  def test_derefed_mod_apply(self):\n    class Foo(nnx.Module):\n      def __init__(self, c: float, *, rngs: nnx.Rngs):\n        self.w = nnx.Param(\n          jax.random.uniform(rngs.params(), ()),\n        )\n        self.c = nnx.Variable(c)\n\n      def __call__(self, x, *, rngs: nnx.Rngs):\n        return self.w * x + rngs.e.normal(()) + self.c\n\n    foo = Foo(c=1.0, rngs=nnx.Rngs(0))\n\n    graphdef, state = nnx.split(foo)\n\n    assert isinstance(graphdef.nodes[0], nnx.graphlib.NodeDef | nnx.graphlib.NodeRef)\n    assert isinstance(state, nnx.State)\n    assert isinstance(state['w'], nnx.Param)\n    assert isinstance(state['c'], nnx.Variable)\n\n    y, (graphdef, state) = graphdef.apply(state)(x=2.0, rngs=nnx.Rngs(e=1))\n\n    assert isinstance(y, jax.Array)\n\n  def test_modules_iterator(self):\n    class Foo(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.submodules = nnx.data([\n          {'a': nnx.Linear(1, 1, rngs=rngs)},\n          {'b': nnx.Conv(1, 1, 1, rngs=rngs)},\n        ])\n        self.linear = nnx.Linear(1, 1, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, rngs=rngs)\n\n    module = Foo(rngs=nnx.Rngs(0))\n\n    modules = list(nnx.iter_modules(module))\n\n    assert len(modules) == 5\n    assert modules[0][0] == ('dropout',)\n    assert isinstance(modules[0][1], nnx.Dropout)\n    assert modules[1][0] == ('linear',)\n    assert isinstance(modules[1][1], nnx.Linear)\n    assert modules[2][0] == ('submodules', 0, 'a')\n    assert isinstance(modules[2][1], nnx.Linear)\n    assert modules[3][0] == ('submodules', 1, 'b')\n    assert isinstance(modules[3][1], nnx.Conv)\n    assert modules[4][0] == ()\n    assert isinstance(modules[4][1], Foo)\n\n  @parameterized.parameters(True, False)\n  def test_children_modules_iterator(self, graph):\n    class Foo(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.submodules = nnx.data([\n          {'a': nnx.Linear(1, 1, rngs=rngs)},\n          {'b': nnx.Conv(1, 1, 1, rngs=rngs)},\n        ])\n        self.linear = nnx.Linear(1, 1, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, rngs=rngs)\n\n    module = Foo(rngs=nnx.Rngs(0))\n\n    modules = list(nnx.iter_children(module, graph=graph))\n\n    assert len(modules) == 2\n    assert modules[0][0] == 'dropout'\n    assert isinstance(modules[0][1], nnx.Dropout)\n    assert modules[1][0] == 'linear'\n    assert isinstance(modules[1][1], nnx.Linear)\n\n  def test_state_in_module(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = nnx.data(nnx.State({'b': nnx.Param(jnp.array(1.0))}))\n\n    foo = Foo()\n\n    graphdef, state = nnx.split(foo)\n\n    assert isinstance(state, nnx.State)\n    assert isinstance(state['a'], nnx.State)\n\n    foo2 = nnx.merge(graphdef, state)\n\n    assert isinstance(foo2.a, nnx.State)\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/mutable_array_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport dataclasses\nfrom absl.testing import absltest, parameterized\nimport optax\nfrom flax import nnx\nimport flax.errors\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\n\n\nclass TestPytree(absltest.TestCase):\n  def test_pytree(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.node = jnp.array(1)\n        self.meta = 1\n\n    m = nnx.vars_as(Foo(), ref=True)\n\n    m = jax.tree.map(lambda x: x + 1, m)\n\n    assert m.node == 2\n    assert m.meta == 1\n\n  def test_pytree_data_typehint(self):\n    class Foo(nnx.Module):\n      node: jax.Array = nnx.data()\n\n      def __init__(self):\n        self.node = jnp.array(1)\n        self.meta = 1\n\n    m = Foo()\n\n    m = jax.tree.map(lambda x: x + 1, m)\n\n    assert m.node == 2\n    assert m.meta == 1\n\n  def test_pytree_data_instance(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.node = nnx.data(jnp.array(1))\n        self.meta = 1\n\n    m = Foo()\n\n    m = jax.tree.map(lambda x: x + 1, m)\n\n    assert m.node == 2\n    assert m.meta == 1\n\n  def test_pytree_dataclass(self):\n    @nnx.dataclass\n    class Foo(nnx.Module):\n      node: jax.Array = nnx.data()\n      meta: int\n      meta2: int = 3\n      meta3: int = 4\n      meta4: int = 5\n      node2: int = nnx.data(default=6)\n\n    m = Foo(node=jnp.array(1), meta=1)\n\n    m: Foo = jax.tree.map(lambda x: x + 1, m)\n\n    assert m.node == 2\n\n    assert m.meta == 1\n    assert m.meta2 == 3\n    assert m.meta3 == 4\n    assert m.meta4 == 5\n    assert m.node2 == 7\n\n  def test_data_example(self):\n    class Foo(nnx.Pytree):\n      def __init__(self):\n        self.data_attr = nnx.data(42)  # pytree data\n        self.static_attr = 'hello'  # static attribute\n\n    foo = Foo()\n\n    self.assertEqual(jax.tree.leaves(foo), [42])\n\n  def test_register_data_type(self):\n    @dataclasses.dataclass(frozen=True)\n    class MyType:\n      value: int\n\n    nnx.register_data_type(MyType)\n\n    class Foo(nnx.Pytree):\n      def __init__(self, a):\n        self.a = MyType(a)  # Automatically registered as data\n        self.b = 'hello'  # str not registered as data\n\n    foo = Foo(42)\n\n    self.assertTrue(nnx.is_data(foo.a))\n    self.assertEqual(jax.tree.leaves(foo), [MyType(value=42)])\n\nclass TestVariableRefMode(absltest.TestCase):\n  def test_split_mutable_array(self):\n    m = jax.new_ref(1)\n    graphdef, state = nnx.split(m)\n\n    self.assertIs(m, state)\n\n    m2 = nnx.merge(graphdef, state)\n\n    self.assertIs(m2, m)\n\n  def test_to_arrays_example(self):\n    node = [nnx.Variable(1.0), nnx.Variable(2.0, mode='ref')]\n    mutable_node = nnx.vars_as(node, ref=True)\n    assert isinstance(mutable_node[0].get_raw_value(), jax.Ref)\n    assert isinstance(mutable_node[1].get_raw_value(), jax.Ref)\n\n    shared_array = nnx.Variable(1.0, mode='pytree')\n    node = [shared_array, shared_array]\n    with self.assertRaisesRegex(ValueError, 'Found duplicate at path'):\n      nnx.vars_as(node, ref=True)\n\n    node = [nnx.Variable(1.0), nnx.Variable(2.0)]\n    mutable_node = nnx.vars_as(\n      node, ref=True, only=lambda path, x: path[0] == 0\n    )\n    assert isinstance(mutable_node[0].get_raw_value(), jax.Ref)\n    assert isinstance(mutable_node[1].get_raw_value(), float)\n\n  def test_freeze_and_mutable_with_filter(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = nnx.Param(1)\n        self.b = nnx.BatchStat(2)\n\n    m = nnx.vars_as(Foo(), hijax=True, ref=True)\n    self.assertEqual(m.a.ref, True)\n    self.assertEqual(m.b.ref, True)\n\n    m2 = nnx.vars_as(m, hijax=False, only=nnx.BatchStat)\n    self.assertEqual(m2.a.ref, True)\n    self.assertEqual(m2.a.hijax, True)\n    self.assertEqual(m2.b.ref, True)\n    self.assertEqual(m2.b.hijax, False)\n    self.assertIsNot(m, m2)\n\n    m3 = nnx.vars_as(m2, hijax=True, only=nnx.BatchStat)\n    self.assertEqual(m3.a.ref, True)\n    self.assertEqual(m3.b.ref, True)\n    self.assertEqual(m3.b.hijax, True)\n    self.assertIsNot(m2, m3)\n    self.assertIs(m.a, m3.a)\n\n  def test_freeze_duplicate_error(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = nnx.Param(1, mode='ref')\n        self.b = self.a\n\n    m = Foo()\n\n    with self.assertRaisesRegex(ValueError, 'Found duplicate at path'):\n      nnx.vars_as(m, ref=True)\n\n  def test_mutable_array_split(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = jax.new_ref(1)\n        self.b = self.a\n\n    m = Foo()\n\n    ref_map = nnx.graphlib.RefMap()\n    graphdef, state = nnx.graphlib.flatten(m, ref_index=ref_map, graph=True)\n    self.assertLen(state, 1)\n    self.assertLen(ref_map, 2)  # 1 Foo + 1 ArrayRef\n\n    m1 = nnx.merge(graphdef, state)\n    self.assertIs(m1.a, m1.b)\n    self.assertIsInstance(m1.a, jax.Ref)\n\n  def test_mutable_array_split_merge_in_variable(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = nnx.Param(1, ref=True)\n        self.b = self.a\n\n    m = Foo()\n\n    ref_map = nnx.graphlib.RefMap()\n    graphdef, state = nnx.graphlib.flatten(m, ref_index=ref_map, graph=True)\n    self.assertLen(state, 1)\n    self.assertLen(ref_map, 3)  # 1 Foo + 1 Param + 1 Ref\n\n    m1 = nnx.merge(graphdef, state)\n    self.assertIs(m1.a, m1.b)\n    self.assertIsInstance(m1.a, nnx.Param)\n\n  def test_mutable_array_split_merge_in_variable_shared_array(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        m_array = 1\n        self.a = nnx.Param(m_array, ref=True)\n        self.b = nnx.Param(m_array, ref=True)\n\n    m = Foo()\n\n    ref_map = nnx.graphlib.RefMap()\n    graphdef, state = nnx.graphlib.flatten(m, ref_index=ref_map, graph=True)\n    self.assertLen(state, 2)\n    self.assertLen(ref_map, 5)  # 1 Foo + 2 Param + 2 Ref\n\n    m1 = nnx.merge(graphdef, state)\n    # Each variable will own its own array and ref.\n    self.assertIsInstance(m1.a, nnx.Param)\n\n  def test_mutable_example(self):\n    tree = [nnx.Variable(1.0), nnx.Variable(2.0, ref=True)]\n    assert tree[0].ref == False\n    assert tree[1].ref == True\n    mutable_tree = nnx.vars_as(tree, ref=True)\n    assert isinstance(mutable_tree[0].get_raw_value(), jax.Ref)\n    assert isinstance(mutable_tree[1].get_raw_value(), jax.Ref)\n\n  def test_mutable_array_split_freeze(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = jax.new_ref(1)\n        self.b = self.a\n\n    m = Foo()\n\n    ref_map = nnx.graphlib.RefMap()\n    graphdef, state = nnx.graphlib.flatten(m, ref_index=ref_map, graph=True)\n    state = nnx.vars_as(state, hijax=False)\n    self.assertLen(state, 1)\n\n    m1 = nnx.merge(graphdef, state)\n    self.assertIs(m1.a, m1.b)\n    self.assertIsInstance(m1.a, jax.Ref)\n\n  def test_update_context(self):\n    m1 = nnx.vars_as(nnx.Linear(1, 1, rngs=nnx.Rngs(0)), ref=True)\n    with nnx.update_context('example'):\n      with nnx.split_context('example') as ctx:\n        graphdef, state = ctx.split(m1)\n\n      with nnx.merge_context('example', True) as ctx:\n        m2 = ctx.merge(graphdef, state)\n\n      m_out1 = nnx.vars_as(nnx.Linear(1, 1, rngs=nnx.Rngs(0)), ref=True)\n\n      with nnx.split_context('example') as ctx:\n        graphdef_out, state_out = ctx.split((m2, m_out1, m2))\n\n      self.assertIsInstance(\n        state_out[0]['kernel'].get_value(), nnx.graphlib.NoUpdate\n      )\n      self.assertIsInstance(\n        state_out[0]['bias'].get_value(), nnx.graphlib.NoUpdate\n      )\n      self.assertIsInstance(\n        state_out[1]['kernel'].get_value(), nnx.graphlib.ArrayRefOutput\n      )\n      self.assertIsInstance(\n        state_out[1]['bias'].get_value(), nnx.graphlib.ArrayRefOutput\n      )\n      # 2 ArrayRefOutput + 2 NoUpdate, however, NoUpdate are empty nodes\n      self.assertLen(jax.tree.leaves(state_out), 2)\n\n      with nnx.merge_context('example', False) as ctx:\n        m3, m_out2, _ = ctx.merge(graphdef_out, state_out)\n\n      self.assertIs(m3, m1)\n      self.assertIsNot(m_out2, m_out1)\n\n  def test_update_context_flatten(self):\n    m1 = nnx.vars_as(nnx.Linear(1, 1, rngs=nnx.Rngs(0)), ref=True)\n    with nnx.update_context('example'):\n      with nnx.split_context('example') as ctx:\n        graphdef, state = ctx.flatten(m1)\n\n      with nnx.merge_context('example', True) as ctx:\n        m2 = ctx.merge(graphdef, state)\n\n      m_out1 = nnx.vars_as(nnx.Linear(1, 1, rngs=nnx.Rngs(0)), ref=True)\n\n      with nnx.split_context('example') as ctx:\n        graphdef_out, state_out = ctx.flatten((m2, m_out1, m2))\n\n      state_out_dict = dict(state_out)\n\n      self.assertIsInstance(\n        state_out_dict[(0, 'kernel')].get_value(), nnx.graphlib.NoUpdate\n      )\n      self.assertIsInstance(\n        state_out_dict[(0, 'bias')].get_value(), nnx.graphlib.NoUpdate\n      )\n      self.assertIsInstance(\n        state_out_dict[(1, 'kernel')].get_value(), nnx.graphlib.ArrayRefOutput\n      )\n      self.assertIsInstance(\n        state_out_dict[(1, 'bias')].get_value(), nnx.graphlib.ArrayRefOutput\n      )\n      # 2 ArrayRefOutput + 2 NoUpdate, however, NoUpdate are empty nodes\n      self.assertLen(jax.tree.leaves(state_out), 2)\n\n      with nnx.merge_context('example', False) as ctx:\n        m3, m_out2, _ = ctx.merge(graphdef_out, state_out)\n\n      self.assertIs(m3, m1)\n      self.assertIsNot(m_out2, m_out1)\n\n  def test_update_context_to_tree1(self):\n    m1 = nnx.vars_as(nnx.Linear(1, 1, rngs=nnx.Rngs(0)), ref=True)\n    with nnx.update_context('example'):\n      m1_tree = nnx.to_tree((m1,), ctxtag='example')\n\n      (m2,) = nnx.from_tree(m1_tree, ctxtag='example', is_inner=True)\n\n      m_out1 = nnx.vars_as(nnx.Linear(1, 1, rngs=nnx.Rngs(0)), ref=True)\n\n      # with nnx.split_context('example') as ctx:\n      #   graphdef_out, state_out = ctx.split((m2, m_out1))\n      out_tree = nnx.to_tree(((m2,), m_out1, m2), ctxtag='example')\n\n      self.assertIsInstance(\n        out_tree[0][0].states[0]['kernel'].get_value(), nnx.graphlib.NoUpdate\n      )\n      self.assertIsInstance(\n        out_tree[0][0].states[0]['bias'].get_value(), nnx.graphlib.NoUpdate\n      )\n      self.assertIsInstance(\n        out_tree[1].states[0]['kernel'].get_value(), nnx.graphlib.ArrayRefOutput\n      )\n      self.assertIsInstance(\n        out_tree[1].states[0]['bias'].get_value(), nnx.graphlib.ArrayRefOutput\n      )\n      self.assertEmpty(out_tree[2].states[0])  # Repeated m2 State\n\n      # 2 ArrayRefOutput + 2 NoUpdate, however, NoUpdate are empty nodes\n      self.assertLen(jax.tree.leaves(out_tree), 2)\n\n      # with nnx.merge_context('example', False) as ctx:\n      #   m3, m_out2 = ctx.merge(graphdef_out, state_out)\n      (m3,), m_out2, _ = nnx.from_tree(\n        out_tree, ctxtag='example', is_inner=False\n      )\n\n      self.assertIs(m3, m1)\n      self.assertIsNot(m_out2, m_out1)\n\n  def test_update_context_to_tree2(self):\n    m1 = nnx.vars_as(nnx.Linear(1, 1, rngs=nnx.Rngs(0)), ref=True)\n    with nnx.update_context('example') as ctx:\n      m1_tree = nnx.to_tree((m1,), ctxtag='example')\n\n      (m2,) = nnx.from_tree(m1_tree, ctxtag='example', is_inner=True)\n\n      m_out1 = nnx.vars_as(nnx.Linear(1, 1, rngs=nnx.Rngs(0)), ref=True)\n\n      # with nnx.split_context('example') as ctx:\n      #   graphdef_out, state_out = ctx.split((m2, m_out1))\n      out_tree = nnx.to_tree(((m2,), m_out1, m2), ctxtag='example')\n\n      self.assertIsInstance(\n        out_tree[0][0].states[0]['kernel'].get_value(), nnx.graphlib.NoUpdate\n      )\n      self.assertIsInstance(\n        out_tree[0][0].states[0]['bias'].get_value(), nnx.graphlib.NoUpdate\n      )\n      self.assertIsInstance(\n        out_tree[1].states[0]['kernel'].get_value(), nnx.graphlib.ArrayRefOutput\n      )\n      self.assertIsInstance(\n        out_tree[1].states[0]['bias'].get_value(), nnx.graphlib.ArrayRefOutput\n      )\n      self.assertEmpty(out_tree[2].states[0])  # Repeated m2 State\n\n      # 2 ArrayRefOutput + 2 NoUpdate, however, NoUpdate are empty nodes\n      self.assertLen(jax.tree.leaves(out_tree), 2)\n\n      # with nnx.merge_context('example', False) as ctx:\n      #   m3, m_out2 = ctx.merge(graphdef_out, state_out)\n      (m3,), m_out2, _ = nnx.from_tree(\n        out_tree, ctxtag='example', is_inner=False\n      )\n\n      self.assertIs(m3, m1)\n      self.assertIsNot(m_out2, m_out1)\n\n  def test_update_context_to_tree_trivial_prefix(self):\n    m1 = nnx.vars_as(nnx.Linear(1, 1, rngs=nnx.Rngs(0)), ref=True)\n    with nnx.update_context('example'):\n      m1_tree = nnx.to_tree((m1,), ctxtag='example', prefix=0)\n\n      (m2,) = nnx.from_tree(m1_tree, ctxtag='example', is_inner=True, prefix=0)\n\n      m_out1 = nnx.vars_as(nnx.Linear(1, 1, rngs=nnx.Rngs(0)), ref=True)\n\n      # with nnx.split_context('example') as ctx:\n      #   graphdef_out, state_out = ctx.split((m2, m_out1))\n      out_tree = nnx.to_tree(((m2,), m_out1, m2), ctxtag='example', prefix=0)\n\n      self.assertIsInstance(\n        out_tree[0][0].states[0]['kernel'].get_value(), nnx.graphlib.NoUpdate\n      )\n      self.assertIsInstance(\n        out_tree[0][0].states[0]['bias'].get_value(), nnx.graphlib.NoUpdate\n      )\n      self.assertIsInstance(\n        out_tree[1].states[0]['kernel'].get_value(), nnx.graphlib.ArrayRefOutput\n      )\n      self.assertIsInstance(\n        out_tree[1].states[0]['bias'].get_value(), nnx.graphlib.ArrayRefOutput\n      )\n      self.assertEmpty(out_tree[2].states[0])  # Repeated m2 State\n\n      # 2 ArrayRefOutput + 2 NoUpdate, however, NoUpdate are empty nodes\n      self.assertLen(jax.tree.leaves(out_tree), 2)\n\n      # with nnx.merge_context('example', False) as ctx:\n      #   m3, m_out2 = ctx.merge(graphdef_out, state_out)\n      (m3,), m_out2, _ = nnx.from_tree(\n        out_tree, ctxtag='example', is_inner=False, prefix=0\n      )\n\n      self.assertIs(m3, m1)\n      self.assertIsNot(m_out2, m_out1)\n\n  def test_simple_jit(self):\n    m1 = nnx.vars_as(nnx.Linear(1, 1, rngs=nnx.Rngs(0)), ref=True)\n    m_out1 = None\n\n    @nnx.jit\n    def f(m2):\n      nonlocal m_out1\n      m_out1 = nnx.vars_as(nnx.Linear(1, 1, rngs=nnx.Rngs(0)), ref=True)\n      return m_out1\n\n    m_out2 = f(m1)\n\n    self.assertIsNot(m_out1, m_out2)\n    self.assertIsInstance(m_out2.kernel, nnx.Param)\n    self.assertIsInstance(m_out2.kernel[...], jax.Array)\n\n  def test_jit_mutable(self):\n    @nnx.dataclass\n    class Foo(nnx.Pytree):\n      a: jax.Ref = nnx.data()\n\n    m1 = Foo(a=jax.new_ref(1))\n\n    @nnx.jit\n    def f(m2: Foo):\n      m2.a[...] += 1\n      return m2\n\n    m_out1 = f(m1)\n    self.assertEqual(m_out1.a[...], 2)\n    self.assertIs(m_out1, m1)\n    self.assertIsInstance(m_out1.a, jax.Ref)\n\n  def test_static(self):\n    class C(nnx.Module):\n      def __init__(self, meta):\n        self.meta = meta\n\n    n = 0\n\n    @jax.jit\n    def f(x):\n      nonlocal n\n      n += 1\n\n    f(C(1))\n    assert n == 1\n    f(C(1))\n    assert n == 1\n    f(C(2))\n    assert n == 2\n    f(C(2))\n    assert n == 2\n\n  def test_variable_creation(self):\n    v = nnx.Variable(jnp.array(1), ref=True)\n    self.assertEqual(v[...], 1)\n    self.assertTrue(v.ref)\n    self.assertIsInstance(v.get_raw_value(), jax.Ref)\n\n  def test_variable_metadata(self):\n    v = nnx.Variable(jnp.array(1), a=2, b=3)\n    self.assertEqual(v.a, 2)\n    self.assertEqual(v.b, 3)\n\n  def test_object(self):\n    class Params(nnx.Pytree):\n      def __init__(self, din: int, dout: int):\n        self.w = nnx.Param(jnp.zeros((din, dout), jnp.float32))\n        self.b = nnx.Param(jnp.zeros((dout,), jnp.float32))\n        self.count = nnx.Variable(jnp.array(0))\n\n    params = Params(3, 4)\n    params = nnx.vars_as(params, ref=True)\n\n    paths_leaves, treedef = jax.tree.flatten_with_path(params)\n    paths, leaves = zip(*paths_leaves)\n\n    self.assertLen(paths_leaves, 3)\n    self.assertEqual(leaves[0].shape, (4,))  # b\n    self.assertEqual(leaves[1].shape, ())  # count\n    self.assertEqual(leaves[2].shape, (3, 4))  # w\n    self.assertEqual(\n      paths[0],\n      (jax.tree_util.GetAttrKey('b'), jax.tree_util.GetAttrKey('value')),\n    )\n    self.assertEqual(\n      paths[1],\n      (jax.tree_util.GetAttrKey('count'), jax.tree_util.GetAttrKey('value')),\n    )\n    self.assertEqual(\n      paths[2],\n      (jax.tree_util.GetAttrKey('w'), jax.tree_util.GetAttrKey('value')),\n    )\n\n    params = jax.tree.unflatten(treedef, leaves)\n\n    self.assertEqual(params.w.shape, (3, 4))\n    self.assertEqual(params.b.shape, (4,))\n    self.assertEqual(params.count.shape, ())\n    self.assertIsInstance(params.w, nnx.Variable)\n    self.assertIsInstance(params.w[...], jax.Array)\n    self.assertIsInstance(params.b, nnx.Variable)\n    self.assertIsInstance(params.b[...], jax.Array)\n    self.assertIsInstance(params.count, nnx.Variable)\n    self.assertIsInstance(params.count[...], jax.Array)\n\n    @jax.jit\n    def linear(params: Params, x: jax.Array):\n      params.count[...] += 1\n      return x @ params.w[...] + params.b[...][None]\n\n    x = jnp.ones((1, 3))\n    y = linear(params, x)\n    self.assertEqual(y.shape, (1, 4))\n    self.assertEqual(params.count[...], 1)\n    y = linear(params, x)\n    self.assertEqual(params.count[...], 2)\n\n  def test_object_state(self):\n    class Params(nnx.Pytree):\n      def __init__(self, din: int, dout: int):\n        self.w = jnp.zeros((din, dout), jnp.float32)\n        self.b = jnp.zeros((dout,), jnp.float32)\n        self.count = nnx.data(0)\n\n    params = Params(3, 4)\n\n    with self.assertRaises(flax.errors.TraceContextError):\n\n      @jax.jit\n      def f():\n        params.count = 1\n\n      f()\n\n    @jax.jit\n    def f(params: Params):\n      params.count = 1\n      return params\n\n    params = f(params)\n    self.assertEqual(params.count, 1)\n\n  def test_rngs_create(self):\n    rngs = nnx.Rngs(0)\n\n    paths_leaves = jax.tree.leaves_with_path(rngs)\n    paths, leaves = zip(*paths_leaves)\n    self.assertLen(paths_leaves, 2)\n    self.assertEqual(leaves[0].shape, ())  # key\n    self.assertEqual(leaves[1].shape, ())  # count\n    self.assertEqual(\n      paths[0],\n      (\n        jax.tree_util.GetAttrKey('default'),\n        jax.tree_util.GetAttrKey('count'),\n        jax.tree_util.GetAttrKey('value'),\n      ),\n    )\n    self.assertEqual(\n      paths[1],\n      (\n        jax.tree_util.GetAttrKey('default'),\n        jax.tree_util.GetAttrKey('key'),\n        jax.tree_util.GetAttrKey('value'),\n      ),\n    )\n\n  def test_rngs_call(self):\n    rngs = nnx.Rngs(0)\n    key = rngs()\n    self.assertIsInstance(key, jax.Array)\n\n\nclass TestOptimizer(absltest.TestCase):\n  def test_optimize_arrays(self):\n    class Model(nnx.Module):\n      def __init__(self, rngs):\n        self.w = jax.random.uniform(rngs(), (2, 4))\n        self.count = jnp.array(0)\n\n      def __call__(self, x):\n        self.count += 1\n        return x @ self.w\n\n    x = jax.random.normal(jax.random.key(0), (5, 2))\n    y = jnp.ones((5, 4))\n\n    wrt = lambda path, x: path[-1] == 'w'\n    model = Model(nnx.Rngs(1))\n    optimizer = nnx.Optimizer(model, tx=optax.adam(1e-3), wrt=wrt)\n\n    @jax.jit\n    def train_step(model, optimizer, x, y):\n      graphdef, params, nondiff = nnx.split(model, wrt, ...)\n\n      def loss_fn(params):\n        model = nnx.merge(graphdef, params, nondiff)\n        return jnp.mean((model(x) - y) ** 2), nnx.state(model, nnx.Not(wrt))\n\n      (loss, updates), grads = jax.value_and_grad(loss_fn, has_aux=True)(params)\n      nnx.update(model, updates)\n      optimizer.update(model, grads)\n\n      return loss, model, optimizer\n\n    loss, model, optimizer = train_step(model, optimizer, x, y)\n\n    self.assertNotEqual(loss, 0.0)\n    self.assertEqual(model.count[...], 1)\n    self.assertEqual(optimizer.step[...], 1)\n\n  @nnx.var_defaults(hijax=True)\n  def test_optimize_hijax(self):\n    class Model(nnx.Module):\n      def __init__(self, rngs):\n        self.w = nnx.Variable(jax.random.uniform(rngs(), (2, 4)))\n        self.count = nnx.Variable(jnp.array(0))\n\n      def __call__(self, x):\n        self.count[...] += 1\n        return x @ self.w\n\n    x = jax.random.normal(jax.random.key(0), (5, 2))\n    y = jnp.ones((5, 4))\n\n    wrt = lambda path, x: path[-1] == 'w'\n    model = Model(nnx.Rngs(1))\n    optimizer = nnx.Optimizer(model, tx=optax.adam(1e-3), wrt=wrt)\n\n    @jax.jit\n    def train_step(model, optimizer, x, y):\n      graphdef, params, nondiff = nnx.split(model, wrt, ...)\n\n      def loss_fn(params):\n        model = nnx.merge(graphdef, params, nondiff)\n        return jnp.mean((model(x) - y) ** 2)\n\n      loss, grads = jax.value_and_grad(loss_fn)(nnx.vars_as(params, hijax=False))\n      optimizer.update(params, grads)\n      return loss\n\n    loss = train_step(model, optimizer, x, y)\n\n    self.assertNotEqual(loss, 0.0)\n\nclass TestHijaxVariables(parameterized.TestCase):\n  def test_variable_to_hijax(self):\n    v_low = nnx.Param(jnp.array(1), a='hi')\n    v_hi = nnx.vars_as(v_low, hijax=True)\n\n    self.assertTrue(v_hi.hijax)\n    self.assertEqual(v_hi[...], 1)\n    self.assertIsInstance(v_hi, nnx.Param)\n\n    v_hi[...] = 2\n    self.assertEqual(v_hi[...], 2)\n\n    @jax.jit\n    def set(v_hi, a):\n      self.assertIsInstance(v_hi, nnx.Param)\n      v_hi[...] = a\n      self.assertEqual(v_hi.a, 'hi')\n      self.assertTrue(v_hi.hijax)\n      v_hi[...] += 5\n      return v_hi + 2\n\n    y = set(v_hi, 10)\n    self.assertEqual(v_hi[...], 15)\n    self.assertEqual(y, 17)\n\n    v_low = nnx.vars_as(v_hi, hijax=False)\n    self.assertIsInstance(v_low, nnx.Param)\n    self.assertFalse(v_low.hijax)\n    self.assertEqual(v_low[...], 15)\n\n  def test_from_metadata(self):\n    value = 1\n    metadata = {\n      'a': 'hi',\n      'hijax': False,\n      'ref': False,\n    }\n    v_low = nnx.Param.from_metadata(value, metadata)\n    self.assertIsInstance(v_low, nnx.Param)\n    self.assertFalse(v_low.hijax)\n\n    metadata['hijax'] = True\n    v_hi = nnx.Param.from_metadata(value, metadata)\n    self.assertIsInstance(v_hi, nnx.Param)\n    self.assertTrue(v_hi.hijax)\n\n  def test_variable_to_hijax_clean(self):\n    v_low = nnx.Param(jnp.array([1]), tag='hello')\n    print()\n    print(v_low)\n    assert not v_low.hijax\n    v_hi = nnx.vars_as(v_low, hijax=True)\n    v_hi[...] = jnp.array([2])\n    assert v_hi.hijax\n    print(v_hi)\n    assert v_hi[...] == 2\n\n    @jax.jit\n    def set(v_hi, a):\n      v_hi[...] = a\n      print(v_hi)\n      assert v_hi.tag == 'hello'\n\n    set(v_hi, 10)\n\n    assert v_hi[...] == 10\n\n    v_low = nnx.vars_as(v_hi, hijax=False)\n\n    assert not v_low.hijax\n    assert v_low[...] == 10\n\n\n\n  def test_pytree_value(self):\n    v = nnx.Variable({'a': jnp.array(0), 'b': jnp.array(2)}, hijax=True)\n\n    @jax.jit\n    def inc_and_double(v):\n      v['a'] += 1\n      v['b'] *= 2\n\n    inc_and_double(v)\n\n    self.assertEqual(v['a'], 1)\n    self.assertEqual(v['b'], 4)\n\n  def test_hijax_dynamic_structure(self):\n    x = jnp.ones((4, 5))\n    metrics = nnx.Variable({}, hijax=True)\n\n    @jax.jit\n    def f(x, metrics: nnx.Variable):\n      metrics['x_sum'] = jnp.sum(x)\n\n    self.assertEmpty(metrics)\n    f(x, metrics)\n    self.assertIn('x_sum', metrics)\n    self.assertEqual(metrics['x_sum'], 20)\n\n  def test_hijax_and_pytree(self):\n    class Foo(nnx.Pytree):\n      def __init__(self, din, dout, rngs: nnx.Rngs):\n        self.w = nnx.Param(rngs.uniform((din, dout)))\n        self.b = nnx.Param(jnp.zeros((dout,)))\n        self.count = nnx.Variable(0)\n\n    foo = Foo(2, 4, nnx.Rngs(1))\n    assert not foo.w.hijax\n    assert not foo.b.hijax\n\n    foo = nnx.vars_as(foo, hijax=True)\n\n    assert foo.w.hijax\n    assert foo.b.hijax\n\n    @jax.jit\n    def forward(foo, x):\n      foo.count[...] += 1\n      return x @ foo.w + foo.b[None]\n\n    x = jnp.ones((1, 2))\n    y = forward(foo, x)\n    assert y.shape == (1, 4)\n    assert foo.count[...] == 1\n\n  def test_use_hijax(self):\n    v_low = nnx.Param(1, a='hi')\n    self.assertFalse(v_low.hijax)\n\n    v_hi = nnx.Param(1, a='hi', hijax=True)\n    self.assertTrue(v_hi.hijax)\n\n    with nnx.var_defaults(hijax=True):\n      v2 = nnx.Param(1, a='hi')\n      self.assertIs(type(v2), nnx.variablelib.HijaxVariable)\n      self.assertTrue(v2.hijax)\n\n  @nnx.var_defaults(hijax=True)\n  def test_hijax_rngs(self):\n    rngs = nnx.Rngs(0)\n    self.assertIs(type(rngs.default.key), nnx.variablelib.HijaxVariable)\n    self.assertIs(type(rngs.default.count), nnx.variablelib.HijaxVariable)\n\n    @jax.jit\n    def f(rngs: nnx.Rngs):\n      return rngs()\n\n    k1 = f(rngs)\n    k2 = f(rngs)\n\n    assert k1 != k2\n\n  @absltest.skip(reason='not yet supported')\n  def test_return_hijax_from_transform(self):\n    @jax.jit\n    def create_var():\n      return nnx.Param(1, hijax=True)\n\n    v = create_var()\n    self.assertTrue(v.hijax)\n\n  @absltest.skip('not yet supported')\n  @nnx.var_defaults(hijax=True)\n  def test_lower(self):\n    v = nnx.Param(jnp.ones((2, 3)))\n\n    @jax.jit\n    def f(v):\n      v[...] += 1\n      return v[...]\n\n    e = f.lower(v)\n    y = e.out_info[2]\n    self.assertEqual(y.shape, ())\n\n  @nnx.var_defaults(hijax=True)\n  def test_eval_shape(self):\n    v = nnx.Param(jnp.array(0))\n\n    def f(v):\n      v[...] += 1\n      return v[...]\n\n    y = jax.eval_shape(f, v)\n\n    self.assertEqual(y.shape, ())\n\n  @nnx.var_defaults(hijax=True)\n  def test_no_qdd_grad(self):\n    v = nnx.Param(jnp.array(3.0), hijax=False)\n\n    self.assertFalse(v.hijax)\n\n    def f(v):\n      return v[...] ** 2\n\n    grad = jax.grad(f)(v)\n\n    self.assertIsInstance(grad, nnx.Param)\n    self.assertEqual(grad[...], 6.0)\n\n  @nnx.var_defaults(hijax=True)\n  def test_no_qdd_grad_new(self):\n    x = jnp.array(3.0)\n\n    def f(x):\n      v = nnx.Param(x, hijax=False)\n      self.assertFalse(v.hijax)\n      return v[...] ** 2\n\n    grad = jax.grad(f)(x)\n\n    self.assertIsInstance(grad, jax.Array)\n    self.assertEqual(grad, 6.0)\n\n  @parameterized.product(\n    hijax=[True, False],\n    ref=[True, False],\n  )\n  def test_variable_properties(self, hijax, ref):\n    v = nnx.Variable(jnp.array(1), hijax=hijax, ref=ref)\n    self.assertEqual(v.hijax, hijax)\n    self.assertEqual(v.ref, ref)\n    if hijax:\n      self.assertIsInstance(v, nnx.variablelib.HijaxVariable)\n    else:\n      self.assertNotIsInstance(v, nnx.variablelib.HijaxVariable)\n    if ref:\n      self.assertIsInstance(v.get_raw_value(), jax.Ref)\n    else:\n      self.assertNotIsInstance(v.get_raw_value(), jax.Ref)\n\n  @parameterized.product(\n    hijax=[True, False],\n    ref=[True, False],\n  )\n  def test_variable_copy_properties(self, hijax, ref):\n    v_original = nnx.Variable(jnp.array(1))\n\n    v = v_original.copy(hijax=hijax, ref=ref)\n    self.assertEqual(v.hijax, hijax)\n    self.assertEqual(v.ref, ref)\n    if hijax:\n      self.assertIsInstance(v, nnx.variablelib.HijaxVariable)\n    else:\n      self.assertNotIsInstance(v, nnx.variablelib.HijaxVariable)\n    if ref:\n      self.assertIsInstance(v.get_raw_value(), jax.Ref)\n    else:\n      self.assertNotIsInstance(v.get_raw_value(), jax.Ref)\n\n  @parameterized.product(\n    hijax=[True, False],\n    ref=[True, False],\n  )\n  def test_variable_vars_as_properties(self, hijax, ref):\n    v_original = nnx.Variable(jnp.array(1))\n\n    v = nnx.vars_as(v_original, hijax=hijax, ref=ref)\n    self.assertEqual(v.hijax, hijax)\n    self.assertEqual(v.ref, ref)\n    if hijax:\n      self.assertIsInstance(v, nnx.variablelib.HijaxVariable)\n    else:\n      self.assertNotIsInstance(v, nnx.variablelib.HijaxVariable)\n    if ref:\n      self.assertIsInstance(v.get_raw_value(), jax.Ref)\n    else:\n      self.assertNotIsInstance(v.get_raw_value(), jax.Ref)\n\n\nclass TestVarDefaults(absltest.TestCase):\n  def test_defaults(self):\n    defaults = nnx.var_defaults()\n    self.assertIsInstance(defaults, nnx.variablelib.VarDefaults)\n    # Default values might depend on config/env, but generally hijax=False, ref=False initially\n    self.assertFalse(defaults.hijax)\n    self.assertFalse(defaults.ref)\n\n  def test_context_manager_hijax(self):\n    with nnx.var_defaults(hijax=True):\n      self.assertTrue(nnx.var_defaults().hijax)\n      v = nnx.Variable(1)\n      self.assertTrue(v.hijax)\n\n    self.assertFalse(nnx.var_defaults().hijax)\n    v2 = nnx.Variable(1)\n    self.assertFalse(v2.hijax)\n\n  def test_context_manager_ref(self):\n    with nnx.var_defaults(ref=True):\n      self.assertTrue(nnx.var_defaults().ref)\n      v = nnx.Variable(1)\n      self.assertTrue(v.ref)\n\n    self.assertFalse(nnx.var_defaults().ref)\n    v2 = nnx.Variable(1)\n    self.assertFalse(v2.ref)\n\n  def test_context_manager_nested(self):\n    with nnx.var_defaults(hijax=True, ref=False):\n      self.assertTrue(nnx.var_defaults().hijax)\n      self.assertFalse(nnx.var_defaults().ref)\n\n      with nnx.var_defaults(ref=True):\n        self.assertTrue(nnx.var_defaults().hijax)\n        self.assertTrue(nnx.var_defaults().ref)\n\n        with nnx.var_defaults(hijax=False):\n          self.assertFalse(nnx.var_defaults().hijax)\n          self.assertTrue(nnx.var_defaults().ref)\n\n      self.assertTrue(nnx.var_defaults().hijax)\n      self.assertFalse(nnx.var_defaults().ref)\n\n  def test_mapping_protocol(self):\n    defaults = nnx.var_defaults()\n    self.assertIn('hijax', defaults)\n    self.assertIn('ref', defaults)\n    self.assertEqual(len(defaults), 2)\n    self.assertEqual(list(defaults), ['hijax', 'ref'])\n    self.assertEqual(defaults['hijax'], defaults.hijax)\n    self.assertEqual(defaults['ref'], defaults.ref)\n\n  def test_decorator(self):\n    @nnx.var_defaults(hijax=True, ref=True)\n    def f():\n      return nnx.var_defaults()\n\n    defaults = f()\n    self.assertTrue(defaults.hijax)\n    self.assertTrue(defaults.ref)\n    self.assertFalse(nnx.var_defaults().hijax)\n\n  def test_variable_init_override(self):\n    with nnx.var_defaults(hijax=True):\n      v = nnx.Variable(1, hijax=False)\n      self.assertFalse(v.hijax)\n\n    with nnx.var_defaults(ref=True):\n      v = nnx.Variable(1, ref=False)\n      self.assertFalse(v.ref)\n\nclass HijaxTransformCoverageTest(absltest.TestCase):\n  # ------------\n  # grad\n  # ------------\n  # with differentiable hijax arguments (immutable variable)\n  def test_hitypes_as_grad_args(self):\n    v = nnx.Variable((jnp.array(2.0), jnp.array(3.0)), hijax=False)\n\n    def loss_fn(v):\n      x = v[0]\n      return x ** 2\n\n    grads = jax.grad(loss_fn)(v)\n    np.testing.assert_allclose(grads[0], 4.0)\n\n  def test_hitypes_as_nondiff_grad_args(self):\n    v = nnx.Variable((jnp.array(2.0), jnp.array(3.0)), hijax=False)\n    x = jnp.array(3.0)\n\n    def loss_fn(x, v):\n      y = v[1]\n      return x ** 2 + y\n\n    grad = jax.grad(loss_fn)(x, v)\n    np.testing.assert_allclose(grad, 6.0)\n\n  def test_hitypes_as_captured_args(self):\n    v = nnx.Variable((jnp.array(2.0), jnp.array(3.0)), hijax=False)\n\n    def loss_fn(x):\n      y = v[1]\n      return x ** 2 + y\n\n    grad = jax.grad(loss_fn)(jnp.array(4.0))\n    np.testing.assert_allclose(grad, 8.0)\n\n  # with differentiable mutable hijax arguments\n  @absltest.skip(\"Not yet implemented\")\n  def test_mutable_hitypes_as_grad_args(self):\n    v = nnx.Variable(jnp.array(2.0), hijax=True)\n\n    def loss_fn(v):\n      return v[...] ** 2\n\n    grads = jax.grad(loss_fn)(v)\n    # NOTE: unclear what the tangent type will be here\n\n  # with non-differentiable mutable hijax arguments\n  def test_mutable_hitypes_as_nondiff_grad_args(self):\n    v = nnx.Variable(jnp.array(2.0), hijax=True)\n    x = jnp.array(3.0)\n\n    def loss_fn(x, v):\n      v[...] = jax.lax.stop_gradient(x * 2)\n      return x ** 2 + v[...]\n\n    grad = jax.grad(loss_fn)(x, v)\n    np.testing.assert_allclose(v[...], 6.0)\n    np.testing.assert_allclose(grad, 6.0)\n\n  # with mutable hijax captured arguments\n  def test_mutable_hitypes_as_captured_args(self):\n    v = nnx.Variable(jnp.array(2.0), hijax=True)\n\n    def loss_fn(x):\n      v[...] = jax.lax.stop_gradient(x * 3)\n      return x ** 2 + v[...]\n\n    grad = jax.grad(loss_fn)(jnp.array(4.0))\n    np.testing.assert_allclose(v[...], 12.0)\n    np.testing.assert_allclose(grad, 8.0)\n\n  #------------\n  # scan\n  #------------\n  # with hijax carry arguments (immutable variable)\n  @absltest.skip(\"scan not yet supported for hijax Variables\")\n  def test_hitypes_as_scan_carry(self):\n    v = nnx.Variable((jnp.array(1.0), jnp.array(2.0)), hijax=True, mutable=False)\n\n    def body(v, _):\n      x, y = v\n      return nnx.Variable((x + 1.0, y + 2.0), hijax=True, mutable=False), None\n\n    v_out, _ = jax.lax.scan(body, v, None, length=5)\n    x, y = v_out[...]\n    np.testing.assert_allclose(x, 6.0)\n    np.testing.assert_allclose(y, 12.0)\n\n  # with hijax extensive arguments (immutable variable)\n  @absltest.skip(\"scan not yet supported for hijax Variables\")\n  def test_hitypes_as_scan_extensive(self):\n    v = nnx.Variable((jnp.arange(5), -jnp.arange(5)), hijax=True, mutable=False)\n\n    def body(_, v_i):\n      x, y = v_i\n      v_i = nnx.Variable((x * 2, y * 2), hijax=True, mutable=False)\n      return None, v_i\n\n    _, v_out = jax.lax.scan(body, None, v)\n    x, y = v_out\n    np.testing.assert_allclose(x, jnp.arange(5) * 2)\n    np.testing.assert_allclose(y, -jnp.arange(5) * 2)\n\n  # with hijax captured arguments (immutable variable)\n  @absltest.skip(\"scan not yet supported for hijax Variables\")\n  def test_hitypes_as_scan_captured(self):\n    v = nnx.Variable((jnp.array(3.0), jnp.array(4.0)), hijax=True, mutable=False)\n    carry0 = jnp.array(1.0)\n    xs = jnp.arange(5, dtype=jnp.float32)\n\n    def body(carry, x):\n      a, b = v\n      carry = a * carry + b\n      y = a * x + b\n      return carry, nnx.Variable(y, hijax=True, mutable=False)\n\n    carry, ys_v = jax.lax.scan(body, carry0, xs)\n    ys = ys_v[...]\n    np.testing.assert_allclose(carry, 727.0)\n    np.testing.assert_allclose(ys, 3.0 * xs + 4.0)\n\n  # with mutable hijax carry arguments\n  @absltest.skip(\"has_qdd not yet supported for mutable Variable in scan carry\")\n  def test_mutable_hitypes_as_scan_carry(self):\n    v = nnx.Variable(jnp.array(1.0), hijax=True)\n\n    def body(v, _):\n      v[...] = v[...] * 2\n      return v, None\n\n    v_out, _ = jax.lax.scan(body, v, None, length=5)\n    np.testing.assert_allclose(v_out[...], 32.0)\n\n  # with mutable hijax extensive arguments\n  @absltest.skip(\"Variable doesn't have shape attribute needed for scan extensive\")\n  def test_mutable_hitypes_as_scan_extensive(self):\n    vs = [nnx.Variable(jnp.float32(i), hijax=True) for i in range(5)]\n\n    def body(_, v_i):\n      val = v_i[...]\n      v_i[...] = val * 2\n      return None, v_i\n\n    _, vs_out = jax.lax.scan(body, None, vs)\n    for i, v in enumerate(vs_out):\n      np.testing.assert_allclose(v[...], i * 2)\n\n  # with mutable hijax captured arguments\n  def test_mutable_hitypes_as_scan_captured(self):\n    v = nnx.Variable(jnp.array(3.0), hijax=True)\n\n    def body(_, __):\n      v[...] = v[...] + 1.0\n      return None, None\n\n    jax.lax.scan(body, None, None, length=5)\n    np.testing.assert_allclose(v[...], 8.0)\n\n  def test_hijax_variable_in_jit_graph_updates_false(self):\n    v = nnx.Variable(jnp.array(1.0), hijax=True)\n\n    @nnx.jit(graph=True, graph_updates=False)\n    def f(v, v2):\n      self.assertIs(v, v2)\n      v[...] += 1.0\n      return v[...] * 2\n\n    y = f(v, v)\n    np.testing.assert_allclose(v[...], 2.0)\n    np.testing.assert_allclose(y, 4.0)\n\n    y = f(v, v)\n    np.testing.assert_allclose(v[...], 3.0)\n    np.testing.assert_allclose(y, 6.0)\n\n\nif __name__ == '__main__':\n  absltest.main()\n\n"
  },
  {
    "path": "tests/nnx/nn/attention_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax, jax.numpy as jnp\nfrom jax.lax import Precision\n\nfrom flax import linen\nfrom flax import nnx\nfrom flax.nnx.nn.attention import combine_masks\nfrom flax.typing import Dtype, PrecisionLike\n\nimport numpy as np\n\nimport typing as tp\nfrom absl.testing import parameterized\nfrom absl.testing import absltest\n\ntry:\n  # JAX v0.8.0 and newer\n  from jax import enable_x64\nexcept ImportError:\n  from jax.experimental import enable_x64\n\n\nclass TestMultiHeadAttention(parameterized.TestCase):\n  def test_basic(self):\n    module = nnx.MultiHeadAttention(\n      num_heads=2,\n      in_features=3,\n      qkv_features=6,\n      out_features=6,\n      rngs=nnx.Rngs(0),\n    )\n    y = module(jnp.ones((1, 7, 3)), decode=False)\n    assert y.shape == (1, 7, 6)\n\n\n  def test_multihead_sow_attention_weights(self):\n    class Model(nnx.Module):\n      attention_kwargs: dict\n\n      def __init__(self, attention_kwargs, rng):\n        self.attention_layers = nnx.data([\n          nnx.MultiHeadAttention(**attention_kwargs, rngs=rng) for i in range(3)\n        ])\n\n      def __call__(self, x, sow_weights=False):\n        x = self.attention_layers[0](x, sow_weights=sow_weights)\n        x = self.attention_layers[1](x)\n        x = self.attention_layers[2](x, sow_weights=sow_weights)\n        return x\n\n    rng = nnx.Rngs(0)\n    x = jnp.ones((4, 6, 8))\n\n    module = Model(\n      dict(\n        in_features=8,\n        num_heads=8,\n        kernel_init=nnx.initializers.ones_init(),\n        bias_init=nnx.initializers.zeros_init(),\n        deterministic=False,\n      ),\n      rng,\n    )\n    module.set_attributes(decode=False)\n\n    _, intermediates = nnx.capture(module, nnx.Intermediate)(x, True)\n    assert intermediates['attention_layers'][0]['attention_weights'][\n      0\n    ].shape == (4, 8, 6, 6)\n    assert 1 not in intermediates['attention_layers']\n    assert intermediates['attention_layers'][2]['attention_weights'][\n      0\n    ].shape == (4, 8, 6, 6)\n\n    _, intermediates = nnx.capture(module, nnx.Intermediate)(x)\n    assert not intermediates  # empty\n\n  def test_autoregressive_decode_with_x64(self):\n    with enable_x64():\n      x = jnp.ones((1, 4, 4))\n      module = nnx.MultiHeadAttention(\n        in_features=4,\n        num_heads=2,\n        qkv_features=4,\n        decode=True,\n        rngs=nnx.Rngs(0),\n      )\n      module.init_cache(x.shape, dtype=x.dtype)\n      assert module.cached_key.shape == (1, 4, 2, 2)\n      assert module.cached_value.shape == (1, 4, 2, 2)\n\n      y1 = module(x[:, :1, :])\n      y2 = module(x[:, 1:2, :])\n\n      assert y1.shape == (1, 1, 4)\n      assert y2.shape == (1, 1, 4)\n\n  @parameterized.product(keep_rngs=[True, False])\n  def test_keep_rngs(self, keep_rngs):\n    rngs = nnx.Rngs(42)\n    module = nnx.MultiHeadAttention(\n      in_features=4,\n      num_heads=2,\n      qkv_features=4,\n      decode=True,\n      rngs=rngs,\n      dropout_rate=0.5,\n      keep_rngs=keep_rngs\n    )\n    if keep_rngs:\n      assert module.rngs is not None\n    else:\n      assert module.rngs is None\n    if keep_rngs:\n      _, _, nondiff = nnx.split(module, nnx.Param, ...)\n      assert isinstance(nondiff['rngs']['count'], nnx.RngCount)\n      assert isinstance(nondiff['rngs']['key'], nnx.RngKey)\n    else:\n      nnx.split(module, nnx.Param)\n\n  @parameterized.product(use_padding=[True, False], is_cross_attention=[True, False])\n  def test_causal_mask_equivalence(\n    self,\n    use_padding: bool,\n    is_cross_attention: bool\n  ):\n    batch_size = 1\n    num_heads = 2\n    q_len = 2\n    kv_len = 4 if is_cross_attention else q_len\n    head_dim = 4\n\n    q = jax.random.normal(\n      key=jax.random.key(0),\n      shape=(batch_size, 1, q_len, num_heads, head_dim)\n    )\n    k = jax.random.normal(\n      key=jax.random.key(1),\n      shape=(batch_size, 1, kv_len, num_heads, head_dim)\n    )\n    v = jax.random.normal(\n      key=jax.random.key(2),\n      shape=(batch_size, 1, kv_len, num_heads, head_dim)\n    )\n\n    causal_mask = jnp.tril(jnp.ones(\n        shape=(q_len, kv_len),\n        dtype=jnp.bool_\n      )\n    )\n    causal_mask = jnp.broadcast_to(\n      array=causal_mask,\n      shape=(batch_size, 1, num_heads, q_len, kv_len)\n    )\n\n    padding_mask = None\n\n    if use_padding:\n      padding_mask = jnp.ones(\n        shape=(batch_size, 1, 1, q_len, kv_len),\n        dtype=jnp.bool_,\n      )\n      padding_mask = padding_mask.at[..., -2:].set(False)\n\n    manual_mask = combine_masks(padding_mask, causal_mask, dtype=q.dtype)\n\n    # Jax.nn path with precombined mask and is_causal = False\n    attn_jax = nnx.dot_product_attention(\n      query=q,\n      key=k,\n      value=v,\n      mask=manual_mask,\n      is_causal=False,\n      deterministic=True,\n      module=None,\n    )\n\n    class DummyModule(nnx.Module):\n      pass\n\n    # nnx path with padding mask and is_causal = True (internally combines them)\n    dummy = DummyModule()\n    def _run(m):\n      return nnx.dot_product_attention(\n        query=q,\n        key=k,\n        value=v,\n        mask=padding_mask,\n        is_causal=True,\n        deterministic=True,\n        module=m,\n      )\n    attn_manual, _ = nnx.capture(_run, nnx.Intermediate)(dummy)\n\n    np.testing.assert_allclose(attn_jax, attn_manual, atol=1e-6)\n\n# TODO: add all possible constructor argument values to parameterized.product\nclass TestLinenConsistency(parameterized.TestCase):\n  @parameterized.product(\n    use_bias=[True, False],\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n    precision=[Precision.DEFAULT, Precision.HIGH, Precision.HIGHEST],\n    decode=[True, False],\n    normalize_qk=[True, False],\n  )\n  def test_nnx_attention_equivalence(\n    self,\n    use_bias: bool,\n    dtype: tp.Optional[Dtype],\n    param_dtype: Dtype,\n    precision: PrecisionLike,\n    decode: bool,\n    normalize_qk: bool,\n  ):\n    key = jax.random.key(42)\n    rngs = nnx.Rngs(42)\n\n    num_heads = 2\n    in_features = 3\n    qkv_features = 6\n    out_features = 6\n\n    x = jax.numpy.ones((1, in_features))\n    model_nnx = nnx.MultiHeadAttention(\n      num_heads=num_heads,\n      in_features=in_features,\n      qkv_features=qkv_features,\n      out_features=out_features,\n      use_bias=use_bias,\n      dtype=dtype,\n      param_dtype=param_dtype,\n      precision=precision,\n      decode=decode,\n      normalize_qk=normalize_qk,\n      rngs=rngs,\n    )\n    model = linen.MultiHeadDotProductAttention(\n      num_heads=num_heads,\n      qkv_features=qkv_features,\n      out_features=out_features,\n      use_bias=use_bias,\n      dtype=dtype,\n      param_dtype=param_dtype,\n      precision=precision,\n      decode=decode,\n      normalize_qk=normalize_qk,\n    )\n    variables = model.init(key, x)\n\n    for qkvo in ('query', 'key', 'value', 'out'):\n      getattr(model_nnx, qkvo).kernel[...] = variables['params'][qkvo]['kernel']\n      if use_bias:\n        getattr(model_nnx, qkvo).bias[...] = variables['params'][qkvo]['bias']\n    if decode:\n      model_nnx.init_cache(x.shape, dtype=dtype)\n\n    out_nnx = model_nnx(x)\n    out, cache = model.apply(variables, x, mutable=['cache'])\n    np.testing.assert_array_equal(out, out_nnx)\n\n\nclass TestKVFeatures(parameterized.TestCase):\n\n  def test_varying_num_features(self):\n    key = jax.random.key(42)\n    rngs = nnx.Rngs(42)\n\n    num_heads = 2\n    in_features = 3\n    in_kv_features = 4\n    qkv_features = 6\n    out_features = 6\n\n    x = jax.numpy.ones((1, in_features))\n    y = jax.random.normal(key, (1, in_kv_features))\n    layer = nnx.MultiHeadAttention(\n      num_heads=num_heads,\n      in_features=in_features,\n      qkv_features=qkv_features,\n      out_features=out_features,\n      in_kv_features=in_kv_features,\n      rngs=rngs,\n      decode=False\n    )\n\n    self.assertIsNotNone(layer(x, y))\n\nclass TestGQADotProductAttention(parameterized.TestCase):\n\n  def test_gqa_shapes(self):\n    B, T, S = 2, 4, 5\n    D = 8\n    num_heads_q = 6\n    num_heads_kv = 3\n\n    k1, k2, k3 = jax.random.split(jax.random.key(0), 3)\n    query = jax.random.normal(k1, (B, T, num_heads_q, D))\n    key   = jax.random.normal(k2, (B, S, num_heads_kv, D))\n    value = jax.random.normal(k3, (B, S, num_heads_kv, D))\n\n    output = nnx.dot_product_attention(query, key, value)\n    expected_shape = (B, T, num_heads_q, D)\n    self.assertEqual(output.shape, expected_shape)\n\n  def test_gqa_invalid_heads(self):\n    B, T, D = 1, 4, 8\n    query = jnp.ones((B, T, 5, D))\n    key   = jnp.ones((B, T, 2, D))\n    value = key\n\n    with self.assertRaisesRegex(ValueError, \"must be a multiple\"):\n        nnx.dot_product_attention(query, key, value)\n\n  def test_gqa_multihead_attention(self):\n    in_feat = 128\n    n_heads = 32\n    n_kv_heads = 8\n    qkv_feat = 2048\n    head_dim = qkv_feat // n_heads\n\n    model = nnx.MultiHeadAttention(\n      num_heads=n_heads,\n      in_features=in_feat,\n      qkv_features=qkv_feat,\n      num_kv_heads=n_kv_heads,\n      rngs=nnx.Rngs(0),\n    )\n\n    assert model.query.kernel.shape == (in_feat, n_heads, head_dim)\n    assert model.key.kernel.shape == (in_feat, n_kv_heads, head_dim)\n    assert model.value.kernel.shape == (in_feat, n_kv_heads, head_dim)\n\n    x = jnp.ones((1, 10, in_feat))\n    y = model(x, decode=False)\n    assert y.shape == (1, 10, in_feat)\n\n    model.init_cache((1, 10, in_feat))\n    assert model.cached_key.shape == (1, 10, n_kv_heads, head_dim)\n\n    x_token = jnp.ones((1, 1, in_feat))\n    y_token = model(x_token, decode=True)\n    assert y_token.shape == (1, 1, in_feat)\n    assert model.cache_index == 1\n\n  def test_gqa_parity_with_jax(self):\n    class DummyModule(nnx.Module):\n      pass\n\n    dummy_module = DummyModule()\n\n    B, T, S, D = 2, 8, 8, 16\n    num_heads_q = 4\n    num_heads_kv = 2\n\n    rng = jax.random.key(42)\n    k1, k2, k3 = jax.random.split(rng, 3)\n\n    query = jax.random.normal(k1, (B, T, num_heads_q, D))\n    key   = jax.random.normal(k2, (B, S, num_heads_kv, D))\n    value = jax.random.normal(k3, (B, S, num_heads_kv, D))\n\n    jax_out = jax.nn.dot_product_attention(query, key, value)\n\n    # NNX should handle broadcasting internally\n    def _run(m):\n      return nnx.dot_product_attention(query, key, value, module=m)\n    nnx_out, _ = nnx.capture(_run, nnx.Intermediate)(dummy_module)\n\n    np.testing.assert_allclose(nnx_out, jax_out, atol=1e-3, rtol=1e-3)\n\n\nif __name__ == '__main__':\n  absltest.main()\n\n\n"
  },
  {
    "path": "tests/nnx/nn/conv_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom collections.abc import Sequence\nfrom functools import partial\nimport typing as tp\n\nimport jax\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom jax import numpy as jnp\nfrom jax.lax import Precision\nimport numpy as np\n\nfrom flax import linen\nfrom flax import nnx\nfrom flax.typing import PaddingLike, Dtype, PrecisionLike\n\n\nclass TestConvLinenConsistency(parameterized.TestCase):\n  @parameterized.product(\n    strides=[None, (2, 3)],\n    padding=['VALID', 'CIRCULAR', 'REFLECT', (4, 2)],\n    input_dilation=[(2, 3)],\n    kernel_dilation=[(2, 3)],\n    feature_group_count=[3],\n    use_bias=[True, False],\n    use_mask=[False, True],\n    dtype=[jnp.float32],\n    param_dtype=[jnp.float16],\n    precision=[Precision.HIGHEST],\n    preferred_element_type=[None, jnp.float32],\n  )\n  def test_nnx_linen_conv_equivalence(\n    self,\n    strides: tp.Union[None, int, tp.Sequence[int]],\n    padding: PaddingLike,\n    input_dilation: tp.Union[None, int, tp.Sequence[int]],\n    kernel_dilation: tp.Union[None, int, tp.Sequence[int]],\n    feature_group_count: int,\n    use_bias: bool,\n    use_mask: bool,\n    dtype: tp.Optional[Dtype],\n    param_dtype: Dtype,\n    precision: PrecisionLike,\n    preferred_element_type: tp.Optional[Dtype],\n  ):\n    key = jax.random.key(42)\n    rngs = nnx.Rngs(42)\n    IN_FEATURES = 3\n    OUT_FEATURES = 6\n    INPUT_SHAPE = (24, 9, IN_FEATURES)\n    kernel_size = (7, 4)\n    if use_mask:\n      mask = jnp.tril(jnp.ones((7, 4, 1, 6)))\n    else:\n      mask = None\n\n    # Cannot use string padding specification for transpose conv\n    if isinstance(input_dilation, Sequence) or (\n      isinstance(input_dilation, int) and input_dilation > 1\n    ):\n      padding = (4, 2)\n\n    x = jax.numpy.ones(INPUT_SHAPE)\n    model_nnx = nnx.Conv(\n      IN_FEATURES,\n      OUT_FEATURES,\n      kernel_size,\n      strides,\n      padding=padding,\n      input_dilation=input_dilation,\n      kernel_dilation=kernel_dilation,\n      feature_group_count=feature_group_count,\n      use_bias=use_bias,\n      mask=mask,\n      dtype=dtype,\n      param_dtype=param_dtype,\n      precision=precision,\n      preferred_element_type=preferred_element_type,\n      rngs=rngs,\n    )\n    if preferred_element_type is not None:\n      conv_general_dilated = partial(\n        jax.lax.conv_general_dilated,\n        preferred_element_type=preferred_element_type,\n      )\n    else:\n      conv_general_dilated = None\n    model = linen.Conv(\n      OUT_FEATURES,\n      kernel_size=kernel_size,\n      strides=strides,\n      padding=padding,\n      input_dilation=input_dilation,\n      kernel_dilation=kernel_dilation,\n      feature_group_count=feature_group_count,\n      use_bias=use_bias,\n      mask=mask,\n      dtype=dtype,\n      param_dtype=param_dtype,\n      precision=precision,\n      conv_general_dilated=conv_general_dilated,\n    )\n    variables = model.init(key, x)\n    model_nnx.kernel[...] = variables['params']['kernel']\n    if use_bias:\n      model_nnx.bias[...] = variables['params']['bias']\n\n    out_nnx = model_nnx(x)\n    out = model.apply(variables, x)\n    assert isinstance(out, jax.Array)\n    np.testing.assert_array_equal(out, out_nnx)\n\n  @parameterized.product(\n    strides=[None, (2, 3)],\n    padding=['VALID', 'CIRCULAR', (4, 2)],\n    kernel_dilation=[(2, 3)],\n    use_bias=[True, False],\n    use_mask=[False, True],\n    dtype=[jnp.float32],\n    param_dtype=[jnp.float16],\n    precision=[Precision.HIGHEST],\n    preferred_element_type=[None, jnp.float32],\n  )\n  def test_nnx_linen_convtranspose_equivalence(\n    self,\n    strides: tp.Union[None, tp.Sequence[int]],\n    padding: PaddingLike,\n    kernel_dilation: tp.Union[None, tp.Sequence[int]],\n    use_bias: bool,\n    use_mask: bool,\n    dtype: tp.Optional[Dtype],\n    param_dtype: Dtype,\n    precision: PrecisionLike,\n    preferred_element_type: tp.Optional[Dtype],\n  ):\n    key = jax.random.key(42)\n    rngs = nnx.Rngs(42)\n    IN_FEATURES = 3\n    OUT_FEATURES = 6\n    INPUT_SHAPE = (24, 9, IN_FEATURES)\n    kernel_size = (7, 4)\n    if use_mask:\n      mask = jnp.tril(jnp.ones((7, 4, 3, 6)))\n    else:\n      mask = None\n\n    x = jax.numpy.ones(INPUT_SHAPE)\n    model_nnx = nnx.ConvTranspose(\n      IN_FEATURES,\n      OUT_FEATURES,\n      kernel_size,\n      strides,\n      padding=padding,\n      kernel_dilation=kernel_dilation,\n      use_bias=use_bias,\n      mask=mask,\n      dtype=dtype,\n      param_dtype=param_dtype,\n      precision=precision,\n      preferred_element_type=preferred_element_type,\n      rngs=rngs,\n    )\n    model = linen.ConvTranspose(\n      OUT_FEATURES,\n      kernel_size=kernel_size,\n      strides=strides,\n      padding=padding,\n      kernel_dilation=kernel_dilation,\n      use_bias=use_bias,\n      mask=mask,\n      dtype=dtype,\n      param_dtype=param_dtype,\n      precision=precision,\n      preferred_element_type=preferred_element_type,\n    )\n    variables = model.init(key, x)\n    model_nnx.kernel[...] = variables['params']['kernel']\n    if use_bias:\n      assert model_nnx.bias is not None\n      model_nnx.bias[...] = variables['params']['bias']\n\n    out_nnx = model_nnx(x)\n    out = model.apply(variables, x)\n    assert isinstance(out, jax.Array)\n    np.testing.assert_array_equal(out, out_nnx)\n\n\nif __name__ == '__main__':\n  absltest.main()"
  },
  {
    "path": "tests/nnx/nn/embed_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing as tp\n\nimport jax\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom jax import numpy as jnp\nimport numpy as np\n\nfrom flax import linen\nfrom flax import nnx\nfrom flax.typing import Dtype\n\n\nclass TestLinenConsistency(parameterized.TestCase):\n  @parameterized.product(\n    input_dtype=[jnp.int16, jnp.int32],\n    num_embeddings=[1, 7],\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n  )\n  def test_nnx_linen_equivalence(\n    self,\n    input_dtype: tp.Optional[Dtype],\n    num_embeddings: int,\n    dtype: tp.Optional[Dtype],\n    param_dtype: Dtype,\n  ):\n    key = jax.random.key(42)\n    rngs = nnx.Rngs(42)\n    IN_FEATURES = 32\n    NUM_EMBEDDINGS = num_embeddings\n\n    x = jax.numpy.arange(NUM_EMBEDDINGS, dtype=input_dtype)\n    model_nnx = nnx.eval_shape(\n      lambda rngs: nnx.Embed(\n        NUM_EMBEDDINGS,\n        IN_FEATURES,\n        dtype=dtype,\n        param_dtype=param_dtype,\n        rngs=rngs,\n      ),\n      rngs,\n    )\n    model = linen.Embed(\n      NUM_EMBEDDINGS, IN_FEATURES, dtype=dtype, param_dtype=param_dtype\n    )\n    variables = model.init(key, x)\n    model_nnx.embedding.set_value(variables['params']['embedding'])\n\n    out_nnx = model_nnx(x)\n    out = model.apply(variables, x)\n    assert isinstance(out, jax.Array)\n    np.testing.assert_array_equal(out, out_nnx)\n\n    x = jax.numpy.ones((10,), dtype=input_dtype) * 10\n    out_nnx = model_nnx(x)\n    out = model.apply(variables, x)\n    assert isinstance(out, jax.Array)\n    np.testing.assert_array_equal(out, out_nnx)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/nn/linear_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom functools import partial\nimport typing as tp\n\nimport jax\nimport jax.numpy as jnp\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom jax.lax import Precision\nimport numpy as np\n\nfrom flax import linen\nfrom flax import nnx\nfrom flax.typing import Dtype, PrecisionLike, Shape\n\n\nclass TestLinearGeneral(parameterized.TestCase):\n  @parameterized.product(\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n    precision=[Precision.DEFAULT, Precision.HIGH, Precision.HIGHEST],\n    preferred_element_type=[None, jnp.float32],\n  )\n  def test_basic(\n    self,\n    dtype,\n    param_dtype,\n    precision,\n    preferred_element_type,\n  ):\n    module = nnx.LinearGeneral(\n      2,\n      3,\n      rngs=nnx.Rngs(0),\n      dtype=dtype,\n      param_dtype=param_dtype,\n      precision=precision,\n      preferred_element_type=preferred_element_type,\n    )\n    y = module(jnp.ones((1, 2)))\n\n    assert y.shape == (1, 3)\n    if preferred_element_type is not None:\n      assert y.dtype == preferred_element_type\n    assert module.kernel.shape == (2, 3)\n    assert module.kernel.dtype == param_dtype\n    assert module.bias is not None\n    assert module.bias.shape == (3,)\n\n  def test_basic_multi_features(self):\n    module = nnx.LinearGeneral(2, (3, 4), rngs=nnx.Rngs(0))\n    y = module(jnp.ones((1, 2)))\n\n    assert y.shape == (1, 3, 4)\n    assert module.kernel.shape == (2, 3, 4)\n    assert module.bias is not None\n    assert module.bias.shape == (3, 4)\n\n\nclass TestLinenConsistency(parameterized.TestCase):\n  @parameterized.product(\n    use_bias=[True, False],\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n    precision=[Precision.DEFAULT, Precision.HIGH, Precision.HIGHEST],\n    preferred_element_type=[None, jnp.float32],\n  )\n  def test_nnx_linear_equivalence(\n    self,\n    use_bias: bool,\n    dtype: tp.Optional[Dtype],\n    param_dtype: Dtype,\n    precision: PrecisionLike,\n    preferred_element_type: tp.Optional[Dtype],\n  ):\n    key = jax.random.key(42)\n    rngs = nnx.Rngs(42)\n    IN_FEATURES = 32\n    OUT_FEATURES = 64\n\n    x = jax.numpy.ones((1, IN_FEATURES))\n    model_nnx = nnx.eval_shape(\n      lambda rngs: nnx.Linear(\n        IN_FEATURES,\n        OUT_FEATURES,\n        use_bias=use_bias,\n        dtype=dtype,\n        param_dtype=param_dtype,\n        precision=precision,\n        preferred_element_type=preferred_element_type,\n        rngs=rngs,\n      ),\n      rngs,\n    )\n    if preferred_element_type is not None:\n      dot_general = partial(\n        jax.lax.dot_general,\n        preferred_element_type=preferred_element_type,\n      )\n    else:\n      dot_general = None\n    model = linen.Dense(\n      OUT_FEATURES,\n      use_bias=use_bias,\n      dtype=dtype,\n      param_dtype=param_dtype,\n      precision=precision,\n      dot_general=dot_general,\n    )\n    variables = model.init(key, x)\n    model_nnx.kernel.set_value(variables['params']['kernel'])\n    if use_bias:\n      model_nnx.bias.set_value(variables['params']['bias'])\n\n    out_nnx = model_nnx(x)\n    out = model.apply(variables, x)\n    assert isinstance(out, jax.Array)\n    np.testing.assert_array_equal(out, out_nnx)\n\n  @parameterized.product(\n    einsum_str=['defab,bcef->adefc', 'd...ab,bc...->ad...c'],\n    bias_shape=[None, (6, 7, 5)],\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n    precision=[Precision.DEFAULT, Precision.HIGH, Precision.HIGHEST],\n    preferred_element_type=[None, jnp.float32],\n  )\n  def test_nnx_einsum_equivalence(\n    self,\n    einsum_str,\n    bias_shape: tp.Optional[Shape],\n    dtype: tp.Optional[Dtype],\n    param_dtype: Dtype,\n    precision: PrecisionLike,\n    preferred_element_type: tp.Optional[Dtype],\n  ):\n    key = jax.random.key(42)\n    rngs = nnx.Rngs(42)\n    INPUT_SHAPE = (8, 6, 7, 3, 4)\n    KERNEL_SHAPE = (4, 5, 6, 7)\n\n    x = jax.random.normal(key, INPUT_SHAPE)\n    model_nnx = nnx.Einsum(\n      einsum_str,\n      KERNEL_SHAPE,\n      bias_shape,\n      dtype=dtype,\n      param_dtype=param_dtype,\n      precision=precision,\n      preferred_element_type=preferred_element_type,\n      rngs=rngs,\n    )\n    model = linen.Einsum(\n      KERNEL_SHAPE,\n      einsum_str,\n      use_bias=True if bias_shape is not None else False,\n      dtype=dtype,\n      param_dtype=param_dtype,\n      precision=precision,\n      preferred_element_type=preferred_element_type,\n    )\n\n    variables = model.init(key, x)\n    variables['params']['kernel'] = model_nnx.kernel[...]\n    if bias_shape is not None:\n      assert model_nnx.bias is not None\n      variables['params']['bias'] = model_nnx.bias[...]\n    out_nnx = model_nnx(x)\n    out = model.apply(variables, x)\n    assert isinstance(out, jax.Array)\n    np.testing.assert_array_equal(out, out_nnx)\n\n    variables = model.init(key, x)\n    model_nnx.kernel.set_value(variables['params']['kernel'])\n    if bias_shape is not None:\n      assert model_nnx.bias is not None\n      model_nnx.bias.set_value(variables['params']['bias'])\n    out_nnx = model_nnx(x)\n    out = model.apply(variables, x)\n    assert isinstance(out, jax.Array)\n    np.testing.assert_array_equal(out, out_nnx)\n\n  def test_einsum_op(self):\n    def custom_einsum(*args, **kwargs):\n      out = jnp.einsum(*args, **kwargs)\n      return out.reshape((1, *out.shape))\n    model = nnx.Einsum('ab,bc->ac', (3, 4), einsum_op=custom_einsum,\n                       rngs=nnx.Rngs(42))\n    y = model(jnp.ones((2, 3)))\n    assert y.shape == (1, 2, 4)\n\n\nclass TestPReLUConsistency(parameterized.TestCase):\n  @parameterized.product(\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n  )\n  def test_equivalence(self, dtype, param_dtype):\n    key = jax.random.key(42)\n    x = jnp.linspace(-10, 10, 20, dtype=dtype)\n    negative_slope_init = 0.02\n    nnx_prelu = nnx.PReLU(negative_slope_init=negative_slope_init, param_dtype=param_dtype)\n    linen_prelu = linen.PReLU(negative_slope_init=negative_slope_init, param_dtype=param_dtype)\n\n    variables = linen_prelu.init(key, x)\n    expected = linen_prelu.apply(variables, x)\n    output = nnx_prelu(x)\n    np.testing.assert_array_equal(output, expected)\n\n    # Check gradients\n    @jax.jit\n    def nnx_loss_function(model):\n      y = model(x)\n      return y.mean()\n\n    @jax.jit\n    def linen_loss_function(variables):\n      y = linen_prelu.apply(variables, x)\n      return y.mean()\n\n    expected_loss, expected_grads = jax.value_and_grad(linen_loss_function)(variables)\n    loss, grads = jax.value_and_grad(nnx_loss_function)(nnx_prelu)\n\n    np.testing.assert_array_equal(loss, expected_loss)\n    np.testing.assert_array_equal(\n      expected_grads['params']['negative_slope'], grads.negative_slope[...]\n    )\n\n\nclass TestLayersSameGraph(parameterized.TestCase):\n\n  @parameterized.product(\n      module_args_kwargs_initargs=[\n          (nnx.LinearGeneral, (2, (3, 4)), (\"kernel_init\", \"bias_init\")),\n          (nnx.Linear, (2, 4), (\"kernel_init\", \"bias_init\")),\n          (nnx.Einsum, (\"ik,kj->ij\", (5, 4), 5), (\"kernel_init\", \"bias_init\")),\n          (nnx.Conv, (2, 4, 3), (\"kernel_init\", \"bias_init\")),\n          (nnx.ConvTranspose, (2, 4, 3), (\"kernel_init\", \"bias_init\")),\n          (nnx.Embed, (2, 4), (\"embedding_init\",)),\n          (\n              nnx.MultiHeadAttention,\n              (8, 5, 16),\n              (\"kernel_init\", \"out_kernel_init\", \"bias_init\", \"out_bias_init\"),\n          ),\n          (nnx.BatchNorm, (3,), (\"scale_init\", \"bias_init\")),\n          (nnx.LayerNorm, (3,), (\"scale_init\", \"bias_init\")),\n          (nnx.RMSNorm, (3,), (\"scale_init\",)),\n          (nnx.GroupNorm, (6, 3), (\"scale_init\", \"bias_init\")),\n          (nnx.InstanceNorm, (6,), (\"scale_init\", \"bias_init\")),\n          (\n              nnx.LSTMCell,\n              (4, 5),\n              (\"kernel_init\", \"recurrent_kernel_init\", \"bias_init\"),\n          ),\n          (\n              nnx.OptimizedLSTMCell,\n              (4, 5),\n              (\"kernel_init\", \"recurrent_kernel_init\", \"bias_init\"),\n          ),\n          (\n              nnx.SimpleCell,\n              (4, 5),\n              (\"kernel_init\", \"recurrent_kernel_init\", \"bias_init\"),\n          ),\n          (\n              nnx.GRUCell,\n              (4, 5),\n              (\"kernel_init\", \"recurrent_kernel_init\", \"bias_init\"),\n          ),\n      ],\n  )\n  def test(self, module_args_kwargs_initargs):\n    module_cls, args, init_argnames = module_args_kwargs_initargs\n    kwargs = {\"rngs\": nnx.Rngs(0)}\n    init_zeros = nnx.initializers.zeros\n    init_ones = nnx.initializers.ones\n    init1_kwargs = {k: init_zeros for k in init_argnames}\n    init2_kwargs = {k: init_ones for k in init_argnames}\n    mod1 = module_cls(*args, **init1_kwargs, **kwargs)\n    mod2 = module_cls(*args, **init2_kwargs, **kwargs)\n    g1, g2 = nnx.graphdef(mod1), nnx.graphdef(mod2)\n    assert g1 == g2\n\n\nclass TestLayersParamsMetadata(parameterized.TestCase):\n\n  @parameterized.product(\n      module_args_kwargs_initargs=[\n          (nnx.LinearGeneral, (2, (3, 4)), ((\"kernel\", 2, ()), (\"bias\", 1, ()))),\n          (nnx.Linear, (2, 4), ((\"kernel\", 2, ()), (\"bias\", 1, ()))),\n          (nnx.Einsum, (\"ik,kj->ij\", (5, 4), 5), ((\"kernel\", 2, ()), (\"bias\", 1, ()))),\n          (nnx.Conv, (2, 4, 3), ((\"kernel\", 2, ()), (\"bias\", 1, ()))),\n          (nnx.ConvTranspose, (2, 4, 3), ((\"kernel\", 2, ()), (\"bias\", 1, ()))),\n          (nnx.Embed, (2, 4), ((\"embedding\", 2, ()), )),\n          (\n              partial(nnx.MultiHeadAttention, normalize_qk=True),\n              (8, 5, 16),\n              (\n                (\"kernel\", 2, ((\"query\", \"kernel\"), (\"key\", \"kernel\"), (\"value\", \"kernel\"))),\n                (\"out_kernel\", 2, ((\"out\", \"kernel\"), )),\n                (\"bias\", 1, ((\"query\", \"bias\"), (\"key\", \"bias\"), (\"value\", \"bias\"))),\n                (\"out_bias\", 1, ((\"out\", \"bias\"), )),\n                (\"query_ln_scale\", 1, ((\"query_ln\", \"scale\"), )),\n                (\"key_ln_scale\", 1, ((\"key_ln\", \"scale\"), )),\n              ),\n          ),\n          (nnx.BatchNorm, (3,), ((\"scale\", 1, ()), (\"bias\", 1, ()))),\n          (nnx.LayerNorm, (3,), ((\"scale\", 1, ()), (\"bias\", 1, ()))),\n          (nnx.RMSNorm, (3,), ((\"scale\", 1, ()), )),\n          (nnx.GroupNorm, (6, 3), ((\"scale\", 1, ()), (\"bias\", 1, ()))),\n          (nnx.InstanceNorm, (6,), ((\"scale\", 1, ()), (\"bias\", 1, ()))),\n          (\n            nnx.LoRA,\n            (3, 2, 4),\n            (\n              (\"a\", 2, ((None, \"lora_a\"), )),\n              (\"b\", 2, ((None, \"lora_b\"), )),\n            )\n          ),\n          (\n            partial(nnx.LoRALinear, lora_rank=4),\n            (3, 2),\n            (\n              (\"a\", 2, ((\"lora\", \"lora_a\"), )),\n              (\"b\", 2, ((\"lora\", \"lora_b\"), )),\n            )\n          ),\n          (\n              nnx.LSTMCell,\n              (4, 5),\n              (\n                (\n                  \"kernel\",\n                  2,\n                  ((name, \"kernel\") for name in [\"ii\", \"if_\", \"ig\", \"io\"])\n                ),\n                (\n                  \"recurrent_kernel\",\n                  2,\n                  ((name, \"kernel\") for name in [\"hi\", \"hf\", \"hg\", \"ho\"])\n                ),\n                (\n                  \"bias\",\n                  1,\n                  ((name, \"bias\") for name in [\"hi\", \"hf\", \"hg\", \"ho\"])\n                ),\n              ),\n          ),\n          (\n              nnx.OptimizedLSTMCell,\n              (4, 5),\n              (\n                (\"kernel\", 2, ((\"dense_i\", \"kernel\"), )),\n                (\"recurrent_kernel\", 2, ((\"dense_h\", \"kernel\"), )),\n                (\"bias\", 1, ((\"dense_h\", \"bias\"), )),\n              )\n          ),\n          (\n              nnx.SimpleCell,\n              (4, 5),\n              (\n                (\"kernel\", 2, ((\"dense_i\", \"kernel\"), )),\n                (\"bias\", 1, ((\"dense_i\", \"bias\"), )),\n                (\"recurrent_kernel\", 2, ((\"dense_h\", \"kernel\"), )),\n              )\n          ),\n          (\n              nnx.GRUCell,\n              (4, 5),\n              (\n                (\"kernel\", 2, ((\"dense_i\", \"kernel\"), )),\n                (\"bias\", 1, ((\"dense_i\", \"bias\"), )),\n                (\"recurrent_kernel\", 2, ((\"dense_h\", \"kernel\"), )),\n              )\n          ),\n      ],\n  )\n  def test(self, module_args_kwargs_initargs):\n    module_cls, args, metadata_argnames = module_args_kwargs_initargs\n    kwargs = {\"rngs\": nnx.Rngs(0)}\n    out_sharding = (\"din\", \"dout\")\n    metadata_kwargs = {\n      f\"{key}_metadata\": {\"out_sharding\": out_sharding[:le]}\n      for key, le, _ in metadata_argnames\n    }\n\n    mesh = jax.make_mesh(\n      (1, 1),\n      out_sharding,\n      axis_types=(jax.sharding.AxisType.Auto,) * len(out_sharding),\n    )\n    with jax.set_mesh(mesh):\n      module = module_cls(*args, **metadata_kwargs, **kwargs)\n\n    for key, le, attrs in metadata_argnames:\n      attrs = attrs if attrs else ((None, key), )\n      for attr_name, param_name in attrs:\n        attr = getattr(module, attr_name) if attr_name is not None else module\n        param = getattr(attr, param_name)\n        self.assertIsNotNone(param.out_sharding)\n        self.assertEqual(param.out_sharding, out_sharding[:le])\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/nn/lora_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax\nfrom absl.testing import absltest\nimport numpy as np\n\nfrom flax import nnx\nfrom jax import numpy as jnp\n\n\nclass TestLora(absltest.TestCase):\n  def test_basic(self):\n    module = nnx.LoRA(3, 2, 4, rngs=nnx.Rngs(0))\n    x = jax.random.normal(jax.random.key(0), (1, 3))\n    y = module(x)\n\n    assert y.shape == (1, 4)\n    assert module.lora_a.shape == (3, 2)\n    assert module.lora_b.shape == (2, 4)\n    np.testing.assert_allclose(y, x @ module.lora_a @ module.lora_b)\n\n  def test_lora_base_module(self):\n    rngs = nnx.Rngs(0)\n    linear = nnx.Linear(3, 4, use_bias=False, rngs=rngs)\n    module = nnx.LoRA(3, 2, 4, base_module=linear, rngs=rngs)\n    x = jax.random.normal(jax.random.key(0), (1, 3))\n    y = module(x)\n\n    assert y.shape == (1, 4)\n    assert module.base_module == linear\n    assert module.base_module.kernel.shape == (3, 4)\n    assert module.base_module.bias == None\n    assert module.lora_a.shape == (3, 2)\n    assert module.lora_b.shape == (2, 4)\n    np.testing.assert_allclose(\n      y, x @ linear.kernel + x @ module.lora_a @ module.lora_b\n    )\n\n  def test_layer_swap_lora(self):\n    class MLP(nnx.Module):\n      def __init__(self, dim, rngs: nnx.Rngs):\n        self.linear1 = nnx.Linear(dim, dim, rngs=rngs)\n        self.linear2 = nnx.Linear(dim, dim, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.linear1(x)\n        return self.linear2(x)\n\n    rngs = nnx.Rngs(0)\n    model = MLP(3, rngs=rngs)\n    x = jax.random.normal(jax.random.key(0), (1, 3))\n    y = model(x)\n\n    # Replace one of the linear layers as LoRA linear layer.\n    model.linear2 = nnx.LoRA(3, 4, 3, base_module=model.linear2, rngs=rngs)\n    lora_y = model(x)\n\n    assert y.shape == (1, 3)\n    assert lora_y.shape == (1, 3)\n    np.testing.assert_allclose(y, lora_y)\n    a, b = model.linear2.lora_a[...], model.linear2.lora_b[...]\n    np.testing.assert_allclose(y + model.linear1(x) @ a @ b, lora_y)\n\n  def test_layer_swap_loralinear(self):\n    class MLP(nnx.Module):\n      def __init__(self, dim, rngs: nnx.Rngs):\n        self.linear1 = nnx.Linear(dim, dim, rngs=rngs)\n        self.linear2 = nnx.Linear(dim, dim, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.linear1(x)\n        return self.linear2(x)\n\n    rngs = nnx.Rngs(0)\n    model = MLP(3, rngs=rngs)\n    x = jax.random.normal(jax.random.key(0), (1, 3))\n    y = model(x)\n\n    # Replace one of the linear layers as LoRA linear layer.\n    _, state = nnx.split(\n      model.linear2\n    )  # To keep the kernel and bias of linear2\n    model.linear2 = nnx.LoRALinear(3, 3, lora_rank=4, rngs=rngs)\n    nnx.update(model.linear2, state)\n    lora_y = model(x)\n\n    assert y.shape == (1, 3)\n    assert lora_y.shape == (1, 3)\n    np.testing.assert_allclose(y, lora_y)\n    a, b = model.linear2.lora.lora_a[...], model.linear2.lora.lora_b[...]\n    np.testing.assert_allclose(y + model.linear1(x) @ a @ b, lora_y)\n\n  def test_lora_param_type(self):\n    rngs = nnx.Rngs(0)\n    model = nnx.LoRA(3, 4, 2, lora_param_type=nnx.LoRAParam, rngs=rngs)\n    _, lora_params, params = nnx.split(model, nnx.LoRAParam, nnx.Param)\n    assert params == {}\n    assert ('lora_a' in lora_params) and ('lora_b' in lora_params)\n    np.testing.assert_allclose(lora_params['lora_a'][...], model.lora_a[...])\n\n    model = nnx.LoRA(3, 4, 2, lora_param_type=nnx.Param, rngs=rngs)\n    _, params, lora_params = nnx.split(model, nnx.Param, nnx.LoRAParam)\n    assert ('lora_a' in params) and ('lora_b' in params)\n    np.testing.assert_allclose(params['lora_a'][...], model.lora_a[...])\n    assert lora_params == {}\n\n  def test_dtype(self):\n    rngs = nnx.Rngs(0)\n    model = nnx.LoRA(3, 4, 2, dtype=jnp.float16, param_dtype=jnp.float32,\n                     rngs=rngs)\n    assert model.lora_a.dtype == jnp.float32\n    y = model(jnp.ones((1, 3)).astype(jnp.float32))\n    assert y.dtype == jnp.float16\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/nn/normalization_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing as tp\n\nimport jax\nimport jax.numpy as jnp\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nimport numpy as np\n\nfrom flax import linen\nfrom flax import nnx\nfrom flax.typing import Dtype\n\n\nclass TestLinenConsistency(parameterized.TestCase):\n  @parameterized.product(\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n    use_fast_variance=[True, False],\n    mask=[None, np.array([True, False, True, False, True])],\n  )\n  def test_nnx_linen_batchnorm_equivalence(\n    self,\n    dtype: tp.Optional[Dtype],\n    param_dtype: Dtype,\n    use_fast_variance: bool,\n    mask: tp.Optional[np.ndarray],\n  ):\n    class NNXModel(nnx.Module):\n      def __init__(self, dtype, param_dtype, use_fast_variance, rngs):\n        self.norm_layer = nnx.BatchNorm(\n          5,\n          use_running_average=False,\n          dtype=dtype,\n          param_dtype=param_dtype,\n          use_fast_variance=use_fast_variance,\n          promote_dtype=lambda x, **kwargs: x, # ensure same behavior as Linen\n          rngs=rngs,\n        )\n        self.linear = nnx.Linear(\n          5, 4, dtype=dtype, param_dtype=param_dtype, rngs=rngs\n        )\n\n      def __call__(self, x, *, mask=None):\n        x = self.norm_layer(x, mask=mask)\n        x = self.linear(x)\n        return x\n\n    class LinenModel(linen.Module):\n      dtype: tp.Optional[Dtype] = None\n      param_dtype: Dtype = jnp.float32\n      use_fast_variance: bool = True\n\n      def setup(self):\n        self.norm_layer = linen.BatchNorm(\n          use_running_average=False,\n          dtype=self.dtype,\n          param_dtype=self.param_dtype,\n          use_fast_variance=use_fast_variance,\n        )\n        self.linear = linen.Dense(\n          4, dtype=self.dtype, param_dtype=self.param_dtype\n        )\n\n      def __call__(self, x, *, mask=None):\n        x = self.norm_layer(x, mask=mask)\n        x = self.linear(x)\n        return x\n\n    rngs = nnx.Rngs(42)\n    x = jax.random.normal(jax.random.key(0), (10, 5))\n\n    linen_model = LinenModel(\n      dtype=dtype, param_dtype=param_dtype, use_fast_variance=use_fast_variance\n    )\n    variables: dict = linen_model.init(jax.random.key(1), x)\n    linen_out, batch_stats = linen_model.apply(\n      variables, x, mask=mask, mutable=['batch_stats']\n    )\n\n    nnx_model = NNXModel(\n      dtype=dtype,\n      param_dtype=param_dtype,\n      use_fast_variance=use_fast_variance,\n      rngs=rngs,\n    )\n    nnx_model.linear.kernel[...] = variables['params']['linear']['kernel']\n    nnx_model.linear.bias[...] = variables['params']['linear']['bias']\n\n    linen_out, updates = linen_model.apply(\n      variables, x, mask=mask, mutable=['batch_stats']\n    )\n    variables.update(updates)\n    nnx_out = nnx_model(x, mask=mask)\n    np.testing.assert_array_equal(linen_out, nnx_out)\n    # Compare BatchNorm parameters\n    np.testing.assert_array_equal(\n      variables['params']['norm_layer']['scale'],\n      nnx_model.norm_layer.scale[...],\n    )\n    np.testing.assert_array_equal(\n      variables['params']['norm_layer']['bias'], nnx_model.norm_layer.bias[...]\n    )\n    np.testing.assert_array_equal(\n      variables['batch_stats']['norm_layer']['mean'],\n      nnx_model.norm_layer.mean[...],\n    )\n    np.testing.assert_array_equal(\n      variables['batch_stats']['norm_layer']['var'],\n      nnx_model.norm_layer.var[...],\n    )\n\n\n\n  @parameterized.product(\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n    use_fast_variance=[True, False],\n    mask=[None, np.array([True, False, True, False, True])],\n  )\n  def test_nnx_linen_layernorm_equivalence(\n    self,\n    dtype: tp.Optional[Dtype],\n    param_dtype: Dtype,\n    use_fast_variance: bool,\n    mask: tp.Optional[np.ndarray],\n  ):\n    class NNXModel(nnx.Module):\n      def __init__(self, dtype, param_dtype, use_fast_variance, rngs):\n        self.norm_layer = nnx.LayerNorm(\n          5,\n          dtype=dtype,\n          param_dtype=param_dtype,\n          use_fast_variance=use_fast_variance,\n          promote_dtype=lambda x, **kwargs: x, # ensure same behavior as Linen\n          rngs=rngs,\n        )\n        self.linear = nnx.Linear(\n          5, 4, dtype=dtype, param_dtype=param_dtype, rngs=rngs\n        )\n\n      def __call__(self, x, *, mask=None):\n        x = self.norm_layer(x, mask=mask)\n        x = self.linear(x)\n        return x\n\n    class LinenModel(linen.Module):\n      dtype: tp.Optional[Dtype] = None\n      param_dtype: Dtype = jnp.float32\n      use_fast_variance: bool = True\n\n      def setup(self):\n        self.norm_layer = linen.LayerNorm(\n          dtype=self.dtype,\n          param_dtype=self.param_dtype,\n          use_fast_variance=self.use_fast_variance,\n        )\n        self.linear = linen.Dense(\n          4, dtype=self.dtype, param_dtype=self.param_dtype\n        )\n\n      def __call__(self, x, *, mask=None):\n        x = self.norm_layer(x, mask=mask)\n        x = self.linear(x)\n        return x\n\n    rngs = nnx.Rngs(42)\n    x = jax.random.normal(jax.random.key(0), (10, 5))\n\n    linen_model = LinenModel(\n      dtype=dtype, param_dtype=param_dtype, use_fast_variance=use_fast_variance\n    )\n    variables = linen_model.init(jax.random.key(1), x)\n    linen_out = linen_model.apply(variables, x, mask=mask)\n    assert isinstance(linen_out, jax.Array)\n\n    nnx_model = NNXModel(\n      dtype=dtype,\n      param_dtype=param_dtype,\n      use_fast_variance=use_fast_variance,\n      rngs=rngs,\n    )\n    nnx_model.linear.kernel[...] = variables['params']['linear']['kernel']\n    nnx_model.linear.bias[...] = variables['params']['linear']['bias']\n\n    nnx_out = nnx_model(x, mask=mask)\n    np.testing.assert_array_equal(linen_out, nnx_out)\n\n  @parameterized.product(\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n    use_fast_variance=[True, False],\n    mask=[None, np.array([True, False, True, False, True])],\n  )\n  def test_nnx_linen_rmsnorm_equivalence(\n    self,\n    dtype: tp.Optional[Dtype],\n    param_dtype: Dtype,\n    use_fast_variance: bool,\n    mask: tp.Optional[np.ndarray],\n  ):\n    class NNXModel(nnx.Module):\n      def __init__(self, dtype, param_dtype, use_fast_variance, rngs):\n        self.norm_layer = nnx.RMSNorm(\n          5,\n          dtype=dtype,\n          param_dtype=param_dtype,\n          use_fast_variance=use_fast_variance,\n          promote_dtype=lambda x, **kwargs: x,  # ensure same behavior as Linen\n          rngs=rngs,\n        )\n        self.linear = nnx.Linear(\n          5, 4, dtype=dtype, param_dtype=param_dtype, rngs=rngs\n        )\n\n      def __call__(self, x, *, mask=None):\n        x = self.norm_layer(x, mask=mask)\n        x = self.linear(x)\n        return x\n\n    class LinenModel(linen.Module):\n      dtype: tp.Optional[Dtype] = None\n      param_dtype: Dtype = jnp.float32\n      use_fast_variance: bool = True\n\n      def setup(self):\n        self.norm_layer = linen.RMSNorm(\n          dtype=self.dtype,\n          param_dtype=self.param_dtype,\n          use_fast_variance=self.use_fast_variance,\n        )\n        self.linear = linen.Dense(\n          4, dtype=self.dtype, param_dtype=self.param_dtype\n        )\n\n      def __call__(self, x, *, mask=None):\n        x = self.norm_layer(x, mask=mask)\n        x = self.linear(x)\n        return x\n\n    rngs = nnx.Rngs(42)\n    x = jax.random.normal(jax.random.key(0), (10, 5))\n\n    linen_model = LinenModel(\n      dtype=dtype, param_dtype=param_dtype, use_fast_variance=use_fast_variance\n    )\n    variables = linen_model.init(jax.random.key(1), x)\n    linen_out = linen_model.apply(variables, x, mask=mask)\n\n    nnx_model = NNXModel(\n      dtype=dtype,\n      param_dtype=param_dtype,\n      use_fast_variance=use_fast_variance,\n      rngs=rngs,\n    )\n    nnx_model.linear.kernel.set_value(variables['params']['linear']['kernel'])\n    nnx_model.linear.bias.set_value(variables['params']['linear']['bias'])\n\n    nnx_out = nnx_model(x, mask=mask)\n    assert isinstance(linen_out, jax.Array)\n    np.testing.assert_array_equal(linen_out, nnx_out)\n\n  @parameterized.product(\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n    use_fast_variance=[True, False],\n    mask=[None, np.array([True, False, True, False, True, False])],\n  )\n  def test_nnx_linen_groupnorm_equivalence(\n    self,\n    dtype: tp.Optional[Dtype],\n    param_dtype: Dtype,\n    use_fast_variance: bool,\n    mask: tp.Optional[np.ndarray],\n  ):\n    class NNXModel(nnx.Module):\n      def __init__(self, dtype, param_dtype, use_fast_variance, rngs):\n        self.norm_layer = nnx.GroupNorm(\n          6,\n          num_groups=3,\n          dtype=dtype,\n          param_dtype=param_dtype,\n          use_fast_variance=use_fast_variance,\n          promote_dtype=lambda x, **kwargs: x, # ensure same behavior as Linen\n          rngs=rngs,\n        )\n        self.linear = nnx.Linear(\n          6, 4, dtype=dtype, param_dtype=param_dtype, rngs=rngs\n        )\n\n      def __call__(self, x, *, mask=None):\n        x = self.norm_layer(x, mask=mask)\n        x = self.linear(x)\n        return x\n\n    class LinenModel(linen.Module):\n      dtype: tp.Optional[Dtype] = None\n      param_dtype: Dtype = jnp.float32\n      use_fast_variance: bool = True\n\n      def setup(self):\n        self.norm_layer = linen.GroupNorm(\n          num_groups=3,\n          dtype=self.dtype,\n          param_dtype=self.param_dtype,\n          use_fast_variance=self.use_fast_variance,\n        )\n        self.linear = linen.Dense(\n          4, dtype=self.dtype, param_dtype=self.param_dtype\n        )\n\n      def __call__(self, x, *, mask=None):\n        x = self.norm_layer(x, mask=mask)\n        x = self.linear(x)\n        return x\n\n    rngs = nnx.Rngs(42)\n    x = jax.random.normal(jax.random.key(0), (10, 6))\n\n    linen_model = LinenModel(\n      dtype=dtype, param_dtype=param_dtype, use_fast_variance=use_fast_variance\n    )\n    variables = linen_model.init(jax.random.key(1), x)\n    linen_out = linen_model.apply(variables, x, mask=mask)\n\n    nnx_model = NNXModel(\n      dtype=dtype,\n      param_dtype=param_dtype,\n      use_fast_variance=use_fast_variance,\n      rngs=rngs,\n    )\n    nnx_model.linear.kernel[...] = variables['params']['linear']['kernel']\n    nnx_model.linear.bias[...] = variables['params']['linear']['bias']\n\n    nnx_out = nnx_model(x, mask=mask)\n    assert isinstance(linen_out, jax.Array)\n    np.testing.assert_array_equal(linen_out, nnx_out)\n\n  @parameterized.product(\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n    scale_init=[\n      nnx.initializers.ones,\n      nnx.initializers.constant(10.0),\n      nnx.initializers.constant(0.5),\n    ],\n  )\n  def test_nnx_linen_weightnorm_equivalence(\n      self,\n      dtype: tp.Optional[Dtype],\n      param_dtype: Dtype,\n      scale_init: nnx.Initializer,\n  ):\n    class NNXModel(nnx.Module):\n      def __init__(self, dtype, param_dtype, rngs):\n        self.dense = nnx.Linear(\n          8, 4, dtype=dtype, param_dtype=param_dtype, rngs=rngs\n        )\n        self.normed = nnx.WeightNorm(\n          self.dense,\n          use_scale=True,\n          scale_init=scale_init,\n          feature_axes=-1,\n          dtype=dtype,\n          param_dtype=param_dtype,\n          rngs=rngs,\n        )\n\n      def __call__(self, x, *, mask=None):\n        return self.normed(x)\n\n    class LinenModel(linen.Module):\n      dtype: tp.Optional[Dtype] = None\n      param_dtype: Dtype = jnp.float32\n\n      def setup(self):\n        self.dense = linen.Dense(\n          4, dtype=self.dtype, param_dtype=self.param_dtype\n        )\n        self.weight_norm = linen.WeightNorm(\n          self.dense, variable_filter={'kernel'}, scale_init=scale_init\n        )\n\n      def __call__(self, x, *, mask=None):\n        return self.weight_norm(x)\n\n    rngs = nnx.Rngs(42)\n\n    x = jax.random.normal(jax.random.key(0), (10, 8))\n\n    linen_model = LinenModel(dtype=dtype, param_dtype=param_dtype)\n    variables = linen_model.init(jax.random.key(1), x)\n\n    nnx_model = NNXModel(dtype=dtype, param_dtype=param_dtype, rngs=rngs)\n    nnx_model.dense.kernel.set_value(variables['params']['dense']['kernel'])\n    nnx_model.dense.bias.set_value(variables['params']['dense']['bias'])\n\n    linen_out = linen_model.apply(variables, x)\n    nnx_out = nnx_model(x)\n\n    np.testing.assert_array_equal(\n      variables['params']['weight_norm']['dense/kernel/scale'],\n      nnx_model.normed.scales[('kernel',)])\n    np.testing.assert_array_equal(linen_out, nnx_out)\n\n  @parameterized.product(\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n    use_fast_variance=[True, False],\n    mask=[None, np.array([True, False, True, False, True, False])],\n  )\n  def test_nnx_linen_instancenorm_equivalence(\n    self,\n    dtype: tp.Optional[Dtype],\n    param_dtype: Dtype,\n    use_fast_variance: bool,\n    mask: tp.Optional[np.ndarray],\n  ):\n    class NNXModel(nnx.Module):\n      def __init__(self, dtype, param_dtype, use_fast_variance, rngs):\n        self.norm_layer = nnx.InstanceNorm(\n          6,\n          dtype=dtype,\n          param_dtype=param_dtype,\n          use_fast_variance=use_fast_variance,\n          rngs=rngs,\n        )\n        self.linear = nnx.Linear(\n          6, 4, dtype=dtype, param_dtype=param_dtype, rngs=rngs\n        )\n\n      def __call__(self, x, *, mask=None):\n        x = self.norm_layer(x, mask=mask)\n        x = self.linear(x)\n        return x\n\n    class LinenModel(linen.Module):\n      dtype: tp.Optional[Dtype] = None\n      param_dtype: Dtype = jnp.float32\n      use_fast_variance: bool = True\n\n      def setup(self):\n        self.norm_layer = linen.InstanceNorm(\n          dtype=self.dtype,\n          param_dtype=self.param_dtype,\n          use_fast_variance=self.use_fast_variance,\n        )\n        self.linear = linen.Dense(\n          4, dtype=self.dtype, param_dtype=self.param_dtype\n        )\n\n      def __call__(self, x, *, mask=None):\n        x = self.norm_layer(x, mask=mask)\n        x = self.linear(x)\n        return x\n\n    rngs = nnx.Rngs(42)\n    x = jax.random.normal(jax.random.key(0), (10, 6))\n\n    linen_model = LinenModel(\n      dtype=dtype, param_dtype=param_dtype, use_fast_variance=use_fast_variance\n    )\n    variables = linen_model.init(jax.random.key(1), x)\n    linen_out = linen_model.apply(variables, x, mask=mask)\n\n    nnx_model = NNXModel(\n      dtype=dtype,\n      param_dtype=param_dtype,\n      use_fast_variance=use_fast_variance,\n      rngs=rngs,\n    )\n    nnx_model.linear.kernel[...] = variables['params']['linear']['kernel']\n    nnx_model.linear.bias[...] = variables['params']['linear']['bias']\n\n    nnx_out = nnx_model(x, mask=mask)\n    assert isinstance(linen_out, jax.Array)\n    np.testing.assert_array_equal(linen_out, nnx_out)\n\n  @parameterized.product(\n    dtype=[jnp.float32, jnp.float16],\n    param_dtype=[jnp.float32, jnp.float16],\n    n_steps=[1, 10],\n    update_stats=[True, False],\n  )\n  def test_nnx_linen_spectralnorm_equivalence(\n    self,\n    dtype: tp.Optional[Dtype],\n    param_dtype: Dtype,\n    n_steps: int,\n    update_stats: bool,\n  ):\n    class NNXModel(nnx.Module):\n      def __init__(self, dtype, param_dtype, rngs):\n        self.seq = nnx.Sequential(\n          nnx.Linear(\n            5, 4, dtype=dtype, param_dtype=param_dtype, rngs=rngs\n          ),\n          nnx.relu,\n          nnx.BatchNorm(\n            4,\n            dtype=dtype,\n            param_dtype=param_dtype,\n            rngs=rngs,\n            use_running_average=not update_stats,\n          ),\n        )\n        self.norm_layer = nnx.SpectralNorm(\n          self.seq,\n          n_steps=n_steps,\n          dtype=dtype,\n          param_dtype=param_dtype,\n          update_stats=update_stats,\n          rngs=rngs,\n        )\n\n      def __call__(self, x):\n        return self.norm_layer(x)\n\n    class LinenModel(linen.Module):\n      dtype: tp.Optional[Dtype] = None\n      param_dtype: Dtype = jnp.float32\n\n      def setup(self):\n        self.seq = linen.Sequential([\n          linen.Dense(\n            4, dtype=self.dtype, param_dtype=self.param_dtype\n          ),\n          linen.relu,\n          linen.BatchNorm(\n            dtype=self.dtype,\n            param_dtype=self.param_dtype,\n            use_running_average=not update_stats,\n          ),\n        ])\n        self.norm_layer = linen.SpectralNorm(self.seq, n_steps=n_steps)\n\n      def __call__(self, x):\n        return self.norm_layer(x, update_stats=update_stats)\n\n    rngs = nnx.Rngs(42)\n    x = jax.random.normal(jax.random.key(0), (10, 5))\n\n    linen_model = LinenModel(dtype=dtype, param_dtype=param_dtype)\n    variables = linen_model.init(jax.random.key(1), x)\n\n    nnx_model = NNXModel(\n      dtype=dtype, param_dtype=param_dtype, rngs=rngs\n    )\n    # Setup the same weights and batch stats\n    var_params_seq_0 = variables['params']['seq']['layers_0']\n    nnx_model.seq.layers[0].kernel.set_value(var_params_seq_0['kernel'])\n    nnx_model.seq.layers[0].bias.set_value(var_params_seq_0['bias'])\n\n    var_params_seq_2 = variables['params']['seq']['layers_2']\n    nnx_model.seq.layers[2].scale.set_value(var_params_seq_2['scale'])\n    nnx_model.seq.layers[2].bias.set_value(var_params_seq_0['bias'])\n\n    var_norm_layer = variables['batch_stats']['norm_layer']\n    nnx_model.norm_layer.batch_stats[\n      ('layers', 0, 'kernel', 'u')\n    ].set_value(var_norm_layer['seq/layers_0/kernel/u'])\n    nnx_model.norm_layer.batch_stats[\n      ('layers', 0, 'kernel', 'sigma')\n    ].set_value(var_norm_layer['seq/layers_0/kernel/sigma'])\n\n    linen_out = linen_model.apply(variables, x, mutable=['batch_stats'])\n    nnx_out = nnx_model(x)\n    np.testing.assert_array_equal(linen_out[0], nnx_out)\n\n    np.testing.assert_array_equal(\n      nnx_model.norm_layer.batch_stats[(\"layers\", 0, \"kernel\", \"u\")],\n      linen_out[1]['batch_stats']['norm_layer']['seq/layers_0/kernel/u'],\n    )\n    np.testing.assert_array_equal(\n      nnx_model.norm_layer.batch_stats[(\"layers\", 0, \"kernel\", \"sigma\")],\n      linen_out[1]['batch_stats']['norm_layer']['seq/layers_0/kernel/sigma'],\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/nn/recurrent_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax, jax.numpy as jnp\nfrom jax import random\n\nfrom flax import linen\nfrom flax import nnx\nfrom flax.nnx.nn import initializers\n\nimport numpy as np\n\nfrom absl.testing import absltest\n\n\nclass TestLSTMCell(absltest.TestCase):\n  def test_basic(self):\n    module = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      rngs=nnx.Rngs(0),\n    )\n    x = jnp.ones((2, 3))\n    carry = module.initialize_carry(x.shape, nnx.Rngs(0))\n    new_carry, y = module(carry, x)\n    self.assertEqual(y.shape, (2, 4))\n\n  def test_lstm_sequence(self):\n    \"\"\"Test LSTMCell over a sequence of inputs.\"\"\"\n    module = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      rngs=nnx.Rngs(0),\n    )\n    x = random.normal(random.PRNGKey(1), (5, 2, 3))  # seq_len, batch, feature\n    carry = module.initialize_carry(x.shape[1:], nnx.Rngs(0))\n    outputs = []\n    for t in range(x.shape[0]):\n      carry, y = module(carry, x[t])\n      outputs.append(y)\n    outputs = jnp.stack(outputs)\n    self.assertEqual(outputs.shape, (5, 2, 4))\n\n  def test_lstm_with_different_dtypes(self):\n    \"\"\"Test LSTMCell with different data types.\"\"\"\n    module = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      dtype=jnp.bfloat16,\n      param_dtype=jnp.bfloat16,\n      rngs=nnx.Rngs(0),\n    )\n    x = jnp.ones((2, 3), dtype=jnp.bfloat16)\n    carry = module.initialize_carry(x.shape, nnx.Rngs(0))\n    new_carry, y = module(carry, x)\n    self.assertEqual(y.dtype, jnp.bfloat16)\n    self.assertEqual(y.shape, (2, 4))\n\n  def test_lstm_with_custom_activations(self):\n    \"\"\"Test LSTMCell with custom activation functions.\"\"\"\n    module = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      gate_fn=jax.nn.relu,\n      activation_fn=jax.nn.elu,\n      rngs=nnx.Rngs(0),\n    )\n    x = jnp.ones((1, 3))\n    carry = module.initialize_carry(x.shape, nnx.Rngs(0))\n    new_carry, y = module(carry, x)\n    self.assertEqual(y.shape, (1, 4))\n\n  def test_lstm_initialize_carry(self):\n    \"\"\"Test the initialize_carry method.\"\"\"\n    module = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      rngs=nnx.Rngs(0),\n    )\n    x_shape = (1, 3)\n    carry = module.initialize_carry(\n      x_shape, nnx.Rngs(0), carry_init=initializers.ones\n    )\n    c, h = carry\n    self.assertTrue(jnp.all(c == 1.0))\n    self.assertTrue(jnp.all(h == 1.0))\n    self.assertEqual(c.shape, (1, 4))\n    self.assertEqual(h.shape, (1, 4))\n\n  def test_lstm_with_variable_sequence_length(self):\n    \"\"\"Test LSTMCell with variable sequence lengths.\"\"\"\n    module = nnx.LSTMCell(in_features=3, hidden_features=4, rngs=nnx.Rngs(0))\n\n    # Simulate a batch with variable sequence lengths\n    x = jnp.array(\n      [\n        [[1, 2, 3], [4, 5, 6], [0, 0, 0]],  # Sequence length 2\n        [[7, 8, 9], [10, 11, 12], [13, 14, 15]],  # Sequence length 3\n      ]\n    )  # Shape: (batch_size=2, max_seq_length=3, features=3)\n\n    seq_lengths = jnp.array([2, 3])  # Actual lengths for each sequence\n    batch_size = x.shape[0]\n    max_seq_length = x.shape[1]\n    carry = module.initialize_carry((batch_size, 3), nnx.Rngs(0))\n    outputs = []\n    for t in range(max_seq_length):\n      input_t = x[:, t, :]\n      carry, y = module(carry, input_t)\n      outputs.append(y)\n    outputs = jnp.stack(\n      outputs, axis=1\n    )  # Shape: (batch_size, max_seq_length, hidden_features)\n\n    # Zero out outputs beyond the actual sequence lengths\n    mask = jnp.arange(max_seq_length)[None, :] < seq_lengths[:, None]\n    outputs = outputs * mask[:, :, None]\n    self.assertEqual(outputs.shape, (2, 3, 4))\n\n  def test_lstm_stateful(self):\n    \"\"\"Test that LSTMCell maintains state across calls.\"\"\"\n    module = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      rngs=nnx.Rngs(0),\n    )\n    x1 = jnp.ones((1, 3))\n    x2 = jnp.ones((1, 3)) * 2\n    carry = module.initialize_carry(x1.shape, nnx.Rngs(0))\n    carry, y1 = module(carry, x1)\n    carry, y2 = module(carry, x2)\n    self.assertEqual(y1.shape, (1, 4))\n    self.assertEqual(y2.shape, (1, 4))\n\n  def test_lstm_equivalence_with_flax_linen(self):\n    \"\"\"Test that nnx.LSTMCell produces the same outputs as flax.linen.LSTMCell.\"\"\"\n    in_features = 3\n    hidden_features = 4\n    key = random.PRNGKey(42)\n    x = random.normal(key, (1, in_features))\n\n    # Initialize nnx.LSTMCell\n    rngs_nnx = nnx.Rngs(0)\n    module_nnx = nnx.LSTMCell(\n      in_features=in_features,\n      hidden_features=hidden_features,\n      rngs=rngs_nnx,\n    )\n    carry_nnx = module_nnx.initialize_carry(x.shape, rngs_nnx)\n    # Initialize flax.linen.LSTMCell\n    module_linen = linen.LSTMCell(\n      features=hidden_features,\n    )\n    carry_linen = module_linen.initialize_carry(random.PRNGKey(0), x.shape)\n    variables_linen = module_linen.init(random.PRNGKey(1), carry_linen, x)\n\n    # Copy parameters from flax.linen.LSTMCell to nnx.LSTMCell\n    params_linen = variables_linen['params']\n    # Map the parameters from linen to nnx\n    # Assuming the parameter names and shapes are compatible\n    # For a precise mapping, you might need to adjust parameter names\n    # Get the parameters from nnx module\n    nnx_params = module_nnx.__dict__\n\n    # Map parameters from linen to nnx\n    for gate in ['i', 'f', 'g', 'o']:\n      # Input kernels (input to gate)\n      if gate == 'f':\n        nnx_layer = getattr(module_nnx, f'if_')\n      else:\n        nnx_layer = getattr(module_nnx, f'i{gate}')\n      linen_params = params_linen[f'i{gate}']\n      nnx_layer.kernel[...] = linen_params['kernel']\n      if nnx_layer.use_bias:\n        nnx_layer.bias[...] = linen_params['bias']\n      # Hidden kernels (hidden state to gate)\n      nnx_layer = getattr(module_nnx, f'h{gate}')\n      linen_params = params_linen[f'h{gate}']\n      nnx_layer.kernel[...] = linen_params['kernel']\n      if nnx_layer.use_bias:\n        nnx_layer.bias[...] = linen_params['bias']\n\n    # Run both modules\n    new_carry_nnx, y_nnx = module_nnx(carry_nnx, x)\n    new_carry_linen, y_linen = module_linen.apply(\n      variables_linen, carry_linen, x\n    )\n\n    # Compare outputs\n    np.testing.assert_allclose(y_nnx, y_linen, atol=1e-5)\n    # Compare carries\n    for c_nnx, c_linen in zip(new_carry_nnx, new_carry_linen):\n      np.testing.assert_allclose(c_nnx, c_linen, atol=1e-5)\n\n\nclass TestRNN(absltest.TestCase):\n  def test_rnn_with_lstm_cell(self):\n    \"\"\"Test RNN module using LSTMCell.\"\"\"\n    # Initialize the LSTMCell\n    cell = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      rngs=nnx.Rngs(0),\n    )\n\n    # Initialize the RNN module with the LSTMCell\n    rnn = nnx.RNN(cell)\n\n    # Create input data (batch_size=2, seq_length=5, features=3)\n    x = jnp.ones((2, 5, 3))\n\n    # Initialize the carry\n    carry = cell.initialize_carry((2, 3), nnx.Rngs(0))\n\n    # Run the RNN module\n    outputs = rnn(x, initial_carry=carry)\n\n    self.assertEqual(\n      outputs.shape, (2, 5, 4)\n    )  # Output features should match hidden_features\n\n  def test_rnn_with_gru_cell(self):\n    \"\"\"Test RNN module using GRUCell.\"\"\"\n    # Initialize the GRUCell\n    cell = nnx.GRUCell(\n      in_features=3,\n      hidden_features=4,\n      rngs=nnx.Rngs(1),\n    )\n\n    # Initialize the RNN module with the GRUCell\n    rnn = nnx.RNN(cell)\n\n    # Create input data (batch_size=2, seq_length=5, features=3)\n    x = jnp.ones((2, 5, 3))\n\n    # Initialize the carry\n    carry = cell.initialize_carry((2, 3), nnx.Rngs(1))\n\n    # Run the RNN module\n    outputs = rnn(x, initial_carry=carry)\n\n    self.assertEqual(\n      outputs.shape, (2, 5, 4)\n    )  # Output features should match hidden_features\n\n  def test_rnn_time_major(self):\n    \"\"\"Test RNN module with time_major=True.\"\"\"\n    # Initialize the LSTMCell\n    cell = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      rngs=nnx.Rngs(2),\n    )\n\n    # Initialize the RNN module with time_major=True\n    rnn = nnx.RNN(cell, time_major=True)\n\n    # Create input data (seq_length=5, batch_size=2, features=3)\n    x = jnp.ones((5, 2, 3))\n\n    # Initialize the carry\n    carry = cell.initialize_carry(x.shape[1:2] + x.shape[2:], nnx.Rngs(2))\n\n    # Run the RNN module\n    outputs = rnn(x, initial_carry=carry)\n\n    self.assertEqual(\n      outputs.shape, (5, 2, 4)\n    )  # Output features should match hidden_features\n\n  def test_rnn_reverse(self):\n    \"\"\"Test RNN module with reverse=True.\"\"\"\n    # Initialize the LSTMCell\n    cell = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      rngs=nnx.Rngs(3),\n    )\n\n    # Initialize the RNN module with reverse=True\n    rnn = nnx.RNN(cell, reverse=True)\n\n    # Create input data (batch_size=2, seq_length=5, features=3)\n    x = jnp.tile(jnp.arange(5), (2, 1)).reshape(\n      2, 5, 1\n    )  # Distinct values to check reversal\n    x = jnp.concatenate([x, x, x], axis=-1)  # Shape: (2, 5, 3)\n\n    # Run the RNN module\n    outputs = rnn(x)\n\n    # Check if the outputs are in reverse order\n    outputs_reversed = outputs[:, ::-1, :]\n    # Since we used distinct input values, we can compare outputs to check reversal\n    # For simplicity, just check the shapes here\n    self.assertEqual(outputs.shape, (2, 5, 4))\n    self.assertEqual(outputs_reversed.shape, (2, 5, 4))\n\n  def test_rnn_with_seq_lengths(self):\n    \"\"\"Test RNN module with variable sequence lengths.\"\"\"\n    # Initialize the LSTMCell\n    cell = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      rngs=nnx.Rngs(4),\n    )\n\n    # Initialize the RNN module\n    rnn = nnx.RNN(cell, return_carry=True)\n\n    # Create input data with padding (batch_size=2, seq_length=5, features=3)\n    x = jnp.array(\n      [\n        [\n          [1, 1, 1],\n          [2, 2, 2],\n          [3, 3, 3],\n          [0, 0, 0],\n          [0, 0, 0],\n        ],  # Sequence length 3\n        [\n          [4, 4, 4],\n          [5, 5, 5],\n          [6, 6, 6],\n          [7, 7, 7],\n          [8, 8, 8],\n        ],  # Sequence length 5\n      ]\n    )  # Shape: (2, 5, 3)\n\n    seq_lengths = jnp.array([3, 5])  # Actual lengths for each sequence\n\n    # Initialize the carry\n    carry = cell.initialize_carry((2, 3), nnx.Rngs(4))\n\n    # Run the RNN module\n    final_carry, outputs = rnn(x, initial_carry=carry, seq_lengths=seq_lengths)\n\n    self.assertEqual(outputs.shape, (2, 5, 4))\n\n    self.assertEqual(\n      final_carry[0].shape, (2, 4)\n    )  # c: (batch_size, hidden_features)\n    self.assertEqual(\n      final_carry[1].shape, (2, 4)\n    )  # h: (batch_size, hidden_features)\n\n    # Todo: a better test by matching the outputs with the expected values\n\n  def test_rnn_with_keep_order(self):\n    \"\"\"Test RNN module with reverse=True and keep_order=True.\"\"\"\n    # Initialize the LSTMCell\n    cell = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      rngs=nnx.Rngs(5),\n    )\n\n    # Initialize the RNN module with reverse=True and keep_order=True\n    rnn = nnx.RNN(cell, reverse=True, keep_order=True)\n\n    # Create input data (batch_size=2, seq_length=5, features=3)\n    x = jnp.tile(jnp.arange(5), (2, 1)).reshape(\n      2, 5, 1\n    )  # Distinct values to check reversal\n    x = jnp.concatenate([x, x, x], axis=-1)  # Shape: (2, 5, 3)\n\n    # Initialize the carry\n    carry = cell.initialize_carry((2, 3), nnx.Rngs(5))\n\n    # Run the RNN module\n    outputs = rnn(x, initial_carry=carry)\n\n    # Check if the outputs are in the original order despite processing in reverse\n    self.assertEqual(outputs.shape, (2, 5, 4))\n\n  def test_rnn_equivalence_with_flax_linen(self):\n    \"\"\"Test that nnx.RNN produces the same outputs as flax.linen.RNN.\"\"\"\n    in_features = 3\n    hidden_features = 4\n    seq_length = 5\n    batch_size = 2\n    key = random.PRNGKey(42)\n\n    # Create input data\n    x = random.normal(key, (batch_size, seq_length, in_features))\n\n    # Initialize nnx.LSTMCell and RNN\n    rngs_nnx = nnx.Rngs(0)\n    cell_nnx = nnx.LSTMCell(\n      in_features=in_features,\n      hidden_features=hidden_features,\n      rngs=rngs_nnx,\n    )\n    rnn_nnx = nnx.RNN(cell_nnx)\n\n    # Initialize flax.linen.LSTMCell and RNN\n    cell_linen = linen.LSTMCell(features=hidden_features)\n    rnn_linen = linen.RNN(cell_linen)\n    carry_linen = cell_linen.initialize_carry(random.PRNGKey(0), x[:, 0].shape)\n    variables_linen = rnn_linen.init(random.PRNGKey(1), x)\n\n    # Copy parameters from flax.linen to nnx\n    params_linen = variables_linen['params']['cell']\n    # Copy cell parameters\n    for gate in ['i', 'f', 'g', 'o']:\n      # Input kernels\n      if gate == 'f':\n        nnx_layer = getattr(cell_nnx, f'if_')\n      else:\n        nnx_layer = getattr(cell_nnx, f'i{gate}')\n      linen_params = params_linen[f'i{gate}']\n      nnx_layer.kernel[...] = linen_params['kernel']\n      if nnx_layer.use_bias:\n        nnx_layer.bias[...] = linen_params['bias']\n      # Hidden kernels\n      nnx_layer = getattr(cell_nnx, f'h{gate}')\n      linen_params = params_linen[f'h{gate}']\n      nnx_layer.kernel[...] = linen_params['kernel']\n      if nnx_layer.use_bias:\n        nnx_layer.bias[...] = linen_params['bias']\n\n    # Initialize carries\n    carry_nnx = cell_nnx.initialize_carry((batch_size, in_features), rngs_nnx)\n\n    # Run nnx.RNN\n    outputs_nnx = rnn_nnx(x, initial_carry=carry_nnx)\n\n    # Run flax.linen.RNN\n    outputs_linen = rnn_linen.apply(\n      variables_linen, x, initial_carry=carry_linen\n    )\n\n    # Compare outputs\n    np.testing.assert_allclose(outputs_nnx, outputs_linen, atol=1e-5)\n\n  def test_rnn_with_unroll(self):\n    \"\"\"Test RNN module with unroll parameter.\"\"\"\n    # Initialize the LSTMCell\n    cell = nnx.LSTMCell(in_features=3, hidden_features=4, rngs=nnx.Rngs(6))\n\n    # Initialize the RNN module with unroll=2\n    rnn = nnx.RNN(cell, unroll=2)\n\n    # Create input data (batch_size=2, seq_length=6, features=3)\n    x = jnp.ones((2, 6, 3))\n\n    # Initialize the carry\n    carry = cell.initialize_carry((2, 3), nnx.Rngs(6))\n\n    # Run the RNN module\n    outputs = rnn(x, initial_carry=carry)\n\n    self.assertEqual(\n      outputs.shape, (2, 6, 4)\n    )  # Output features should match hidden_features\n\n  def test_rnn_with_custom_cell(self):\n    \"\"\"Test RNN module with a custom RNN cell.\"\"\"\n\n    class CustomRNNCell(nnx.Module):\n      \"\"\"A simple custom RNN cell.\"\"\"\n\n      in_features: int\n      hidden_features: int\n\n      def __init__(self, in_features, hidden_features, rngs):\n        self.in_features = in_features\n        self.hidden_features = hidden_features\n        self.rngs = rngs\n        self.dense = nnx.Linear(\n          in_features=in_features + hidden_features,\n          out_features=hidden_features,\n          rngs=rngs,\n        )\n\n      def __call__(self, carry, inputs):\n        h = carry\n        x = jnp.concatenate([inputs, h], axis=-1)\n        new_h = jax.nn.tanh(self.dense(x))\n        return new_h, new_h\n\n      def initialize_carry(self, input_shape, rngs):\n        batch_size = input_shape[0]\n        h = jnp.zeros((batch_size, self.hidden_features))\n        return h\n\n      @property\n      def num_feature_axes(self) -> int:\n        return 1\n\n    # Initialize the custom RNN cell\n    cell = CustomRNNCell(in_features=3, hidden_features=4, rngs=nnx.Rngs(7))\n\n    # Initialize the RNN module\n    rnn = nnx.RNN(cell)\n\n    # Create input data (batch_size=2, seq_length=5, features=3)\n    x = jnp.ones((2, 5, 3))\n\n    # Initialize the carry\n    carry = cell.initialize_carry((2, 3), cell.rngs)\n\n    # Run the RNN module\n    outputs = rnn(x, initial_carry=carry)\n\n    self.assertEqual(\n      outputs.shape, (2, 5, 4)\n    )  # Output features should match hidden_features\n\n  def test_rnn_with_different_dtypes(self):\n    \"\"\"Test RNN module with different data types.\"\"\"\n    # Initialize the LSTMCell with float16\n    cell = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      dtype=jnp.float16,\n      param_dtype=jnp.float16,\n      rngs=nnx.Rngs(8),\n    )\n\n    # Initialize the RNN module\n    rnn = nnx.RNN(cell)\n\n    # Create input data (batch_size=2, seq_length=5, features=3)\n    x = jnp.ones((2, 5, 3), dtype=jnp.float16)\n\n    # Initialize the carry\n    carry = cell.initialize_carry((2, 3), nnx.Rngs(8))\n\n    # Run the RNN module\n    outputs = rnn(x, initial_carry=carry)\n\n    self.assertEqual(outputs.dtype, jnp.float16)\n    self.assertEqual(outputs.shape, (2, 5, 4))\n\n  def test_rnn_with_variable_batch_size(self):\n    \"\"\"Test RNN module with variable batch sizes.\"\"\"\n    # Initialize the LSTMCell\n    cell = nnx.LSTMCell(\n      in_features=3,\n      hidden_features=4,\n      rngs=nnx.Rngs(9),\n    )\n\n    # Initialize the RNN module\n    rnn = nnx.RNN(cell)\n\n    for batch_size in [1, 2, 5]:\n      # Create input data (batch_size, seq_length=5, features=3)\n      x = jnp.ones((batch_size, 5, 3))\n\n      # Initialize the carry\n      carry = cell.initialize_carry((batch_size, 3), nnx.Rngs(9))\n\n      # Run the RNN module\n      outputs = rnn(x, initial_carry=carry)\n\n      self.assertEqual(outputs.shape, (batch_size, 5, 4))\n\n  def test_recurrent_dropout(self):\n    class LSTMWithRecurrentDropout(nnx.OptimizedLSTMCell):\n      def __init__(\n        self,\n        *,\n        rngs: nnx.Rngs,\n        in_features: int,\n        hidden_features: int,\n        dropout_rate: float,\n        **kwargs,\n      ):\n        super().__init__(\n          in_features=in_features,\n          hidden_features=hidden_features,\n          rngs=rngs,\n          keep_rngs=True,\n          **kwargs,\n        )\n        self.recurrent_dropout = nnx.Dropout(\n          rate=dropout_rate, rng_collection='recurrent_dropout', rngs=rngs\n        )\n\n      def __call__(self, carry, x):\n        h, c = carry\n        new_h, new_c = super().__call__((h, c), x)\n        new_h = jax.tree.map(self.recurrent_dropout, new_h)\n        return new_h, new_c\n\n    class RNNWithRecurrentDropout(nnx.Module):\n      def __init__(\n        self,\n        *,\n        rngs: nnx.Rngs,\n        in_features: int,\n        hidden_features: int = 32,\n        dropout_rate: float = 0.5,\n        recurrent_dropout_rate: float = 0.25,\n      ):\n        cell = LSTMWithRecurrentDropout(\n          in_features=in_features,\n          hidden_features=hidden_features,\n          rngs=rngs,\n          dropout_rate=recurrent_dropout_rate,\n        )\n        self.lstm = nnx.RNN(cell, broadcast_rngs='recurrent_dropout')\n        self.dropout = nnx.Dropout(dropout_rate, rngs=rngs)\n        self.dense = nnx.Linear(\n          in_features=hidden_features, out_features=1, rngs=rngs\n        )\n\n      def __call__(self, x):\n        x = self.lstm(x)\n        x = self.dropout(x)\n        x = x[:, -1, :]  # Use only the final hidden state\n        return self.dense(x)\n\n    model = RNNWithRecurrentDropout(\n      in_features=32,\n      hidden_features=64,\n      dropout_rate=0.2,\n      recurrent_dropout_rate=0.1,\n      rngs=nnx.Rngs(0, recurrent_dropout=1),\n    )\n\n    x = jnp.ones((8, 10, 32))\n    self.assertEqual(model.lstm.cell.recurrent_dropout.rngs.count[...], 0)\n    y = model(x)\n\n    self.assertEqual(y.shape, (8, 1))\n    self.assertEqual(model.lstm.cell.recurrent_dropout.rngs.count[...], 1)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/nn/stochastic_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nimport jax.numpy as jnp\nimport numpy as np\n\nfrom flax import nnx\n\nimport pytest\n\n\nclass TestStochastic:\n  def test_dropout_internal_rngs(self):\n    n = 0\n    m1 = nnx.Dropout(rate=0.5, deterministic=False, rngs=nnx.Rngs(dropout=0))\n    m2 = nnx.Dropout(rate=0.5, deterministic=False)\n    rngs2 = nnx.Rngs(dropout=0).fork()\n\n    @nnx.jit\n    def f(m, x, rngs=None):\n      nonlocal n\n      n += 1\n      return m(x, rngs=rngs)\n\n    x = jnp.ones((1, 10))\n    assert m1.rngs is not None and m1.rngs.count[...] == 0\n\n    y1 = f(m1, x)\n    assert n == 1\n    assert m1.rngs.count[...] == 1\n    y2 = f(m2, x, rngs=rngs2)\n    assert n == 2\n    assert rngs2.dropout.count[...] == 1\n    np.testing.assert_allclose(y1, y2)\n\n    y1 = f(m1, x)\n    assert m1.rngs.count[...] == 2\n    y2 = f(m2, x, rngs=rngs2)\n    assert rngs2.dropout.count[...] == 2\n    np.testing.assert_allclose(y1, y2)\n\n    assert n == 2\n\n  def test_dropout_rng_override(self):\n    m1 = nnx.Dropout(rate=0.5, deterministic=False, rngs=nnx.Rngs(dropout=0))\n    m2 = nnx.Dropout(rate=0.5, deterministic=False, rngs=nnx.Rngs(dropout=1))\n    x = jnp.ones((1, 10))\n\n    y1 = m1(x)\n    y2 = m2(x)\n    with pytest.raises(AssertionError):\n      np.testing.assert_allclose(y1, y2)\n\n    y2 = m2(x, rngs=nnx.Rngs(dropout=0).fork())\n    np.testing.assert_allclose(y1, y2)\n\n  def test_dropout_arg_override(self):\n    m = nnx.Dropout(rate=0.5)\n    x = jnp.ones((1, 10))\n\n    # deterministic call arg provided\n    m(x, deterministic=True)\n    # deterministic constructor arg provided\n    m.set_attributes(deterministic=True)\n    y = m(x)\n    # both deterministic call and constructor arg provided\n    with pytest.raises(AssertionError):\n      np.testing.assert_allclose(\n        y, m(x, deterministic=False, rngs=nnx.Rngs(dropout=0))\n      )\n    # no rng arg provided\n    m.set_attributes(deterministic=False)\n    with pytest.raises(\n      ValueError,\n      match='`deterministic` is False, but no `rngs` argument was provided to Dropout',\n    ):\n      m(x)\n\n  def test_dropout_arg_override_view(self):\n    m = nnx.Dropout(rate=0.5)\n    x = jnp.ones((1, 10))\n\n    # deterministic call arg provided\n    m(x, deterministic=True)\n    # deterministic constructor arg provided\n    new_m = nnx.view(m, deterministic=True)\n    y = new_m(x)\n    # both deterministic call and constructor arg provided\n    with pytest.raises(AssertionError):\n      np.testing.assert_allclose(\n        y, new_m(x, deterministic=False, rngs=nnx.Rngs(dropout=0))\n      )\n    # no rng arg provided\n    new_m = nnx.view(m, deterministic=False)\n    with pytest.raises(\n      ValueError,\n      match='`deterministic` is False, but no `rngs` argument was provided to Dropout',\n    ):\n      new_m(x)"
  },
  {
    "path": "tests/nnx/optimizer_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom flax import nnx\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nimport optax\n\ndef assert_equal(path, x, y):\n  np.testing.assert_array_equal(x, y, err_msg=f'Mismatch at path: {path}')\n\n\ndef assert_not_equal(path, x, y):\n  np.testing.assert_(\n      np.any(np.not_equal(x, y)), msg=f'Unexpected match at path: {path}'\n  )\n\n\nclass Model(nnx.Module):\n\n  def __init__(self, in_features, out_features, rngs):\n    self.linear1 = nnx.Linear(in_features, 3, rngs=rngs)\n    self.linear2 = nnx.Linear(3, out_features, rngs=rngs)\n\n  def __call__(self, x):\n    return self.linear2(self.linear1(x))\n\n\nclass TestOptimizer(parameterized.TestCase):\n\n  @parameterized.parameters(\n      {'module_cls': nnx.Linear},\n      {'module_cls': Model},\n  )\n  def test_split_merge(self, module_cls):\n    x = jax.random.normal(jax.random.key(0), (1, 2))\n    model = module_cls(2, 4, rngs=nnx.Rngs(0))\n    tx = optax.adam(1e-3)\n    optimizer = nnx.Optimizer(model, tx, wrt=nnx.Param)\n    out = model(x)\n    graphdef, optimizer = nnx.split(optimizer)\n    optimizer = nnx.merge(graphdef, optimizer)\n    np.testing.assert_allclose(out, model(x))\n\n  def test_update(self):\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    optimizer = nnx.Optimizer(model, optax.adamw(0.1), wrt=nnx.Param)\n\n    def loss_fn(model):\n      params = nnx.state(model)\n      loss = sum(jnp.sum(x**2) for x in jax.tree.leaves(params))\n      return loss\n\n    grads = nnx.grad(loss_fn)(model)\n    optimizer.update(model, grads)\n\n  def test_sharding_propagation(self):\n    with jax.set_mesh(\n        jax.make_mesh(\n            (1, 1),\n            ('a', 'b'),\n            axis_types=(jax.sharding.AxisType.Auto,) * len(('a', 'b')),\n        )\n    ):\n      model = nnx.Linear(\n          2,\n          3,\n          rngs=nnx.Rngs(0),\n          kernel_init=nnx.with_partitioning(\n              nnx.initializers.lecun_normal(),\n              sharding=('a', 'b'),\n          ),\n          use_bias=False,\n      )\n      optimizer = nnx.Optimizer(model, optax.adamw(0.1), wrt=nnx.Param)\n\n    state = nnx.state(optimizer)\n    partition_spec = nnx.get_partition_spec(state)\n\n    self.assertEqual(state['opt_state'][0]['mu']['kernel'].out_sharding, ('a', 'b'))\n    self.assertEqual(\n      partition_spec['opt_state'][0]['mu']['kernel'].get_value(),\n      jax.sharding.PartitionSpec('a', 'b'),\n    )\n\n  @parameterized.product(\n    module_cls=[nnx.Linear, Model],\n    jit_decorator=[lambda f: f, nnx.jit, jax.jit],\n    optimizer=[optax.sgd, optax.adam],\n  )\n  def test_jit(self, module_cls, jit_decorator, optimizer):\n    x = jax.random.normal(jax.random.key(0), (1, 2))\n    y = jnp.ones((1, 4))\n    model = module_cls(2, 4, rngs=nnx.Rngs(0))\n    tx = optimizer(\n        1e-3\n    )  # TODO: this doesn't work with adam optimizer for some reason\n    state = nnx.ModelAndOptimizer(model, tx)\n\n    if jit_decorator == jax.jit:\n      model_static, model_state = nnx.split(model)\n      loss_fn = lambda graphdef, state, x, y: (\n          (nnx.merge(graphdef, state)(x) - y) ** 2\n      ).mean()\n      initial_loss = loss_fn(model_static, model_state, x, y)\n\n      def jax_jit_train_step(graphdef, state, x, y):\n        state = nnx.merge(graphdef, state)\n        model_static, model_state = nnx.split(model)\n        grads = jax.grad(loss_fn, argnums=1)(model_static, model_state, x, y)\n        state.update(grads)\n        return nnx.split(state)\n\n      graphdef, state = jit_decorator(jax_jit_train_step)(\n          *nnx.split(state), x, y\n      )\n      state = nnx.merge(graphdef, state)\n      new_loss = loss_fn(*nnx.split(state.model), x, y)\n\n    else:\n      loss_fn = lambda model, x, y: ((model(x) - y) ** 2).mean()\n      initial_loss = loss_fn(state.model, x, y)\n\n      def nnx_jit_train_step(optimizer: nnx.Optimizer, x, y):\n        grads = nnx.grad(loss_fn)(optimizer.model, x, y)\n        optimizer.update(grads)\n\n      jit_decorator(nnx_jit_train_step)(state, x, y)\n      new_loss = loss_fn(state.model, x, y)\n\n    self.assertTrue(new_loss < initial_loss)\n\n  @parameterized.product(\n      module_cls=[nnx.Linear, Model],\n      jit_decorator=[lambda f: f, nnx.jit, jax.jit],\n      optimizer=[optax.lbfgs],\n  )\n  def test_jit_linesearch(self, module_cls, jit_decorator, optimizer):\n    x = jax.random.normal(jax.random.key(0), (1, 2))\n    y = jnp.ones((1, 4))\n    model = module_cls(2, 4, rngs=nnx.Rngs(0))\n    tx = optimizer(1e-3)\n    state = nnx.ModelAndOptimizer(model, tx)\n\n    if jit_decorator == jax.jit:\n      model_static, model_state = nnx.split(state.model)\n      loss_fn = lambda graphdef, state, x, y: (\n          (nnx.merge(graphdef, state)(x) - y) ** 2\n      ).mean()\n      initial_loss = loss_fn(model_static, model_state, x, y)\n\n      def jax_jit_train_step(graphdef, state, x, y):\n        state = nnx.merge(graphdef, state)\n        model_static, model_state = nnx.split(state.model)\n        grads = jax.grad(loss_fn, argnums=1)(model_static, model_state, x, y)\n        state.update(\n            grads,\n            grad=grads,\n            value=initial_loss,\n            value_fn=lambda state: loss_fn(model_static, state, x, y),\n        )\n        return nnx.split(state)\n\n      graphdef, state = jit_decorator(jax_jit_train_step)(\n          *nnx.split(state), x, y\n      )\n      state = nnx.merge(graphdef, state)\n      new_loss = loss_fn(*nnx.split(state.model), x, y)\n\n    else:\n      graphdef = nnx.graphdef(model)\n      loss_fn = lambda model, x, y: ((model(x) - y) ** 2).mean()\n\n      loss_fn_split = lambda state: loss_fn(nnx.merge(graphdef, state), x, y)\n\n      initial_loss = loss_fn(state.model, x, y)\n\n      def nnx_jit_train_step(optimizer: nnx.Optimizer, x, y):\n        grads = nnx.grad(loss_fn)(optimizer.model, x, y)\n        optimizer.update(\n            grads, grad=grads, value=initial_loss, value_fn=loss_fn_split\n        )\n\n      jit_decorator(nnx_jit_train_step)(state, x, y)\n      new_loss = loss_fn(state.model, x, y)\n\n    self.assertTrue(new_loss < initial_loss)\n\n  @parameterized.product(\n      module_cls=[nnx.Linear, Model],\n      optimizer=[optax.sgd, optax.adam],\n  )\n  def test_metrics(self, module_cls, optimizer):\n    class TrainState(nnx.ModelAndOptimizer):\n\n      def __init__(self, model, tx, metrics):\n        self.metrics = metrics\n        super().__init__(model, tx)\n\n      def update(self, *, grads, **updates):  # type: ignore[signature-mismatch]\n        self.metrics.update(**updates)\n        super().update(grads)\n\n    x = jax.random.normal(jax.random.key(0), (1, 2))\n    y = jnp.ones((1, 4))\n    model = module_cls(2, 4, rngs=nnx.Rngs(0))\n    tx = optax.adam(1e-3)\n    metrics = nnx.metrics.Average()\n    state = TrainState(model, tx, metrics)\n\n    loss_fn = lambda model: ((model(x) - y) ** 2).mean()\n    grads = nnx.grad(loss_fn)(state.model)\n    state.update(grads=grads, values=loss_fn(state.model))\n    initial_loss = state.metrics.compute()\n    state.update(grads=grads, values=loss_fn(state.model))\n    self.assertTrue(state.metrics.compute() < initial_loss)\n\n  @parameterized.parameters(\n      {'variable': nnx.Param},\n      {'variable': nnx.LoRAParam},\n      {'variable': (nnx.Param, nnx.LoRAParam)},\n  )\n  def test_wrt_update(self, variable):\n    in_features = 4\n    out_features = 10\n    model = nnx.LoRA(\n        in_features=in_features,\n        lora_rank=2,\n        out_features=out_features,\n        base_module=Model(\n            in_features=in_features, out_features=out_features, rngs=nnx.Rngs(0)\n        ),\n        rngs=nnx.Rngs(1),\n    )\n    state = nnx.Optimizer(model, optax.adam(1e-3), wrt=variable)\n    prev_variables, prev_other_variables = nnx.clone(nnx.state(model, variable, ...))\n\n    x = jnp.ones((1, 4))\n    y = jnp.ones((1, 10))\n    loss_fn = lambda model, x, y: ((model(x) - y) ** 2).mean()\n    grad_fn = nnx.grad(loss_fn, argnums=nnx.DiffState(0, variable))\n\n    def step():\n      grads = grad_fn(model, x, y)\n      initial_loss = loss_fn(model, x, y)\n      state.update(model, grads)\n      self.assertTrue(loss_fn(model, x, y) < initial_loss)\n\n    # Since lora_b is initialized to zeros by default, the gradient flow to lora_a\n    # will be zeroed out in first call. Thus, run the step twice to make sure\n    # lora_a is updated.\n    for _ in range(2):\n      step()\n\n    # make sure only the Variable's filtered in `wrt` are changed, and the others are unchanged\n    variables, other_variables = nnx.state(model, variable, ...)\n\n    jax.tree.map_with_path(assert_not_equal, prev_variables, variables)\n\n    if other_variables:\n      jax.tree.map_with_path(\n          assert_equal, prev_other_variables, other_variables\n      )\n\n  @parameterized.parameters(\n      {'variable': nnx.Param},\n      # {'variable': nnx.LoRAParam},\n      {'variable': (nnx.Param, nnx.LoRAParam)},\n  )\n  def test_wrt_update_linesearch(self, variable):\n    in_features = 4\n    out_features = 10\n    model = nnx.LoRA(\n        in_features=in_features,\n        lora_rank=2,\n        out_features=out_features,\n        base_module=Model(\n            in_features=in_features, out_features=out_features, rngs=nnx.Rngs(0)\n        ),\n        rngs=nnx.Rngs(1),\n    )\n    state = nnx.Optimizer(model, optax.lbfgs(), wrt=variable)\n    prev_variables, prev_other_variables = nnx.clone(nnx.state(model, variable, ...))\n\n    x = jnp.ones((1, 4))\n    y = jnp.ones((1, 10))\n    loss_fn = lambda model, x, y: ((model(x) - y) ** 2).mean()\n\n    grad_fn = nnx.grad(loss_fn, argnums=nnx.DiffState(0, variable))\n    graphdef = nnx.graphdef(model)\n    loss_fn_split = lambda state: loss_fn(nnx.merge(graphdef, state), x, y)\n\n    def step():\n      grads = grad_fn(model, x, y)\n      initial_loss = loss_fn(model, x, y)\n      state.update(\n          model, grads, grad=grads, value_fn=loss_fn_split, value=initial_loss\n      )\n      self.assertTrue(loss_fn(model, x, y) < initial_loss)\n\n    # Since lora_b is initialized to zeros by default, the gradient flow to lora_a\n    # will be zeroed out in first call. Thus, run the step twice to make sure\n    # lora_a is updated.\n    for _ in range(2):\n      step()\n\n    # make sure only the Variable's filtered in `wrt` are changed, and the others are unchanged\n    variables, other_variables = nnx.state(model, variable, ...)\n\n    jax.tree.map_with_path(assert_not_equal, prev_variables, variables)\n\n    if other_variables:\n      jax.tree.map_with_path(\n          assert_equal, prev_other_variables, other_variables\n      )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/partitioning_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom absl.testing import absltest\nfrom flax import nnx\nimport jax\nimport jax.numpy as jnp\n\n\nclass TestPartitioning(absltest.TestCase):\n\n  def test_partition(self):\n    m = nnx.Dict(\n      a=nnx.List([nnx.Param(jnp.array(1)), nnx.BatchStat(jnp.array(2))]),\n      b=nnx.Param(jnp.array(2)),\n      c=100,\n    )\n\n    graphdef, params, rest = nnx.split(m, nnx.Param, ...)\n\n    self.assertLen(params, 2)\n    self.assertLen(rest, 1)\n\n    # check params\n    self.assertEqual(params['a'][0][...], m.a[0][...])\n    self.assertEqual(params['b'][...], m.b[...])\n\n    # check rest\n    self.assertEqual(rest['a'][1][...], m.a[1][...])\n\n    m2 = nnx.merge(graphdef, params, rest)\n\n    self.assertEqual(m2.a[0][...], m.a[0][...])\n    self.assertEqual(m2.a[1][...], m.a[1][...])\n    self.assertEqual(m2.b[...], m.b[...])\n    self.assertEqual(m2.c, 100)\n\n  def test_complete_partitioning(self):\n    m = nnx.Dict(\n      a=nnx.List([nnx.Param(1), nnx.Param(2), nnx.Variable(3)]),\n      b=nnx.Dict(c=nnx.Param(1), d=nnx.BatchStat(2)),\n    )\n\n    # no error\n    nnx.split(m, nnx.Param, nnx.BatchStat, nnx.Variable)\n\n  def test_complete_partitioning_plus_ellipsis(self):\n    m = nnx.Dict(\n      a=nnx.List([nnx.Param(1), nnx.Param(2), nnx.Variable(3)]),\n      b=nnx.Dict(c=nnx.Param(1), d=nnx.BatchStat(2)),\n    )\n\n    # no error if additional ... is passed at the end\n    nnx.split(m, nnx.Param, nnx.BatchStat, nnx.Variable, ...)\n\n  def test_inclomplete_partition_error(self):\n    m = nnx.Dict(\n      a=nnx.List([nnx.Param(1), nnx.Param(2), nnx.Variable(3)]),\n      b=nnx.Dict(c=nnx.Param(1), d=nnx.BatchStat(2)),\n    )\n\n    with self.assertRaisesRegex(\n        ValueError, 'Non-exhaustive filters, got a non-empty remainder'\n    ):\n      nnx.split(m, nnx.Param)\n\n  def test_ellipsis_not_last_error(self):\n    m = nnx.Dict(\n      a=nnx.List([nnx.Param(1), nnx.Param(2), nnx.Variable(3)]),\n      b=nnx.Dict(c=nnx.Param(1), d=nnx.BatchStat(2)),\n    )\n\n    with self.assertRaisesRegex(\n        ValueError, '`...` or `True` can only be used as the last filters'\n    ):\n      nnx.split(m, ..., nnx.Param)\n\n  def test_update_from(self):\n    m = nnx.Dict(\n      a=nnx.List([nnx.Param(jnp.array(1)), nnx.BatchStat(jnp.array(3))]),\n      b=nnx.Param(jnp.array(2)),\n      c=100,\n    )\n\n    state = nnx.split(\n      m,\n    )[1]\n    state = jax.tree.map(lambda x: x * 2, state)\n\n    nnx.update(m, state)\n\n    self.assertEqual(m.a[0][...], 2)\n    self.assertEqual(m.a[1][...], 6)\n    self.assertEqual(m.b[...], 4)\n    self.assertEqual(m.c, 100)\n\n  def test_update_from_with_array_leaf(self):\n    m = nnx.Dict(\n      a=nnx.List([nnx.Param(jnp.array(1)), nnx.BatchStat(jnp.array(3))]),\n      b=nnx.Param(jnp.array(2)),\n      c=nnx.Variable(jax.numpy.array(100)),\n    )\n\n    graphdef, state = nnx.split(m)\n    state = jax.tree.map(lambda x: x * 2, state)\n\n    nnx.update(m, state)\n\n    self.assertEqual(m.a[0][...], 2)\n    self.assertEqual(m.a[1][...], 6)\n    self.assertEqual(m.b[...], 4)\n    self.assertEqual(m.c[...], 200)\n\n  def test_grad_example(self):\n    m = nnx.Dict(\n      a=nnx.List([nnx.Param(jnp.array(1.0)), nnx.BatchStat(jnp.array(-10))]),\n      b=nnx.Param(jnp.array(2.0)),\n      c=100,\n    )\n\n    params = nnx.state(m, nnx.Param)\n\n    def loss(params):\n      return sum(2 * p for p in jax.tree_util.tree_leaves(params))\n\n    grads = jax.grad(loss)(params)\n    nnx.update(m, grads)\n\n    self.assertEqual(m.a[0][...], 2.0)\n    self.assertEqual(m.a[1][...], -10)\n    self.assertEqual(m.b[...], 2.0)\n    self.assertEqual(m.c, 100)\n\n  def test_get_paritition(self):\n    m = nnx.Dict(\n      a=nnx.List([nnx.Param(jnp.array(10.0)), nnx.Param(jnp.array(20.0))]),\n      b=nnx.Param(jnp.array(10.0)),\n      c=7,\n      d=5.0,\n    )\n\n    state = nnx.state(m, nnx.Variable)\n    self.assertEqual(state['a'][0][...], m.a[0][...])\n    self.assertEqual(state['a'][1][...], m.a[1][...])\n    self.assertEqual(state['b'][...], m.b[...])\n    self.assertIsNot(state['b'], state['a'][0])\n    self.assertLen(nnx.to_flat_state(state), 3)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/rngs_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom functools import partial\nfrom typing import Any\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nfrom absl.testing import absltest, parameterized\n\nfrom flax import nnx\nfrom flax import errors\n\n\nclass TestRngs(parameterized.TestCase):\n  def test_call(self):\n    rngs = nnx.Rngs(0)\n    key = rngs()\n\n  def test_fallback(self):\n    rngs = nnx.Rngs(0)\n    key = rngs.dropout()\n\n  def test_fallback_error_no_default(self):\n    rngs = nnx.Rngs(some_name=0)\n    with self.assertRaisesRegex(AttributeError, 'No RngStream named'):\n      key = rngs.dropout()\n\n  def test_rng_stream(self):\n    key0 = jax.random.key(0)\n    rngs = nnx.Rngs(params=key0)\n    self.assertEqual(rngs.params.count[...], 0)\n\n    key1 = rngs.params()\n    self.assertEqual(rngs.params.count[...], 1)\n    self.assertIs(rngs.params.key[...], key0)\n    self.assertFalse(jnp.allclose(key0, key1))\n\n    key2 = rngs.params()\n    self.assertEqual(rngs.params.count[...], 2)\n    self.assertIs(rngs.params.key[...], key0)\n    self.assertFalse(jnp.allclose(key1, key2))\n\n  def test_rng_trace_level_constraints(self):\n    rngs = nnx.Rngs(0)\n\n    @jax.jit\n    def f():\n      with self.assertRaisesRegex(\n        errors.TraceContextError,\n        'Cannot mutate RngCount from a different trace level',\n      ):\n        rngs.params()\n\n    f()\n\n    rngs1: Any = None\n\n    @jax.jit\n    def h():\n      nonlocal rngs1\n      rngs1 = nnx.Rngs(1)\n\n    h()\n\n    self.assertIsInstance(rngs1, nnx.Rngs)\n    with self.assertRaisesRegex(\n      errors.TraceContextError,\n      'Cannot mutate RngCount from a different trace level',\n    ):\n      rngs1.params()\n\n  def test_jit_updates(self):\n    class Foo(nnx.Module):\n      def __init__(self, not_rngs):\n        rngs = not_rngs\n        self.linear = nnx.Linear(2, 2, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False)\n\n      def __call__(self, x, rngs):\n        x = self.linear(x)\n        x = self.dropout(x, rngs=rngs)\n        return x\n\n    rngs = nnx.Rngs(0)\n    m = Foo(rngs)\n\n    # +1 for the Linear kernel, +1 for the Linear bias\n    self.assertEqual(rngs['default'].count[...], 2)\n\n    @nnx.jit\n    def f(m: Foo, x: jax.Array, not_rngs: nnx.Rngs):\n      rngs = not_rngs\n      x = m(x, rngs)\n      x = m(x, rngs)\n      return x\n\n    x = jnp.ones((2, 2))\n    x = f(m, x, rngs)\n\n    # +1 for the Dropout mask\n    self.assertEqual(rngs['default'].count[...], 4)\n\n  def test_lifting_rng_state(self):\n    class Foo(nnx.Module):\n      def __init__(self, rngs):\n        self.rngs = rngs\n        self.dropout = nnx.Dropout(0.5, deterministic=False)\n        self.linear = nnx.Linear(2, 3, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.linear(x)\n        x = self.dropout(x, rngs=self.rngs)\n        return x\n\n    rngs = nnx.Rngs(params=0, dropout=1)\n    m = Foo(rngs)\n    graphdef, params, rng_counts, dropout_keys, param_keys = nnx.split(\n      m, nnx.Param, nnx.RngCount, 'dropout', 'params'\n    )\n\n    self.assertEqual(m.rngs.params.count[...], 2)\n    self.assertEqual(m.rngs['dropout'].count[...], 0)\n    self.assertLen(nnx.to_flat_state(dropout_keys), 1)\n    self.assertLen(nnx.to_flat_state(param_keys), 1)\n    self.assertLen(nnx.to_flat_state(rng_counts), 2)\n\n    # split dropout keys\n    split_dropout_keys = jax.tree.map(\n      lambda x: jax.random.split(x, 4), dropout_keys\n    )\n    # replicate params\n    params = jax.tree.map(lambda x: jnp.stack([x] * 4, axis=0), params)\n\n    @partial(\n      jax.vmap,\n      in_axes=(0, 0, None, None, 0),\n      out_axes=(0, 0, None),\n    )\n    def f(params, dropout_keys, param_keys, rng_counts, x):\n      m = nnx.merge(graphdef, params, dropout_keys, param_keys, rng_counts)\n      y = m(x)\n      _, params, rng_counts, dropout_keys, param_keys = nnx.split(\n        m, nnx.Param, nnx.RngCount, 'dropout', 'params'\n      )\n      return y, params, rng_counts\n\n    x = jnp.ones((4, 1, 2))\n    y, params, rng_counts = f(\n      params,\n      split_dropout_keys,\n      param_keys,\n      rng_counts,\n      x,\n    )\n\n    nnx.update(m, params, dropout_keys, param_keys, rng_counts)\n\n    self.assertEqual(y.shape, (4, 1, 3))\n    self.assertEqual(m.rngs.params.count[...], 2)\n    self.assertEqual(m.rngs['dropout'].count[...], 1)\n\n  @parameterized.parameters(True, False)\n  def test_reseed(self, graph):\n    class Model(nnx.Module):\n      def __init__(self, rngs):\n        self.linear = nnx.Linear(2, 3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, rngs=rngs)\n\n      def __call__(self, x):\n        return self.dropout(self.linear(x))\n\n    model = Model(nnx.Rngs(params=0, dropout=42))\n    x = jnp.ones((1, 2))\n\n    y1 = model(x)\n\n    nnx.reseed(model, graph=graph, dropout=42)\n    y2 = model(x)\n\n    np.testing.assert_allclose(y1, y2)\n\n  @parameterized.parameters(True, False)\n  def test_split_rngs(self, graph):\n    rngs = nnx.Rngs(params=0, dropout=1)\n    result = nnx.split_rngs(rngs, splits=5, graph=graph)\n    if graph:\n      self.assertEqual(rngs.params.key.shape, (5,))\n      self.assertEqual(rngs['dropout'].key.shape, (5,))\n      nnx.restore_rngs(result)\n      self.assertEqual(rngs.params.key.shape, ())\n      self.assertEqual(rngs['dropout'].key.shape, ())\n    else:\n      self.assertEqual(rngs.params.key.shape, ())\n      self.assertEqual(rngs['dropout'].key.shape, ())\n      self.assertEqual(result.params.key.shape, (5,))\n      self.assertEqual(result['dropout'].key.shape, (5,))\n\n  @parameterized.parameters(True, False)\n  def test_fork_rngs(self, graph):\n    rngs = nnx.Rngs(params=0, dropout=1)\n    backups = nnx.fork_rngs(rngs, graph=graph)\n    new_key = rngs.params.key.copy()\n    nnx.restore_rngs(backups)\n    self.assertNotEqual(rngs.params.key, new_key)\n\n  def test_random_helpers(self):\n    rngs = nnx.Rngs(0, params=1)\n\n    x_nnx = rngs.normal((2, 3))\n    x_jax = jax.random.normal(jax.random.fold_in(jax.random.key(0), 0), (2, 3))\n    np.testing.assert_allclose(x_nnx, x_jax)\n\n    x_nnx = rngs.params.uniform((2, 3))\n    x_jax = jax.random.uniform(jax.random.fold_in(jax.random.key(1), 0), (2, 3))\n    np.testing.assert_allclose(x_nnx, x_jax)\n\n    x_nnx = rngs.lecun_normal()((2, 3))\n    x_jax = jax.nn.initializers.lecun_normal()(\n      jax.random.fold_in(jax.random.key(0), 1), (2, 3)\n    )\n    np.testing.assert_allclose(x_nnx, x_jax)\n\n    x_nnx = rngs.params.lecun_uniform()((2, 3))\n    x_jax = jax.nn.initializers.lecun_uniform()(\n      jax.random.fold_in(jax.random.key(1), 1), (2, 3)\n    )\n    np.testing.assert_allclose(x_nnx, x_jax)\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/spmd_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nos.environ['XLA_FLAGS'] = '--xla_force_host_platform_device_count=4'\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom flax import nnx\nimport jax\nimport jax.numpy as jnp\nfrom jax.sharding import PartitionSpec as P, NamedSharding, AxisType, reshard\nfrom jax.experimental.layout import Format, Layout\n\nimport optax\n\n\nclass TestSPMD(parameterized.TestCase):\n\n  def setUp(self):\n    if jax.device_count() < 4:\n      self.skipTest('At least 4 devices required')\n\n  def test_init(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.w = nnx.Param(\n          nnx.with_partitioning(\n            lambda: jnp.ones((8, 2)),\n            sharding=('model', 'data'),\n          )()\n        )\n\n      def __call__(self, x):\n        return x @ self.w\n\n    @jax.jit\n    def create_module():\n      return nnx.split(Foo())\n\n    mesh = jax.make_mesh(\n        (2, 2),\n        ('model', 'data'),\n        axis_types=(jax.sharding.AxisType.Auto,) * len(('model', 'data')),\n    )\n\n    with jax.set_mesh(mesh):\n      m: Foo = nnx.merge(*create_module())  # type: ignore[invalid-annotation]\n      x = jax.device_put(jnp.zeros((4, 8)), P(None, 'model'))\n      y = m(x)\n\n    assert m.w.shape == (8, 2)\n    assert m.w.sharding.shard_shape(m.w.shape) == (4, 1)\n\n  def test_init_all_devices(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.w = nnx.Param(\n          nnx.with_partitioning(\n            lambda: jnp.ones((8, 2)),\n            sharding=('model', 'data'),\n          )()\n        )\n\n      def __call__(self, x):\n        return x @ self.w\n\n    @jax.jit\n    def create_module():\n      return nnx.split(Foo())\n\n    mesh = jax.make_mesh(\n        (1, 1),\n        ('model', 'data'),\n        axis_types=(jax.sharding.AxisType.Auto,) * len(('model', 'data')),\n    )\n\n    with jax.set_mesh(mesh):\n      m: Foo = nnx.merge(*create_module())  # type: ignore[invalid-annotation]\n\n    assert m.w.shape == (8, 2)\n    assert m.w.sharding.shard_shape(m.w.shape) == (8, 2)\n\n  def test_shard_optimizer_state(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.w = nnx.Param(\n          nnx.with_partitioning(\n            lambda: jnp.ones((8, 2)),\n            sharding=('row', 'col'),\n          )()\n        )\n\n      def __call__(self, x):\n        return x @ self.w\n\n    mesh = jax.make_mesh(\n        (2, 2),\n        ('row', 'col'),\n        axis_types=(jax.sharding.AxisType.Auto,) * len(('row', 'col')),\n    )\n    with jax.set_mesh(mesh):\n      graphdef, params = nnx.split(Foo())\n      state = nnx.TrainState.create(\n        graphdef,\n        params=params,\n        tx=optax.adam(1e-3),\n      )\n\n    assert state.params['w'].sharding.is_equivalent_to(\n      NamedSharding(mesh, P('row', 'col')), ndim=2)\n    assert state.opt_state[0].mu['w'].sharding.is_equivalent_to(\n      NamedSharding(mesh, P('row', 'col')), ndim=2)\n    assert state.opt_state[0].nu['w'].sharding.is_equivalent_to(\n      NamedSharding(mesh, P('row', 'col')), ndim=2)\n\n  def test_add_remove_axis_in_transform(self):\n    test = self\n    kadds, kremoves, badds, bremoves = [], [], [], []\n    class MLP(nnx.Module):\n\n      @nnx.split_rngs(splits=5)\n      @nnx.vmap(\n          in_axes=(0, 0),\n          transform_metadata={nnx.PARTITION_NAME: 'layers', 'nickname': 'nick'},\n      )\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(\n          4,\n          4,\n          kernel_init=nnx.with_metadata(\n            nnx.initializers.lecun_normal(),\n            out_sharding=('din', 'dout'),\n            nickname=('in', 'out'),\n            on_add_axis=lambda _, idx, name: kadds.append((idx, name)),\n            on_remove_axis=lambda _, idx, name: kremoves.append((idx, name)),\n          ),\n          bias_init=nnx.with_metadata(\n            nnx.initializers.zeros_init(),  # no sharding annotation here!\n            on_add_axis=lambda _, idx, name: badds.append((idx, name)),\n            on_remove_axis=lambda _, idx, name: bremoves.append((idx, name)),\n          ),\n          rngs=rngs,\n        )\n\n      @nnx.scan(\n          in_axes=(0, nnx.Carry),\n          transform_metadata={nnx.PARTITION_NAME: 'layers'}\n      )\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        # test sharding layer axes is not present inside scan\n        test.assertEqual(self.linear.kernel.shape, (4, 4))\n        test.assertEqual(self.linear.kernel.out_sharding, ('din', 'dout'))\n        # at least a remove_axis was already called to remove the layer axis\n        test.assertEqual(kremoves[-1], (0, 'layers'))\n        test.assertEqual(bremoves[-1], (0, 'layers'))\n        return x, None\n\n    mesh = jax.make_mesh(\n        (1, 2, 2),\n        ('layers', 'din', 'dout'),\n        axis_types=(jax.sharding.AxisType.Auto,)\n        * len(('layers', 'din', 'dout')),\n    )\n    with jax.set_mesh(mesh):\n      m = MLP(rngs=nnx.Rngs(0))\n    self.assertEqual(m.linear.kernel.shape, (5, 4, 4))\n    self.assertEqual(m.linear.kernel.out_sharding, ('layers', 'din', 'dout'))\n    self.assertEqual(m.linear.kernel.nickname, ('nick', 'in', 'out'))\n    self.assertEqual(m.linear.bias.shape, (5, 4))\n    # One add_axis called to add the `nnx.vmap` dimension\n    self.assertEqual(kadds, [(0, 'layers')])\n    self.assertEqual(kremoves, [])\n    self.assertEqual(badds, [(0, 'layers')])\n    self.assertEqual(bremoves, [])\n\n    # One remove_axis and one add_axis called when in and out of `nnx.scan`\n    with jax.set_mesh(mesh):\n      _ = m(jnp.ones((5, 4)))\n    self.assertEqual(kadds, [(0, 'layers'), (0, 'layers')])\n    self.assertEqual(kremoves, [(0, 'layers')])\n    self.assertEqual(badds, [(0, 'layers'), (0, 'layers')])\n    self.assertEqual(bremoves, [(0, 'layers')])\n\n  def test_transform_metadata_decorator(self):\n    v = nnx.Param(\n      jnp.array(0),\n      out_sharding=('din', 'dout'),\n      eager_sharding=False,\n    )\n\n    @nnx.transform_metadata(in_axes=0, out_axes=1, partition='din')\n    def f(v):\n      v[...] += 1\n      self.assertEqual(v.out_sharding, ('dout',))\n      v2 = nnx.Param(\n        jnp.array(10),\n        out_sharding=('dmid', 'dout'),\n        eager_sharding=False,\n      )\n      return v2\n\n    v2 = f(v)\n    self.assertEqual(v.out_sharding, ('din', 'dout'))\n    self.assertEqual(v[...], 1)\n    self.assertEqual(v2.out_sharding, ('dmid', 'din', 'dout'))\n    self.assertEqual(v2[...], 10)\n\n\n  @parameterized.product(use_eager_sharding=[True, False])\n  def test_eager_sharding_context(self, use_eager_sharding):\n    rngs = nnx.Rngs(0)\n    with nnx.use_eager_sharding(use_eager_sharding):\n      mesh = jax.make_mesh(\n        (2, 2),\n        ('data', 'model'),\n        axis_types=(jax.sharding.AxisType.Auto, jax.sharding.AxisType.Auto),\n      )\n      with jax.set_mesh(mesh):\n        w = nnx.Param(\n          rngs.lecun_normal()((4, 8)),\n          out_sharding=(None, 'model'))\n        if use_eager_sharding:\n          assert has_sharding_spec(w)\n        else:\n          assert not has_sharding_spec(w)\n\n  def test_out_sharding_linear_layers(self):\n    mesh = jax.make_mesh((2, 2), (\"X\", \"Y\"), axis_types=(AxisType.Explicit, AxisType.Explicit))\n    with jax.set_mesh(mesh):\n      replicated_array = jnp.arange(4).reshape(2, 2)\n      sharded_array = reshard(replicated_array, P(\"X\", None))\n      layers = [\n        nnx.Linear(2, 4, rngs=nnx.Rngs(0)),\n        nnx.LinearGeneral(2, 4, rngs=nnx.Rngs(0)),\n        nnx.Einsum('ab,bc->ac', (2, 4), (4,), rngs=nnx.Rngs(0)),\n      ]\n      for layer in layers:\n        assert 'float32[2@X,4]' in str(jax.typeof(layer(sharded_array)))\n        assert 'float32[2@X,4@Y]' in str(jax.typeof(layer(sharded_array, out_sharding=P(\"X\", \"Y\"))))\n\n  def test_out_sharding_embed(self):\n    mesh = jax.make_mesh((2, 2), (\"X\", \"Y\"), axis_types=(AxisType.Explicit, AxisType.Explicit))\n    with jax.set_mesh(mesh):\n      emb = nnx.Embed(num_embeddings=8, features=4, rngs=nnx.Rngs(0))\n      emb = reshard(emb, P(\"X\"))\n      sharded_array = reshard(jnp.arange(4), P(\"Y\"))\n      self.assertRaises(Exception, emb, sharded_array)\n      self.assertEqual('float32[4@X,4]',\n        str(jax.typeof(emb(sharded_array, out_sharding=P(\"X\")))))\n\n  def test_out_sharding_conv(self):\n    mesh = jax.make_mesh((2, 2), (\"X\", \"Y\"), axis_types=(AxisType.Explicit, AxisType.Explicit))\n    with jax.set_mesh(mesh):\n      replicated_array = jnp.arange(32).reshape(2, 4, 4).astype(jnp.float32)\n      sharded_array = reshard(replicated_array, P(\"X\", None, None))\n      layer = nnx.Conv(4, 8, kernel_size=(3,), rngs=nnx.Rngs(0))\n      assert 'float32[2@X,4,8]' in str(jax.typeof(layer(sharded_array)))\n      assert 'float32[2@X,4@Y,8]' in str(jax.typeof(layer(sharded_array, out_sharding=P(\"X\", \"Y\", None))))\n\n  def test_out_sharding_embed_attend(self):\n    mesh = jax.make_mesh((2, 2), (\"X\", \"Y\"), axis_types=(AxisType.Explicit, AxisType.Explicit))\n    with jax.set_mesh(mesh):\n      replicated_array = jnp.arange(8).reshape(2, 4).astype(jnp.float32)\n      sharded_array = reshard(replicated_array, P(\"X\", None))\n      layer = nnx.Embed(num_embeddings=10, features=4, rngs=nnx.Rngs(0))\n      assert 'float32[2@X,10]' in str(jax.typeof(layer.attend(sharded_array)))\n      assert 'float32[2@X,10@Y]' in str(jax.typeof(layer.attend(sharded_array, out_sharding=P(\"X\", \"Y\"))))\n\n  def test_out_sharding_dropout(self):\n    mesh = jax.make_mesh((2, 2), (\"X\", \"Y\"), axis_types=(AxisType.Explicit, AxisType.Explicit))\n    with jax.set_mesh(mesh):\n      replicated_array = jnp.arange(8).reshape(2, 4).astype(jnp.float32)\n      sharded_array = reshard(replicated_array, P(\"X\", None))\n      layers = [\n        nnx.Dropout(rate=0.5, rngs=nnx.Rngs(0)),\n        nnx.Dropout(rate=0.5, broadcast_dims=(1,), rngs=nnx.Rngs(0)),\n      ]\n      for layer in layers:\n        assert 'float32[2@X,4]' in str(jax.typeof(layer(sharded_array)))\n\n        @jax.jit\n        def func(x, rngs):\n          return layer(x, rngs=rngs)\n\n        assert 'float32[2@X,4]' in str(jax.typeof(func(sharded_array, nnx.Rngs(0))))\n\n  @parameterized.product(use_hijax=[True, False])\n  def test_logical_rules(self, use_hijax):\n    self.enter_context(nnx.var_defaults(hijax=use_hijax))\n    class Foo(nnx.Module):\n\n      def __init__(self):\n        self.w = nnx.Param(\n            nnx.with_partitioning(\n                lambda: jnp.ones((8, 2)),\n                sharding=('row-alias', 'col-alias'),\n                sharding_rules=(('row-alias', 'row'),),\n            )()\n        )\n        self.b = nnx.Param(\n            nnx.with_partitioning(\n                lambda: jnp.zeros((2,)), sharding=('col-alias',)\n            )()\n        )\n\n      def __call__(self, x):\n        return x @ self.w + self.b\n\n    mesh = jax.make_mesh(\n        (1, 2, 2),\n        ('layers', 'row', 'col'),\n        axis_types=(jax.sharding.AxisType.Auto,)\n        * len(('layers', 'row', 'col')),\n    )\n    with jax.set_mesh(mesh), nnx.logical_axis_rules((('col-alias', 'col'),)):\n      model = Foo()\n      optimizer = nnx.Optimizer(model, optax.adam(1e-3), wrt=nnx.Param)\n\n    assert model.w.sharding.is_equivalent_to(\n      NamedSharding(mesh, P('row', 'col')), ndim=2)\n    assert optimizer.opt_state[0].mu['w'].sharding.is_equivalent_to(\n      NamedSharding(mesh, P('row', 'col')), ndim=2)\n    assert optimizer.opt_state[0].nu['w'].sharding.is_equivalent_to(\n      NamedSharding(mesh, P('row', 'col')), ndim=2)\n\n  def test_get_abstract_model(self):\n    class Foo(nnx.Module):\n      def __init__(self, rngs):\n        self.linear = nnx.Linear(\n          8, 8, rngs=rngs, use_bias=False,\n          kernel_init=nnx.with_partitioning(\n            nnx.initializers.lecun_normal(), (None, 'model')))\n        self.shared = self.linear.kernel\n\n    mesh = jax.make_mesh(\n        (2, 2),\n        ('batch', 'model'),\n        axis_types=(jax.sharding.AxisType.Auto,) * len(('batch', 'model')),\n    )\n    gdef, abs_state = nnx.get_abstract_model(lambda: Foo(nnx.Rngs(0)), mesh)\n    assert len(jax.tree.leaves(abs_state)) == 1\n    assert jax.tree.leaves(abs_state)[0].sharding.is_equivalent_to(\n      NamedSharding(mesh, P(None, 'model')), ndim=2)\n\n  @parameterized.parameters('auto', 'explicit', 'mixed')\n  def test_sharding_axis_types(self, mode):\n    if mode == 'auto':\n      axis_types = (jax.sharding.AxisType.Auto, jax.sharding.AxisType.Auto)\n    elif mode == 'explicit':\n      axis_types = (jax.sharding.AxisType.Explicit, jax.sharding.AxisType.Explicit)\n    else:\n      axis_types = (jax.sharding.AxisType.Auto, jax.sharding.AxisType.Explicit)\n\n    mesh = jax.make_mesh(\n      (2, 2),\n      ('row', 'col'),\n      axis_types=axis_types,\n    )\n    if mode == 'mixed':\n      with self.assertRaises(ValueError):\n        nnx.Variable(\n          jnp.ones((4, 4)),\n          out_sharding=('row', 'col'),\n          mesh=mesh,\n        )\n    else:\n      v = nnx.Variable(\n        jnp.ones((4, 4)),\n        out_sharding=('row', 'col'),\n        mesh=mesh,\n      )\n      self.assertEqual(v.sharding.mesh, mesh)\n      self.assertEqual(v.sharding.spec, P('row', 'col'))\n\n  def test_eval_shape_with_explicit_sharding(self):\n    axis_types = (jax.sharding.AxisType.Explicit, jax.sharding.AxisType.Explicit)\n    mesh1 = jax.make_mesh((2, 2), (\"a\", \"b\"), axis_types)\n    class Model(nnx.Module):\n        def __init__(self):\n          self.p1 = nnx.Param(\n            reshard(jnp.ones((4,4)), NamedSharding(mesh1, P('a', 'b'))),\n            mesh=mesh1)\n\n    abs_model = nnx.eval_shape(lambda: Model())\n    self.assertEqual(abs_model.p1.sharding.spec, P('a', 'b'))\n\n  def test_eval_shape_with_sharding0(self):\n    # based on https://github.com/google/flax/issues/5110\n    mesh1 = jax.make_mesh((2, 2), (\"a\", \"b\"), (jax.sharding.AxisType.Auto, jax.sharding.AxisType.Auto))\n    mesh2 = jax.make_mesh((1, 4), (\"c\", \"d\"), (jax.sharding.AxisType.Auto, jax.sharding.AxisType.Auto))\n\n    class Model(nnx.Module):\n        def __init__(self):\n            self.p1 = nnx.Linear(16, 16, rngs=nnx.Rngs(0), kernel_metadata={\"out_sharding\": (\"a\", \"b\"), \"mesh\": mesh1})\n            self.p2 = nnx.Linear(16, 16, rngs=nnx.Rngs(0), kernel_metadata={\"out_sharding\": (\"c\", \"d\"), \"mesh\": mesh2})\n\n    abs_model = nnx.eval_shape(lambda: Model())\n    assert isinstance(abs_model.p1.kernel.sharding, jax.sharding.NamedSharding)\n    assert abs_model.p1.kernel.sharding.mesh.axis_names == mesh1.axis_names\n    assert abs_model.p1.kernel.sharding.spec == jax.P(\"a\", \"b\")\n    assert isinstance(abs_model.p2.kernel.sharding, jax.sharding.NamedSharding)\n    assert abs_model.p2.kernel.sharding.mesh.axis_names == mesh2.axis_names\n    assert abs_model.p2.kernel.sharding.spec == jax.P(\"c\", \"d\")\n\n  def test_eval_shape_with_sharding1(self):\n    class Model(nnx.Module):\n        def __init__(self):\n            self.linear = nnx.Linear(10, 10, rngs=nnx.Rngs(0), kernel_metadata={\"out_sharding\": (\"a\", \"b\")})\n\n    mesh = jax.make_mesh((2, 2), (\"a\", \"b\"), (jax.sharding.AxisType.Auto, jax.sharding.AxisType.Auto))\n    with jax.set_mesh(mesh):\n        abs_model = nnx.eval_shape(lambda: Model())\n    assert isinstance(abs_model.linear.kernel.sharding, jax.sharding.NamedSharding)\n    assert abs_model.linear.kernel.sharding.mesh.axis_names == mesh.axis_names\n    assert abs_model.linear.kernel.sharding.spec == jax.P(\"a\", \"b\")\n\n  @parameterized.product(axis_type_name=['auto', 'explicit'])\n  def test_variable_out_sharding_types(self, axis_type_name):\n    if axis_type_name == 'auto':\n      axis_types = (jax.sharding.AxisType.Auto, jax.sharding.AxisType.Auto)\n    else: # 'explicit'\n      axis_types = (jax.sharding.AxisType.Explicit, jax.sharding.AxisType.Explicit)\n\n    mesh = jax.make_mesh(\n        (2, 2),\n        ('data', 'model'),\n        axis_types=axis_types,\n    )\n\n    with jax.set_mesh(mesh):\n      value = jnp.ones((4, 4))\n\n      # Test with PartitionSpec\n      v_pspec = nnx.Variable(value, out_sharding=P('data', 'model'))\n      self.assertEqual(v_pspec.sharding.spec, P('data', 'model'))\n\n    # Test with NamedSharding\n    ns = NamedSharding(mesh, P('data', None))\n    v_namedsharding = nnx.Variable(value, out_sharding=ns)\n    self.assertEqual(v_namedsharding.sharding, ns)\n\n    # Test with Format\n    if axis_type_name == 'auto':\n      v_format = nnx.Variable(value, out_sharding=Format(Layout(major_to_minor=(1, 0)), ns))\n      self.assertEqual(v_format.sharding, ns)\n\n  def test_get_abstract_with_abstract_mesh(self):\n    mesh = jax.make_mesh(\n        (2, 2),\n        ('a', 'b'),\n        axis_types=(jax.sharding.AxisType.Auto,) * 2,\n    )\n    with jax.set_mesh(mesh):\n      abs_model = nnx.eval_shape(\n          lambda: nnx.Linear(\n              4,\n              8,\n              rngs=nnx.Rngs(0),\n              kernel_metadata={'out_sharding': ('a', 'b')},\n          )\n      )\n      abs_model = nnx.abstract_with_sharding(abs_model)\n\n    self.assertIsInstance(abs_model.kernel, nnx.Param)\n    self.assertEqual(abs_model.kernel.sharding.spec, P('a', 'b'))\n    self.assertEqual(\n        abs_model.kernel.sharding.mesh.axis_names,\n        mesh.axis_names,\n    )\n\n  def test_get_abstract_with_per_variable_mesh(self):\n    mesh1 = jax.make_mesh(\n        (2, 2),\n        ('a', 'b'),\n        axis_types=(jax.sharding.AxisType.Auto,) * 2,\n    )\n    mesh2 = jax.make_mesh(\n        (1, 4),\n        ('c', 'd'),\n        axis_types=(jax.sharding.AxisType.Auto,) * 2,\n    )\n\n    class Model(nnx.Module):\n      def __init__(self):\n        self.p1 = nnx.Linear(\n            4,\n            8,\n            rngs=nnx.Rngs(0),\n            kernel_metadata={'out_sharding': ('a', 'b'), 'mesh': mesh1},\n        )\n        self.p2 = nnx.Linear(\n            4,\n            8,\n            rngs=nnx.Rngs(0),\n            kernel_metadata={'out_sharding': ('c', 'd'), 'mesh': mesh2},\n        )\n\n    abs_model = nnx.eval_shape(lambda: Model())\n    abs_model = nnx.abstract_with_sharding(abs_model)\n\n    self.assertEqual(abs_model.p1.kernel.sharding.spec, P('a', 'b'))\n    self.assertEqual(abs_model.p1.kernel.sharding.mesh, mesh1)\n    self.assertEqual(abs_model.p2.kernel.sharding.spec, P('c', 'd'))\n    self.assertEqual(abs_model.p2.kernel.sharding.mesh, mesh2)\n\n  def test_get_abstract_no_sharding_metadata(self):\n    abs_model = nnx.eval_shape(lambda: nnx.Linear(4, 8, rngs=nnx.Rngs(0)))\n    abs_model = nnx.abstract_with_sharding(abs_model)\n\n    self.assertIsInstance(abs_model.kernel, nnx.Param)\n    self.assertIsNone(\n        getattr(abs_model.kernel.get_value(), 'sharding', None)\n    )\n\ndef has_sharding_spec(array):\n    sharding = array.sharding\n    if hasattr(sharding, 'spec'):\n        # For NamedSharding or PositionalSharding\n        return sharding.spec is not None and any(\n            s is not None for s in sharding.spec\n        )\n    return False\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/state_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom absl.testing import absltest\n\nfrom flax import nnx\nimport jax\nfrom jax import numpy as jnp\n\n\nclass StateTest(absltest.TestCase):\n  def test_create_state(self):\n    state = nnx.State(\n      {'a': nnx.Param(jnp.array(1)), 'b': {'c': nnx.Param(jnp.array(2))}}\n    )\n\n    assert state['a'][...] == 1\n    assert state['b']['c'][...] == 2\n\n  def test_get_attr(self):\n    state = nnx.State(\n      {'a': nnx.Param(jnp.array(1)), 'b': {'c': nnx.Param(jnp.array(2))}}\n    )\n\n    assert state.a[...] == 1\n    assert state.b.c[...] == 2\n\n  def test_set_attr(self):\n    state = nnx.State(\n      {'a': nnx.Param(jnp.array(1)), 'b': {'c': nnx.Param(jnp.array(2))}}\n    )\n\n    state.a[...] = 3\n    state.b.c[...] = 4\n\n    assert state['a'][...] == 3\n    assert state['b']['c'][...] == 4\n\n  def test_set_attr_variables(self):\n    state = nnx.State(\n      {'a': nnx.Param(jnp.array(1)), 'b': {'c': nnx.Param(jnp.array(2))}}\n    )\n\n    state.a[...] = 3\n    state.b.c[...] = 4\n\n    assert isinstance(state.a, nnx.Param)\n    assert state.a[...] == 3\n    assert isinstance(state.b.c, nnx.Param)\n    assert state.b.c[...] == 4\n\n  def test_add_nested_attr(self):\n    state = nnx.State(\n      {'a': nnx.Param(jnp.array(1)), 'b': {'c': nnx.Param(jnp.array(2))}}\n    )\n    state.b.d = nnx.Param(jnp.array(5))\n\n    assert state['b']['d'][...] == 5\n\n  def test_delete_nested_attr(self):\n    state = nnx.State(\n      {'a': nnx.Param(jnp.array(1)), 'b': {'c': nnx.Param(jnp.array(2))}}\n    )\n    del state['b']['c']\n\n    assert 'c' not in state['b']\n\n  def test_integer_access(self):\n    class Foo(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.layers = nnx.List([\n          nnx.Linear(1, 2, rngs=rngs),\n          nnx.Linear(2, 3, rngs=rngs)\n        ])\n\n    module = Foo(rngs=nnx.Rngs(0))\n    state = nnx.state(module)\n\n    assert module.layers[0].kernel.shape == (1, 2)\n    assert state.layers[0].kernel.shape == (1, 2)\n    assert module.layers[1].kernel.shape == (2, 3)\n    assert state.layers[1].kernel.shape == (2, 3)\n\n  def test_pure_dict(self):\n    module = nnx.Linear(4, 5, rngs=nnx.Rngs(0))\n    state = nnx.state(module)\n    pure_dict = nnx.to_pure_dict(state)\n    assert isinstance(pure_dict, dict)\n    assert isinstance(pure_dict['kernel'], jax.Array)\n    assert isinstance(pure_dict['bias'], jax.Array)\n    nnx.replace_by_pure_dict(state, jax.tree.map(jnp.zeros_like, pure_dict))\n    assert isinstance(state, nnx.State)\n    assert isinstance(state['kernel'], nnx.Variable)\n    assert jnp.array_equal(state['kernel'][...], jnp.zeros((4, 5)))\n    assert type(state['kernel']) == nnx.Param\n    nnx.update(module, state)\n    assert jnp.array_equal(module(jnp.ones((3, 4))), jnp.zeros((3, 5)))\n\n  def test_diff(self):\n    class MLPs(nnx.Module):\n      def __init__(self, dim, rngs: nnx.Rngs, n=4):\n        self.layers = nnx.List()\n        for _ in range(n):\n          self.layers.append(nnx.Linear(dim, dim, rngs=rngs, use_bias=False))\n\n      def __call__(self, x):\n        for layer in self.layers:\n          x = layer(x)\n        return x\n\n    model1 = MLPs(4, rngs=nnx.Rngs(0), n=4)\n    model2 = MLPs(4, rngs=nnx.Rngs(1), n=4)\n    model3 = MLPs(4, rngs=nnx.Rngs(1), n=5)\n\n    self.assertEqual(\n      nnx.statelib.diff(nnx.state(model2), nnx.state(model1)),\n      nnx.state({})\n    )\n    self.assertNotEqual(\n      nnx.statelib.diff(nnx.state(model3), nnx.state(model1)),\n      nnx.state({})\n    )\n    self.assertEqual(\n      nnx.statelib.diff(nnx.state(model1), nnx.state(model3)),\n      nnx.state({})\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()"
  },
  {
    "path": "tests/nnx/summary_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport jax.numpy as jnp\nfrom absl.testing import absltest\n\nfrom flax import nnx\n\nCONSOLE_TEST_KWARGS = dict(force_terminal=False, no_color=True, width=10_000)\n\n\nclass SummaryTest(absltest.TestCase):\n  def test_tabulate(self):\n    class Block(nnx.Module):\n      def __init__(self, din, dout, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.bn = nnx.BatchNorm(dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.2, rngs=rngs)\n\n      def forward(self, x):\n        return nnx.relu(self.dropout(self.bn(self.linear(x))))\n\n    class Foo(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.block1 = Block(32, 128, rngs=rngs)\n        self.block2 = Block(128, 10, rngs=rngs)\n\n      def __call__(self, x):\n        return self.block2.forward(self.block1.forward(x))\n\n    foo = Foo(nnx.Rngs(0))\n    x = jnp.ones((1, 32))\n    table_repr_ = nnx.tabulate(\n      foo, x, console_kwargs=CONSOLE_TEST_KWARGS\n    )\n    table_repr = table_repr_.splitlines()\n\n    self.assertIn('Foo Summary', table_repr[0])\n    self.assertIn('path', table_repr[2])\n    self.assertIn('type', table_repr[2])\n    self.assertIn('BatchStat', table_repr[2])\n    self.assertIn('Param', table_repr[2])\n    self.assertIn('block1/forward', table_repr[6])\n    self.assertIn('Block', table_repr[6])\n    self.assertIn('block1/linear', table_repr[8])\n    self.assertIn('Linear', table_repr[8])\n    self.assertIn('block1/bn', table_repr[13])\n    self.assertIn('BatchNorm', table_repr[13])\n    self.assertIn('block1/dropout', table_repr[18])\n    self.assertIn('Dropout', table_repr[18])\n    self.assertIn('block2/forward', table_repr[20])\n    self.assertIn('Block', table_repr[20])\n    self.assertIn('block2/linear', table_repr[22])\n    self.assertIn('Linear', table_repr[22])\n    self.assertIn('block2/bn', table_repr[27])\n    self.assertIn('BatchNorm', table_repr[27])\n    self.assertIn('block2/dropout', table_repr[32])\n    self.assertIn('Dropout', table_repr[32])\n\n    self.assertIn('Total', table_repr[34])\n    self.assertIn('276 (1.1 KB)', table_repr[34])\n    self.assertIn('5,790 (23.2 KB)', table_repr[34])\n    self.assertIn('4 (24 B)', table_repr[34])\n    self.assertIn('Total Parameters: 6,070 (24.3 KB)', table_repr[37])\n\n  def test_multiple_inputs_and_outputs(self):\n    class CustomMLP(nnx.Module):\n      def __init__(self):\n        self.weight = nnx.Param(jnp.ones((4, 8)))\n        self.bias = nnx.Param(jnp.ones(8))\n\n      def __call__(self, x, x2):\n        y = x @ self.weight\n        y += self.bias[None]\n        y += x2\n        return x, y, 2 * y\n\n    cmlp = CustomMLP()\n    x = jnp.ones((1, 4))\n    x2 = jnp.ones((1, 8))\n    table_repr = nnx.tabulate(\n      cmlp, x, x2, console_kwargs=CONSOLE_TEST_KWARGS\n    ).splitlines()\n\n    self.assertIn('CustomMLP Summary', table_repr[0])\n    self.assertIn('float32[1,4]', table_repr[4])\n    self.assertIn('float32[1,8]', table_repr[5])\n    self.assertIn('float32[1,8]', table_repr[6])\n\n  def test_tabulate_empty_dict_first_arg(self):\n    class Model(nnx.Module):\n      def subroutine(self, foo, x):\n        return x\n\n      def __call__(self, x):\n        return self.subroutine({}, x)\n\n    model = Model()\n    out = nnx.tabulate(\n      model, jnp.zeros((1, 8)), depth=1, console_kwargs=CONSOLE_TEST_KWARGS\n    )\n    # Ensure empty dict argument is preserved and array input is shown\n    self.assertIn('{}', out)\n    self.assertIn('float32[1,8]', out)\n\n  def test_tabulate_empty_dict_last_arg(self):\n    class Model(nnx.Module):\n      def subroutine(self, foo, x):\n        return x\n\n      def __call__(self, x):\n        return self.subroutine(x, {})\n\n    model = Model()\n    out = nnx.tabulate(\n      model, jnp.zeros((1, 8)), depth=1, console_kwargs=CONSOLE_TEST_KWARGS\n    )\n    # Ensure trailing empty dict is not dropped\n    self.assertIn('{}', out)\n\n  def test_tabulate_empty_dict_and_none_kwarg(self):\n    class Model(nnx.Module):\n      def subroutine(self, x, *, foo=None):\n        return x\n\n      def __call__(self, x):\n        # One call with empty dict, one with None\n        _ = self.subroutine(x, foo={})\n        return self.subroutine(x, foo=None)\n\n    model = Model()\n    out = nnx.tabulate(\n      model, jnp.zeros((1, 8)), depth=2, console_kwargs=CONSOLE_TEST_KWARGS\n    )\n    # Distinguish {} and None in output\n    self.assertIn('{}', out)\n    self.assertIn('None', out)\n\n  def test_tabulate_empty_dict_property(self):\n    class Model(nnx.Module):\n      def __init__(self):\n        self.foo = {}\n\n      def subroutine(self, foo, x):\n        return x\n\n      def __call__(self, x):\n        return self.subroutine(self.foo, x)\n\n    model = Model()\n    out = nnx.tabulate(\n      model, jnp.zeros((1, 1024)), depth=1, console_kwargs=CONSOLE_TEST_KWARGS\n    )\n    # Should not crash and should show the empty dict argument\n    self.assertIn('{}', out)\n\n\n  def test_no_dup_flops(self):\n    class Model(nnx.Module):\n      def g(self, x):\n        return x**2\n      def __call__(self, x):\n        return self.g(x)\n    m = Model()\n    x = jnp.ones(4)\n    table_rep = nnx.tabulate(m, x, compute_flops=True)\n    table_lines = table_rep.splitlines()\n    self.assertEqual(sum(\" g \" in l for l in table_lines), 1)\n\n\n  def test_flops(self):\n    class Model(nnx.Module):\n      def __init__(self):\n        self.weight = nnx.Param(jnp.ones(4))\n\n      def __call__(self, x1):\n        return jnp.sum((x1 * self.weight)**2)\n    m = Model()\n    x = jnp.ones(4)\n    table_repr1 = nnx.tabulate(\n      m, x, compute_flops=True\n    ).splitlines()\n    self.assertIn('flops', table_repr1[2])\n    self.assertNotIn('vjp_flops', table_repr1[2])\n    table_repr2 = nnx.tabulate(\n      m, x, compute_flops=True, compute_vjp_flops=True\n    ).splitlines()\n    self.assertIn('vjp_flops', table_repr2[2])\n\n  def test_nested(self):\n    class Block(nnx.Module):\n      def __init__(self, rngs):\n        self.linear = nnx.Linear(2, 2, rngs=rngs)\n        self.bn = nnx.BatchNorm(2, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.linear(x)\n        x = self.bn(x)\n        return nnx.relu(x)\n\n    class Model(nnx.Module):\n      def __init__(self, rngs):\n        self.block1 = Block(rngs)\n        self.block2 = Block(rngs)\n\n      def __call__(self, x):\n        x = self.block1(x)\n        x = self.block2(x)\n        return x\n\n    m = Model(nnx.Rngs(0))\n    x = jnp.ones((4, 2))\n    table = nnx.tabulate(m, x, compute_flops=True, compute_vjp_flops=True)\n    # We should see 3 calls per block, plus one overall call\n    self.assertEqual(sum([s.startswith(\"├─\") for s in table.splitlines()]), 7)\n\n  def test_time_complexity(self):\n    counter = []\n\n    class Block(nnx.Module):\n      def __init__(self, rngs):\n        self.linear = nnx.Linear(2, 2, rngs=rngs)\n\n      def __call__(self, x):\n        counter.append(1)\n        return self.linear(x)\n\n    class Model(nnx.Module):\n      def __init__(self, rngs):\n        for d in range(10):\n          setattr(self, f\"linear{d}\", Block(rngs))\n\n      def __call__(self, x):\n        for d in range(10):\n          x = getattr(self, f\"linear{d}\")(x)\n        return x\n\n    m = Model(nnx.Rngs(0))\n    x = jnp.ones((4, 2))\n    nnx.tabulate(m, x, compute_flops=True, compute_vjp_flops=False)\n    self.assertEqual(len(counter), 10)\n\n  def test_shared(self):\n    class Block(nnx.Module):\n      def __init__(self, linear: nnx.Linear, *, rngs):\n        self.linear = linear\n        self.bn = nnx.BatchNorm(2, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.linear(x)\n        x = self.bn(x)\n        return nnx.relu(x)\n\n    class Model(nnx.Module):\n      def __init__(self, rngs):\n        shared = nnx.Linear(2, 2, rngs=rngs)\n        self.block1 = Block(shared, rngs=rngs)\n        self.block2 = Block(shared, rngs=rngs)\n\n      def __call__(self, x):\n        x = self.block1(x)\n        x = self.block2(x)\n        return x\n\n    m = Model(nnx.Rngs(0))\n    x = jnp.ones((4, 2))\n    table = nnx.tabulate(m, x, compute_vjp_flops=True)\n    # We should see 3 calls per block, plus one overall call, minus the shared call\n    self.assertEqual(sum([s.startswith(\"├─\") for s in table.splitlines()]), 6)\n\n  def test_tabulate_with_variable_hooks(self):\n    \"\"\"Test that tabulate works with Variables implementing hooks and custom metadata.\"\"\"\n\n    class Custom:\n      def __repr__(self):\n        return \"<CustomMetadata>\"\n\n    class VarWithHooks(nnx.Variable):\n        def on_get_value(self, value):\n            return value\n\n        def on_set_value(self, value):\n            return value + 1.0\n\n    class Model(nnx.Module):\n        def __init__(self):\n            # Variable with hooks\n            self.hooked_param = VarWithHooks(value=jnp.ones((2, 3)))\n            self.hooked_param.set_metadata('description', 'Custom parameter')\n            self.hooked_param.set_metadata('trainable', True)\n\n            # Variable with custom non-serializable metadata\n            self.custom_param = nnx.Param(jnp.ones((2, 2)))\n            self.custom_param.set_metadata('custom_obj', Custom())\n\n        def __call__(self, x):\n          return jnp.dot(x, self.hooked_param[...]) + self.custom_param.sum()\n\n    module = Model()\n    # Should not raise yaml.representer.RepresenterError\n    table_repr = nnx.tabulate(module, jnp.ones((1, 2)), console_kwargs=CONSOLE_TEST_KWARGS)\n    self.assertIsNotNone(table_repr)\n\n    # Verify table contains expected content\n    self.assertIn('Model Summary', table_repr)\n    self.assertIn('hooked_param', table_repr)\n    self.assertIn('on_set_value', table_repr)\n    self.assertIn('<CustomMetadata>', table_repr)\n\n    # Verify metadata is preserved in the module\n    self.assertEqual(module.hooked_param.get_metadata('description'), 'Custom parameter')\n    self.assertEqual(module.hooked_param.get_metadata('trainable'), True)\n\n  def test_tabulate_concrete_shape(self):\n    class Net(nnx.Module):\n        def __init__(self):\n            self.rngs = nnx.Rngs(0)\n\n        def __call__(self, x):\n            return self.rngs.uniform((x.shape[0], 10))\n\n    net = Net()\n    x = jnp.zeros((4, 8))\n    nnx.tabulate(net, x, console_kwargs={\"width\": 200})\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/test_traversals.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.nnx.traversal.\"\"\"\nfrom absl.testing import absltest\nfrom flax.core import freeze\nfrom flax.nnx import traversals\nimport jax\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass TraversalTest(absltest.TestCase):\n  def test_flatten_mapping(self):\n    xs = {'foo': 1, 'bar': {'a': 2, 'b': {}}}\n    flat_xs = traversals.flatten_mapping(xs)\n    self.assertEqual(\n      flat_xs,\n      {\n        ('foo',): 1,\n        ('bar', 'a'): 2,\n      },\n    )\n    flat_xs = traversals.flatten_mapping(freeze(xs))\n    self.assertEqual(\n      flat_xs,\n      {\n        ('foo',): 1,\n        ('bar', 'a'): 2,\n      },\n    )\n    flat_xs = traversals.flatten_mapping(xs, sep='/')\n    self.assertEqual(\n      flat_xs,\n      {\n        'foo': 1,\n        'bar/a': 2,\n      },\n    )\n\n  def test_unflatten_mapping(self):\n    expected_xs = {'foo': 1, 'bar': {'a': 2}}\n    xs = traversals.unflatten_mapping(\n      {\n        ('foo',): 1,\n        ('bar', 'a'): 2,\n      }\n    )\n    self.assertEqual(xs, expected_xs)\n    xs = traversals.unflatten_mapping(\n      {\n        'foo': 1,\n        'bar/a': 2,\n      },\n      sep='/',\n    )\n    self.assertEqual(xs, expected_xs)\n\n  def test_flatten_mapping_keep_empty(self):\n    ys = {'a': {}}\n    xs = {'foo': 1, 'bar': {'a': 2, 'b': {}}}\n    flat_ys = traversals.flatten_mapping(ys, keep_empty_nodes=True)\n    flat_xs = traversals.flatten_mapping(xs, keep_empty_nodes=True)\n    empty_node = flat_ys[('a',)]\n    self.assertEqual(\n      flat_xs,\n      {\n        ('foo',): 1,\n        ('bar', 'a'): 2,\n        ('bar', 'b'): empty_node,\n      },\n    )\n    xs_restore = traversals.unflatten_mapping(flat_xs)\n    self.assertEqual(xs, xs_restore)\n\n  def test_flatten_mapping_is_leaf(self):\n    xs = {'foo': {'c': 4}, 'bar': {'a': 2, 'b': {}}}\n    flat_xs = traversals.flatten_mapping(\n      xs, is_leaf=lambda k, x: len(k) == 1 and len(x) == 2\n    )\n    self.assertEqual(\n      flat_xs,\n      {\n        ('foo', 'c'): 4,\n        ('bar',): {'a': 2, 'b': {}},\n      },\n    )\n    xs_restore = traversals.unflatten_mapping(flat_xs)\n    self.assertEqual(xs, xs_restore)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/transforms_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nos.environ['XLA_FLAGS'] = '--xla_force_host_platform_device_count=4'\n\nimport dataclasses\nfrom functools import partial\nimport typing as tp\n\nfrom absl.testing import absltest\nfrom absl.testing import parameterized\nfrom flax import nnx\nfrom flax.nnx.transforms.iteration import pure_jax_fancy_scan\nfrom flax.nnx.transforms import general\nimport jax\nfrom jax.experimental import checkify, mesh_utils\nimport jax.numpy as jnp\nimport numpy as np\nimport optax\nfrom flax import errors\n\n\nclass TestJIT(parameterized.TestCase):\n  def test_jit(self):\n    m = nnx.Dict(a=nnx.Param(1))\n\n    @nnx.jit\n    def g(m: nnx.Dict):\n      m.a = 2\n      return 1.0\n\n    out = g(m)\n\n    assert m.a == 2\n    assert out == 1.0\n\n  def test_mutable_array_input_output(self):\n    m = jax.new_ref(jnp.array(1.0))\n\n    @nnx.jit\n    def f(m: jax.Ref):\n      m[...] += 1.0\n      m2 = jax.new_ref(jnp.array(10.0))\n      return m2, m\n\n    m2, m_out = f(m)\n\n    self.assertEqual(m[...], 2.0)\n    self.assertIs(m, m_out)\n    self.assertIsInstance(m2, jax.Ref)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_simple_double_call(self, graph_mode, graph_updates):\n    n = 0\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    @nnx.jit(graph=graph_mode, graph_updates=graph_updates)\n    def f(m: nnx.Linear, x: jnp.ndarray) -> jnp.ndarray:\n      nonlocal n\n      n += 1\n      return m(x)\n\n    x = jnp.ones((1, 2))\n    y = f(m, x)\n\n    self.assertEqual(n, 1)\n    self.assertEqual(y.shape, (1, 3))\n\n    y = f(m, x)\n\n    self.assertEqual(n, 1)\n    self.assertEqual(y.shape, (1, 3))\n\n  def test_jit_on_init(self):\n    n = 0\n\n    class Foo(nnx.Module):\n      @nnx.jit(static_argnums=(1, 2))\n      def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n        nonlocal n\n        n += 1\n\n        key = rngs.params()\n        self.w = nnx.Param(jax.random.normal(key, shape=(din, dout)))\n        self.din = din\n        self.dout = dout\n\n    m = Foo(2, 3, rngs=nnx.Rngs(0))\n    assert n == 1\n    assert m.w.shape == (2, 3)\n    assert m.din == 2\n    assert m.dout == 3\n    assert isinstance(m.din, int)\n    assert isinstance(m.dout, int)\n    assert isinstance(m.w[...], jax.Array)\n\n    m = Foo(2, 3, rngs=nnx.Rngs(0))\n    assert n == 1\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_jit_on_call(self, graph_mode, graph_updates):\n    n = 0\n\n    class Foo(nnx.Module):\n      def __init__(self, din: int, dout: int, *, rngs: nnx.Rngs):\n        key = rngs.params()\n        self.w = nnx.Param(jax.random.normal(key, shape=(din, dout)))\n        self.din = din\n        self.dout = dout\n\n      @nnx.jit(graph=graph_mode, graph_updates=graph_updates)\n      def __call__(self, x: jax.Array) -> jax.Array:\n        nonlocal n\n        n += 1\n        return jnp.dot(x, self.w)\n\n    m = Foo(2, 3, rngs=nnx.Rngs(0))\n    assert m.w.shape == (2, 3)\n    assert m.din == 2\n    assert m.dout == 3\n    assert isinstance(m.din, int)\n    assert isinstance(m.dout, int)\n    assert isinstance(m.w[...], jax.Array)\n\n    y = m(jnp.ones((1, 2)))\n    assert y.shape == (1, 3)\n    assert n == 1\n    y = m(jnp.ones((1, 2)))\n    assert n == 1\n\n  def test_cached_unflatten(self):\n    n = 0\n\n    class Foo(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.a = nnx.Linear(2, 2, rngs=rngs)\n        self.b = nnx.BatchNorm(2, rngs=rngs)\n\n    @nnx.jit\n    def f(m: Foo):\n      nonlocal n\n      n += 1\n      m.a, m.b = m.b, m.a  # type: ignore\n\n    m = Foo(rngs=nnx.Rngs(0))\n    a = m.a\n    b = m.b\n    a_kernel = a.kernel[...]\n    a_bias = a.bias[...]\n    b_scale = b.scale[...]\n    b_bias = b.bias[...]\n    b_mean = b.mean[...]\n    b_var = b.var[...]\n\n    f(m)\n\n    assert n == 1\n    assert m.a is b\n    assert m.b is a\n    np.testing.assert_allclose(a_kernel, a.kernel[...])\n    np.testing.assert_allclose(a_bias, a.bias[...])\n    np.testing.assert_allclose(b_scale, b.scale[...])\n    np.testing.assert_allclose(b_bias, b.bias[...])\n    np.testing.assert_allclose(b_mean, b.mean[...])\n    np.testing.assert_allclose(b_var, b.var[...])\n\n    f(m)\n\n    assert n == 2\n    assert m.a is a\n    assert m.b is b\n\n    f(m)\n\n    assert n == 2\n    assert m.a is b\n    assert m.b is a\n\n    f(m)\n\n    assert n == 2\n    assert m.a is a\n    assert m.b is b\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_jit_custom_vjp(self, graph_mode, graph_updates):\n    @nnx.custom_vjp(graph=graph_mode, graph_updates=graph_updates)\n    def f(x, y):\n      return jnp.sin(x) * y\n\n    def f_fwd(x, y):\n      return f(x, y), (jnp.cos(x), jnp.sin(x), y)\n\n    def f_bwd(res, g):\n      cos_x, sin_x, y = res\n      return (cos_x * g * y, sin_x * g)\n\n    f.defvjp(f_fwd, f_bwd)\n\n    nnx_out = nnx.jit(f, graph=graph_mode, graph_updates=graph_updates)(jnp.array([1.0, 2.0]), jnp.array([3.0, 4.0]))\n    jax_out = jax.jit(f)(jnp.array([1.0, 2.0]), jnp.array([3.0, 4.0]))\n    assert (nnx_out == jax_out).all()\n\n  def test_cached_unflatten_same_type(self):\n    n = 0\n\n    class Foo(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.a = nnx.Linear(2, 2, rngs=rngs)\n        self.b = nnx.Linear(2, 2, rngs=rngs)\n\n    @nnx.jit\n    def f(m: Foo):\n      nonlocal n\n      n += 1\n      m.a, m.b = m.b, m.a\n\n    m = Foo(rngs=nnx.Rngs(0))\n    a = m.a\n    b = m.b\n\n    f(m)\n\n    assert n == 1\n    assert m.a is b\n    assert m.b is a\n\n    f(m)\n\n    assert n == 1\n    assert m.a is a\n    assert m.b is b\n\n  def test_objects_in_pytree(self):\n    n = 0\n\n    class Foo(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.a = nnx.Linear(2, 2, rngs=rngs)\n        self.b = nnx.Linear(2, 2, rngs=rngs)\n\n    class FooDict(tp.TypedDict):\n      foo: Foo\n\n    @nnx.jit\n    def f(tree: tuple[FooDict]):\n      nonlocal n\n      n += 1\n      m = tree[0]['foo']\n      m.a, m.b = m.b, m.a\n\n    m = Foo(rngs=nnx.Rngs(0))\n    a = m.a\n    b = m.b\n\n    f(({'foo': m},))\n\n    assert n == 1\n    assert m.a is b\n    assert m.b is a\n\n    f(({'foo': m},))\n\n    assert n == 1\n    assert m.a is a\n    assert m.b is b\n\n  def test_cached_unflatten_swap_variables(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = nnx.Param(1)\n        self.b = nnx.Param(2)\n\n    @nnx.jit\n    def f(m: Foo):\n      m.a, m.b = m.b, m.a\n\n    m = Foo()\n    a = m.a\n    b = m.b\n\n    f(m)\n\n    assert m.a is b\n    assert m.b is a\n\n  def test_cached_unflatten_add_self_reference(self):\n    n = 0\n\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.ref: tp.Optional[Foo] = nnx.data(None)  # type: ignore[name-error]\n\n    @nnx.jit\n    def f(m: Foo):\n      nonlocal n\n      n += 1\n      m.ref = m\n\n    m = Foo()\n\n    f(m)\n\n    assert n == 1\n    assert m.ref is m\n\n    f(m)\n\n    assert n == 2\n    assert m.ref is m\n\n    f(m)\n\n    assert n == 2\n    assert m.ref is m\n\n  def test_cached_unflatten_ref_in_output(self):\n    n = 0\n\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.ref: tp.Optional[Foo] = nnx.data(None)  # type: ignore[name-error]\n\n    @nnx.jit\n    def f(m: Foo):\n      nonlocal n\n      n += 1\n      m.ref = m\n      return m\n\n    m = Foo()\n\n    m2 = f(m)\n\n    assert n == 1\n    assert m.ref is m\n    assert m2 is m\n\n    m2 = f(m)\n\n    assert n == 2\n    assert m.ref is m\n    assert m2 is m\n\n    m2 = f(m)\n\n    assert n == 2\n    assert m.ref is m\n    assert m2 is m\n\n  def test_apply_shardings(self):\n    n_devices = max(jax.local_device_count() // 2, 1)\n    devices = mesh_utils.create_device_mesh(\n      (n_devices, jax.local_device_count() // n_devices)\n    )\n    mesh = jax.sharding.Mesh(devices, ('a', 'b'))\n\n    def sharding(*args):\n      return jax.sharding.NamedSharding(mesh, jax.sharding.PartitionSpec(*args))\n\n    state_sharding = nnx.StateSharding(\n      {\n        nnx.PathContains('kernel'): sharding('a', 'b'),\n        nnx.PathContains('bias'): sharding('b'),\n      }\n    )\n\n    m = nnx.Linear(16, 32, rngs=nnx.Rngs(0))\n\n    self.assertNotIsInstance(m.kernel.sharding, jax.sharding.NamedSharding)\n\n    @nnx.jit(in_shardings=(state_sharding,))\n    def constrain_object(m):\n      pass\n\n    constrain_object(m)\n\n    self.assertIsInstance(m.kernel.sharding, jax.sharding.NamedSharding)\n\n  def test_cache_args(self):\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    @nnx.jit\n    def f(cached_m: nnx.Linear, m: nnx.Linear):\n      self.assertIsNot(cached_m, m)\n      self.assertIs(cached_m.kernel, m.kernel)\n      self.assertIs(cached_m.bias, m.bias)\n      return cached_m\n\n    cached_f = nnx.cached_partial(f, m)\n    cached_m = cached_f(m)\n\n    self.assertIsNot(m, cached_m)\n    self.assertIs(m.kernel, cached_m.kernel)\n    self.assertIs(m.bias, cached_m.bias)\n\n    # test that cached m is reused\n    cached_m2 = cached_f(m)\n    self.assertIs(cached_m, cached_m2)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_jit_wrapped(self, graph_mode, graph_updates):\n    class Foo(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.count = nnx.Variable(jnp.array(0))\n\n      @nnx.jit(graph=graph_mode, graph_updates=graph_updates)\n      def __call__(self, x: jax.Array) -> jax.Array:\n        self.count[...] += 1\n        return x * 2\n\n    m = Foo(rngs=nnx.Rngs(0))\n    x = jnp.array(3.0)\n\n    @nnx.jit(graph=graph_mode, graph_updates=graph_updates)\n    def f(m: nnx.Linear, x):\n      return m(x)\n\n    lowered = f.lower(m, x)\n    compiled = lowered.compile()\n    text = compiled.as_text()\n    cost_analysis = compiled.cost_analysis()\n    self.assertIsNotNone(cost_analysis)\n    self.assertIsNotNone(text)\n\n    y = compiled(m, x)\n    np.testing.assert_allclose(y, 6.0)\n    self.assertEqual(m.count[...], 1)\n    y = compiled(m, x)\n    self.assertEqual(m.count[...], 2)\n\n  @parameterized.parameters(\n    {'graph_mode': True, 'graph_updates': True, 'static_argnums': (2,), 'static_argnames': None},\n    {'graph_mode': True, 'graph_updates': True, 'static_argnums': None, 'static_argnames': ('use_relu',)},\n    {'graph_mode': True, 'graph_updates': False, 'static_argnums': (2,), 'static_argnames': None},\n    {'graph_mode': True, 'graph_updates': False, 'static_argnums': None, 'static_argnames': ('use_relu',)},\n    {'graph_mode': False, 'graph_updates': False, 'static_argnums': (2,), 'static_argnames': None},\n    {'graph_mode': False, 'graph_updates': False, 'static_argnums': None, 'static_argnames': ('use_relu',)},\n  )\n  def test_jit_static_args_with_shardings(self, graph_mode, graph_updates, static_argnums, static_argnames):\n    \"\"\"Test static arguments work correctly with in_shardings.\"\"\"\n    n_devices = jax.local_device_count()\n    devices = mesh_utils.create_device_mesh((n_devices,))\n    mesh = jax.sharding.Mesh(devices, ('data',))\n\n    def fn(x, scale, use_relu):\n      y = x * scale\n      if use_relu:\n        y = jnp.maximum(y, 0)\n      return y.sum()\n\n    x = jnp.linspace(-1.0, 1.0, 16, dtype=jnp.float32).reshape(4, 4)\n    x_sharding = jax.sharding.NamedSharding(mesh, jax.sharding.PartitionSpec('data'))\n\n    f = nnx.jit(fn, in_shardings=(x_sharding, None),\n                static_argnums=static_argnums, static_argnames=static_argnames,\n                graph=graph_mode, graph_updates=graph_updates)\n    y_relu = f(x, 0.5, True)\n    y_no_relu = f(x, 0.5, False)\n    self.assertNotEqual(y_relu, y_no_relu)\n\n  @parameterized.parameters(\n    {\n      'static_args': {'static_argnums': (2, 3)},\n    },\n    {\n      'static_args': {'static_argnames': ('static_arg1', 'static_arg2')},\n    },\n  )\n  def test_with_sharding_and_static_args(self, static_args):\n    n_devices = max(jax.local_device_count() // 2, 1)\n    devices = mesh_utils.create_device_mesh(\n      (n_devices, jax.local_device_count() // n_devices)\n    )\n    mesh = jax.sharding.Mesh(devices, ('a', 'b'))\n\n    def sharding(*args):\n      return jax.sharding.NamedSharding(mesh, jax.sharding.PartitionSpec(*args))\n\n    state_sharding = nnx.StateSharding(\n      {\n        nnx.PathContains('kernel'): sharding('a', 'b'),\n        nnx.PathContains('bias'): sharding('b'),\n      }\n    )\n\n    m = nnx.Linear(16, 32, rngs=nnx.Rngs(0))\n    self.assertNotIsInstance(m.kernel.sharding, jax.sharding.NamedSharding)\n\n    @nnx.jit(\n      in_shardings=(state_sharding, None),\n      **static_args,\n    )\n    def constrain_object(m, scale: float, static_arg1: bool, static_arg2: bool):\n      new_sharding = jax.sharding.NamedSharding(mesh, jax.sharding.PartitionSpec('b', 'a'))\n      m.kernel = jax.lax.with_sharding_constraint(m.kernel, new_sharding)\n      return None\n\n    constrain_object(m, 0.5, True, True)\n    self.assertEqual(m.kernel.sharding.spec, jax.sharding.PartitionSpec(\"a\", \"b\"))\n\n\nclass TestTreeJIT(parameterized.TestCase):\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_tree_jit_basic(self, graph, graph_updates):\n    m = nnx.Dict(a=nnx.Param(jnp.array(1)))\n\n    @nnx.jit(graph=graph, graph_updates=graph_updates)\n    def g(m: nnx.Dict):\n      m.a[...] = 2\n      return 1.0\n\n    out = g(m)\n\n    assert m.a[...] == 2\n    assert out == 1.0\n\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_tree_jit_module(self, graph, graph_updates):\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    @nnx.jit(graph=graph, graph_updates=graph_updates)\n    def f(m, x):\n      return m(x)\n\n    x = jnp.ones((1, 2))\n    y = f(m, x)\n    self.assertEqual(y.shape, (1, 3))\n\n  def test_tree_jit_variable_update(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.count = nnx.Variable(jnp.array(0))\n\n      @nnx.jit(graph=False)\n      def __call__(self, x):\n        self.count[...] += 1\n        return x * 2\n\n    m = Foo()\n    y = m(jnp.array(3.0))\n    np.testing.assert_allclose(y, 6.0)\n    self.assertEqual(m.count[...], 1)\n    y = m(jnp.array(3.0))\n    self.assertEqual(m.count[...], 2)\n\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_tree_jit_no_retrace(self, graph, graph_updates):\n    n = 0\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    @nnx.jit(graph=graph, graph_updates=graph_updates)\n    def f(m, x):\n      nonlocal n\n      n += 1\n      return m(x)\n\n    x = jnp.ones((1, 2))\n    y = f(m, x)\n    self.assertEqual(n, 1)\n    self.assertEqual(y.shape, (1, 3))\n\n    y = f(m, x)\n    self.assertEqual(n, 1)\n    self.assertEqual(y.shape, (1, 3))\n\n  def test_tree_jit_static_argnums(self):\n    @nnx.jit(graph=False, static_argnums=(1,))\n    def f(x, use_relu):\n      if use_relu:\n        return jnp.maximum(x, 0)\n      return x\n\n    x = jnp.array([-1.0, 2.0])\n    y_relu = f(x, True)\n    np.testing.assert_allclose(y_relu, jnp.array([0.0, 2.0]))\n    y_no_relu = f(x, False)\n    np.testing.assert_allclose(y_no_relu, x)\n\n  def test_tree_jit_no_input_output_aliasing(self):\n    v = nnx.Param(jnp.array(1.0))\n\n    @nnx.jit(graph=False)\n    def f(v):\n      return v\n\n    with self.assertRaisesRegex(ValueError, 'does not support returning input Variables as outputs'):\n      f(v)\n\n  def test_tree_jit_no_shared_variable_refs(self):\n    v = nnx.Param(jnp.array(1.0))\n\n    @nnx.jit(graph=False)\n    def f(v1, v2):\n      pass\n\n    with self.assertRaisesRegex(\n        ValueError, 'found at paths'\n    ):\n      f(v, v)\n\n  def test_tree_jit_new_variable_output_ok(self):\n    @nnx.jit(graph=False)\n    def f(x):\n      return nnx.Param(x + 1)\n\n    v = f(jnp.array(1.0))\n    self.assertIsInstance(v, nnx.Param)\n    np.testing.assert_allclose(v[...], 2.0)\n\n  def test_tree_jit_donate_argnums_unchanged_var(self):\n    v = nnx.Param(jnp.array(1.0))\n\n    @nnx.jit(graph=False, donate_argnums=(0,))\n    def f(v):\n      return v[...] + 1.0\n\n    out = f(v)\n    np.testing.assert_allclose(out, 2.0)\n    np.testing.assert_allclose(v[...], 1.0)\n\n    out = f(v)\n    np.testing.assert_allclose(out, 2.0)\n    np.testing.assert_allclose(v[...], 1.0)\n\n  def test_tree_jit_donate_argnums_module(self):\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    original_kernel = jnp.copy(m.kernel[...])\n\n    @nnx.jit(graph=False, donate_argnums=(0,))\n    def f(m, x):\n      return m(x)\n\n    x = jnp.ones((1, 2))\n    y = f(m, x)\n    self.assertEqual(y.shape, (1, 3))\n    np.testing.assert_allclose(m.kernel[...], original_kernel)\n\n    y = f(m, x)\n    self.assertEqual(y.shape, (1, 3))\n    np.testing.assert_allclose(m.kernel[...], original_kernel)\n\n  def test_tree_jit_donate_argnums_with_mutation(self):\n    v = nnx.Param(jnp.array(0.0))\n\n    @nnx.jit(graph=False, donate_argnums=(0,))\n    def f(v):\n      v[...] += 1.0\n      return None\n\n    f(v)\n    np.testing.assert_allclose(v[...], 1.0)\n    f(v)\n    np.testing.assert_allclose(v[...], 2.0)\n\n  def test_tree_jit_donate_argnames(self):\n    v = nnx.Param(jnp.array(1.0))\n\n    @nnx.jit(graph=False, donate_argnames=('v',))\n    def f(v):\n      return v[...] + 1.0\n\n    out = f(v=v)\n    np.testing.assert_allclose(out, 2.0)\n    np.testing.assert_allclose(v[...], 1.0)\n\n    out = f(v=v)\n    np.testing.assert_allclose(out, 2.0)\n    np.testing.assert_allclose(v[...], 1.0)\n\n  def test_tree_jit_donate_selective(self):\n    donated = nnx.Param(jnp.array(1.0))\n    not_donated = nnx.Param(jnp.array(2.0))\n\n    @nnx.jit(graph=False, donate_argnums=(0,))\n    def f(donated, not_donated):\n      return donated[...] + not_donated[...]\n\n    out = f(donated, not_donated)\n    np.testing.assert_allclose(out, 3.0)\n    np.testing.assert_allclose(donated[...], 1.0)\n    np.testing.assert_allclose(not_donated[...], 2.0)\n\n    out = f(donated, not_donated)\n    np.testing.assert_allclose(out, 3.0)\n\n  @parameterized.parameters(True, False)\n  def test_jit_partial_basic(self, graph_mode):\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    def f(m, x):\n      return m(x)\n\n    f_jit = nnx.jit_partial(f, m, graph=graph_mode, graph_updates=False)\n    x = jnp.ones((1, 2))\n    y = f_jit(x)\n    self.assertEqual(y.shape, (1, 3))\n\n  @parameterized.parameters(True, False)\n  def test_jit_partial_lower_compile(self, graph_mode):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.count = nnx.Variable(jnp.array(0))\n        self.w = nnx.Param(jnp.ones((2, 3)))\n\n    m = Foo()\n\n    def f(m, x):\n      m.count[...] += 1\n      return x @ m.w[...]\n\n    f_jit = nnx.jit_partial(f, m, graph=graph_mode, graph_updates=False)\n    compiled = f_jit.lower(jnp.ones((1, 2))).compile()\n\n    x = jnp.ones((1, 2))\n    y = compiled(x)\n    self.assertEqual(y.shape, (1, 3))\n    np.testing.assert_allclose(y, jnp.ones((1, 3)) * 2)\n    self.assertEqual(m.count[...], 1)\n\n    y = compiled(x)\n    self.assertEqual(m.count[...], 2)\n\n  @parameterized.parameters(True, False)\n  def test_jit_partial_variable_update(self, graph_mode):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.count = nnx.Variable(jnp.array(0))\n\n    m = Foo()\n\n    def f(m, x):\n      m.count[...] += 1\n      return x * 2\n\n    f_jit = nnx.jit_partial(f, m, graph=graph_mode, graph_updates=False)\n    y = f_jit(jnp.array(3.0))\n    np.testing.assert_allclose(y, 6.0)\n    self.assertEqual(m.count[...], 1)\n    y = f_jit(jnp.array(3.0))\n    self.assertEqual(m.count[...], 2)\n\n  @parameterized.parameters(True, False)\n  def test_jit_partial_multiple_args(self, graph_mode):\n    m1 = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    m2 = nnx.Linear(3, 4, rngs=nnx.Rngs(1))\n\n    def f(m1, m2, x):\n      return m2(m1(x))\n\n    f_jit = nnx.jit_partial(f, m1, m2, graph=graph_mode, graph_updates=False)\n    x = jnp.ones((1, 2))\n    y = f_jit(x)\n    self.assertEqual(y.shape, (1, 4))\n\n  @parameterized.parameters(True, False)\n  def test_jit_partial_no_retrace(self, graph_mode):\n    n = 0\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    def f(m, x):\n      nonlocal n\n      n += 1\n      return m(x)\n\n    f_jit = nnx.jit_partial(f, m, graph=graph_mode, graph_updates=False)\n    x = jnp.ones((1, 2))\n    f_jit(x)\n    self.assertEqual(n, 1)\n    f_jit(x)\n    self.assertEqual(n, 1)\n\n  @parameterized.parameters(True, False)\n  def test_jit_partial_no_retrace_after_mutation(self, graph_mode):\n    n = 0\n\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.w = nnx.Param(jnp.ones((2, 3)))\n\n    m = Foo()\n\n    def f(m, x):\n      nonlocal n\n      n += 1\n      return x @ m.w[...]\n\n    f_jit = nnx.jit_partial(f, m, graph=graph_mode, graph_updates=False)\n    x = jnp.ones((1, 2))\n    f_jit(x)\n    self.assertEqual(n, 1)\n    m.w[...] = jnp.zeros((2, 3))\n    y = f_jit(x)\n    self.assertEqual(n, 1)\n    np.testing.assert_allclose(y, jnp.zeros((1, 3)))\n\n  @parameterized.parameters(True, False)\n  def test_jit_partial_no_partial_args(self, graph_mode):\n    f_partial = nnx.jit_partial(lambda x: x * 2, graph=graph_mode, graph_updates=False)\n    y = f_partial(jnp.array(3.0))\n    np.testing.assert_allclose(y, 6.0)\n\n  @parameterized.parameters((False,))\n  def test_jit_partial_in_shardings_none_broadcast(self, graph_mode):\n    n_devices = jax.local_device_count()\n    devices = mesh_utils.create_device_mesh((n_devices,))\n    mesh = jax.sharding.Mesh(devices, ('data',))\n\n    m = nnx.Linear(4, 3, rngs=nnx.Rngs(0))\n\n    def f(m, x):\n      return m(x)\n\n    f_jit = nnx.jit_partial(f, m, in_shardings=(None, None), graph=graph_mode, graph_updates=False)\n    x = jnp.ones((n_devices, 4))\n    y = f_jit(x)\n    self.assertEqual(y.shape, (n_devices, 3))\n\n  @parameterized.parameters((False,))\n  def test_jit_partial_in_shardings_named(self, graph_mode):\n    n_devices = jax.local_device_count()\n    devices = mesh_utils.create_device_mesh((n_devices,))\n    mesh = jax.sharding.Mesh(devices, ('data',))\n    PS = jax.sharding.PartitionSpec\n\n    v = nnx.Param(jnp.ones((n_devices, 4)))\n\n    def f(v, x):\n      return v[...] + x\n\n    x_sharding = jax.sharding.NamedSharding(mesh, PS('data'))\n    v_sharding = jax.sharding.NamedSharding(mesh, PS('data'))\n    f_jit = nnx.jit_partial(\n        f, v, in_shardings=(v_sharding, x_sharding), graph=graph_mode,\n        graph_updates=False)\n    x = jnp.ones((n_devices, 4))\n    y = f_jit(x)\n    self.assertEqual(y.shape, (n_devices, 4))\n\n  @parameterized.parameters((False,))\n  def test_jit_partial_mixed_shardings(self, graph_mode):\n    n_devices = jax.local_device_count()\n    devices = mesh_utils.create_device_mesh((n_devices,))\n    mesh = jax.sharding.Mesh(devices, ('data',))\n    PS = jax.sharding.PartitionSpec\n\n    m1 = nnx.Linear(4, 3, rngs=nnx.Rngs(0))\n    m2 = nnx.Linear(3, 2, rngs=nnx.Rngs(1))\n\n    def f(m1, m2, x):\n      return m2(m1(x))\n\n    x_sharding = jax.sharding.NamedSharding(mesh, PS('data'))\n    f_jit = nnx.jit_partial(\n        f, m1, m2, in_shardings=(None, None, x_sharding), graph=graph_mode,\n        graph_updates=False)\n    x = jnp.ones((n_devices, 4))\n    y = f_jit(x)\n    self.assertEqual(y.shape, (n_devices, 2))\n\n  @parameterized.parameters(True, False)\n  def test_jit_partial_in_shardings_non_tuple(self, graph_mode):\n    n_devices = jax.local_device_count()\n    devices = mesh_utils.create_device_mesh((n_devices,))\n    mesh = jax.sharding.Mesh(devices, ('data',))\n    PS = jax.sharding.PartitionSpec\n\n    m = nnx.Linear(4, 3, rngs=nnx.Rngs(0))\n\n    def f(m, x):\n      return m(x)\n\n    sharding = jax.sharding.NamedSharding(mesh, PS())\n    f_jit = nnx.jit_partial(f, m, in_shardings=sharding, graph=graph_mode, graph_updates=False)\n    x = jnp.ones((n_devices, 4))\n    y = f_jit(x)\n    self.assertEqual(y.shape, (n_devices, 3))\n\n  @parameterized.parameters(True, False)\n  def test_jit_partial_train_step(self, graph_mode):\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    optimizer = nnx.Optimizer(model, optax.adamw(1e-3), wrt=nnx.Param)\n\n    def train_step(model, optimizer, x, y):\n      def loss_fn(model):\n        return jnp.mean((model(x) - y) ** 2)\n      loss, grads = nnx.value_and_grad(loss_fn)(model)\n      optimizer.update(model, grads)\n      return loss\n\n    train_step_fn = nnx.jit_partial(train_step, model, optimizer, graph=graph_mode, graph_updates=False)\n    for _ in range(2):\n      x, y = jnp.ones((10, 2)), jnp.ones((10, 3))\n      loss = train_step_fn(x, y)\n      self.assertIsInstance(loss, jax.Array)\n\n  def test_jit_partial_shared_variable(self):\n    v = nnx.Param(jnp.array(1.0))\n\n    class Container(nnx.Module):\n      def __init__(self, v):\n        self.v = v\n\n    c1 = Container(v)\n    c2 = Container(v)\n\n    def f(c1, c2, x):\n      c1.v[...] += x\n      return c1.v[...] + c2.v[...]\n\n    f_jit = nnx.jit_partial(f, c1, c2, graph=True, graph_updates=False)\n    y = f_jit(jnp.array(1.0))\n    np.testing.assert_allclose(y, 4.0)\n    np.testing.assert_allclose(v[...], 2.0)\n\n  def test_jit_inconsistent_aliasing(self):\n    v = nnx.Param(jnp.array(1.0))\n    P = jax.sharding.PartitionSpec\n\n    @nnx.jit(\n      in_shardings=(P(), P('x')),\n      graph=True, graph_updates=False,\n    )\n    def f(a, b):\n      return a[...] + b[...]\n\n    mesh = jax.sharding.Mesh(jax.devices(), ('x',))\n    with mesh:\n      with self.assertRaisesRegex(ValueError, 'Inconsistent aliasing'):\n        f(v, v)\n\n\nclass TestEvalShape(parameterized.TestCase):\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_eval_shape(self, graph, graph_updates):\n    abs_model = nnx.eval_shape(\n      lambda: nnx.Linear(1, 2, rngs=nnx.Rngs(0)),\n      graph=graph, graph_updates=graph_updates,\n    )\n    self.assertIsInstance(abs_model, nnx.Linear)\n    self.assertIsInstance(abs_model.kernel.get_value(), jax.ShapeDtypeStruct)\n\n  def test_eval_shape_mutable_array(self):\n    with nnx.var_defaults(hijax=True):\n      abs_model = nnx.eval_shape(lambda: nnx.Linear(1, 2, rngs=nnx.Rngs(0)))\n    self.assertIsInstance(abs_model, nnx.Linear)\n    self.assertIsInstance(abs_model.kernel.get_value(), jax.ShapeDtypeStruct)\n    self.assertEqual(abs_model.kernel.shape, (1, 2))\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_eval_shape_with_module_input(self, graph, graph_updates):\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    def f(m, x):\n      return m(x)\n\n    x = jnp.ones((4, 2))\n    out = nnx.eval_shape(f, model, x, graph=graph, graph_updates=graph_updates)\n    self.assertIsInstance(out, jax.ShapeDtypeStruct)\n    self.assertEqual(out.shape, (4, 3))\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_eval_shape_no_state_update(self, graph, graph_updates):\n    count = nnx.Variable(jnp.array(0))\n\n    def f(c):\n      c[...] += 1\n      return jnp.ones((3, 4)) * c[...]\n\n    out = nnx.eval_shape(f, count, graph=graph, graph_updates=graph_updates)\n    self.assertIsInstance(out, jax.ShapeDtypeStruct)\n    self.assertEqual(out.shape, (3, 4))\n    self.assertEqual(count[...], 0)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_eval_shape_no_input_output_aliasing(self, graph, graph_updates):\n    v = nnx.Param(jnp.array(1.0))\n\n    def f(v):\n      return v\n\n    with self.assertRaises(ValueError):\n      nnx.eval_shape(f, v, graph=graph, graph_updates=graph_updates)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_eval_shape_no_shared_variable_refs(self, graph, graph_updates):\n    v = nnx.Param(jnp.array(1.0))\n\n    def f(v1, v2):\n      v1[...] += 1\n      return None\n\n    with self.assertRaises(ValueError):\n      nnx.eval_shape(f, v, v, graph=graph, graph_updates=graph_updates)\n\nclass TestShardMap(parameterized.TestCase):\n  def test_basic_shardmap(self):\n    n_devices = jax.local_device_count()\n    devices = mesh_utils.create_device_mesh((n_devices,))\n    mesh = jax.sharding.Mesh(devices, ('a',))\n    PS = jax.sharding.PartitionSpec\n\n    state_sharding = nnx.StateSharding(\n      {\n        nnx.PathContains('kernel'): PS(None, 'a'),\n        nnx.PathContains('bias'): PS(),\n      }\n    )\n\n    m = nnx.Linear(16, 32, rngs=nnx.Rngs(0))\n\n    self.assertNotIsInstance(m.kernel.sharding, jax.sharding.NamedSharding)\n\n    @nnx.shard_map(mesh=mesh, in_specs=(state_sharding,), out_specs=None)\n    def f(m: nnx.Linear):\n      self.assertEqual(\n        m.kernel.shape, (m.in_features, m.out_features // n_devices)\n      )\n      self.assertEqual(m.bias.shape, (m.out_features,))\n\n    f(m)\n\n    self.assertIsInstance(m.kernel.sharding, jax.sharding.NamedSharding)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_basic_shardmap_variables(self, graph, graph_updates):\n    n_devices = jax.local_device_count()\n    devices = mesh_utils.create_device_mesh((n_devices,))\n    mesh = jax.sharding.Mesh(devices, ('a',))\n    P = jax.sharding.PartitionSpec\n\n    rngs = nnx.Rngs(0)\n    w = nnx.Param(jax.random.normal(rngs.params(), (16, 32)))\n    b = nnx.Param(jax.random.normal(rngs.params(), (32,)))\n    count = nnx.BatchStat(jnp.array(0))\n\n    self.assertNotIsInstance(w.sharding, jax.sharding.NamedSharding)\n\n    @nnx.shard_map(\n      mesh=mesh, in_specs=(P(None, 'a'), P(), P()), out_specs=None,\n      graph=graph, graph_updates=graph_updates,\n    )\n    def f(w, b, count):\n      count[...] += 1\n      self.assertEqual(w.shape, (16, 32 // n_devices))\n      self.assertEqual(b.shape, (32,))\n\n    f(w, b, count)\n\n    if graph and graph_updates:\n      self.assertIsInstance(w.sharding, jax.sharding.NamedSharding)\n      self.assertIsInstance(b.sharding, jax.sharding.NamedSharding)\n    self.assertEqual(count[...], 1)\n\n  def test_from_state(self):\n    n_devices = jax.local_device_count()\n    devices = mesh_utils.create_device_mesh((n_devices,))\n    mesh = jax.sharding.Mesh(devices, ('a',))\n    PS = jax.sharding.PartitionSpec\n\n    state_spec = nnx.State(\n      {\n        'kernel': PS(None, 'a'),\n        'bias': PS(),\n      }\n    )\n    state_sharding = nnx.StateSharding(state_spec)\n\n    m = nnx.Linear(16, 32, rngs=nnx.Rngs(0))\n\n    self.assertNotIsInstance(m.kernel.sharding, jax.sharding.NamedSharding)\n\n    @nnx.shard_map(mesh=mesh, in_specs=(state_sharding,), out_specs=None)\n    def f(m: nnx.Linear):\n      self.assertEqual(\n        m.kernel.shape, (m.in_features, m.out_features // n_devices)\n      )\n      self.assertEqual(m.bias.shape, (m.out_features,))\n\n    f(m)\n\n    self.assertIsInstance(m.kernel.sharding, jax.sharding.NamedSharding)\n    self.assertIsInstance(m.bias.sharding, jax.sharding.NamedSharding)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_simple_data_parallel(self, graph, graph_updates):\n    P = jax.sharding.PartitionSpec\n    n_devices = jax.local_device_count()\n\n    mesh = jax.sharding.Mesh(jax.local_devices(), ('data',))\n\n    with jax.set_mesh(mesh):\n      m = nnx.Linear(\n          in_features=2,\n          out_features=3,\n          kernel_metadata={'out_sharding': jax.P(None)},\n          bias_metadata={'out_sharding': jax.P(None)},\n          rngs=nnx.Rngs(0),\n    )\n    x = jnp.ones((32, 2))\n\n    @nnx.shard_map(\n      mesh=mesh, in_specs=(P(None), P('data')), out_specs=P('data'),\n      graph=graph, graph_updates=graph_updates,\n    )\n    def f(m, x):\n      self.assertEqual(x.shape, (32 // n_devices, 2))\n      return m(x)\n\n    y = f(m, x)\n\n    self.assertEqual(y.shape, (32, 3))\n    self.assertIsInstance(y.sharding, jax.sharding.NamedSharding)\n    if graph and graph_updates:\n      self.assertIsInstance(m.kernel.sharding, jax.sharding.NamedSharding)\n      self.assertIsInstance(m.bias.sharding, jax.sharding.NamedSharding)\n\n  def test_simple_tensor_parallel(self):\n    P = jax.sharding.PartitionSpec\n\n    mesh = jax.sharding.Mesh(jax.local_devices(), ('model',))\n\n    class MLP(nnx.Module):\n      def __init__(self, din, dhidden, dout, *, rngs: nnx.Rngs):\n        self.linear1 = nnx.Linear(din, dhidden, use_bias=False, rngs=rngs)\n        self.linear2 = nnx.Linear(dhidden, dout, use_bias=False, rngs=rngs)\n\n      def __call__(self, x):\n        return self.linear2(jax.nn.relu(self.linear1(x)))\n\n    m = MLP(2, 64, 3, rngs=nnx.Rngs(0))\n    x = jnp.ones((32, 2))\n\n    def path_ends_with(path_suffix):\n      return lambda path, value: path[-len(path_suffix) :] == path_suffix\n\n    model_sharding = nnx.StateSharding(\n      {\n        path_ends_with(('linear1', 'kernel')): P(None, 'model'),\n        path_ends_with(('linear2', 'kernel')): P('model', None),\n      }\n    )\n\n    @nnx.shard_map(\n      mesh=mesh, in_specs=(model_sharding, P(None)), out_specs=P(None)\n    )\n    def f(m, x):\n      y = m(x)\n      return jax.lax.psum(y, 'model')\n\n    y = f(m, x)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_shardmap_with_sharding_names(self, graph, graph_updates):\n    n_devices = jax.local_device_count()\n    P = jax.sharding.PartitionSpec\n    mesh = jax.sharding.Mesh(jax.local_devices(), ('data',))\n\n    with jax.set_mesh(mesh):\n      w = nnx.Param(jnp.ones((8, 4)), out_sharding=('data', None))\n      b = nnx.Param(jnp.ones((4,)), out_sharding=(None,))\n\n    self.assertIsInstance(w.get_raw_value().sharding, jax.sharding.NamedSharding)\n    self.assertEqual(w.out_sharding, ('data', None))\n    self.assertEqual(b.out_sharding, (None,))\n\n    @nnx.shard_map(\n      mesh=mesh, in_specs=(P('data', None), P(None)), out_specs=P('data', None),\n      graph=graph, graph_updates=graph_updates,\n    )\n    def f(w, b):\n      self.assertEqual(w.shape, (8 // n_devices, 4))\n      self.assertEqual(b.shape, (4,))\n      return w + b[None]\n\n    y = f(w, b)\n    self.assertEqual(y.shape, (8, 4))\n    self.assertIsInstance(y.sharding, jax.sharding.NamedSharding)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_shardmap_sharding_names_mutation(self, graph, graph_updates):\n    n_devices = jax.local_device_count()\n    P = jax.sharding.PartitionSpec\n    mesh = jax.sharding.Mesh(jax.local_devices(), ('data',))\n\n    with jax.set_mesh(mesh):\n      w = nnx.Param(jnp.zeros((8, 4)), out_sharding=('data', None))\n      count = nnx.BatchStat(jnp.array(0))\n\n    @nnx.shard_map(\n      mesh=mesh, in_specs=(P('data', None), P()), out_specs=P('data', None),\n      graph=graph, graph_updates=graph_updates,\n    )\n    def f(w, count):\n      count[...] += 1\n      self.assertEqual(w.shape, (8 // n_devices, 4))\n      return w + 1.0\n\n    y = f(w, count)\n    self.assertEqual(count[...], 1)\n    self.assertEqual(y.shape, (8, 4))\n    np.testing.assert_allclose(w[...], jnp.zeros((8, 4)))\n\n  def test_shardmap_shared_variable(self):\n    P = jax.sharding.PartitionSpec\n    mesh = jax.sharding.Mesh(jax.local_devices(), ('data',))\n\n    v = nnx.Param(jnp.array(1.0))\n\n    class Container(nnx.Module):\n      def __init__(self, v):\n        self.v = v\n\n    c1 = Container(v)\n    c2 = Container(v)\n\n    @nnx.shard_map(\n      mesh=mesh, in_specs=(P(), P(), P()), out_specs=P(),\n      graph=True, graph_updates=True,\n    )\n    def f(c1, c2, x):\n      c1.v[...] += x\n      return c1.v[...] + c2.v[...]\n\n    y = f(c1, c2, jnp.array(1.0))\n    np.testing.assert_allclose(y, 4.0)\n    np.testing.assert_allclose(v[...], 2.0)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_shardmap_module_variable_update(self, graph, graph_updates):\n    P = jax.sharding.PartitionSpec\n    mesh = jax.sharding.Mesh(jax.local_devices(), ('data',))\n\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.count = nnx.Variable(jnp.array(0))\n\n    m = Foo()\n\n    @nnx.shard_map(\n      mesh=mesh, in_specs=(P(), P()), out_specs=P(),\n      graph=graph, graph_updates=graph_updates,\n    )\n    def f(m, x):\n      m.count[...] += 1\n      return x * 2\n\n    y = f(m, jnp.array(3.0))\n    np.testing.assert_allclose(y, 6.0)\n    self.assertEqual(m.count[...], 1)\n    y = f(m, jnp.array(3.0))\n    self.assertEqual(m.count[...], 2)\n\n  def test_shard_map_inconsistent_aliasing(self):\n    v = nnx.Param(jnp.array(1.0))\n    P = jax.sharding.PartitionSpec\n    mesh = jax.sharding.Mesh(jax.devices(), ('x',))\n\n    @nnx.shard_map(\n      mesh=mesh,\n      in_specs=(P(), P('x')),\n      out_specs=P(),\n      graph=True, graph_updates=False,\n    )\n    def f(a, b):\n      return a[...] + b[...]\n\n    with self.assertRaisesRegex(ValueError, 'Inconsistent aliasing'):\n      f(v, v)\n\n\nclass TestGrad(parameterized.TestCase):\n  def test_grad(self):\n    p1 = nnx.Param(10.0)\n    p2 = nnx.Param(20.0)\n\n    m = nnx.Dict(\n      a=nnx.List([p1, p2]),\n      b=p1,\n      c=7,\n      d=5.0,\n    )\n\n    @nnx.grad\n    def f(m: nnx.Dict):\n      # sum all params\n      return m['a'][0][...] + m['a'][1][...] + m['b'][...]\n\n    grads = f(m)\n\n    assert m.a[0] is m.b\n    assert isinstance(grads, nnx.State)\n    assert grads['a'][0][...] == 2.0\n    assert issubclass(type(grads['a'][0]), nnx.Variable)\n    assert grads['a'][1][...] == 1.0\n    assert issubclass(type(grads['a'][1]), nnx.Variable)\n    assert len(nnx.to_flat_state(grads)) == 2\n\n    nnx.update(m, grads)\n\n    assert m['a'][0] is m.b\n    assert m['a'][0][...] == 2.0\n    assert m['a'][1][...] == 1.0\n    assert m['b'][...] == 2.0\n    assert m['c'] == 7\n    assert m['d'] == 5.0\n\n  def test_grad_with_multiple_ref_types(self):\n    m = nnx.Dict(\n      a=nnx.List([nnx.Param(jnp.array(10.0)), nnx.BatchStat(jnp.array(20.0))]),\n      b=nnx.Param(jnp.array(10.0)),\n      c=7,\n      d=5.0,\n    )\n\n    @nnx.grad\n    def f(m: nnx.Dict):\n      # sum all params\n      return m.a[0] + m.a[1] + m.b\n\n    grads = f(m)\n\n    assert isinstance(grads, nnx.State)\n    assert grads['a'][0][...] == 1.0\n    assert issubclass(type(grads['a'][0]), nnx.Param)\n    assert len(grads) == 2\n\n    nnx.update(m, grads)\n\n    assert m.a[0][...] == 1.0\n    assert m.a[1][...] == 20.0\n    assert m.b[...] == 1.0\n    assert m.c == 7\n    assert m.d == 5.0\n\n  def test_grad_with_type_predicate(self):\n    m = nnx.Dict(\n      a=nnx.List([nnx.Param(jnp.array(10.0)), nnx.BatchStat(jnp.array(20.0))]),\n      b=nnx.Param(jnp.array(10.0)),\n      c=7,\n      d=5.0,\n    )\n\n    @nnx.grad(argnums=nnx.DiffState(0, nnx.BatchStat))\n    def f(m: nnx.Dict):\n      # sum all params\n      return m.a[0] + m.a[1] + m.b\n\n    grads = f(m)\n\n    assert isinstance(grads, nnx.State)\n    assert grads['a'][1][...] == 1.0\n    assert issubclass(type(grads['a'][1]), nnx.BatchStat)\n    assert len(grads) == 1\n\n    nnx.update(m, grads)\n\n    assert m.a[0][...] == 10.0\n    assert m.a[1][...] == 1.0\n    assert m.b[...] == 10.0\n    assert m.c == 7\n    assert m.d == 5.0\n\n  def test_multiple_inputs(self):\n    rngs = nnx.Rngs(0)\n    m = nnx.Linear(2, 3, rngs=rngs)\n    loss_fn = lambda m, x, y: jnp.mean((m(x) - y) ** 2)\n    grad_fn = nnx.grad(loss_fn)\n    x = jax.random.uniform(rngs(), (1, 2))\n    y = jnp.ones((1, 3))\n    grads = grad_fn(m, x, y)\n\n    assert 'kernel' in grads\n    assert grads['kernel'].shape == (2, 3)\n    assert 'bias' in grads\n    assert grads['bias'].shape == (3,)\n\n  @parameterized.parameters(\n    {\n      'loss_fn': lambda m1, m2, x, y: jnp.mean((m2(m1(x)) - y) ** 2),\n      'argnums': (0, 1),\n    },\n    {\n      'loss_fn': lambda x, m1, y, m2: jnp.mean((m2(m1(x)) - y) ** 2),\n      'argnums': (1, 3),\n    },\n  )\n  def test_multiple_graph_nodes(self, loss_fn, argnums):\n    rngs = nnx.Rngs(0)\n    m1 = nnx.Linear(2, 3, rngs=rngs)\n    m2 = nnx.Linear(3, 3, rngs=rngs)\n    grad_fn = nnx.grad(loss_fn, argnums=argnums)\n    x = jax.random.uniform(rngs(), (1, 2))\n    y = jnp.ones((1, 3))\n    inputs = [x, y]\n    inputs.insert(argnums[0], m1)\n    inputs.insert(argnums[1], m2)\n    grads_m1, grads_m2 = grad_fn(*inputs)\n\n    assert 'kernel' in grads_m1\n    assert grads_m1['kernel'].shape == (2, 3)\n    assert 'bias' in grads_m1\n    assert grads_m1['bias'].shape == (3,)\n    assert 'kernel' in grads_m2\n    assert grads_m2['kernel'].shape == (3, 3)\n    assert 'bias' in grads_m2\n    assert grads_m2['bias'].shape == (3,)\n\n  def test_multiple_args(self):\n    m1 = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    m2 = nnx.Linear(2, 3, rngs=nnx.Rngs(1))\n\n    m1_diffstate = nnx.DiffState(0, nnx.PathContains('kernel'))\n    m2_diffstate = nnx.DiffState(1, nnx.PathContains('bias'))\n\n    @nnx.grad(argnums=(m1_diffstate, m2_diffstate))\n    def loss_fn(m1: nnx.Linear, m2: nnx.Linear):\n      return jnp.mean(m1.kernel * m2.kernel) + jnp.mean(m1.bias * m2.bias)\n\n    grads_m1, grads_m2 = loss_fn(m1, m2)\n\n    self.assertIn('kernel', grads_m1)\n    self.assertNotIn('bias', grads_m1)\n    self.assertNotIn('kernel', grads_m2)\n    self.assertIn('bias', grads_m2)\n\n  def test_multiple_args_in_pytrees(self):\n    m1 = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    m2 = nnx.Linear(2, 3, rngs=nnx.Rngs(1))\n\n    m1_diffstate = nnx.DiffState(0, nnx.PathContains('kernel'))\n    m2_diffstate = nnx.DiffState(1, nnx.PathContains('bias'))\n\n    @nnx.grad(argnums=(m1_diffstate, m2_diffstate))\n    def loss_fn(l1: list[nnx.Linear], l2: list[nnx.Linear]):\n      return jnp.mean(l1[0].kernel * l2[0].kernel) + jnp.mean(\n        l1[0].bias * l2[0].bias\n      )\n\n    grads_m1, grads_m2 = loss_fn([m1], [m2])\n\n    self.assertIn('kernel', grads_m1[0])\n    self.assertNotIn('bias', grads_m1[0])\n    self.assertNotIn('kernel', grads_m2[0])\n    self.assertIn('bias', grads_m2[0])\n\n  def test_value_and_grad_multiple_args_in_pytrees(self):\n    m1 = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    m2 = nnx.Linear(2, 3, rngs=nnx.Rngs(1))\n\n    m1_diffstate = nnx.DiffState(0, nnx.PathContains('kernel'))\n    m2_diffstate = nnx.DiffState(1, nnx.PathContains('bias'))\n\n    @nnx.value_and_grad(argnums=(m1_diffstate, m2_diffstate))\n    def loss_fn(l1: list[nnx.Linear], l2: list[nnx.Linear]):\n      return jnp.mean(l1[0].kernel * l2[0].kernel) + jnp.mean(\n        l1[0].bias * l2[0].bias\n      )\n\n    loss, (grads_m1, grads_m2) = loss_fn([m1], [m2])\n\n    self.assertEqual(loss.shape, ())\n    self.assertIn('kernel', grads_m1[0])\n    self.assertNotIn('bias', grads_m1[0])\n    self.assertNotIn('kernel', grads_m2[0])\n    self.assertIn('bias', grads_m2[0])\n\n  def test_value_and_grad_with_aux(self):\n    m1 = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n    m2 = nnx.Linear(2, 3, rngs=nnx.Rngs(1))\n\n    m1_diffstate = nnx.DiffState(0, nnx.PathContains('kernel'))\n    m2_diffstate = nnx.DiffState(1, nnx.PathContains('bias'))\n\n    @nnx.value_and_grad(argnums=(m1_diffstate, m2_diffstate), has_aux=True)\n    def loss_fn(l1: list[nnx.Linear], l2: list[nnx.Linear]):\n      loss = jnp.mean(l1[0].kernel * l2[0].kernel) + jnp.mean(\n        l1[0].bias * l2[0].bias\n      )\n      l1[0].kernel.set_value(jnp.array(-1.0))\n      m3 = nnx.Linear(2, 3, rngs=nnx.Rngs(2))\n      return loss, m3\n\n    (loss, m3), (grads_m1, grads_m2) = loss_fn([m1], [m2])\n\n    self.assertEqual(m1.kernel[...], -1.0)\n    self.assertEqual(loss.shape, ())\n    self.assertIsInstance(m3, nnx.Linear)\n    self.assertIn('kernel', grads_m1[0])\n    self.assertNotIn('bias', grads_m1[0])\n    self.assertNotIn('kernel', grads_m2[0])\n    self.assertIn('bias', grads_m2[0])\n\n  def test_variables_in_grad(self):\n    p1 = nnx.Param(10.0)\n    p2 = nnx.Param(20.0)\n\n    m = dict(a=[p1, p2], b=p1)\n\n    @nnx.grad\n    def f(m: dict):\n      return m['a'][0] + m['a'][1] + m['b']\n\n    grads = f(m)\n\n    assert m['a'][0] is m['b']\n    assert isinstance(grads, dict)\n    assert issubclass(type(grads['a'][0]), nnx.Variable)\n    assert grads['a'][1][...] == 1.0\n    assert issubclass(type(grads['a'][1]), nnx.Variable)\n    assert len(jax.tree.leaves(grads)) == 2\n\n    jax.tree.map(\n      nnx.update, m, grads, is_leaf=lambda x: isinstance(x, nnx.Variable)\n    )\n\n    assert m['a'][0] is m['b']\n    assert m['a'][0][...] == 2.0\n    assert m['a'][1][...] == 1.0\n    assert m['b'][...] == 2.0\n\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_tree_mode_grad(self, graph, graph_updates):\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    @nnx.grad(graph=graph, graph_updates=graph_updates)\n    def loss_fn(m: nnx.Linear):\n      return jnp.mean(m.kernel) + jnp.mean(m.bias)\n\n    grads = loss_fn(m)\n\n    grad_type = nnx.State if graph and graph_updates else nnx.Linear\n    self.assertIsInstance(grads, grad_type)\n    self.assertEqual(grads.kernel.shape, (2, 3))\n    self.assertEqual(grads.bias.shape, (3,))\n\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_tree_mode_grad_multiple_inputs(self, graph, graph_updates):\n    rngs = nnx.Rngs(0)\n    m = nnx.Linear(2, 3, rngs=rngs)\n    loss_fn = lambda m, x, y: jnp.mean((m(x) - y) ** 2)\n    grad_fn = nnx.grad(loss_fn, graph=graph, graph_updates=graph_updates)\n    x = jax.random.uniform(rngs(), (1, 2))\n    y = jnp.ones((1, 3))\n    grads = grad_fn(m, x, y)\n\n    grad_type = nnx.State if graph and graph_updates else nnx.Linear\n    self.assertIsInstance(grads, grad_type)\n    self.assertEqual(grads.kernel.shape, (2, 3))\n    self.assertEqual(grads.bias.shape, (3,))\n\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_tree_mode_grad_multiple_graph_nodes(self, graph, graph_updates):\n    rngs = nnx.Rngs(0)\n    m1 = nnx.Linear(2, 3, rngs=rngs)\n    m2 = nnx.Linear(3, 3, rngs=rngs)\n    loss_fn = lambda m1, m2, x, y: jnp.mean((m2(m1(x)) - y) ** 2)\n    grad_fn = nnx.grad(loss_fn, argnums=(0, 1), graph=graph,\n                       graph_updates=graph_updates)\n    x = jax.random.uniform(rngs(), (1, 2))\n    y = jnp.ones((1, 3))\n    grads_m1, grads_m2 = grad_fn(m1, m2, x, y)\n\n    grad_type = nnx.State if graph and graph_updates else nnx.Linear\n    self.assertIsInstance(grads_m1, grad_type)\n    self.assertEqual(grads_m1.kernel.shape, (2, 3))\n    self.assertEqual(grads_m1.bias.shape, (3,))\n    self.assertIsInstance(grads_m2, grad_type)\n    self.assertEqual(grads_m2.kernel.shape, (3, 3))\n    self.assertEqual(grads_m2.bias.shape, (3,))\n\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_tree_mode_value_and_grad_with_aux(self, graph, graph_updates):\n    m = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    @nnx.value_and_grad(has_aux=True, graph=graph, graph_updates=graph_updates)\n    def loss_fn(m: nnx.Linear):\n      loss = jnp.mean(m.kernel)\n      m.kernel[...] = jnp.ones_like(m.kernel[...])\n      return loss, {'aux': 1}\n\n    (loss, aux), grads = loss_fn(m)\n\n    self.assertEqual(loss.shape, ())\n    self.assertEqual(aux, {'aux': 1})\n    grad_type = nnx.State if graph and graph_updates else nnx.Linear\n    self.assertIsInstance(grads, grad_type)\n    self.assertEqual(grads.kernel.shape, (2, 3))\n    np.testing.assert_allclose(m.kernel[...], jnp.ones((2, 3)))\n\n\nclass TestCustomVJP(parameterized.TestCase):\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_basic_call(self, graph, graph_updates):\n    m1 = nnx.Linear(1, 1, rngs=nnx.Rngs(0))\n    m2 = nnx.Linear(1, 1, rngs=nnx.Rngs(1))\n\n    @nnx.custom_vjp(graph=graph, graph_updates=graph_updates)\n    def f(m1: nnx.Linear, m2: nnx.Linear):\n      y = m1.kernel * m2.kernel\n      return y\n\n    def f_fwd(m1, m2):\n      y = f(m1, m2)\n      return y, (m1, m2)\n\n    def f_bwd(res, g):\n      inputs_g, out_g = g\n      m1, m2 = res\n      return inputs_g\n\n    f.defvjp(f_fwd, f_bwd)\n\n    y = f(m1, m2)\n\n    self.assertEqual(y.shape, (1, 1))\n\n  @parameterized.parameters(\n    (True,),\n    (False,),\n  )\n  def test_basic_call_with_state(self, graph):\n    m1 = nnx.Linear(1, 1, rngs=nnx.Rngs(0))\n    m2 = nnx.Linear(1, 1, rngs=nnx.Rngs(1))\n    state = nnx.BatchStat(jnp.array(0.0))\n\n    @nnx.custom_vjp(\n      nondiff_argnums=(2,), graph=graph, graph_updates=False,\n    )\n    def f(m1: nnx.Linear, m2: nnx.Linear, state):\n      y = m1.kernel * m2.kernel\n      state[...] = jnp.array(-1.0)\n      return y\n\n    def f_fwd(m1, m2, state):\n      y = f(m1, m2, state)\n      return y, (m1, m2)\n\n    def f_bwd(state, res, g):\n      inputs_g, out_g = g\n      m1, m2 = res\n      return inputs_g\n\n    f.defvjp(f_fwd, f_bwd)\n\n    y = f(m1, m2, state)\n\n    self.assertEqual(state[...], -1.0)\n    self.assertEqual(y.shape, (1, 1))\n\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_jax_example(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      x: nnx.Param[jax.Array]\n      y: nnx.Param[jax.Array]\n      z: int\n\n    @nnx.custom_vjp(graph=graph, graph_updates=graph_updates)\n    def f(m: Foo):\n      m.z += 1\n      return jnp.sin(m.x) * m.y  # type: ignore\n\n    def f_fwd(m: Foo):\n      y = f(m)\n      res = (jnp.cos(m.x), jnp.sin(m.x), m)  # type: ignore\n      return y, res\n\n    def f_bwd(res, g):\n      cos_x, sin_x, m = res\n      if graph and graph_updates:\n        (m_g,), out_g = g\n        self.assertIsInstance(m_g, nnx.State)\n        m_g['x'][...] = cos_x * out_g * m.y\n        m_g['y'][...] = sin_x * out_g\n        return (m_g,)\n      else:\n        out_g = g\n        m_g = nnx.clone(m)\n        m_g.x[...] = cos_x * out_g * m.y\n        m_g.y[...] = sin_x * out_g\n        return (m_g,)\n\n    f.defvjp(f_fwd, f_bwd)\n\n    m = Foo(nnx.Param(jnp.array(1.0)), nnx.Param(jnp.array(2.0)), 0)\n\n    if graph and graph_updates:\n      grads = nnx.grad(f, argnums=nnx.DiffState(0, ...))(m)\n      self.assertIsInstance(grads, nnx.State)\n    else:\n      grads = nnx.grad(\n        f, graph=graph, graph_updates=graph_updates,\n      )(m)\n      self.assertIsInstance(grads, Foo)\n    np.testing.assert_allclose(grads.x[...], jnp.cos(1.0) * 2.0)  # type: ignore\n    np.testing.assert_allclose(grads.y[...], jnp.sin(1.0))  # type: ignore\n    if graph and graph_updates:\n      self.assertEqual(m.z, 1)\n    else:\n      self.assertEqual(m.z, 0)\n\n  def test_diff_state(self):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      x: nnx.Param[jax.Array]\n      y: nnx.Param[jax.Array]\n      z: int\n\n    x_in_path = nnx.PathContains('x')\n    diff_state = nnx.DiffState(0, x_in_path)\n\n    @nnx.custom_vjp(nondiff_argnums=(diff_state,))\n    def f(m: Foo):\n      m.z += 1\n      return jnp.sin(m.x) * m.y  # type: ignore\n\n    def f_fwd(m: Foo):\n      y = f(m)\n      res = (jnp.cos(m.x), m)  # type: ignore\n      return y, res\n\n    def f_bwd(res, g):\n      (m_g,), out_g = g\n      cos_x, m = res\n\n      self.assertIsInstance(m_g, nnx.State)\n      self.assertEqual(out_g.shape, ())\n      self.assertIsInstance(m, Foo)\n\n      m_g['x'][...] = cos_x * out_g * m.y\n      del m_g['y']\n      return (m_g,)\n\n    f.defvjp(f_fwd, f_bwd)\n\n    m = Foo(nnx.Param(jnp.array(1.0)), nnx.Param(jnp.array(2.0)), 0)\n\n    grad: nnx.State = nnx.grad(f, argnums=nnx.DiffState(0, x_in_path))(m)\n\n    np.testing.assert_allclose(grad['x'][...], jnp.cos(1.0) * 2.0)  # type: ignore\n    self.assertEqual(m.z, 1)\n\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_jax_example_with_remat(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      x: nnx.Param[jax.Array]\n      y: nnx.Param[jax.Array]\n      z: int\n\n    @nnx.custom_vjp(graph=graph, graph_updates=graph_updates)\n    @nnx.remat(graph=graph, graph_updates=graph_updates)\n    def f(m: Foo):\n      m.z += 1\n      return jnp.sin(m.x) * m.y  # type: ignore\n\n    def f_fwd(m: Foo):\n      y = f(m)\n      res = (jnp.cos(m.x), jnp.sin(m.x), m)  # type: ignore\n      return y, res\n\n    def f_bwd(res, g):\n      cos_x, sin_x, m = res\n      if graph and graph_updates:\n        (m_g,), out_g = g\n        self.assertIsInstance(m_g, nnx.State)\n        m_g['x'][...] = cos_x * out_g * m.y\n        m_g['y'][...] = sin_x * out_g\n        return (m_g,)\n      else:\n        out_g = g\n        m_g = jax.tree.map(lambda x: x, m)\n        m_g.x[...] = cos_x * out_g * m.y\n        m_g.y[...] = sin_x * out_g\n        return (m_g,)\n\n    f.defvjp(f_fwd, f_bwd)\n\n    m = Foo(nnx.Param(jnp.array(1.0)), nnx.Param(jnp.array(2.0)), 0)\n\n    @nnx.jit(graph=graph, graph_updates=graph_updates)\n    def loss_fn(m):\n      return f(m)\n\n    if graph and graph_updates:\n      grads = nnx.grad(loss_fn, argnums=nnx.DiffState(0, ...))(m)\n      self.assertIsInstance(grads, nnx.State)\n    else:\n      grads = nnx.grad(\n        loss_fn, graph=graph, graph_updates=graph_updates,\n      )(m)\n      self.assertIsInstance(grads, Foo)\n    np.testing.assert_allclose(grads.x[...], jnp.cos(1.0) * 2.0)  # type: ignore\n    np.testing.assert_allclose(grads.y[...], jnp.sin(1.0))  # type: ignore\n    if graph and graph_updates:\n      self.assertEqual(m.z, 1)\n    else:\n      self.assertEqual(m.z, 0)\n\n  def test_two_args(self):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      x: nnx.Param[jax.Array]\n      y: nnx.Param[jax.Array]\n      z: int\n\n    @nnx.custom_vjp\n    def f(m1: Foo, m2: Foo):\n      m1.z += 1\n      y = jnp.sin(m1.x) * m1.y  # type: ignore\n      return y, m2\n\n    def f_fwd(m1: Foo, m2: Foo):\n      y, m2 = f(m1, m2)\n      res = (jnp.cos(m1.x), jnp.sin(m1.x), m1)  # type: ignore\n      return (y, m2), res\n\n    def f_bwd(res, g):\n      (m1_g, m2_g), (y_g, _) = g\n      cos_x, sin_x, m = res\n\n      self.assertIsInstance(m1_g, nnx.State)\n      self.assertIsInstance(m2_g, nnx.State)\n      self.assertEqual(y_g.shape, ())\n      self.assertIsInstance(m, Foo)\n\n      m1_g = nnx.State(dict(x=cos_x * y_g * m.y, y=sin_x * y_g))\n      m2_g = nnx.State(dict(x=m2_g['x'], y=m2_g['y']))\n\n      return m1_g, m2_g\n\n    f.defvjp(f_fwd, f_bwd)\n\n    m1 = Foo(nnx.Param(jnp.array(1.0)), nnx.Param(jnp.array(2.0)), 0)\n    m2 = Foo(nnx.Param(jnp.array(3.0)), nnx.Param(jnp.array(4.0)), 0)\n\n    def loss_fn(m1, m2):\n      y, m2 = f(m1, m2)\n      return y + m2.x * m2.y\n\n    m1_grad: nnx.State\n    m2_grad: nnx.State\n    m1_grad, m2_grad = nnx.grad(\n      loss_fn, argnums=(nnx.DiffState(0, ...), nnx.DiffState(1, ...))\n    )(m1, m2)\n\n    np.testing.assert_allclose(m1_grad['x'][...], jnp.cos(1.0) * 2.0)  # type: ignore\n    np.testing.assert_allclose(m1_grad['y'][...], jnp.sin(1.0))  # type: ignore\n    self.assertEqual(m1.z, 1)\n    np.testing.assert_allclose(m2_grad['x'][...], 4.0)  # type: ignore\n    np.testing.assert_allclose(m2_grad['y'][...], 3.0)  # type: ignore\n\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_non_diff_args(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      x: nnx.Param[jax.Array]\n      y: nnx.Param[jax.Array]\n      z: int\n\n    @nnx.custom_vjp(\n      nondiff_argnums=(0, 2), graph=graph, graph_updates=graph_updates,\n    )\n    def f(a, m: Foo, b):\n      self.assertEqual(a, 1)\n      self.assertEqual(b, 2)\n      m.z += 1\n      return jnp.sin(m.x) * m.y  # type: ignore\n\n    def f_fwd(a, m: Foo, b):\n      self.assertEqual(a, 1)\n      self.assertEqual(b, 2)\n      y = f(a, m, b)\n      res = (jnp.cos(m.x), jnp.sin(m.x), m)  # type: ignore\n      return y, res\n\n    def f_bwd(a, b, res, g):\n      cos_x, sin_x, m = res\n      self.assertEqual(a, 1)\n      self.assertEqual(b, 2)\n      if graph and graph_updates:\n        (m_g,), out_g = g\n        self.assertIsInstance(m_g, nnx.State)\n        m_g['x'][...] = cos_x * out_g * m.y\n        m_g['y'][...] = sin_x * out_g\n        return (m_g,)\n      else:\n        out_g = g\n        m_g = jax.tree.map(lambda x: x, m)\n        m_g.x[...] = cos_x * out_g * m.y\n        m_g.y[...] = sin_x * out_g\n        return (m_g,)\n\n    f.defvjp(f_fwd, f_bwd)\n\n    m = Foo(nnx.Param(jnp.array(1.0)), nnx.Param(jnp.array(2.0)), 0)\n\n    def loss_fn(m):\n      a = 1\n      b = 2\n      return f(a, m, b)\n\n    if graph and graph_updates:\n      grads = nnx.grad(loss_fn, argnums=nnx.DiffState(0, ...))(m)\n      self.assertIsInstance(grads, nnx.State)\n    else:\n      grads = nnx.grad(\n        loss_fn, graph=graph, graph_updates=graph_updates,\n      )(m)\n      self.assertIsInstance(grads, Foo)\n    np.testing.assert_allclose(grads.x[...], jnp.cos(1.0) * 2.0)  # type: ignore\n    np.testing.assert_allclose(grads.y[...], jnp.sin(1.0))  # type: ignore\n    if graph and graph_updates:\n      self.assertEqual(m.z, 1)\n    else:\n      self.assertEqual(m.z, 0)\n\n  def test_docs_example(self):\n    import jax.numpy as jnp\n    from flax import nnx\n\n    class Foo(nnx.Module):\n      def __init__(self, x, y):\n        self.x = nnx.Param(x)\n        self.y = nnx.Param(y)\n\n    @nnx.custom_vjp\n    def f(m: Foo):\n      return jnp.sin(m.x) * m.y  # type: ignore\n\n    def f_fwd(m: Foo):\n      return f(m), (jnp.cos(m.x), jnp.sin(m.x), m)  # type: ignore\n\n    def f_bwd(res, g):\n      ins_g, out_g = g\n      cos_x, sin_x, m = res\n      tangent_m = nnx.State(dict(x=cos_x * out_g * m.y, y=sin_x * out_g))\n      return (tangent_m,)\n\n    f.defvjp(f_fwd, f_bwd)\n\n    m = Foo(x=jnp.array(1.0), y=jnp.array(2.0))\n    grads = nnx.grad(f)(m)\n\n  @parameterized.parameters(\n    {'use_custom_vjp': False},\n    {'use_custom_vjp': True},\n  )\n  def test_issue(self, use_custom_vjp: bool):\n    class MyLinear(nnx.Module):\n      def __init__(\n        self, in_features: int, out_features: int, *, rngs: nnx.Rngs\n      ):\n        kernel_init = nnx.initializers.normal(in_features**-0.5)\n        self.kernel = nnx.Param(\n          kernel_init(rngs.params(), (in_features, out_features), jnp.float32)\n        )\n        self.bias = nnx.Param(jnp.zeros((out_features,), jnp.float32))\n        self.n = nnx.BatchStat(jnp.array(0, jnp.uint32))\n\n    def linear(m: MyLinear, x: jax.Array) -> jax.Array:\n      m.n[...] += 1\n      y = x @ m.kernel + m.bias\n      return y\n\n    def linear_fwd(m: MyLinear, x: jax.Array):\n      return linear(m, x), (m, x)\n\n    def linear_bwd(res, g):\n      m, x = res\n      (m_g, _x_grad), outputs_g = g\n      kernel_grad = outputs_g[None, :] * x[:, None]\n      bias_grad = outputs_g\n      x_grad = m.kernel @ outputs_g\n      assert x_grad.shape == x.shape, 'Shape mismatch for x'\n      assert m.kernel.shape == kernel_grad.shape, 'Shape mismatch for kernel'\n      assert m.bias.shape == bias_grad.shape, 'Shape mismatch for bias'\n      return (m_g, x_grad)\n\n    if use_custom_vjp:\n      linear = nnx.custom_vjp(linear)\n      linear.defvjp(linear_fwd, linear_bwd)\n\n    @nnx.jit\n    def loss_fn(x, mod):\n      y = linear(mod, x)\n      return y.mean()\n\n    mod = MyLinear(10, 5, rngs=nnx.Rngs(0))\n    self.assertEqual(mod.n[...], 0)\n    x = jax.random.normal(jax.random.key(0), (10,))\n    loss, grad = nnx.value_and_grad(loss_fn)(x, mod)\n    self.assertEqual(loss.shape, ())\n    self.assertEqual(grad.shape, (10,))\n    self.assertEqual(mod.n[...], 1)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_tree_mode_basic_call(self, graph, graph_updates):\n    m1 = nnx.Linear(1, 1, rngs=nnx.Rngs(0))\n    m2 = nnx.Linear(1, 1, rngs=nnx.Rngs(1))\n    state = nnx.BatchStat(jnp.array(0.0))\n\n    @nnx.custom_vjp(\n      nondiff_argnums=(2,), graph=graph, graph_updates=graph_updates,\n    )\n    def f(m1: nnx.Linear, m2: nnx.Linear, state):\n      y = m1.kernel * m2.kernel\n      state[...] = jnp.array(-1.0)\n      return y\n\n    def f_fwd(m1, m2, state):\n      y = f(m1, m2, state)\n      return y, (m1, m2)\n\n    def f_bwd(state, res, g):\n      m1, m2 = res\n      return g, g\n\n    f.defvjp(f_fwd, f_bwd)\n\n    y = f(m1, m2, state)\n\n    self.assertEqual(state[...], -1.0)\n    self.assertEqual(y.shape, (1, 1))\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_tree_mode_jax_example(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      x: nnx.Param[jax.Array]\n      y: nnx.Param[jax.Array]\n\n    @nnx.custom_vjp(graph=graph, graph_updates=graph_updates)\n    def f(m: Foo):\n      return jnp.sin(m.x) * m.y\n\n    def f_fwd(m: Foo):\n      y = f(m)\n      res = (jnp.cos(m.x), jnp.sin(m.x), m)\n      return y, res\n\n    def f_bwd(res, g):\n      cos_x, sin_x, m = res\n      m_g = jax.tree.map(lambda x: x, m)\n      m_g.x[...] = cos_x * g * m.y\n      m_g.y[...] = sin_x * g\n      return (m_g,)\n\n    f.defvjp(f_fwd, f_bwd)\n\n    m = Foo(nnx.Param(jnp.array(1.0)), nnx.Param(jnp.array(2.0)))\n\n    grads = nnx.grad(f, graph=graph, graph_updates=graph_updates)(m)\n\n    self.assertIsInstance(grads, Foo)\n    np.testing.assert_allclose(grads.x[...], jnp.cos(1.0) * 2.0)\n    np.testing.assert_allclose(grads.y[...], jnp.sin(1.0))\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_tree_mode_with_remat(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      x: nnx.Param[jax.Array]\n      y: nnx.Param[jax.Array]\n\n    @nnx.custom_vjp(graph=graph, graph_updates=graph_updates)\n    @nnx.remat(graph=graph, graph_updates=graph_updates)\n    def f(m: Foo):\n      return jnp.sin(m.x) * m.y\n\n    def f_fwd(m: Foo):\n      y = f(m)\n      res = (jnp.cos(m.x), jnp.sin(m.x), m)\n      return y, res\n\n    def f_bwd(res, g):\n      cos_x, sin_x, m = res\n      m_g = jax.tree.map(lambda x: x, m)\n      m_g.x[...] = cos_x * g * m.y\n      m_g.y[...] = sin_x * g\n      return (m_g,)\n\n    f.defvjp(f_fwd, f_bwd)\n\n    m = Foo(nnx.Param(jnp.array(1.0)), nnx.Param(jnp.array(2.0)))\n\n    @nnx.jit(graph=graph, graph_updates=graph_updates)\n    def loss_fn(m):\n      return f(m)\n\n    grads = nnx.grad(loss_fn, graph=graph, graph_updates=graph_updates)(m)\n\n    self.assertIsInstance(grads, Foo)\n    np.testing.assert_allclose(grads.x[...], jnp.cos(1.0) * 2.0)\n    np.testing.assert_allclose(grads.y[...], jnp.sin(1.0))\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_tree_mode_non_diff_args(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      x: nnx.Param[jax.Array]\n      y: nnx.Param[jax.Array]\n\n    @nnx.custom_vjp(nondiff_argnums=(0, 2), graph=graph,\n                    graph_updates=graph_updates)\n    def f(a, m: Foo, b):\n      self.assertEqual(a, 1)\n      self.assertEqual(b, 2)\n      return jnp.sin(m.x) * m.y\n\n    def f_fwd(a, m: Foo, b):\n      self.assertEqual(a, 1)\n      self.assertEqual(b, 2)\n      y = f(a, m, b)\n      res = (jnp.cos(m.x), jnp.sin(m.x), m)\n      return y, res\n\n    def f_bwd(a, b, res, g):\n      cos_x, sin_x, m = res\n      self.assertEqual(a, 1)\n      self.assertEqual(b, 2)\n      m_g = jax.tree.map(lambda x: x, m)\n      m_g.x[...] = cos_x * g * m.y\n      m_g.y[...] = sin_x * g\n      return (m_g,)\n\n    f.defvjp(f_fwd, f_bwd)\n\n    m = Foo(nnx.Param(jnp.array(1.0)), nnx.Param(jnp.array(2.0)))\n\n    def loss_fn(m):\n      a = 1\n      b = 2\n      return f(a, m, b)\n\n    grads = nnx.grad(loss_fn, graph=graph, graph_updates=graph_updates)(m)\n\n    self.assertIsInstance(grads, Foo)\n    np.testing.assert_allclose(grads.x[...], jnp.cos(1.0) * 2.0)\n    np.testing.assert_allclose(grads.y[...], jnp.sin(1.0))\n\n  def test_tree_mode_diffstate_error(self):\n    x_in_path = nnx.PathContains('x')\n    diff_state = nnx.DiffState(0, x_in_path)\n    with self.assertRaisesRegex(\n      ValueError,\n      r'`nondiff_argnums` cannot contain `DiffState` objects',\n    ):\n      nnx.custom_vjp(lambda m: m, nondiff_argnums=(diff_state,), graph=False)\n\n  def test_grad_inconsistent_aliasing(self):\n    v = nnx.Param(jnp.array(1.0))\n\n    def f(v_diff, v_nondiff):\n      return v_diff[...] + v_nondiff[...]\n\n    with self.assertRaisesRegex(ValueError, 'Inconsistent aliasing'):\n      nnx.grad(f, argnums=0, graph=True, graph_updates=False)(v, v)\n\n  def test_custom_vjp_inconsistent_aliasing(self):\n    v = nnx.Param(jnp.array(1.0))\n\n    @nnx.custom_vjp(nondiff_argnums=(1,), graph=True, graph_updates=False)\n    def f(v_diff, v_nondiff):\n      return v_diff[...] + v_nondiff[...]\n\n    def f_fwd(v_diff, v_nondiff):\n      return f(v_diff, v_nondiff), ()\n\n    def f_bwd(v_nondiff, res, g):\n      return (nnx.clone(v_nondiff),)\n\n    f.defvjp(f_fwd, f_bwd)\n\n    with self.assertRaisesRegex(ValueError, 'Inconsistent aliasing'):\n      f(v, v)\n\n\n  def test_custom_vjp_diff_arg_mutation_error(self):\n    @nnx.custom_vjp(graph=True, graph_updates=False)\n    def f(m):\n      m.x[...] += 1\n      return m.x[...] * m.y[...]\n\n    def f_fwd(m):\n      return f(m), (m,)\n\n    def f_bwd(res, g):\n      (m,) = res\n      m_g = nnx.clone(m)\n      m_g.x[...] = g * m.y[...]\n      m_g.y[...] = g * m.x[...]\n      return (m_g,)\n\n    f.defvjp(f_fwd, f_bwd)\n\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      x: nnx.Param[jax.Array]\n      y: nnx.Param[jax.Array]\n\n    m = Foo(nnx.Param(jnp.array(1.0)), nnx.Param(jnp.array(2.0)))\n    with self.assertRaisesRegex(\n      ValueError, 'Variables in differentiable argument'\n    ):\n      f(m)\n\n\nclass TestVjpJvp(parameterized.TestCase):\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_vjp_basic(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      w: nnx.Param[jax.Array]\n\n    def f(m: Foo, x):\n      return jnp.sum(m.w * x)\n\n    m = Foo(w=nnx.Param(jnp.array([1.0, 2.0, 3.0])))\n    x = jnp.array([4.0, 5.0, 6.0])\n\n    primals_out, vjp_fn = nnx.vjp(\n      f, m, x, graph=graph, graph_updates=graph_updates,\n    )\n\n    np.testing.assert_allclose(primals_out, 1.0 * 4.0 + 2.0 * 5.0 + 3.0 * 6.0)\n\n    m_grad, x_grad = vjp_fn(jnp.ones_like(primals_out))\n    self.assertIsInstance(m_grad, Foo)\n    np.testing.assert_allclose(m_grad.w[...], x)\n    np.testing.assert_allclose(x_grad, m.w[...])\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_vjp_has_aux(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      w: nnx.Param[jax.Array]\n\n    def f(m: Foo, x):\n      y = jnp.sum(m.w * x)\n      return y, {'input': x}\n\n    m = Foo(w=nnx.Param(jnp.array([1.0, 2.0])))\n    x = jnp.array([3.0, 4.0])\n\n    primals_out, vjp_fn, aux = nnx.vjp(\n      f, m, x, has_aux=True, graph=graph, graph_updates=graph_updates,\n    )\n\n    np.testing.assert_allclose(primals_out, 1.0 * 3.0 + 2.0 * 4.0)\n    np.testing.assert_allclose(aux['input'], x)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_vjp_state_propagation(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      w: nnx.Param[jax.Array]\n      count: nnx.BatchStat[jax.Array]\n\n    def f(m: Foo, x):\n      m.count[...] += 1\n      return jnp.sum(m.w * x)\n\n    m = Foo(\n      w=nnx.Param(jnp.array([1.0, 2.0])),\n      count=nnx.BatchStat(jnp.array(0)),\n    )\n    x = jnp.array([3.0, 4.0])\n\n    self.assertEqual(m.count[...], 0)\n    primals_out, vjp_fn = nnx.vjp(\n      f, m, x, graph=graph, graph_updates=graph_updates,\n    )\n    self.assertEqual(m.count[...], 1)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_vjp_matches_jax(self, graph, graph_updates):\n    def f(w, x):\n      return jnp.sum(w * x)\n\n    w = jnp.array([1.0, 2.0, 3.0])\n    x = jnp.array([4.0, 5.0, 6.0])\n\n    jax_primals, jax_vjp_fn = jax.vjp(f, w, x)\n    jax_grads = jax_vjp_fn(jnp.ones_like(jax_primals))\n\n    nnx_primals, nnx_vjp_fn = nnx.vjp(\n      f, w, x, graph=graph, graph_updates=graph_updates,\n    )\n    nnx_grads = nnx_vjp_fn(jnp.ones_like(nnx_primals))\n\n    np.testing.assert_allclose(nnx_primals, jax_primals)\n    for ng, jg in zip(nnx_grads, jax_grads):\n      np.testing.assert_allclose(ng, jg)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_vjp_decorator(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      w: nnx.Param[jax.Array]\n\n    @nnx.vjp(graph=graph, graph_updates=graph_updates)\n    def f(m: Foo, x):\n      return jnp.sum(m.w * x)\n\n    m = Foo(w=nnx.Param(jnp.array([1.0, 2.0])))\n    x = jnp.array([3.0, 4.0])\n\n    primals_out, vjp_fn = f(m, x)\n    np.testing.assert_allclose(primals_out, 11.0)\n    m_grad, x_grad = vjp_fn(jnp.ones_like(primals_out))\n    np.testing.assert_allclose(m_grad.w[...], x)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_jvp_basic(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      w: nnx.Param[jax.Array]\n\n    def f(m: Foo, x):\n      return jnp.sum(m.w * x)\n\n    m = Foo(w=nnx.Param(jnp.array([1.0, 2.0, 3.0])))\n    x = jnp.array([4.0, 5.0, 6.0])\n\n    m_tangent = jax.tree.map(jnp.ones_like, m)\n    x_tangent = jnp.ones_like(x)\n\n    primals_out, tangent_out = nnx.jvp(\n      f, (m, x), (m_tangent, x_tangent),\n      graph=graph, graph_updates=graph_updates,\n    )\n\n    np.testing.assert_allclose(primals_out, 1.0 * 4.0 + 2.0 * 5.0 + 3.0 * 6.0)\n    expected_tangent = jnp.sum(jnp.ones(3) * x + m.w[...] * jnp.ones(3))\n    np.testing.assert_allclose(tangent_out, expected_tangent)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_jvp_has_aux(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      w: nnx.Param[jax.Array]\n\n    def f(m: Foo, x):\n      y = jnp.sum(m.w * x)\n      return y, {'input': x}\n\n    m = Foo(w=nnx.Param(jnp.array([1.0, 2.0])))\n    x = jnp.array([3.0, 4.0])\n\n    m_tangent = jax.tree.map(jnp.ones_like, m)\n    x_tangent = jnp.ones_like(x)\n\n    primals_out, tangent_out, aux = nnx.jvp(\n      f, (m, x), (m_tangent, x_tangent), has_aux=True,\n      graph=graph, graph_updates=graph_updates,\n    )\n\n    np.testing.assert_allclose(primals_out, 11.0)\n    np.testing.assert_allclose(aux['input'], x)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_jvp_state_propagation(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      w: nnx.Param[jax.Array]\n      count: nnx.BatchStat[jax.Array]\n\n    def f(m: Foo, x):\n      m.count[...] += 1\n      return jnp.sum(m.w * x)\n\n    m = Foo(\n      w=nnx.Param(jnp.array([1.0, 2.0])),\n      count=nnx.BatchStat(jnp.array(0.0)),\n    )\n    x = jnp.array([3.0, 4.0])\n\n    m_tangent = jax.tree.map(jnp.zeros_like, m)\n    x_tangent = jnp.zeros_like(x)\n\n    self.assertEqual(m.count[...], 0.0)\n    primals_out, tangent_out = nnx.jvp(\n      f, (m, x), (m_tangent, x_tangent),\n      graph=graph, graph_updates=graph_updates,\n    )\n    self.assertEqual(m.count[...], 1.0)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_jvp_matches_jax(self, graph, graph_updates):\n    def f(w, x):\n      return jnp.sum(w * x)\n\n    w = jnp.array([1.0, 2.0, 3.0])\n    x = jnp.array([4.0, 5.0, 6.0])\n\n    w_tangent = jnp.ones_like(w)\n    x_tangent = jnp.ones_like(x)\n\n    jax_primals, jax_tangents = jax.jvp(f, (w, x), (w_tangent, x_tangent))\n    nnx_primals, nnx_tangents = nnx.jvp(\n      f, (w, x), (w_tangent, x_tangent),\n      graph=graph, graph_updates=graph_updates,\n    )\n\n    np.testing.assert_allclose(nnx_primals, jax_primals)\n    np.testing.assert_allclose(nnx_tangents, jax_tangents)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_jvp_decorator(self, graph, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      w: nnx.Param[jax.Array]\n\n    @nnx.jvp(graph=graph, graph_updates=graph_updates)\n    def f(m: Foo, x):\n      return jnp.sum(m.w * x)\n\n    m = Foo(w=nnx.Param(jnp.array([1.0, 2.0])))\n    x = jnp.array([3.0, 4.0])\n\n    m_tangent = jax.tree.map(jnp.ones_like, m)\n    x_tangent = jnp.ones_like(x)\n\n    primals_out, tangent_out = f((m, x), (m_tangent, x_tangent))\n    np.testing.assert_allclose(primals_out, 11.0)\n    expected_tangent = jnp.sum(jnp.ones(2) * x + m.w[...] * jnp.ones(2))\n    np.testing.assert_allclose(tangent_out, expected_tangent)\n\n\nclass TestScan(parameterized.TestCase):\n  def test_basic(self):\n    class Block(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        x = nnx.gelu(x)\n        return x\n\n    @nnx.split_rngs(splits=5)\n    @nnx.scan(in_axes=(nnx.Carry, 0), length=5)\n    def create_block(_, rngs: nnx.Rngs):\n      return None, Block(rngs=rngs)\n\n    _, module = create_block(None, nnx.Rngs(0))\n\n    assert module.linear.kernel.shape == (5, 3, 3)\n    assert module.linear.bias.shape == (5, 3)\n\n    @nnx.scan(in_axes=(nnx.Carry, 0, None), length=5)\n    def forward_block(_, block: Block, x: jax.Array):\n      return None, block(x)\n\n    x = jnp.ones((1, 3))\n    out, y = forward_block(None, module, x)\n\n    assert y.shape == (5, 1, 3)\n    assert out is None\n\n  @parameterized.parameters(True, False)\n  def test_variables_in_scan(self, graph_updates):\n    def block_init(din, dout, rngs):\n      w = nnx.Param(jax.random.normal(rngs.params(), (din, dout)))\n      b = nnx.Param(jnp.zeros((dout,)))\n      return w, b\n\n    def block_forward(w, b, x):\n      return nnx.gelu(x @ w + b[None])\n\n    @nnx.split_rngs(splits=5)\n    @nnx.scan(in_axes=0, out_axes=0, length=5, graph_updates=graph_updates)\n    def create_block(rngs: nnx.Rngs):\n      return block_init(3, 3, rngs)\n\n    w, b = create_block(nnx.Rngs(0))\n\n    assert w.shape == (5, 3, 3)\n    assert b.shape == (5, 3)\n\n    @nnx.scan(\n      in_axes=(0, 0, nnx.Carry), out_axes=nnx.Carry,\n      graph_updates=graph_updates,\n    )\n    def stack_forward(w, b, x):\n      return block_forward(w, b, x)\n\n    x = jnp.ones((1, 3))\n    y = stack_forward(w, b, x)\n    assert y.shape == (1, 3)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_variables_as_carries_in_scan(self, graph, graph_updates):\n    w = nnx.Param(jax.random.normal(jax.random.key(0), (3, 3)))\n    b = nnx.Param(jnp.zeros((3,)))\n    count = nnx.BatchStat(0)\n\n    def block_forward(w, b, x):\n      return nnx.gelu(x @ w + b[None])\n\n    @nnx.scan(\n      in_axes=(nnx.Carry, 0), out_axes=(nnx.Carry, 0),\n      graph=graph, graph_updates=graph_updates,\n    )\n    def stack_forward(params, x):\n      w, b, count = params\n      y = block_forward(w, b, x)\n      count[...] += 1\n      return (w, b, count), y\n\n    x = jnp.ones((5, 1, 3))\n    (w, b, count), y = stack_forward((w, b, count), x)\n\n    assert y.shape == (5, 1, 3)\n    assert count[...] == 5\n\n  def test_basic_no_carry(self):\n    class Block(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        x = nnx.gelu(x)\n        return x\n\n    @nnx.split_rngs(splits=5)\n    @nnx.scan(in_axes=(0,), out_axes=0, length=5)\n    def create_block(rngs: nnx.Rngs):\n      return Block(rngs=rngs)\n\n    module = create_block(nnx.Rngs(0))\n\n    assert module.linear.kernel.shape == (5, 3, 3)\n    assert module.linear.bias.shape == (5, 3)\n    # assert module.node.shape == (2,)\n\n    @nnx.scan(in_axes=(0, None), out_axes=0, length=5)\n    def forward_block(block: Block, x: jax.Array):\n      return block(x)\n\n    x = jnp.ones((1, 3))\n    y = forward_block(module, x)\n\n    assert y.shape == (5, 1, 3)\n\n  @parameterized.parameters(True, False)\n  def test_all_carry(self, graph_updates):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      n: nnx.BatchStat[int]\n\n    foo = Foo(n=nnx.BatchStat(0))\n\n    @nnx.scan(\n      in_axes=nnx.Carry, out_axes=nnx.Carry, length=3,\n      graph_updates=graph_updates,\n    )\n    def loop(foo: Foo):\n      foo.n[...] += 1\n      return foo\n\n    foo2 = loop(foo)\n\n    self.assertIs(foo2.n, foo.n)\n    self.assertEqual(foo.n[...], 3)\n\n  def test_all_carry_one_argument_error(self):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      n: nnx.BatchStat[int]\n\n    foo = Foo(n=nnx.BatchStat(0))\n\n    @nnx.scan(in_axes=nnx.Carry, out_axes=nnx.Carry, length=3)\n    def loop(foo: Foo, x): ...\n\n    with self.assertRaisesRegex(\n      ValueError,\n      'When in_axes=Carry, the function must take exactly one argument',\n    ):\n      loop(foo, 0)\n\n  def test_all_carry_new_reference_error(self):\n    class Foo(nnx.Module):\n      def __init__(self, n: nnx.BatchStat[int]):\n        self.n = n\n\n    xs = jnp.arange(3)\n    foo = Foo(n=nnx.BatchStat(0))\n\n    @nnx.scan(in_axes=(nnx.Carry, 0), out_axes=(nnx.Carry, 0))\n    def loop(foo: Foo, x):\n      x = x + 1\n      foo = Foo(nnx.BatchStat(foo.n[...] + 1))  # new reference\n      return foo, x\n\n    with self.assertRaisesRegex(\n      ValueError,\n      'Carry references must be the same between iterations',\n    ):\n      loop(foo, xs)\n\n  @parameterized.parameters(True, False)\n  def test_all_scan(self, graph_updates):\n    class Foo(nnx.Module):\n      def __init__(self, n: nnx.BatchStat[jax.Array]):\n        self.n = n\n\n    xs = jnp.arange(3)\n    foo = Foo(n=nnx.BatchStat(jnp.arange(3)))\n\n    @nnx.scan(in_axes=0, out_axes=0, graph_updates=graph_updates)\n    def loop(foo: Foo, x):\n      x = x + 1\n      foo.n[...] += 1\n      return x\n\n    ys = loop(foo, xs)\n\n    np.testing.assert_allclose(ys, jnp.arange(1, 4))\n    np.testing.assert_allclose(foo.n[...], jnp.arange(1, 4))\n\n  def test_all_broadcast(self):\n    class Foo(nnx.Module):\n      def __init__(self, n: nnx.BatchStat[int]):\n        self.n = n\n\n    xs = jnp.array(1)\n    foo = Foo(n=nnx.BatchStat(2))\n\n    @nnx.scan(in_axes=None, out_axes=0, length=4)\n    def loop(foo: Foo, x):\n      return x + foo.n\n\n    ys = loop(foo, xs)\n\n    np.testing.assert_allclose(ys, 3)\n    self.assertEqual(ys.shape, (4,))\n\n  def test_input_output_carry_mismatch_error(self):\n    with self.assertRaisesRegex(\n      ValueError,\n      'If one of in_axes or out_axes has Carry, the other must also have Carry',\n    ):\n\n      @nnx.scan(in_axes=0, out_axes=(nnx.Carry, 0))\n      def loop(a, b): ...\n\n    with self.assertRaisesRegex(\n      ValueError,\n      'If one of in_axes or out_axes has Carry, the other must also have Carry',\n    ):\n\n      @nnx.scan(in_axes=(nnx.Carry, 0), out_axes=0)\n      def loop(a, b): ...\n\n  def test_double_carry_error(self):\n    with self.assertRaisesRegex(\n      ValueError,\n      'Found multiple Carry definitions',\n    ):\n\n      @nnx.scan(in_axes=(nnx.Carry, nnx.Carry))\n      def loop(a, b): ...\n\n  def test_broadcast_in_output_error(self):\n    with self.assertRaisesRegex(\n      ValueError,\n      'Cannot broadcast output state',\n    ):\n\n      @nnx.scan(in_axes=(nnx.Carry, 0), out_axes=(nnx.Carry, None))\n      def loop(a, b): ...\n\n    with self.assertRaisesRegex(\n      ValueError,\n      'Cannot broadcast output state. Got StateAxes',\n    ):\n\n      @nnx.scan(\n        in_axes=(nnx.Carry, 0), out_axes=(nnx.Carry, nnx.StateAxes({...: None}))\n      )\n      def loop(a, b): ...\n\n  @parameterized.parameters(\n    (True, False), (False, False),\n  )\n  def test_scan_stateful(self, graph, graph_updates):\n    count = nnx.Variable(jnp.array(0))\n\n    @nnx.scan(graph=graph, graph_updates=graph_updates)\n    def f(count, x):\n      count[...] += 1\n      return count, x + count[...]\n\n    xs = jnp.arange(5)\n    count_out, ys = f(count, xs)\n\n    self.assertIs(count_out, count)\n    self.assertEqual(count[...], 5)\n    np.testing.assert_allclose(ys, jnp.array([1, 3, 5, 7, 9]))\n\n  @parameterized.parameters(\n    (True, False), (False, False),\n  )\n  def test_scan_carry_identity_error(self, graph, graph_updates):\n    count = nnx.Variable(jnp.array(0))\n\n    @nnx.scan(graph=graph, graph_updates=graph_updates)\n    def f(count, x):\n      new_count = nnx.Variable(count[...] + 1)\n      return new_count, x\n\n    with self.assertRaisesRegex(\n      ValueError,\n      'scan Variable identity must be preserved',\n    ):\n      f(count, jnp.arange(3))\n\n  def test_tree_mode_custom_axes(self):\n    @nnx.scan(in_axes=nnx.Carry, out_axes=nnx.Carry, length=3, graph=False)\n    def loop(x):\n      return x\n\n    result = loop(jnp.array(1.0))\n    np.testing.assert_allclose(result, jnp.array(1.0))\n\n  @parameterized.parameters(True, False)\n  def test_only_carry(self, graph_updates):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.c = nnx.BatchStat(jnp.array(0))\n\n    @nnx.scan(in_axes=(nnx.Carry,), length=5, graph_updates=graph_updates)\n    def loop(foo: Foo) -> tuple[Foo, jax.Array]:\n      foo.c[...] += 1\n      return foo, foo.c[...]\n\n    foo = Foo()\n    foo2, cs = loop(foo)\n    self.assertIs(foo2.c, foo.c)\n    np.testing.assert_allclose(cs, jnp.arange(1, 6))\n\n  def test_out_axes(self):\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None})\n\n    class MLP(nnx.Module):\n      @nnx.split_rngs(splits=5)\n      @nnx.vmap(in_axes=(state_axes, state_axes), axis_size=5)\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.node = nnx.BatchStat(jnp.ones((2,)))\n\n      @nnx.scan(in_axes=(state_axes, nnx.Carry), out_axes=(nnx.Carry, 1, 2))\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        x = nnx.gelu(x)\n        return x, x, x\n\n    module = MLP(rngs=nnx.Rngs(0))\n\n    assert module.linear.kernel.shape == (5, 3, 3)\n    assert module.linear.bias.shape == (5, 3)\n    assert module.node.shape == (2,)\n\n    x = jnp.ones((1, 3))\n    c, y1, y2 = module(x)\n\n    assert c.shape == (1, 3)\n    assert y1.shape == (1, 5, 3)\n    assert y2.shape == (1, 3, 5)\n\n  def test_in_axes_simple(self):\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None})\n\n    class MLP(nnx.Module):\n      @nnx.vmap(in_axes=(state_axes, 0))\n      def __init__(self, key: jax.Array):\n        rngs = nnx.Rngs(key)\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.node = nnx.Variable(jnp.ones((2,)))\n\n      @nnx.scan(in_axes=(state_axes, nnx.Carry), out_axes=nnx.Carry)\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        x = nnx.gelu(x)\n        return x\n\n    key = jax.random.split(jax.random.key(0), 5)\n    module = MLP(key=key)\n\n    x = jnp.ones((1, 3))\n    y = module(x)\n\n    assert y.shape == (1, 3)\n\n  def test_in_axes(self):\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState, nnx.Intermediate): 0, ...: None})\n\n    class MLP(nnx.Module):\n      @nnx.split_rngs(splits=5)\n      @nnx.vmap(in_axes=(state_axes, state_axes))\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.node = nnx.Variable(jnp.ones((2,)))\n\n      @nnx.scan(in_axes=(state_axes, nnx.Carry, 0))\n      def __call__(\n        self, x: jax.Array, a: jax.Array\n      ) -> tp.Tuple[jax.Array, None]:\n        assert x.shape == a.shape\n        x = x + a\n        x = self.linear(x)\n        x = nnx.gelu(x)\n        self.sow(nnx.Intermediate, \"data\", x)\n        return x, None\n\n    module = MLP(rngs=nnx.Rngs(0))\n\n    assert module.linear.kernel.shape == (5, 3, 3)\n    assert module.linear.bias.shape == (5, 3)\n    assert module.node.shape == (2,)\n\n    x = jnp.ones((1, 3))\n    a = jnp.ones((5, 1, 3))\n    (y, out), intermediates = nnx.capture(module, nnx.Intermediate)(x, a)\n\n    assert y.shape == (1, 3)\n    assert out is None\n\n    assert intermediates['data'][0].shape == (5, 1, 3)\n\n  def test_in_axes_broadcast(self):\n    test = self\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None})\n\n    class MLP(nnx.Module):\n      @nnx.split_rngs(splits=5)\n      @nnx.vmap(in_axes=(state_axes, state_axes))\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.node = nnx.BatchStat(jnp.ones((2,)))\n\n      @nnx.scan(in_axes=(state_axes, nnx.Carry, 0, None))\n      def __call__(\n        self, x: jax.Array, a: jax.Array, b: jax.Array\n      ) -> tp.Tuple[jax.Array, None]:\n        test.assertEqual(x.shape, a.shape)\n        test.assertEqual(x.shape, b.shape)\n        x = x + a + b\n        x = self.linear(x)\n        x = nnx.gelu(x)\n        return x, None\n\n    module = MLP(rngs=nnx.Rngs(0))\n\n    self.assertEqual(module.linear.kernel.shape, (5, 3, 3))\n    self.assertEqual(module.linear.bias.shape, (5, 3))\n    self.assertEqual(module.node.shape, (2,))\n\n    x = jnp.ones((1, 3))\n    a = jnp.ones((5, 1, 3))\n    b = jnp.ones((1, 3))\n    y, out = module(x, a, b)\n\n    self.assertEqual(y.shape, (1, 3))\n    self.assertIsNone(out)\n\n  def test_complex(self):\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None})\n\n    class MLP(nnx.Module):\n      @nnx.split_rngs(splits=5)\n      @nnx.vmap(in_axes=(state_axes, state_axes))\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.bn = nnx.BatchNorm(3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, rngs=rngs)\n        self.node = nnx.Variable(jnp.ones((2,)))\n\n      @nnx.scan(in_axes=(state_axes, nnx.Carry))\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        x = self.bn(x)\n        x = self.dropout(x)\n        x = nnx.gelu(x)\n        return x, None\n\n    module = MLP(rngs=nnx.Rngs(0))\n    module.set_attributes(deterministic=False, use_running_average=False)\n\n    assert module.linear.kernel.shape == (5, 3, 3)\n    assert module.linear.bias.shape == (5, 3)\n    assert module.node.shape == (2,)\n\n    x = jnp.ones((1, 3))\n    y, _ = module(x)\n\n    assert y.shape == (1, 3)\n\n  def test_complex_view(self):\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None})\n\n    class MLP(nnx.Module):\n      @nnx.split_rngs(splits=5)\n      @nnx.vmap(in_axes=(state_axes, state_axes))\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.bn = nnx.BatchNorm(3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, rngs=rngs)\n        self.node = nnx.Variable(jnp.ones((2,)))\n\n      @nnx.scan(in_axes=(state_axes, nnx.Carry))\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        x = self.bn(x)\n        x = self.dropout(x)\n        x = nnx.gelu(x)\n        return x, None\n\n    module = MLP(rngs=nnx.Rngs(0))\n    new_module = nnx.view(module, deterministic=False, use_running_average=False)\n\n    assert new_module.linear.kernel.shape == (5, 3, 3)\n    assert new_module.linear.bias.shape == (5, 3)\n    assert new_module.node.shape == (2,)\n\n    x = jnp.ones((1, 3))\n    y, _ = new_module(x)\n\n    assert y.shape == (1, 3)\n\n  def test_complex_broadcast_dropout(self):\n    state_axes = nnx.StateAxes({(nnx.Param, 'params'): 0, ...: None})\n\n    class MLP(nnx.Module):\n      @nnx.split_rngs(splits=5, only='params')\n      @nnx.vmap(in_axes=(state_axes, state_axes))\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.bn = nnx.BatchNorm(3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, rngs=rngs)\n        self.node = nnx.Variable(jnp.ones((2,)))\n\n      @nnx.split_rngs(splits=5, only='params')\n      @nnx.scan(in_axes=(state_axes, nnx.Carry))\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        x = self.bn(x)\n        x = self.dropout(x)\n        x = nnx.gelu(x)\n        return x, None\n\n    module = MLP(rngs=nnx.Rngs(params=0, dropout=1))\n    module.set_attributes(deterministic=False, use_running_average=False)\n\n    assert module.linear.kernel.shape == (5, 3, 3)\n    assert module.linear.bias.shape == (5, 3)\n    assert module.node.shape == (2,)\n\n    x = jnp.ones((1, 3))\n    y, _ = module(x)\n\n    assert y.shape == (1, 3)\n\n  def test_complex_broadcast_dropout_view(self):\n    state_axes = nnx.StateAxes({(nnx.Param, 'params'): 0, ...: None})\n\n    class MLP(nnx.Module):\n      @nnx.split_rngs(splits=5, only='params')\n      @nnx.vmap(in_axes=(state_axes, state_axes))\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.bn = nnx.BatchNorm(3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, rngs=rngs)\n        self.node = nnx.Variable(jnp.ones((2,)))\n\n      @nnx.split_rngs(splits=5, only='params')\n      @nnx.scan(in_axes=(state_axes, nnx.Carry))\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        x = self.bn(x)\n        x = self.dropout(x)\n        x = nnx.gelu(x)\n        return x, None\n\n    module = MLP(rngs=nnx.Rngs(params=0, dropout=1))\n    new_module = nnx.view(module, deterministic=False, use_running_average=False)\n\n    assert new_module.linear.kernel.shape == (5, 3, 3)\n    assert new_module.linear.bias.shape == (5, 3)\n    assert new_module.node.shape == (2,)\n\n    x = jnp.ones((1, 3))\n    y, _ = new_module(x)\n\n    assert y.shape == (1, 3)\n\n  def test_complex_decorator(self):\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None})\n\n    class Block(nnx.Module):\n      @nnx.split_rngs(splits=5)\n      @nnx.vmap(in_axes=(state_axes, state_axes), axis_size=5)\n      def __init__(self, rngs: nnx.Rngs):\n        self.d = 3\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.bn = nnx.BatchNorm(3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, rngs=rngs)\n        self.node = nnx.Variable(jnp.ones((2,)))\n\n      @nnx.scan(in_axes=(state_axes, nnx.Carry))\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        x = self.bn(x)\n        x = self.dropout(x)\n        x = nnx.gelu(x)\n        return x, None\n\n    module = Block(rngs=nnx.Rngs(0))\n    module.set_attributes(deterministic=False, use_running_average=False)\n\n    assert module.d == 3\n    assert module.linear.kernel.shape == (5, 3, 3)\n    assert module.linear.bias.shape == (5, 3)\n    assert module.node.shape == (2,)\n\n    x = jnp.ones((1, 3))\n    y, out = module(x)\n\n    assert y.shape == (1, 3)\n    assert out is None\n\n  def test_complex_decorator_view(self):\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None})\n\n    class Block(nnx.Module):\n      @nnx.split_rngs(splits=5)\n      @nnx.vmap(in_axes=(state_axes, state_axes), axis_size=5)\n      def __init__(self, rngs: nnx.Rngs):\n        self.d = 3\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.bn = nnx.BatchNorm(3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, rngs=rngs)\n        self.node = nnx.Variable(jnp.ones((2,)))\n\n      @nnx.scan(in_axes=(state_axes, nnx.Carry))\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        x = self.bn(x)\n        x = self.dropout(x)\n        x = nnx.gelu(x)\n        return x, None\n\n    module = Block(rngs=nnx.Rngs(0))\n    new_module = nnx.view(module, deterministic=False, use_running_average=False)\n\n    assert new_module.d == 3\n    assert new_module.linear.kernel.shape == (5, 3, 3)\n    assert new_module.linear.bias.shape == (5, 3)\n    assert new_module.node.shape == (2,)\n\n    x = jnp.ones((1, 3))\n    y, out = new_module(x)\n\n    assert y.shape == (1, 3)\n    assert out is None\n\n\n  def test_scan_with_sharding(self):\n    test = self\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None})\n    transform_metadata = {nnx.PARTITION_NAME: 'layers'}\n\n    class MLP(nnx.Module):\n      @nnx.split_rngs(splits=5)\n      @nnx.vmap(\n        in_axes=(state_axes, state_axes),\n        transform_metadata=transform_metadata,\n      )\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(\n          3,\n          3,\n          kernel_init=nnx.with_metadata(\n            nnx.initializers.lecun_normal(), out_sharding=('din', 'dout')\n          ),\n          bias_init=nnx.with_metadata(\n            nnx.initializers.zeros_init(), out_sharding=('dout',)\n          ),\n          rngs=rngs,\n        )\n\n      @nnx.scan(\n        in_axes=(state_axes, nnx.Carry), transform_metadata=transform_metadata\n      )\n      def __call__(self, x: jax.Array):\n        x = self.linear(x)\n        # test sharding layer axes is not present inside scan\n        test.assertEqual(self.linear.kernel.shape, (3, 3))\n        test.assertEqual(self.linear.kernel.out_sharding, ('din', 'dout'))\n        test.assertEqual(self.linear.bias.shape, (3,))\n        test.assertEqual(self.linear.bias.out_sharding, ('dout',))\n        return x, None\n\n    mesh = jax.make_mesh((1, 1, 1), ('layers', 'din', 'dout'), axis_types=(jax.sharding.AxisType.Auto,) * len(('layers', 'din', 'dout')))\n    with jax.set_mesh(mesh):\n      m = MLP(rngs=nnx.Rngs(0))\n\n    # test sharding layers axes is set\n    self.assertEqual(m.linear.kernel.shape, (5, 3, 3))\n    self.assertEqual(m.linear.kernel.out_sharding, ('layers', 'din', 'dout'))\n    self.assertEqual(m.linear.bias.shape, (5, 3))\n    self.assertEqual(m.linear.bias.out_sharding, ('layers', 'dout'))\n\n    x = jnp.ones((1, 3))\n    with jax.set_mesh(mesh):\n      y, out = m(x)\n\n    # test sharding axes is preserved\n    self.assertEqual(m.linear.kernel.shape, (5, 3, 3))\n    self.assertEqual(m.linear.kernel.out_sharding, ('layers', 'din', 'dout'))\n    self.assertEqual(m.linear.bias.shape, (5, 3))\n    self.assertEqual(m.linear.bias.out_sharding, ('layers', 'dout'))\n\n  def test_cache_tracing_simple(self):\n    n = 0\n    x = jnp.arange(5)\n    count = jnp.array(0)\n\n    @nnx.scan\n    def f(count, x):\n      nonlocal n\n      n += 1\n      return count + 1, x**2\n\n    count, y = f(count, x)\n    assert n == 1\n    assert count == 5\n    np.testing.assert_allclose(y, x**2)\n\n    count, y = f(count, x)\n    assert n == 1\n    assert count == 10\n\n  def test_cache_tracing_object(self):\n    n = 0\n    x = jnp.arange(5)\n    count = jnp.array(0)\n\n    class Foo(nnx.Pytree):\n      @nnx.split_rngs(splits=5)\n      @nnx.vmap(axis_size=5)\n      def __init__(self, rngs: nnx.Rngs):\n        self.x = nnx.Param(jax.random.normal(rngs(), shape=(3,)))\n\n    foo = Foo(rngs=nnx.Rngs(0))\n    assert foo.x.shape == (5, 3)\n\n    @nnx.scan(in_axes=(nnx.Carry, 0, 0))\n    def f(count, x, foo):\n      nonlocal n\n      n += 1\n      assert foo.x.shape == (3,)\n      return count + 1, x**2\n\n    count, y = f(count, x, foo)\n    assert n == 1\n    assert count == 5\n    np.testing.assert_allclose(y, x**2)\n\n    count, y = f(count, x, foo)\n    assert n == 1\n    assert count == 10\n\n  def test_scan_broadcast_keys(self):\n    params_key = jax.random.split(jax.random.key(0), 3)\n    rngs = nnx.Rngs(params=params_key, dropout=1)\n    state_axes = nnx.StateAxes({'params': 0, ...: None})\n\n    @nnx.scan(in_axes=(nnx.Carry, state_axes), length=3)\n    def f(_, rngs: nnx.Rngs):\n      param_key = rngs.params()\n      dropout_key = rngs.dropout()\n      return (), (param_key, dropout_key)\n\n    _, (param_keys, dropout_keys) = f((), rngs)\n\n    assert jnp.not_equal(param_keys[0], param_keys[1])\n    assert jnp.not_equal(param_keys[1], param_keys[2])\n    assert jnp.equal(dropout_keys[0], dropout_keys[1])\n    assert jnp.equal(dropout_keys[1], dropout_keys[2])\n\n  def test_rnn_example(self):\n    class RNNCell(nnx.Module):\n      def __init__(self, input_size, hidden_size, rngs):\n        self.linear = nnx.Linear(\n          hidden_size + input_size, hidden_size, rngs=rngs\n        )\n        self.drop = nnx.Dropout(0.1, rngs=rngs)\n        self.hidden_size = hidden_size\n\n      def __call__(self, carry, x) -> tuple[jax.Array, jax.Array]:\n        carry = self.drop(carry)  # recurrent dropout\n        x = nnx.relu(self.linear(jnp.concatenate([carry, x], axis=-1)))\n        return x, x\n\n      def initial_state(self, batch_size: int):\n        return jnp.zeros((batch_size, self.hidden_size))\n\n    cell = RNNCell(20, 20, nnx.Rngs(params=0, dropout=1))\n\n    state_axes = nnx.StateAxes({'dropout': None, ...: nnx.Carry})\n\n    def rnn_forward(cell: RNNCell, x: jax.Array):\n      carry = cell.initial_state(x.shape[0])\n\n      @nnx.scan(in_axes=(state_axes, nnx.Carry, 1), out_axes=(nnx.Carry, 1))\n      def unroll(cell: RNNCell, carry, x) -> tuple[jax.Array, jax.Array]:\n        return cell(carry, x)\n\n      _, y = unroll(cell, carry, x)\n      return y\n\n    x = jnp.ones((16, 10, 20))\n    y = rnn_forward(cell, x)\n\n  def test_carry_pytree_sow(self):\n    class CarryAsPytree(nnx.Pytree):\n      def __init__(self, data: jax.Array):\n        self.data = data\n\n    class Model(nnx.Module):\n      def __init__(self, num_steps):\n        self.fc = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n        self.num_steps = num_steps\n\n      def _step(self, state):\n        new_data = state.data + 1\n        self.sow(nnx.Intermediate, \"data\", new_data)\n        state.data = new_data\n        return state\n\n      def _step2(self, state: tuple[CarryAsPytree, jax.Array, CarryAsPytree]):\n        out = self.fc(state[1])\n\n        new_data1 = state[0].data + 1\n        new_data2 = state[2].data + 1\n\n        self.sow(nnx.Intermediate, \"data1\", new_data1)\n        self.sow(nnx.Intermediate, \"data2\", new_data2)\n\n        state[0].data = new_data1\n        state[2].data = new_data2\n        return (state[0], out, state[2])\n\n      @nnx.jit(static_argnames=(\"method\"))\n      def __call__(self, state, method):\n        state_axes = nnx.StateAxes({nnx.Intermediate: 0, ...: nnx.Carry})\n        state_final = nnx.scan(\n          method,\n          in_axes=(state_axes, nnx.Carry),\n          out_axes=nnx.Carry,\n          length=self.num_steps,\n        )(self, state)\n\n        return state_final\n\n    num_steps = 5\n    model = Model(num_steps=num_steps)\n    carry = CarryAsPytree(data=jnp.array(0.0))\n    carry_final, intermediates = nnx.capture(model, nnx.Intermediate)(carry, method=Model._step)\n    self.assertEqual(carry_final.data, num_steps)\n    np.testing.assert_array_equal(\n      intermediates['data'][0], 1.0 + jnp.arange(num_steps)\n    )\n\n    carry = (\n      CarryAsPytree(data=jnp.array(0.0)),\n      jnp.ones((10,)),\n      CarryAsPytree(data=jnp.array(10.0))\n    )\n\n    carry_final, intermediates = nnx.capture(model, nnx.Intermediate)(carry, method=Model._step2)\n\n    self.assertEqual(carry_final[0].data, num_steps)\n    self.assertEqual(carry_final[2].data, 10 + num_steps)\n    np.testing.assert_array_equal(\n      intermediates['data1'][0], 1.0 + jnp.arange(num_steps)\n    )\n    np.testing.assert_array_equal(\n      intermediates['data2'][0], 11.0 + jnp.arange(num_steps)\n    )\n\n  def test_broadcast_variable_mutation_rejected(self):\n    v = nnx.Variable(jnp.array(1.0))\n\n    @nnx.scan(\n      in_axes=(None, nnx.Carry, 0), graph=False, graph_updates=False,\n    )\n    def fn(v, carry, x):\n      v[...] = v[...] + 1.0\n      return carry + x, carry\n\n    with self.assertRaisesRegex(ValueError, 'Broadcast.*mutated'):\n      fn(v, jnp.array(0.0), jnp.arange(3.0))\n\n  def test_broadcast_out_axes_rejected(self):\n    @nnx.scan(\n      in_axes=(nnx.Carry, 0), out_axes=(nnx.Carry, None),\n      graph=False, graph_updates=False,\n    )\n    def fn(carry, x):\n      return carry + x, jnp.zeros(3)\n\n    with self.assertRaisesRegex(ValueError, 'broadcast'):\n      fn(jnp.array(0.0), jnp.arange(3.0))\n\n  def test_scan_inconsistent_aliasing(self):\n    v = nnx.Param(jnp.array(0.0))\n\n    @nnx.scan(\n      in_axes=(nnx.Carry, 0),\n      out_axes=(nnx.Carry, 0),\n      graph=True,\n      graph_updates=False,\n    )\n    def f(carry, x):\n      return carry, x[...]\n\n    with self.assertRaisesRegex(ValueError, 'Inconsistent aliasing'):\n      f(v, v)\n\n  def test_scan_input_output_aliasing(self):\n    v = nnx.Param(jnp.arange(5))\n\n    @nnx.scan(in_axes=0, out_axes=0, graph=True, graph_updates=False)\n    def f(carry):\n      return carry\n\n    with self.assertRaisesRegex(ValueError, 'does not support returning input Variables as outputs'):\n      f(v)\n\n\nclass TestRemat(parameterized.TestCase):\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_remat_basic(self, graph, graph_updates):\n    class RematLinear(nnx.Module):\n      def __init__(self, din: int, dout: int, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n\n      @nnx.remat(graph=graph, graph_updates=graph_updates)\n      def __call__(self, x: jax.Array) -> jax.Array:\n        return self.linear(x)\n\n    module = RematLinear(2, 3, nnx.Rngs(0))\n\n    def loss_fn(module, x):\n      y = module(x)\n      return jnp.sum(y)\n\n    grad_type = nnx.State if graph and graph_updates else RematLinear\n    loss, grads = nnx.value_and_grad(\n      loss_fn, graph=graph, graph_updates=graph_updates,\n    )(module, jnp.ones((1, 2)))\n\n    assert loss.shape == ()\n    assert isinstance(grads, grad_type)\n\n  @parameterized.parameters(\n    (True, True),\n    (True, False),\n    (False, False),\n  )\n  def test_remat_variables(self, graph, graph_updates):\n    rngs = nnx.Rngs(0)\n    w = nnx.Param(jax.random.normal(rngs(), (2, 3)))\n    b = nnx.Param(jax.random.normal(rngs(), (3,)))\n    count = nnx.BatchStat(jnp.array(0))\n\n    @nnx.remat(graph=graph, graph_updates=graph_updates)\n    def linear(w, b, count, x):\n      count[...] += 1\n      return x @ w + b[None]\n\n    def loss_fn(w, b, count, x):\n      return jnp.sum(linear(w, b, count, x))\n\n    x = jnp.ones((1, 2))\n    loss, grads = nnx.value_and_grad(\n      loss_fn, argnums=(0, 1), graph=graph, graph_updates=graph_updates,\n    )(w, b, count, x)\n\n    assert loss.shape == ()\n    assert isinstance(grads, tuple)\n    assert len(grads) == 2\n    assert count[...] == 1\n\n  def test_remat_with_scan_decorator(self):\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None})\n\n    class ScanLinear(nnx.Module):\n      @nnx.split_rngs(splits=5)\n      @nnx.vmap(in_axes=(state_axes, state_axes), axis_size=5)\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n\n      @nnx.scan(in_axes=(state_axes, nnx.Carry))\n      @nnx.remat\n      def __call__(self, x: jax.Array) -> tp.Tuple[jax.Array, None]:\n        x = self.linear(x)\n        return x, None\n\n    m = ScanLinear(nnx.Rngs(0))\n\n    assert m.linear.kernel.shape == (5, 3, 3)\n    assert m.linear.bias.shape == (5, 3)\n\n    y, _ = m(jnp.ones((1, 3)))\n    assert y.shape == (1, 3)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_tree_mode_remat_basic(self, graph, graph_updates):\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    @nnx.remat(graph=graph, graph_updates=graph_updates)\n    def forward(model, x):\n      return model(x)\n\n    def loss_fn(model, x):\n      y = forward(model, x)\n      return jnp.sum(y)\n\n    grads = nnx.grad(\n      loss_fn, graph=graph, graph_updates=graph_updates,\n    )(model, jnp.ones((1, 2)))\n    assert grads.kernel.shape == (2, 3)\n    assert grads.bias.shape == (3,)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_tree_mode_remat_stateful(self, graph, graph_updates):\n    class Counter(nnx.Variable):\n      pass\n\n    class Linear(nnx.Module):\n      def __init__(self, din, dout, *, rngs):\n        key = rngs.params()\n        self.w = nnx.Param(jax.random.uniform(key, (din, dout)))\n        self.b = nnx.Param(jnp.zeros((dout,)))\n        self.count = Counter(jnp.array(0))\n\n      def __call__(self, x):\n        self.count[...] += 1\n        return x @ self.w + self.b[None]\n\n    model = Linear(2, 3, rngs=nnx.Rngs(0))\n\n    @nnx.remat(graph=graph, graph_updates=graph_updates)\n    def forward(model, x):\n      return model(x)\n\n    y = forward(model, jnp.ones((1, 2)))\n    assert y.shape == (1, 3)\n    assert model.count[...] == 1\n\n\nclass TestVmap(parameterized.TestCase):\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_vmap_basic(self, graph, graph_updates):\n    class LinearEnsemble(nnx.Module):\n      def __init__(self, num, *, rngs):\n        self.w = nnx.Param(jax.random.uniform(rngs(), (num, 2, 3)))\n\n    model = LinearEnsemble(5, rngs=nnx.Rngs(0))\n    x = jnp.ones((2,))\n\n    @nnx.vmap(in_axes=(0, None), out_axes=0, graph=graph, graph_updates=graph_updates)\n    def forward(model, x):\n      return x @ model.w\n\n    y = forward(model, x)\n    assert y.shape == (5, 3)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_vmap_stateful(self, graph, graph_updates):\n    class Counter(nnx.Variable):\n      pass\n\n    class Linear(nnx.Module):\n      def __init__(self, din, dout, *, rngs):\n        key = rngs.params()\n        self.w = nnx.Param(jax.random.uniform(key, (din, dout)))\n        self.count = Counter(jnp.array(0))\n\n      def __call__(self, x):\n        self.count[...] += 1\n        return x @ self.w\n\n    model = Linear(2, 3, rngs=nnx.Rngs(0))\n\n    @nnx.vmap(in_axes=(None, 0), out_axes=0, graph=graph, graph_updates=graph_updates)\n    def forward(model, x):\n      return model(x)\n\n    x = jnp.ones((5, 2))\n    y = forward(model, x)\n    assert y.shape == (5, 3)\n    assert model.count[...] == 1\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_vmap_variables(self, graph, graph_updates):\n    rngs = nnx.Rngs(0)\n    w = nnx.Param(jax.random.normal(rngs(), (5, 2, 3)))\n    b = nnx.Param(jax.random.normal(rngs(), (5, 3)))\n\n    @nnx.vmap(in_axes=(0, 0, 1), out_axes=1, graph=graph, graph_updates=graph_updates)\n    def forward(w, b, x):\n      return x @ w + b\n\n    x = jax.random.uniform(rngs(), (2, 5))\n    y = forward(w, b, x)\n    assert y.shape == (3, 5)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_vmap_ensemble_forward(self, graph, graph_updates):\n    class Linear(nnx.Module):\n      def __init__(self, din, dout, *, rngs):\n        key = rngs.params()\n        self.w = nnx.Param(jax.random.uniform(key, (din, dout)))\n        self.b = nnx.Param(jnp.zeros((dout,)))\n\n      def __call__(self, x):\n        return x @ self.w + self.b[None]\n\n    @nnx.vmap(in_axes=0, out_axes=0, graph=graph, graph_updates=graph_updates)\n    def create_ensemble(keys):\n      return Linear(2, 3, rngs=nnx.Rngs(keys))\n\n    keys = jax.random.split(jax.random.key(0), 5)\n    ensemble = create_ensemble(keys)\n\n    assert ensemble.w.shape == (5, 2, 3)\n    assert ensemble.b.shape == (5, 3)\n\n    @nnx.vmap(in_axes=(0, None), out_axes=0, graph=graph, graph_updates=graph_updates)\n    def forward(model, x):\n      return model(x)\n\n    x = jnp.ones((1, 2))\n    y = forward(ensemble, x)\n    assert y.shape == (5, 1, 3)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_vmap_replicate(self, graph, graph_updates):\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    @nnx.vmap(in_axes=(None, 0), out_axes=0, graph=graph, graph_updates=graph_updates)\n    def forward(model, x):\n      return model(x)\n\n    x = jnp.ones((5, 1, 2))\n    y = forward(model, x)\n    assert y.shape == (5, 1, 3)\n\n  def test_basic(self):\n    @nnx.split_rngs(splits=5)\n    @nnx.vmap(in_axes=0, out_axes=0, axis_size=5)\n    def create_block(rngs: nnx.Rngs):\n      return nnx.Linear(2, 3, rngs=rngs)\n\n    rngs = nnx.Rngs(0)\n\n    block = create_block(rngs)\n\n    self.assertEqual(block.kernel.shape, (5, 2, 3))\n    self.assertEqual(rngs.default.count[...], 1)\n\n    @nnx.vmap(in_axes=(0, 1), out_axes=1)\n    def forward(block: nnx.Linear, x):\n      self.assertEqual(block.kernel.shape, (2, 3))\n      self.assertEqual(block.bias.shape, (3,))\n      self.assertEqual(x.shape, (2,))\n      return block(x)\n\n    x = jax.random.uniform(rngs(), (2, 5))\n    y = forward(block, x)\n\n    self.assertEqual(y.shape, (3, 5))\n\n  def test_basic_variables(self):\n    @nnx.split_rngs(splits=5)\n    @nnx.vmap(in_axes=0, out_axes=0, axis_size=5)\n    def create_block(rngs: nnx.Rngs):\n      w = nnx.Param(jax.random.normal(rngs(), (2, 3)))\n      b = nnx.Param(jax.random.normal(rngs(), (3,)))\n      return w, b\n\n    rngs = nnx.Rngs(0)\n    w, b = create_block(rngs)\n\n    self.assertEqual(w.shape, (5, 2, 3))\n    self.assertEqual(b.shape, (5, 3))\n    self.assertEqual(rngs.default.count[...], 1)\n\n    @nnx.vmap(in_axes=(0, 0, 1), out_axes=1)\n    def forward(w, b, x):\n      self.assertEqual(w.shape, (2, 3))\n      self.assertEqual(b.shape, (3,))\n      self.assertEqual(x.shape, (2,))\n      return x @ w + b\n\n    x = jax.random.uniform(rngs(), (2, 5))\n    y = forward(w, b, x)\n\n    self.assertEqual(y.shape, (3, 5))\n\n  def test_state_axes(self):\n    class Block(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False, rngs=rngs)\n\n      def __call__(self, x: jax.Array) -> jax.Array:\n        x = self.linear(x)\n        x = nnx.relu(x)\n        x = self.dropout(x)\n        return x\n\n    @nnx.vmap(\n      in_axes=0,\n      out_axes=nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None}),\n    )\n    def create_block(rngs: nnx.Rngs):\n      rngs = nnx.clone(rngs)\n      return Block(rngs)\n\n    rngs = nnx.Rngs(0)\n    initial_key = rngs.default.key[...]\n\n    backups = nnx.split_rngs(rngs, splits=5)\n    module = create_block(rngs)\n    nnx.restore_rngs(backups)\n\n    assert rngs.default.count[...] == 1\n    assert rngs.default.key[...] == initial_key\n    assert not jnp.allclose(\n      module.linear.kernel[0],\n      module.linear.kernel[1],\n    )\n    assert module.linear.kernel.shape == (5, 3, 3)\n    assert module.linear.bias.shape == (5, 3)\n\n    x = jnp.ones((5, 1, 3))\n\n    @nnx.vmap(\n      in_axes=(nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None}), 0),\n    )\n    def forward_block(module, x):\n      return module(x)\n\n    backups = nnx.split_rngs(rngs, splits=5)\n    y = forward_block(module, x)\n    nnx.restore_rngs(backups)\n\n    assert y.shape == (5, 1, 3)\n    assert rngs.default.count[...] == 2\n    assert rngs.default.key[...] == initial_key\n\n    y2 = forward_block(module, x)\n\n    assert not jnp.allclose(y, y2)\n\n  def test_split_rngs_context_manager(self):\n    class Block(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False, rngs=rngs)\n\n      def __call__(self, x: jax.Array) -> jax.Array:\n        x = self.linear(x)\n        x = nnx.relu(x)\n        x = self.dropout(x)\n        return x\n\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None})\n\n    @nnx.vmap(in_axes=(state_axes,), out_axes=state_axes)\n    def create_block(rngs: nnx.Rngs):\n      return Block(rngs)\n\n    rngs = nnx.Rngs(0)\n    initial_key = rngs.default.key[...]\n\n    module = create_block(rngs.split(5))\n\n    assert rngs.default.count[...] == 1\n    assert rngs.default.key[...] == initial_key\n    assert not jnp.allclose(\n      module.linear.kernel[0],\n      module.linear.kernel[1],\n    )\n    assert module.linear.kernel.shape == (5, 3, 3)\n    assert module.linear.bias.shape == (5, 3)\n\n    x = jnp.ones((5, 1, 3))\n\n    @nnx.vmap(in_axes=(state_axes, 0))\n    def forward_block(module, x):\n      return module(x)\n\n    y = forward_block(module, x)\n\n    assert y.shape == (5, 1, 3)\n    assert rngs.default.key[...] == initial_key\n\n    y2 = forward_block(module, x)\n\n    assert not jnp.allclose(y, y2)\n\n  def test_split_rngs_decorator(self):\n    class Block(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False, rngs=rngs)\n\n      def __call__(self, x: jax.Array) -> jax.Array:\n        x = self.linear(x)\n        x = nnx.relu(x)\n        x = self.dropout(x)\n        return x\n\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None})\n\n    @nnx.split_rngs(splits=5)\n    @nnx.vmap(in_axes=(state_axes,), out_axes=state_axes)\n    def create_block(rngs: nnx.Rngs):\n      return Block(rngs)\n\n    rngs = nnx.Rngs(0)\n    initial_key = rngs.default.key[...]\n\n    module = create_block(rngs)\n\n    assert rngs.default.count[...] == 1\n    assert rngs.default.key[...] == initial_key\n    assert not jnp.allclose(\n      module.linear.kernel[0],\n      module.linear.kernel[1],\n    )\n    assert module.linear.kernel.shape == (5, 3, 3)\n    assert module.linear.bias.shape == (5, 3)\n\n    x = jnp.ones((5, 1, 3))\n\n    @nnx.vmap(in_axes=(state_axes, 0))\n    def forward_block(module, x):\n      self.assertEqual(x.shape, (1, 3))\n      return module(x)\n\n    y = forward_block(module, x)\n\n    assert y.shape == (5, 1, 3)\n    assert rngs.default.key[...] == initial_key\n\n    y2 = forward_block(module, x)\n\n    assert not jnp.allclose(y, y2)\n\n  def test_state_axes_simple(self):\n    class Block(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(2, 3, rngs=rngs)\n        self.bn = nnx.BatchNorm(3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.1, deterministic=False, rngs=rngs)\n\n      def __call__(self, x: jax.Array) -> jax.Array:\n        return nnx.relu(self.dropout(self.bn(self.linear(x))))\n\n    state_axes = nnx.StateAxes({(nnx.BatchStat, 'dropout'): 0, ...: None})\n\n    @nnx.vmap(in_axes=(state_axes,), out_axes=state_axes)\n    def create_block(rngs: nnx.Rngs):\n      return Block(rngs)\n\n    rngs = nnx.Rngs(params=0, dropout=1)\n    nnx.split_rngs(rngs, splits=5, only='dropout')\n\n    module = create_block(rngs)\n\n    assert module.linear.kernel.shape == (2, 3)\n    assert module.bn.scale.shape == (3,)\n    assert module.bn.mean.shape == (5, 3)\n\n    @nnx.vmap(in_axes=(state_axes, 0), out_axes=0)\n    def forward_block(module, x):\n      return module(x)\n\n    x = jnp.ones((5, 1, 2))\n    y = forward_block(module, x)\n\n    assert y.shape == (5, 1, 3)\n\n  def test_split_rngs_decorator_simple(self):\n    class Block(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(2, 3, rngs=rngs)\n        self.bn = nnx.BatchNorm(3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.1, deterministic=False, rngs=rngs)\n\n      def __call__(self, x: jax.Array) -> jax.Array:\n        return nnx.relu(self.dropout(self.bn(self.linear(x))))\n\n    state_axes = nnx.StateAxes({(nnx.BatchStat, 'dropout'): 0, ...: None})\n\n    @nnx.split_rngs(splits=5, only='dropout')\n    @nnx.vmap(in_axes=(state_axes,), out_axes=state_axes)\n    def create_block(rngs: nnx.Rngs):\n      return Block(rngs)\n\n    rngs = nnx.Rngs(params=0, dropout=1)\n\n    module = create_block(rngs)\n\n    assert module.linear.kernel.shape == (2, 3)\n    assert module.bn.scale.shape == (3,)\n    assert module.bn.mean.shape == (5, 3)\n    assert module.dropout.rngs is not None\n    self.assertEqual(module.dropout.rngs.key.shape, (5,))\n\n    @nnx.vmap(in_axes=(state_axes, 0), out_axes=0)\n    def forward_block(module: Block, x):\n      assert module.dropout.rngs is not None\n      self.assertEqual(module.dropout.rngs.key.shape, ())\n      return module(x)\n\n    x = jnp.ones((5, 1, 2))\n    y = forward_block(module, x)\n\n    assert module.dropout.rngs is not None\n    self.assertEqual(module.dropout.rngs.key.shape, (5,))\n    assert y.shape == (5, 1, 3)\n\n  def test_state_axes_super_simple(self):\n    class Block(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(2, 3, rngs=rngs)\n        self.bn = nnx.BatchNorm(3, rngs=rngs)\n        self.dropout = nnx.Dropout(0.1, deterministic=False, rngs=rngs)\n\n      def __call__(self, x: jax.Array) -> jax.Array:\n        return nnx.relu(self.dropout(self.bn(self.linear(x))))\n\n    @nnx.vmap(in_axes=0, out_axes=0)\n    def create_block(rngs: nnx.Rngs):\n      return Block(rngs)\n\n    rngs = nnx.Rngs(0)\n    nnx.split_rngs(rngs, splits=5)\n\n    module = create_block(rngs)\n\n    assert module.linear.kernel.shape == (5, 2, 3)\n    assert module.bn.scale.shape == (5, 3)\n    assert module.bn.mean.shape == (5, 3)\n\n    @nnx.vmap(in_axes=(0, 0), out_axes=0)\n    def forward_block(module, x):\n      return module(x)\n\n    x = jnp.ones((5, 1, 2))\n    y = forward_block(module, x)\n\n    assert y.shape == (5, 1, 3)\n\n  def test_replicate(self):\n    din = 3\n    dout = 10\n\n    class Block(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False, rngs=rngs)\n\n      def __call__(self, x: jax.Array) -> jax.Array:\n        return self.dropout(nnx.relu(self.linear(x)))\n\n    def create_block(rngs: nnx.Rngs):\n      return Block(rngs)\n\n    state_axes = nnx.StateAxes({nnx.RngState: 0, ...: None})\n\n    @nnx.split_rngs(splits=5)\n    @partial(nnx.vmap, in_axes=(state_axes, 0), out_axes=0)\n    def forward_block(module: Block, x):\n      return module(x)\n\n    rngs = nnx.Rngs(0)\n    module = create_block(rngs)\n    initial_key = module.dropout.rngs.key[...]\n\n    assert module.dropout.rngs.count[...] == 0\n    assert module.linear.kernel.shape == (din, dout)\n    assert module.linear.bias.shape == (dout,)\n\n    x = jnp.ones((5, 1, din))\n\n    y = forward_block(module, x)\n\n    assert y.shape == (5, 1, dout)\n    assert module.dropout.rngs.count[...] == 1\n\n    assert not jnp.allclose(y[0], y[1])\n\n    y2 = forward_block(module, x)\n\n    # dropout is working!\n    assert not jnp.allclose(y, y2)\n\n    assert module.dropout.rngs.key[...] == initial_key\n\n  def test_consistent_aliasing_inputs(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = nnx.Param(jnp.zeros((5, 5)))\n\n    m = Foo()\n\n    @nnx.vmap(in_axes=(0, 1))\n    def f(m1, m2):\n      pass\n\n    with self.assertRaisesRegex(ValueError, 'Inconsistent aliasing detected'):\n      f(m, m)\n\n  def test_consistent_aliasing_input_output(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = nnx.Param(jnp.zeros((2, 3)))\n\n    m = Foo()\n\n    @partial(nnx.vmap, in_axes=0, out_axes=1)\n    def f(m):\n      return m\n\n    with self.assertRaisesRegex(ValueError, 'Inconsistent aliasing detected'):\n      m2 = f(m)\n\n  def test_consistent_aliasing_shared(self):\n    class Shared(nnx.Module):\n      def __init__(self):\n        self.a = nnx.Param(jnp.zeros((3, 3)))\n\n    class Foo(nnx.Module):\n      def __init__(self, shared: Shared):\n        self.a = shared\n\n    shared = Shared()\n    m1 = Foo(shared)\n    m2 = Foo(shared)\n\n    @nnx.vmap(in_axes=(0, 1))\n    def f(m1, m2):\n      pass\n\n    with self.assertRaisesRegex(\n      ValueError,\n      r'Inconsistent aliasing detected([\\s\\S]*)Param([\\s\\S]*)a:'\n      r' 0([\\s\\S]*)a: 1',\n    ):\n      f(m1, m2)\n\n  def test_equivalent_state_axes_mapping(self):\n    m = nnx.Linear(3, 3, rngs=nnx.Rngs(0))\n\n    sa1 = nnx.StateAxes({...: 0})\n    sa2 = nnx.StateAxes({nnx.Param: 0})\n\n    @nnx.vmap(in_axes=(0, sa1, sa2))\n    def f(m1, m2, m3):\n      pass\n\n    f(m, m, m)\n\n  def test_equivalent_state_sharding_mapping(self):\n    m = nnx.Linear(4, 4, rngs=nnx.Rngs(0))\n\n    mesh = jax.sharding.Mesh(jax.devices(), ('mp',))\n    sharding = jax.sharding.NamedSharding(\n      mesh, jax.sharding.PartitionSpec('mp')\n    )\n\n    sa1 = nnx.StateSharding({...: sharding})\n    sa2 = nnx.StateSharding({nnx.Param: sharding})\n\n    @nnx.jit(in_shardings=(sharding, sa1, sa2))\n    def f(m1, m2, m3):\n      pass\n\n    f(m, m, m)\n\n  def test_captured_module_in_return_error(self):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.a = jnp.zeros((4, 4))\n\n    m = Foo()\n\n    @nnx.vmap(in_axes=0, out_axes=0)\n    def f(x):\n      return x, m\n\n    with self.assertRaisesRegex(\n      errors.TraceContextError,\n      r'Trying to extract graph node from different trace level.*Foo',\n    ):\n      x = jnp.zeros((4,))\n      f(x)\n\n  def test_vmap_and_cond_passthrough(self):\n    class Broadcast(nnx.Variable[nnx.A]): ...\n\n    class Vectorized(nnx.Variable[nnx.A]): ...\n\n    class Env(nnx.Module):\n      def __init__(self):\n        self.broadcast = Broadcast(jnp.array(1))\n        self.index = Vectorized(jnp.arange(8))\n        self.step = Vectorized(jnp.zeros((8,), jnp.uint32))\n\n    env = Env()\n\n    @nnx.vmap(in_axes=(nnx.StateAxes({Broadcast: None, Vectorized: 0}),))\n    def f(env: Env):\n      self.assertEqual(env.step.shape, ())\n\n      def increment(env: Env):\n        env.step[...] += 1\n\n      def no_nothing(env: Env):\n        pass\n\n      is_even = env.index % 2 == 0\n      nnx.cond(is_even, increment, no_nothing, env)\n\n    f(env)\n\n    np.testing.assert_array_equal(env.step[...], [1, 0, 1, 0, 1, 0, 1, 0])\n\n  def test_vmap_and_cond_passthrough_error(self):\n    class Broadcast(nnx.Variable[nnx.A]): ...\n\n    class Vectorized(nnx.Variable[nnx.A]): ...\n\n    class Env(nnx.Module):\n      def __init__(self):\n        self.broadcast = Broadcast(jnp.array(1))\n        self.index = Vectorized(jnp.arange(8))\n        self.step = Vectorized(jnp.zeros((8,), jnp.uint32))\n\n    env = Env()\n\n    @nnx.vmap(in_axes=(nnx.StateAxes({Broadcast: None, Vectorized: 0}),))\n    def f(env: Env):\n      self.assertEqual(env.step.shape, ())\n\n      def increment(env: Env):\n        env.step[...] += 1\n        env.broadcast[...] += 1\n\n      def no_nothing(env: Env):\n        pass\n\n      is_even = env.index % 2 == 0\n      nnx.cond(is_even, increment, no_nothing, env)\n\n    with self.assertRaisesRegex(\n      ValueError,\n      r\"at vmap.*'broadcast'.*got axis spec None but output was batched on\"\n      r' axis 0',\n    ):\n      f(env)\n\n  def test_example(self):\n    class Model(nnx.Module):\n      def __init__(self, din, dout, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, rngs=rngs)\n        self.bn = nnx.BatchNorm(dout, rngs=rngs)\n\n      def __call__(self, x):\n        return nnx.relu(self.dropout(self.bn(self.linear(x))))\n\n    @nnx.vmap(in_axes=0, out_axes=0)\n    def initialize_ensemble(key):\n      rngs = nnx.Rngs(key)\n      return Model(2, 3, rngs=rngs)\n\n    keys = jax.random.split(jax.random.key(0), 5)\n    ensemble = initialize_ensemble(keys)\n\n    self.assertEqual(ensemble.linear.kernel.shape, (5, 2, 3))\n\n    @nnx.vmap(in_axes=(0, None), out_axes=0)\n    def forward(model, x):\n      return model(x)\n\n    x = jnp.ones((4, 2))\n    y = forward(ensemble, x)\n    self.assertEqual(y.shape, (5, 4, 3))\n\n  def test_example_with_vectorization(self):\n    class LinearEnsemble(nnx.Module):\n      def __init__(self, num, rngs):\n        self.w = nnx.Param(jax.random.uniform(rngs(), (num, 2, 3)))\n\n    model = LinearEnsemble(5, rngs=nnx.Rngs(0))\n\n    @nnx.vmap(in_axes=(0, None), out_axes=0)\n    def forward(model, x):\n      self.assertEqual(model.w.shape, (2, 3))\n      return jnp.dot(x, model.w)\n\n    x = jnp.ones((4, 2))\n    y = forward(model, x)\n\n    self.assertEqual(y.shape, (5, 4, 3))\n\n  def test_metadata(self):\n    @nnx.vmap(\n      in_axes=(None,),\n      out_axes=0,\n      axis_size=5,\n      transform_metadata={nnx.spmd.PARTITION_NAME: 'c'},\n    )\n    def create_block(rngs: nnx.Rngs):\n      return nnx.Linear(\n        16,\n        32,\n        rngs=rngs,\n        kernel_init=nnx.with_partitioning(\n          nnx.initializers.lecun_normal(), ('a', 'b')\n        ),\n      )\n\n\n    mesh = jax.make_mesh((1, 1, 1), ('a', 'b', 'c'), axis_types=(jax.sharding.AxisType.Auto,) * len(('a', 'b', 'c')))\n    with jax.set_mesh(mesh):\n      m = create_block(nnx.Rngs(0))\n    self.assertEqual(m.kernel.shape, (5, 16, 32))\n    self.assertEqual(m.kernel.out_sharding, ('c', 'a', 'b'))\n\n  def test_state_axes_from_state(self):\n    class Model(nnx.Module):\n      def __init__(self, din, dout, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.bn = nnx.BatchNorm(dout, rngs=rngs)\n\n    model = Model(2, 3, rngs=nnx.Rngs(0))\n    state = nnx.state(model)\n\n    state['linear']['kernel'] = 0\n    state['linear']['bias'] = 1\n    state['bn']['scale'] = 0\n    state['bn']['mean'] = 1\n    state['bn']['var'] = 0\n    state['bn']['bias'] = None\n\n    state_axes = nnx.StateAxes(state)\n\n    self.assertEqual(state_axes.map_prefix(('linear', 'kernel'), None), 0)\n    self.assertEqual(state_axes.map_prefix(('linear', 'bias'), None), 1)\n    self.assertEqual(state_axes.map_prefix(('bn', 'scale'), None), 0)\n    self.assertEqual(state_axes.map_prefix(('bn', 'mean'), None), 1)\n    self.assertEqual(state_axes.map_prefix(('bn', 'var'), None), 0)\n    self.assertEqual(state_axes.map_prefix(('bn', 'bias'), None), None)\n\n    @nnx.vmap(out_axes=state_axes, axis_size=5)\n    def create_block():\n      return Model(2, 3, rngs=nnx.Rngs(0))\n\n    model = create_block()\n\n    self.assertEqual(model.linear.kernel.shape, (5, 2, 3))\n    self.assertEqual(model.linear.bias.shape, (3, 5))\n    self.assertEqual(model.bn.scale.shape, (5, 3))\n    self.assertEqual(model.bn.mean.shape, (3, 5))\n    self.assertEqual(model.bn.var.shape, (5, 3))\n    self.assertEqual(model.bn.bias.shape, (3,))\n\n\n  def test_vmap_inconsistent_aliasing(self):\n    v = nnx.Param(jnp.arange(3.0))\n\n    @nnx.vmap(in_axes=(0, None), graph=True, graph_updates=False)\n    def f(v_mapped, v_broadcast):\n      return v_mapped[...] + v_broadcast[...]\n\n    with self.assertRaisesRegex(ValueError, 'Inconsistent aliasing'):\n      f(v, v)\n\n\nclass TestPmap(parameterized.TestCase):\n  def test_basic_single(self):\n    class Block(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 10, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False, rngs=rngs)\n\n      def __call__(self, x: jax.Array) -> jax.Array:\n        x = self.linear(x)\n        x = nnx.elu(x)\n        x = self.dropout(x)\n        return x\n\n    state_axes = nnx.StateAxes({(nnx.Param, nnx.RngState): 0, ...: None})\n\n    @nnx.split_rngs(splits=1)\n    @nnx.pmap(in_axes=(state_axes,), out_axes=state_axes, axis_size=1,\n              graph=True)\n    def create_block(rngs: nnx.Rngs):\n      return Block(rngs)\n\n    rngs = nnx.Rngs(0)\n    module = create_block(rngs)\n    initial_key = module.dropout.rngs.key[...]\n\n    assert module.dropout.rngs.count[0] == 0\n    assert module.linear.kernel.shape == (1, 3, 10)\n    assert module.linear.bias.shape == (1, 10)\n\n    x = jnp.ones((1, 1, 3))\n\n    @nnx.pmap(in_axes=(state_axes, 0), axis_size=1, graph=True)\n    def forward_block(module, x):\n      return module(x)\n\n    y = forward_block(module, x)\n\n    assert y.shape == (1, 1, 10)\n    assert module.dropout.rngs.count[0] == 1\n    assert module.dropout.rngs.key[...] == initial_key\n\n    y2 = forward_block(module, x)\n\n    assert not jnp.allclose(y, y2)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_basic_demo_single(self, graph, graph_updates):\n    class Block(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(20, 20, rngs=rngs)\n        self.dropout = nnx.Dropout(0.2, deterministic=False, rngs=rngs)\n\n      def __call__(self, x: jax.Array) -> jax.Array:\n        return self.dropout(nnx.relu(self.linear(x)))\n\n    @nnx.split_rngs(splits=1)\n    @nnx.pmap(axis_size=1, graph=graph, graph_updates=graph_updates)\n    def create_block(rngs: nnx.Rngs):\n      return Block(rngs)\n\n    @nnx.pmap(axis_size=1, graph=graph, graph_updates=graph_updates)\n    def forward_block(module: Block, x):\n      return module(x)\n\n    rngs = nnx.Rngs(0)\n    module = create_block(rngs)\n\n    assert module.dropout.rngs.count[...] == 0\n    assert module.linear.kernel.shape == (1, 20, 20)\n    assert module.linear.bias.shape == (1, 20)\n\n    x = jnp.ones((1, 10, 20))\n\n    y = forward_block(module, x)\n\n    assert y.shape == (1, 10, 20)\n    assert module.dropout.rngs.count[...] == 1\n\n    y2 = forward_block(module, x)\n\n    # dropout is working!\n    assert not jnp.allclose(y, y2)\n\n  def test_replicate_single(self):\n    din = 3\n    dout = 10\n\n    class Block(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False, rngs=rngs)\n\n      def __call__(self, x: jax.Array) -> jax.Array:\n        return self.dropout(nnx.relu(self.linear(x)))\n\n    def create_block(rngs: nnx.Rngs):\n      return Block(rngs)\n\n    state_axes = nnx.StateAxes({nnx.RngState: 0, ...: None})\n\n    @nnx.split_rngs(splits=1)\n    @partial(nnx.pmap, in_axes=(state_axes, 0), out_axes=0, axis_size=1,\n             graph=True)\n    def forward_block(module: Block, x):\n      return module(x)\n\n    rngs = nnx.Rngs(0)\n    module = create_block(rngs)\n    initial_key = module.dropout.rngs.key[...]\n\n    assert module.dropout.rngs.count[...] == 0\n    assert module.linear.kernel.shape == (din, dout)\n    assert module.linear.bias.shape == (dout,)\n\n    x = jnp.ones((1, 5, din))\n\n    y = forward_block(module, x)\n\n    assert y.shape == (1, 5, dout)\n    assert module.dropout.rngs.count[...] == 1\n\n    y2 = forward_block(module, x)\n\n    # dropout is working!\n    assert not jnp.allclose(y, y2)\n\n    assert module.dropout.rngs.key[...] == initial_key\n\n\nclass TestCond(parameterized.TestCase):\n  def test_basic(self):\n    class TimeStep(tp.NamedTuple):\n      step: nnx.Variable[jax.Array]\n      reward: nnx.Variable[jax.Array]\n\n      @staticmethod\n      def zero():\n        return TimeStep(\n            step=nnx.Variable(jnp.array(0)), reward=nnx.Variable(jnp.array(0.0))\n        )\n\n    @nnx.dataclass\n    class Foo(nnx.Pytree):\n      timestep: TimeStep = nnx.data()\n\n      def update(self):\n        def reward_2(self: Foo):\n          self.timestep = TimeStep(\n              step=nnx.Variable(self.timestep.step + 1),\n              reward=nnx.Variable(jnp.array(2.0)),\n          )\n\n        def reward_0(self: Foo):\n          self.timestep = TimeStep(\n              step=nnx.Variable(self.timestep.step + 1),\n              reward=nnx.Variable(jnp.array(0.0)),\n          )\n\n        nnx.cond(self.timestep.step % 2 == 0, reward_2, reward_0, self)\n\n    foo = Foo(timestep=TimeStep.zero())\n    foo.update()\n    self.assertEqual(foo.timestep.step[...], 1)\n    self.assertEqual(foo.timestep.reward[...], 2.0)\n    foo.update()\n    self.assertEqual(foo.timestep.step[...], 2)\n    self.assertEqual(foo.timestep.reward[...], 0.0)\n    foo.update()\n    self.assertEqual(foo.timestep.step[...], 3)\n    self.assertEqual(foo.timestep.reward[...], 2.0)\n    foo.update()\n    self.assertEqual(foo.timestep.step[...], 4)\n    self.assertEqual(foo.timestep.reward[...], 0.0)\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_basic_variable(self, graph, graph_updates):\n    def collatz(x):\n      def even(x):\n        x[...] = x // 2\n\n      def odd(x):\n        x[...] = 3 * x + 1\n\n      return nnx.cond(\n        x % 2 == 0, even, odd, x,\n        graph=graph, graph_updates=graph_updates,\n      )\n\n    x = nnx.Variable(jnp.array(8))\n    collatz(x)\n    self.assertEqual(x[...], 4)\n    collatz(x)\n    self.assertEqual(x[...], 2)\n    collatz(x)\n    self.assertEqual(x[...], 1)\n    collatz(x)\n    self.assertEqual(x[...], 4)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_cond_and_vmap(self, graph, graph_updates):\n    class Env(nnx.Pytree):\n      def __init__(self):\n        self.index = nnx.Variable(jnp.arange(8))\n        self.step = nnx.Variable(jnp.zeros((8,), jnp.uint32))\n\n    env = Env()\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    @nnx.vmap(in_axes=(0, None), out_axes=None, graph=graph, graph_updates=graph_updates)\n    def f(env: Env, model: nnx.Linear):\n      self.assertEqual(env.index.shape, ())\n\n      def increment(env: Env):\n        env.step[...] += 1\n\n      def no_nothing(env: Env):\n        pass\n\n      is_even = env.index % 2 == 0\n      nnx.cond(\n        is_even, increment, no_nothing, env,\n        graph=graph, graph_updates=graph_updates,\n      )\n\n    f(env, model)\n\n    np.testing.assert_array_equal(\n      env.step[...], np.array([1, 0, 1, 0, 1, 0, 1, 0], np.uint32)\n    )\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_cond_different_variable_per_branch(self, graph, graph_updates):\n    a = nnx.Variable(jnp.array(0))\n    b = nnx.Variable(jnp.array(0))\n\n    def update_a(a, b):\n      a[...] += 1\n\n    def update_b(a, b):\n      b[...] += 10\n\n    nnx.cond(\n      True, update_a, update_b, a, b,\n      graph=graph, graph_updates=graph_updates,\n    )\n    self.assertEqual(a[...], 1)\n    self.assertEqual(b[...], 0)\n\n    nnx.cond(\n      False, update_a, update_b, a, b,\n      graph=graph, graph_updates=graph_updates,\n    )\n    self.assertEqual(a[...], 1)\n    self.assertEqual(b[...], 10)\n\n  def test_cond_shared_references(self):\n\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      a: nnx.Variable\n      b: nnx.Variable\n\n    v = nnx.Variable(jnp.array(0))\n    m = Foo(a=v, b=v)\n\n    def true_fn(m):\n      m.a[...] += 1\n\n    def false_fn(m):\n      m.b[...] += 2\n\n    nnx.cond(True, true_fn, false_fn, m, graph=True, graph_updates=False)\n    np.testing.assert_allclose(m.a[...], 1)\n    np.testing.assert_allclose(m.b[...], 1)\n    nnx.cond(False, true_fn, false_fn, m, graph=True, graph_updates=False)\n    np.testing.assert_allclose(m.a[...], 3)\n    np.testing.assert_allclose(m.b[...], 3)\n\n    with self.assertRaises(ValueError):\n      nnx.cond(True, true_fn, false_fn, m, graph=False)\n\nclass TestSwitch(parameterized.TestCase):\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_basic(self, graph, graph_updates):\n    class RoundTable(nnx.Module):\n      def __init__(self):\n        self.next_index = 0\n        self.linear = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n        self.linear.kernel[...] = jnp.identity(10)\n        self.rounds_count = nnx.Variable(jnp.array(0))\n\n      def __call__(self, x):\n        def fn0(m, x):\n          m.rounds_count[...] += 1\n          return m.linear(x)\n        def fn1(m, x):\n          return m.linear(x) * 2\n        def fn2(m, x):\n          m.linear.kernel[...] = jnp.zeros((10, 10))\n          return m.linear(x)\n\n        y = nnx.switch(\n          self.next_index, (fn0, fn1, fn2), self, x,\n          graph=graph, graph_updates=graph_updates,\n        )\n        self.next_index = (self.next_index + 1) % 3\n        return y\n\n    model = RoundTable()\n    x = jnp.ones((10,))\n    np.testing.assert_array_equal(model(x), x)\n    assert model.rounds_count[...] == 1\n    assert model.next_index == 1\n    np.testing.assert_array_equal(model(x), x * 2)\n    assert model.rounds_count[...] == 1\n    assert model.next_index == 2\n    np.testing.assert_array_equal(model(x), jnp.zeros((10,)))\n    assert model.rounds_count[...] == 1\n    assert model.next_index == 0\n    np.testing.assert_array_equal(model(x), jnp.zeros((10,)))\n    assert model.rounds_count[...] == 2\n    assert model.next_index == 1\n\n  @parameterized.parameters(\n    (True, False),\n    (False, False),\n  )\n  def test_switch_variable(self, graph, graph_updates):\n    def add_1(x):\n      x[...] += 1\n\n    def add_10(x):\n      x[...] += 10\n\n    def add_100(x):\n      x[...] += 100\n\n    x = nnx.Variable(jnp.array(0))\n    nnx.switch(0, (add_1, add_10, add_100), x,\n               graph=graph, graph_updates=graph_updates)\n    self.assertEqual(x[...], 1)\n    nnx.switch(1, (add_1, add_10, add_100), x,\n               graph=graph, graph_updates=graph_updates)\n    self.assertEqual(x[...], 11)\n    nnx.switch(2, (add_1, add_10, add_100), x,\n               graph=graph, graph_updates=graph_updates)\n    self.assertEqual(x[...], 111)\n\n  def test_switch_shared_references(self):\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      a: nnx.Variable\n      b: nnx.Variable\n\n    v = nnx.Variable(jnp.array(0))\n    m = Foo(a=v, b=v)\n\n    def add_a(m):\n      m.a[...] += 1\n\n    def add_b(m):\n      m.b[...] += 10\n\n    nnx.switch(0, (add_a, add_b), m, graph=True, graph_updates=False)\n    np.testing.assert_allclose(m.a[...], 1)\n    np.testing.assert_allclose(m.b[...], 1)\n\n    nnx.switch(1, (add_a, add_b), m, graph=True, graph_updates=False)\n    np.testing.assert_allclose(m.a[...], 11)\n    np.testing.assert_allclose(m.b[...], 11)\n\n    with self.assertRaises(ValueError):\n      nnx.switch(0, (add_a, add_b), m, graph=False)\n\nclass TestWhileLoop(parameterized.TestCase):\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_basic(self, graph, graph_updates):\n    def fwd_fn(input):\n      m, x, c = input\n      y = m(x)\n      return m, y, c - 1.0\n\n    module = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    module.kernel[...] = jnp.identity(10) * 2\n    x = 1e1 * jax.random.normal(jax.random.key(0), (10,))\n\n    _, y, _ = nnx.while_loop(\n      lambda input: input[-1] > 0, fwd_fn, (module, x, 3.0),\n      graph=graph, graph_updates=graph_updates)\n    np.testing.assert_array_equal(y, x * 8)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_multiple_objects(self, graph, graph_updates):\n    def fwd_fn(input):\n      m1, (w2,), x, c = input\n      y = m1(x) @ w2\n      return m1, (w2,), y, c - 1.0\n\n    m1 = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    m1.kernel[...] = jnp.identity(10) * 2\n    w2 = nnx.Variable(jnp.identity(10) * 0.5)\n    x = 1e1 * jax.random.normal(jax.random.key(0), (10,))\n\n    _, _, y, _ = nnx.while_loop(\n      lambda input: input[-1] > 0, fwd_fn, (m1, (w2,), x, 3.0),\n      graph=graph, graph_updates=graph_updates)\n    np.testing.assert_allclose(y, x)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_nested_module(self, graph, graph_updates):\n    def fwd_fn(input):\n      m, x, c = input\n      y = m(x)\n      return m, y, c - 1.0\n\n    module = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    module.kernel[...] = jnp.identity(10) * 2\n    module = nnx.Sequential(module)\n    x = 1e1 * jax.random.normal(jax.random.key(0), (10,))\n\n    _, y, _ = nnx.while_loop(\n      lambda input: input[-1] > 0, fwd_fn, (module, x, 3.0),\n      graph=graph, graph_updates=graph_updates)\n    np.testing.assert_array_equal(y, x * 8)\n\n  def test_shared_module(self):\n    m1 = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    m2 = nnx.Linear(10, 10, use_bias=False, rngs=nnx.Rngs(0))\n    m2.kernel = m1.kernel\n    module = nnx.Sequential(m1, m2)\n    self.assertLen(jax.tree.leaves(nnx.state(module)), 2)  # only m1 params\n\n    def fwd_fn(input):\n      m, x, c = input\n      y = m(x)\n      m.layers[0].kernel[...] = jnp.zeros_like(m.layers[0].kernel[...])\n      return m, y, c - 1.0\n\n    x = 1e1 * jax.random.normal(jax.random.key(0), (10,))\n    _, y, _ = nnx.while_loop(\n      lambda input: input[-1] > 0, fwd_fn, (module, x, 2.0))\n    self.assertLen(jax.tree.leaves(nnx.state(module)), 2)  # only m1 params\n    np.testing.assert_array_equal(\n      m1.kernel[...],\n      jnp.zeros((10, 10)),\n    )\n    np.testing.assert_array_equal(\n      m2.kernel[...],\n      jnp.zeros((10, 10)),\n    )\n    np.testing.assert_array_equal(y, jnp.zeros((10,)))\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_value_changed(self, graph, graph_updates):\n    def fwd_fn(input):\n      m, x, c = input\n      m.kernel[...] = jnp.zeros_like(m.kernel)\n      y = m(x)\n      return m, y, c - 1.0\n\n    module = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    x = 1e1 * jax.random.normal(jax.random.key(0), (10,))\n\n    _, y, _ = nnx.while_loop(\n      lambda input: input[-1] > 0, fwd_fn, (module, x, 3.0),\n      graph=graph, graph_updates=graph_updates)\n    np.testing.assert_array_equal(\n      module.kernel[...],\n      jnp.zeros((10, 10)),\n    )\n    np.testing.assert_array_equal(y, jnp.zeros((10,)))\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_ref_changed(self, graph, graph_updates):\n    def fwd_fn(input):\n      m, x, c = input\n      y = m(x)\n      m.kernel = nnx.Param(jnp.zeros_like(m.kernel))\n      return m, y, c - 1.0\n\n    module = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    x = 1e1 * jax.random.normal(jax.random.key(0), (10,))\n\n    with self.assertRaises(ValueError):\n      _, y, _ = nnx.while_loop(\n        lambda input: input[-1] > 0, fwd_fn, (module, x, 2.0),\n        graph=graph, graph_updates=graph_updates)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_structure_changed(self, graph, graph_updates):\n    def fwd_fn(input):\n      m, x, c = input\n      m = nnx.Linear(10, 10, use_bias=False, rngs=nnx.Rngs(1))\n      m.kernel[...] = jnp.identity(10) * 2\n      y = m(x)\n      return m, y, c - 1.0\n\n    module = nnx.Linear(10, 10, use_bias=True, rngs=nnx.Rngs(0))\n    x = 1e1 * jax.random.normal(jax.random.key(0), (10,))\n\n    with self.assertRaises((ValueError, TypeError)):\n      _, y, _ = nnx.while_loop(\n        lambda input: input[-1] > 0, fwd_fn, (module, x, 3.0),\n        graph=graph, graph_updates=graph_updates)\n\n  def test_repeated_object(self):\n    m = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n\n    def body_fn(val):\n      count, m, _ = val\n      return count + 1, m, m\n\n    count, m, _ = nnx.while_loop(\n      lambda val: val[0] < 2,\n      body_fn,\n      (0, m, m),\n    )\n\n  def test_immut_fori_loop(self):\n    def immut_fn(i, carry):\n        g_accum = carry\n        grads = jax.tree.map(jnp.ones_like, g_accum)\n        g_accum = jax.tree.map(lambda gm, g: gm + g, g_accum, grads)\n        return g_accum\n\n    model = nnx.Linear(10, 10, rngs=nnx.Rngs(0), use_bias=False)\n    g_accum = jax.tree.map(jnp.zeros_like, nnx.state(model))\n    nnx.fori_loop(0, 2, immut_fn, g_accum)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_fori_loop_grad_accum(self, graph, graph_updates):\n    accum = nnx.Variable(jnp.zeros((10, 10)))\n\n    def accum_fn(i, accum):\n      accum[...] += 1\n      return accum\n\n    accum = nnx.fori_loop(0, 3, accum_fn, accum,\n                          graph=graph, graph_updates=graph_updates)\n    np.testing.assert_array_equal(accum[...], jnp.full((10, 10), 3.0))\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_fori_loop_basic(self, graph, graph_updates):\n    def fwd_fn(i, input):\n      m, x = input\n      m.kernel[...] = jnp.identity(10) * i\n      return m, m(x)\n\n    module = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    x = jax.random.normal(jax.random.key(0), (10,))\n\n    _, y = nnx.fori_loop(2, 4, fwd_fn, (module, x),\n                         graph=graph, graph_updates=graph_updates)\n    np.testing.assert_array_equal(y, x * 2 * 3)\n\n  def test_fori_loop_with_sharing(self):\n    class A(nnx.Pytree):\n      def __init__(self):\n        self.params = nnx.Param(jnp.zeros((10,), dtype=int))\n\n    class B(nnx.Pytree):\n      def __init__(self, a: A):\n        self.a = a\n\n    class C(nnx.Pytree):\n      def __init__(self, a: A):\n        self.a = a\n\n    class D(nnx.Pytree):\n      def __init__(self):\n        self.a = A()\n        self.b = B(self.a)\n        self.c = C(self.a)\n\n    def increment(_, d: D) -> D:\n      d.a.params[...] += 1\n      return d\n\n    @nnx.jit\n    def rollout(d: D):\n      nnx.fori_loop(0, 10, increment, d)\n\n    d = D()\n    rollout(d)\n\n    np.testing.assert_array_equal(\n      d.a.params[...], np.full((10,), 10, dtype=int)\n    )\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_loops_multiple_modules(self, graph, graph_updates):\n    class Foo(nnx.Module):\n      def __init__(self):\n        self.param = nnx.Param(jnp.zeros((1,)))\n      def __call__(self, x):\n        return self.param\n\n    def loop_fn(inputs):\n      return inputs\n    while_loop_fn = lambda inputs: (*loop_fn(inputs[:-1]), inputs[-1]-1)\n    fori_loop_fn = lambda i, inputs: loop_fn(inputs)\n    a = Foo()\n    b = Foo()\n    nnx.while_loop(lambda input: input[-1] > 0, while_loop_fn, (a, b, 2),\n                   graph=graph, graph_updates=graph_updates)\n    nnx.fori_loop(0, 2, fori_loop_fn, (a, b), graph=graph)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_tree_mode_while_loop_stateful(self, graph, graph_updates):\n    class Counter(nnx.Module):\n      def __init__(self):\n        self.count = nnx.Variable(jnp.array(0))\n\n    counter = Counter()\n    module = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    module.kernel[...] = jnp.identity(10) * 2\n    x = jax.random.normal(jax.random.key(0), (10,))\n\n    def body_fn(val):\n      counter, module, x, i = val\n      counter.count[...] += 1\n      x = module(x)\n      return counter, module, x, i - 1\n\n    counter, module, y, _ = nnx.while_loop(\n      lambda val: val[-1] > 0,\n      body_fn,\n      (counter, module, x, 3),\n      graph=graph,\n      graph_updates=graph_updates,\n    )\n    np.testing.assert_array_equal(counter.count[...], 3)\n    np.testing.assert_array_equal(y, x * 8)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_tree_mode_while_loop_inside_jit(self, graph, graph_updates):\n    module = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    module.kernel[...] = jnp.identity(10) * 2\n    x = jax.random.normal(jax.random.key(0), (10,))\n\n    @nnx.jit(graph=graph, graph_updates=graph_updates)\n    def f(module, x):\n      def body_fn(val):\n        m, x, c = val\n        return m, m(x), c - 1.0\n      _, y, _ = nnx.while_loop(\n        lambda val: val[-1] > 0,\n        body_fn,\n        (module, x, 3.0),\n        graph=graph,\n      )\n      return y\n\n    y = f(module, x)\n    np.testing.assert_array_equal(y, x * 8)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_tree_mode_fori_loop_stateful(self, graph, graph_updates):\n    class Counter(nnx.Module):\n      def __init__(self):\n        self.count = nnx.Variable(jnp.array(0))\n\n    counter = Counter()\n    module = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    module.kernel[...] = jnp.identity(10) * 2\n    x = jax.random.normal(jax.random.key(0), (10,))\n\n    def body_fn(i, val):\n      counter, module, x = val\n      counter.count[...] += 1\n      x = module(x)\n      return counter, module, x\n\n    counter, module, y = nnx.fori_loop(\n      0, 3, body_fn, (counter, module, x),\n      graph=graph, graph_updates=graph_updates,\n    )\n    np.testing.assert_array_equal(counter.count[...], 3)\n    np.testing.assert_array_equal(y, x * 8)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_tree_mode_fori_loop_inside_jit(self, graph, graph_updates):\n    module = nnx.Linear(10, 10, rngs=nnx.Rngs(0))\n    module.kernel[...] = jnp.identity(10) * 2\n    x = jax.random.normal(jax.random.key(0), (10,))\n\n    @nnx.jit(graph=graph, graph_updates=graph_updates)\n    def f(module, x):\n      def body_fn(i, val):\n        m, x = val\n        return m, m(x)\n      _, y = nnx.fori_loop(\n        0, 3, body_fn, (module, x), graph=graph,\n      )\n      return y\n\n    y = f(module, x)\n    np.testing.assert_array_equal(y, x * 8)\n\nclass TestSplitMergeInputs(absltest.TestCase):\n  def test_split_inputs(self):\n    class StatefulLinear(nnx.Linear):\n      def __init__(self, din: int, dout: int, rngs: nnx.Rngs):\n        super().__init__(din, dout, rngs=rngs)\n        self.counter = nnx.BatchStat(jnp.array(0, jnp.uint32))\n\n      def __call__(self, x):\n        self.counter[...] += 1\n        return super().__call__(x)\n\n    model = StatefulLinear(3, 4, rngs=nnx.Rngs(0))\n\n    @general.split_inputs\n    @jax.jit\n    @general.merge_inputs\n    def forward(model, x):\n      return model(x)\n\n    x = jnp.ones((2, 3))\n    y = forward(model, x)\n\n    self.assertEqual(model.counter[...], 1)\n\n  def test_split_inputs_cond(self):\n    class Counter(nnx.Linear):\n      def __init__(self):\n        self.count = nnx.BatchStat(jnp.array(0, jnp.uint32))\n\n      def increment(self):\n        self.count[...] += 1\n\n    counter = Counter()\n\n    @general.merge_inputs\n    def increment(counter: Counter):\n      counter.increment()\n\n    @general.merge_inputs\n    def no_nothing(counter: Counter):\n      pass\n\n    general.split_inputs(jax.lax.cond)(True, increment, no_nothing, counter)\n\n    self.assertEqual(counter.count[...], 1)\n\n    general.split_inputs(jax.lax.cond)(False, increment, no_nothing, counter)\n\n    self.assertEqual(counter.count[...], 1)\n\n  def test_split_inputs_vmap(self):\n    class EnvState(nnx.Variable[nnx.A]):\n      pass\n\n    class Env(nnx.Pytree):\n      def __init__(self):\n        self.index = EnvState(jnp.arange(8))\n        self.step = EnvState(jnp.zeros((8,), jnp.uint32))\n\n    env = Env()\n    model = nnx.Linear(2, 3, rngs=nnx.Rngs(0))\n\n    # internally merge_inputs returns (args, out)\n    in_axes = (0, None)\n    out_axes = (in_axes, None)\n\n    @general.split_inputs\n    @partial(jax.vmap, in_axes=in_axes, out_axes=out_axes)\n    @general.merge_inputs\n    def f(env: Env, model: nnx.Linear):\n      self.assertEqual(env.index.shape, ())\n\n      @general.merge_inputs\n      def increment(env: Env):\n        env.step[...] += 1\n\n      @general.merge_inputs\n      def no_nothing(env: Env):\n        pass\n\n      is_even = env.index % 2 == 0\n      general.split_inputs(jax.lax.cond)(is_even, increment, no_nothing, env)\n\n    f(env, model)\n\n    np.testing.assert_array_equal(\n      env.step[...], np.array([1, 0, 1, 0, 1, 0, 1, 0], np.uint32)\n    )\n\nclass TestCheckify(parameterized.TestCase):\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_basic(self, graph, graph_updates):\n\n    @dataclasses.dataclass\n    class Foo(nnx.Module):\n      a: nnx.Param\n\n    @nnx.jit(graph=graph, graph_updates=graph_updates)\n    def f(m):\n      y = jnp.sin(m.a)  # error\n      return m.a + y\n\n    m = Foo(a=nnx.Param(jnp.inf))\n    err, out = nnx.checkify(\n      f, errors=checkify.float_checks,\n      graph=graph, graph_updates=graph_updates,\n    )(m)\n\n    with self.assertRaisesRegex(ValueError, 'nan generated by primitive: sin'):\n      err.throw()\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_checkify_stateful(self, graph, graph_updates):\n    count = nnx.Variable(jnp.array(0))\n\n    @nnx.jit(graph=graph, graph_updates=graph_updates)\n    def f(c):\n      c[...] += 1\n      return c[...]\n\n    err, out = nnx.checkify(\n      f, graph=graph, graph_updates=graph_updates,\n    )(count)\n    self.assertEqual(count[...], 1)\n    np.testing.assert_allclose(out, 1)\n\n\nclass TestBoundMethodTransforms(parameterized.TestCase):\n  def test_remat_with_bound_method_raises(self):\n    class M(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 3, rngs=rngs)\n        self.count = nnx.BatchStat(0)\n\n      def block(self, x: jax.Array) -> jax.Array:\n        self.count[...] += 1\n        return self.linear(x)\n\n    m = M(rngs=nnx.Rngs(0))\n    with self.assertRaisesRegex(ValueError, 'bound methods'):\n      nnx.remat(m.block)\n\n  def test_jit_with_bound_method_raises(self):\n    class M(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(2, 3, rngs=rngs)\n      def apply(self, x: jax.Array, scale: int):\n        return self.linear(x) * scale\n\n    m = M(rngs=nnx.Rngs(0))\n    with self.assertRaisesRegex(ValueError, 'bound methods'):\n      nnx.jit(m.apply, static_argnums=1)\n\n  def test_vmap_with_bound_method_raises(self):\n    class M(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(2, 3, rngs=rngs)\n      def __call__(self, x: jax.Array):\n        return self.linear(x)\n\n    m = M(rngs=nnx.Rngs(0))\n    with self.assertRaisesRegex(ValueError, 'bound methods'):\n      nnx.vmap(m.__call__, in_axes=(0,), out_axes=0)\n\n  def test_eval_shape_with_bound_method_raises(self):\n    class M(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(2, 3, rngs=rngs)\n      def __call__(self, x: jax.Array):\n        return self.linear(x)\n\n    m = M(rngs=nnx.Rngs(0))\n    x_spec = jax.ShapeDtypeStruct((1, 2), jnp.float32)\n    with self.assertRaisesRegex(ValueError, 'bound methods'):\n      nnx.eval_shape(m.__call__, x_spec)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_grad_with_bound_method_raises(self, graph_mode, graph_updates):\n    class M(nnx.Module):\n      def __init__(self):\n        self.w = nnx.Param(jnp.array(1.0))\n      def loss(self, s: float):\n        return (self.w * s) ** 2\n\n    m = M()\n    with self.assertRaisesRegex(ValueError, 'bound methods'):\n      nnx.grad(m.loss, graph=graph_mode)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_value_and_grad_with_bound_method_raises(self, graph_mode, graph_updates):\n    class TestModel(nnx.Module):\n      def __init__(self, *, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(2, 1, rngs=rngs)\n\n      def loss_fn(self, x, y):\n        pred = self.linear(x)\n        return jnp.mean((pred - y) ** 2)\n\n    model = TestModel(rngs=nnx.Rngs(0))\n    with self.assertRaisesRegex(ValueError, 'bound methods'):\n      nnx.value_and_grad(model.loss_fn, graph=graph_mode)\n\n  def test_checkify_with_bound_method_raises(self):\n    \"\"\"Test that checkify raises error for bound methods.\"\"\"\n    class M(nnx.Module):\n      def __call__(self, x: jax.Array):\n        return x + 1\n\n    m = M()\n    with self.assertRaisesRegex(ValueError, 'bound methods'):\n      nnx.checkify(m.__call__)\n\n  def test_pmap_with_bound_method_raises(self):\n    \"\"\"Test that pmap raises error for bound methods.\"\"\"\n    class M(nnx.Module):\n      def __call__(self, x: jax.Array):\n        return x + 1\n\n    m = M()\n    with self.assertRaisesRegex(ValueError, 'bound methods'):\n      nnx.pmap(m.__call__)\n\n  def test_shard_map_with_bound_method_raises(self):\n    \"\"\"Test that shard_map raises error for bound methods.\"\"\"\n    class M(nnx.Module):\n      def __call__(self, x: jax.Array):\n        return x + 1\n\n    m = M()\n    mesh = jax.sharding.Mesh(jax.local_devices()[:1], ('data',))\n    with self.assertRaisesRegex(ValueError, 'bound methods'):\n      nnx.shard_map(m.__call__, mesh=mesh, in_specs=None, out_specs=None)\n\n  def test_custom_vjp_with_bound_method_raises(self):\n    \"\"\"Test that custom_vjp raises error for bound methods.\"\"\"\n    class M(nnx.Module):\n      def __call__(self, x: jax.Array):\n        return x + 1\n\n    m = M()\n    with self.assertRaisesRegex(ValueError, 'bound methods'):\n      nnx.custom_vjp(m.__call__)\n\n  def test_scan_bound_method_raises(self):\n    class M(nnx.Module):\n      def __call__(self, x: jax.Array):\n        return x + 1\n    m = M()\n    with self.assertRaisesRegex(ValueError, 'bound methods'):\n      _ = nnx.scan(m.__call__, in_axes=(0,), out_axes=0)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_tree_mode_pmap_basic(self, graph, graph_updates):\n    class LinearEnsemble(nnx.Module):\n      def __init__(self, num, *, rngs):\n        self.w = nnx.Param(jax.random.uniform(rngs(), (num, 2, 3)))\n\n    model = LinearEnsemble(1, rngs=nnx.Rngs(0))\n    x = jnp.ones((2,))\n\n    @nnx.pmap(in_axes=(0, None), out_axes=0, axis_size=1,\n              graph=graph, graph_updates=graph_updates)\n    def forward(model, x):\n      return x @ model.w\n\n    y = forward(model, x)\n    assert y.shape == (1, 3)\n\n  @parameterized.parameters(\n    (True, True), (True, False), (False, False),\n  )\n  def test_tree_mode_pmap_stateful(self, graph, graph_updates):\n    class Counter(nnx.Variable):\n      pass\n\n    class Linear(nnx.Module):\n      def __init__(self, din, dout, *, rngs):\n        key = rngs.params()\n        self.w = nnx.Param(jax.random.uniform(key, (din, dout)))\n        self.count = Counter(jnp.array(0))\n\n      def __call__(self, x):\n        self.count[...] += 1\n        return x @ self.w\n\n    model = Linear(2, 3, rngs=nnx.Rngs(0))\n\n    @nnx.pmap(in_axes=(None, 0), out_axes=0, axis_size=1,\n              graph=graph, graph_updates=graph_updates)\n    def forward(model, x):\n      return model(x)\n\n    x = jnp.ones((1, 2))\n    y = forward(model, x)\n    assert y.shape == (1, 3)\n    assert model.count.get_value() == 1\n\n  def test_tree_mode_pmap_split_merge(self):\n    class Block(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(3, 10, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False, rngs=rngs)\n\n      def __call__(self, x: jax.Array) -> jax.Array:\n        x = self.linear(x)\n        x = nnx.elu(x)\n        x = self.dropout(x)\n        return x\n\n    rngs = nnx.Rngs(0)\n\n    @nnx.split_rngs(splits=1, graph=False)\n    @nnx.pmap(in_axes=0, out_axes=(None, 0, 0, None), axis_size=1, graph=False)\n    def create_block(rngs):\n      block = Block(rngs)\n      graphdef, params_state, rng_state, rest_state = nnx.split(\n          block, nnx.Param, nnx.RngState, ...,\n      )\n      return graphdef, params_state, rng_state, rest_state\n\n    graphdef, params_state, rng_state, rest_state = create_block(rngs)\n\n    assert rng_state.dropout.rngs.count[0] == 0\n    assert params_state.linear.kernel.shape == (1, 3, 10)\n    assert params_state.linear.bias.shape == (1, 10)\n\n    x = jnp.ones((1, 1, 3))\n\n    @nnx.pmap(in_axes=(0, 0, None, 0), axis_size=1, graph=False)\n    def forward_block(params_state, rng_state, rest_state, x):\n      return nnx.merge(graphdef, params_state, rng_state, rest_state)(x)\n\n    y = forward_block(params_state, rng_state, rest_state, x)\n\n    assert y.shape == (1, 1, 10)\n\n    y2 = forward_block(params_state, rng_state, rest_state, x)\n\n    assert not jnp.allclose(y, y2)\n\n  def test_tree_mode_pmap_replicate(self):\n    din = 3\n    dout = 10\n\n    class Block(nnx.Module):\n      def __init__(self, rngs: nnx.Rngs):\n        self.linear = nnx.Linear(din, dout, rngs=rngs)\n        self.dropout = nnx.Dropout(0.5, deterministic=False, rngs=rngs)\n\n      def __call__(self, x: jax.Array) -> jax.Array:\n        return self.dropout(nnx.relu(self.linear(x)))\n\n    rngs = nnx.Rngs(0)\n    module = Block(rngs)\n\n    assert module.dropout.rngs.count[...] == 0\n    assert module.linear.kernel.shape == (din, dout)\n    assert module.linear.bias.shape == (dout,)\n\n    module = nnx.split_rngs(module, splits=1, graph=False)\n    graphdef, rng_state, rest_state = nnx.split(\n        module, nnx.RngState, ...,\n    )\n\n    @nnx.pmap(in_axes=(0, None, 0), out_axes=0, axis_size=1, graph=False)\n    def forward_block(rng_state, rest_state, x):\n      module = nnx.merge(graphdef, rng_state, rest_state)\n      y = module(x)\n      return y\n\n    x = jnp.ones((1, 5, din))\n\n    y = forward_block(rng_state, rest_state, x)\n\n    assert y.shape == (1, 5, dout)\n    assert module.dropout.rngs.count[0] == 1\n\n    y2 = forward_block(rng_state, rest_state, x)\n\n    assert module.dropout.rngs.count[0] == 2\n    assert not jnp.allclose(y, y2)\n\n\nclass TestPureJaxFancyScan(absltest.TestCase):\n\n  def test_carry_and_scan(self):\n    def cumsum(carry, x):\n      carry = carry + x\n      return carry, carry\n\n    final_carry, ys = pure_jax_fancy_scan(\n        cumsum, jnp.array(0.0), jnp.arange(5.0),\n        in_axes=(nnx.Carry, 0), out_axes=(nnx.Carry, 0),\n    )\n    np.testing.assert_allclose(final_carry, 10.0)\n    np.testing.assert_allclose(ys, jnp.array([0., 1., 3., 6., 10.]))\n\n  def test_carry_only_output(self):\n    def sum_fn(carry, x):\n      return carry + x\n\n    result = pure_jax_fancy_scan(\n        sum_fn, jnp.array(0.0), jnp.arange(5.0),\n        in_axes=(nnx.Carry, 0), out_axes=nnx.Carry,\n    )\n    np.testing.assert_allclose(result, 10.0)\n\n  def test_broadcast_args(self):\n    def scale_cumsum(carry, scale, x):\n      carry = carry + x * scale\n      return carry, carry\n\n    final_carry, _ = pure_jax_fancy_scan(\n        scale_cumsum, jnp.array(0.0), jnp.array(2.0), jnp.arange(5.0),\n        in_axes=(nnx.Carry, None, 0), out_axes=(nnx.Carry, 0),\n    )\n    np.testing.assert_allclose(final_carry, 20.0)\n\n  def test_pytree_carry(self):\n    def dict_scan(carry, x):\n      carry = {'a': carry['a'] + x['a'], 'b': carry['b'] + x['b']}\n      return carry, carry\n\n    xs = {'a': jnp.arange(3.0), 'b': jnp.ones(3)}\n    init = {'a': jnp.array(0.0), 'b': jnp.array(0.0)}\n    final_carry, _ = pure_jax_fancy_scan(\n        dict_scan, init, xs,\n        in_axes=(nnx.Carry, 0), out_axes=(nnx.Carry, 0),\n    )\n    np.testing.assert_allclose(final_carry['a'], 3.0)\n    np.testing.assert_allclose(final_carry['b'], 3.0)\n\n  def test_no_carry_all_scanned(self):\n    def double(x):\n      return (x * 2,)\n\n    (ys,) = pure_jax_fancy_scan(\n        double, jnp.arange(5.0),\n        in_axes=(0,), out_axes=(0,),\n    )\n    np.testing.assert_allclose(ys, jnp.arange(5.0) * 2)\n\n  def test_reverse(self):\n    def cumsum(carry, x):\n      carry = carry + x\n      return carry, carry\n\n    final_carry, _ = pure_jax_fancy_scan(\n        cumsum, jnp.array(0.0), jnp.arange(5.0),\n        in_axes=(nnx.Carry, 0), out_axes=(nnx.Carry, 0), reverse=True,\n    )\n    np.testing.assert_allclose(final_carry, 10.0)\n\n  def test_pytree_prefix_in_axes(self):\n    def fn(carry, x):\n      carry = carry + x['a'] + x['b']\n      return carry, carry\n\n    xs = {'a': jnp.arange(3.0), 'b': jnp.array(1.0)}\n    final_carry, _ = pure_jax_fancy_scan(\n        fn, jnp.array(0.0), xs,\n        in_axes=(nnx.Carry, {'a': 0, 'b': None}), out_axes=(nnx.Carry, 0),\n    )\n    np.testing.assert_allclose(final_carry, 6.0)\n\n  def test_nested_carry_rejected(self):\n    with self.assertRaises(ValueError):\n      pure_jax_fancy_scan(\n          lambda x: x,\n          {'a': jnp.array(1.0)},\n          in_axes=({'a': nnx.Carry},), out_axes=nnx.Carry,\n      )\n\n  def test_broadcast_out_axes_rejected(self):\n    with self.assertRaises(ValueError):\n      pure_jax_fancy_scan(\n          lambda c, x: (c, x),\n          jnp.array(0.0), jnp.arange(3.0),\n          in_axes=(nnx.Carry, 0), out_axes=(nnx.Carry, None),\n      )\n\n  def test_none_broadcast_input(self):\n    def fn(carry, _unused, x):\n      carry = carry + x\n      return carry, carry\n\n    final_carry, _ = pure_jax_fancy_scan(\n        fn, jnp.array(0.0), None, jnp.arange(3.0),\n        in_axes=(nnx.Carry, None, 0), out_axes=(nnx.Carry, 0),\n    )\n    np.testing.assert_allclose(final_carry, 3.0)\n\n  def test_none_nested_in_arg(self):\n    def fn(carry, x):\n      carry = carry + x['a']\n      return carry, carry\n\n    xs = {'a': jnp.arange(3.0), 'b': None}\n    final_carry, _ = pure_jax_fancy_scan(\n        fn, jnp.array(0.0), xs,\n        in_axes=(nnx.Carry, 0), out_axes=(nnx.Carry, 0),\n    )\n    np.testing.assert_allclose(final_carry, 3.0)\n\n  def test_nested_carry_in_out_axes_rejected(self):\n    with self.assertRaises(ValueError):\n      pure_jax_fancy_scan(\n          lambda c, x: (c, x),\n          jnp.array(0.0), jnp.arange(3.0),\n          in_axes=(nnx.Carry, 0), out_axes=({'a': nnx.Carry},),\n      )\n\n  def test_carry_in_in_axes_only_rejected(self):\n    with self.assertRaises(ValueError):\n      pure_jax_fancy_scan(\n          lambda c, x: (c + x,),\n          jnp.array(0.0), jnp.arange(3.0),\n          in_axes=(nnx.Carry, 0), out_axes=(0,),\n      )\n\n  def test_carry_in_out_axes_only_rejected(self):\n    with self.assertRaises(ValueError):\n      pure_jax_fancy_scan(\n          lambda x: x,\n          jnp.arange(3.0),\n          in_axes=(0,), out_axes=nnx.Carry,\n      )\n\n  def test_non_tuple_carry_only(self):\n    def f(carry):\n      return carry + 1.0\n\n    result = pure_jax_fancy_scan(\n        f, jnp.array(0.0),\n        in_axes=nnx.Carry, out_axes=nnx.Carry, length=5,\n    )\n    np.testing.assert_allclose(result, 5.0)\n\n  def test_non_tuple_scan_only(self):\n    def f(x):\n      return x * 2\n\n    ys = pure_jax_fancy_scan(\n        f, jnp.arange(5.0),\n        in_axes=0, out_axes=0,\n    )\n    np.testing.assert_allclose(ys, jnp.arange(5.0) * 2)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/nnx/variable_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing as tp\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nimport pytest\n\nfrom absl.testing import absltest, parameterized\nfrom flax import nnx\n\nA = tp.TypeVar('A')\n\nclass TestVariable(parameterized.TestCase):\n  def test_pytree(self):\n    r1 = nnx.Param(1)\n    self.assertEqual(r1.get_value(), 1)\n\n    r2 = jax.tree.map(lambda x: x + 1, r1)\n\n    self.assertEqual(r1.get_value(), 1)\n    self.assertEqual(r2.get_value(), 2)\n    self.assertIsNot(r1, r2)\n\n  def test_overloads_module(self):\n    class Linear(nnx.Module):\n      def __init__(self, din, dout, rngs: nnx.Rngs):\n        key = rngs()\n        self.w = nnx.Param(jax.random.normal(key, (din, dout)))\n        self.b = nnx.Param(jax.numpy.zeros((dout,)))\n\n      def __call__(self, x: jax.Array):\n        return x @ self.w + self.b\n\n    linear = Linear(3, 4, nnx.Rngs(0))\n    x = jax.numpy.ones((3,))\n    y = linear(x)\n    self.assertEqual(y.shape, (4,))\n\n  def test_jax_array(self):\n    class Linear(nnx.Module):\n      def __init__(self, din, dout, rngs: nnx.Rngs):\n        key = rngs()\n        self.w = nnx.Param(jax.random.normal(key, (din, dout)))\n        self.b = nnx.Param(jax.numpy.zeros((dout,)))\n\n      def __call__(self, x: jax.Array):\n        return jnp.dot(x, self.w) + self.b  # type: ignore[arg-type]\n\n    linear = Linear(3, 4, nnx.Rngs(0))\n    x = jax.numpy.ones((3,))\n    y = linear(x)\n    self.assertEqual(y.shape, (4,))\n\n  def test_proxy_access(self):\n    v = nnx.Param(jnp.ones((2, 3)))\n    t = v.T\n\n    self.assertEqual(t.shape, (3, 2))\n\n  def test_proxy_call(self):\n    class Callable(tp.NamedTuple):\n      value: int\n\n      def __call__(self, x):\n        return self.value * x\n\n    v = nnx.Param(Callable(2))\n    result = v(3)\n\n    self.assertEqual(result, 6)\n\n  def test_binary_ops(self):\n    v1 = nnx.Param(jnp.array(2))\n    v2 = nnx.Param(jnp.array(3))\n\n    result = v1 + v2\n\n    self.assertEqual(result, 5)\n    self.assertFalse(v1 == v2)\n\n    v1[...] += v2\n\n    self.assertEqual(v1[...], 5)\n\n  @parameterized.product(\n    v1=[np.array([1, 2]), np.array(2), 3],\n    v2=[np.array([1, 2]), np.array(2), 3],\n  )\n  def test_eq_op(self, v1, v2):\n    p1 = nnx.Param(jnp.asarray(v1) if isinstance(v1, np.ndarray) else v1)\n    p2 = nnx.Param(jnp.asarray(v2) if isinstance(v2, np.ndarray) else v2)\n    if isinstance(v1, np.ndarray) or isinstance(v2, np.ndarray):\n      self.assertEqual((p1 == p2).all(), (v1 == v2).all())\n    else:\n      self.assertEqual(p1 == p2, v1 == v2)\n\n  def test_mutable_array_context(self):\n    initial_mode = nnx.var_defaults().hijax\n    with nnx.var_defaults(hijax=False):\n      v = nnx.Variable(jnp.array(1.0))\n      self.assertEqual(nnx.var_defaults().hijax, False)\n      self.assertNotIsInstance(v[...], jax.Ref)\n\n      with nnx.var_defaults(hijax=True):\n        v = nnx.Variable(jnp.array(1.0))\n        self.assertEqual(nnx.var_defaults().hijax, True)\n        self.assertIsInstance(v[...], jax.Array)\n\n      v = nnx.Variable(jnp.array(2.0))\n      self.assertIsInstance(v[...], jax.Array)\n      self.assertEqual(nnx.var_defaults().hijax, False)\n\n      nnx.var_defaults(hijax=True)\n\n      v = nnx.Variable(jnp.array(0.0))\n      self.assertEqual(nnx.var_defaults().hijax, True)\n      self.assertIsInstance(v[...], jax.Array)\n\n    v = nnx.Variable(jnp.array(1.0))\n    self.assertEqual(nnx.var_defaults().hijax, initial_mode)\n    self.assertIsInstance(v[...], jax.Array)\n\n  def test_get_set_metadata(self):\n    v = nnx.Variable(jnp.array(1.0))\n    self.assertEqual(\n        v.get_metadata(),\n        {\n            'hijax': False,\n            'ref': False,\n            'eager_sharding': True,\n        },\n    )\n    v.set_metadata(a=1, b=2)\n    self.assertEqual(v.get_metadata('a'), 1)\n    self.assertEqual(v.get_metadata('b'), 2)\n    v.set_metadata({\n        'b': 3,\n        'c': 4,\n        'hijax': False,\n        'ref': False,\n        'eager_sharding': True,\n    })\n    self.assertEqual(\n        v.get_metadata(),\n        {\n            'b': 3,\n            'c': 4,\n            'hijax': False,\n            'ref': False,\n            'eager_sharding': True,\n        },\n    )\n    self.assertEqual(v.get_metadata('b'), 3)\n    self.assertEqual(v.get_metadata('c'), 4)\n    c = v.get_metadata('c')\n    self.assertEqual(c, 4)\n    x = v.get_metadata('x', default=10)\n    self.assertEqual(x, 10)\n\n  def test_set_module_metadata(self):\n    class Module(nnx.Module):\n      def __init__(self):\n        self.v = nnx.Variable(jnp.array(0.0))\n        self.p = nnx.Param(jnp.array(1.0))\n\n    m = Module()\n    self.assertNotIn('foo', m.v.get_metadata())\n    self.assertNotIn('foo', m.p.get_metadata())\n    nnx.set_metadata(m, foo='bar')\n    # Check that foo was added but the default metadata is still there\n    v_metadata = m.v.get_metadata()\n    p_metadata = m.p.get_metadata()\n    self.assertEqual(v_metadata['foo'], 'bar')\n    self.assertEqual(p_metadata['foo'], 'bar')\n    # Check that default metadata is preserved\n    self.assertIn('hijax', v_metadata)\n    self.assertIn('ref', v_metadata)\n\n    self.assertNotIn('differentiable', m.v.get_metadata())\n    self.assertNotIn('differentiable', m.p.get_metadata())\n    nnx.set_metadata(m, differentiable=False, only=nnx.Param)\n    # Check that v still has foo but not differentiable\n    v_metadata = m.v.get_metadata()\n    self.assertEqual(v_metadata['foo'], 'bar')\n    self.assertNotIn('differentiable', v_metadata)\n    # Check that p has both foo and differentiable\n    p_metadata = m.p.get_metadata()\n    self.assertEqual(p_metadata['foo'], 'bar')\n    self.assertEqual(p_metadata['differentiable'], False)\n\n  @pytest.mark.skip(reason=\"Ref doesn't support broadcasting yet\")\n  def test_broadcasting(self):\n    v = nnx.Param(jnp.array([1.0, 2.0, 3.0]))\n    x = v[None]\n    self.assertEqual(x.shape, (1, 3))\n\n  def test_set_metadata_out_sharding(self):\n    v = nnx.Variable(jnp.array(1.0))\n\n    v.set_metadata(out_sharding=jax.sharding.PartitionSpec(None))\n    self.assertEqual(\n      v.get_metadata('out_sharding'), jax.sharding.PartitionSpec(None)\n    )\n\n    v.set_metadata('out_sharding', jax.sharding.PartitionSpec('x'))\n    self.assertEqual(\n      v.get_metadata('out_sharding'), jax.sharding.PartitionSpec('x')\n    )\n\n    v.set_metadata({\n      'out_sharding': jax.sharding.PartitionSpec('y'),\n      'hijax': False,\n      'ref': False,\n      'eager_sharding': True,\n    })\n    self.assertEqual(\n      v.get_metadata('out_sharding'), jax.sharding.PartitionSpec('y')\n    )\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/pickle_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.errors.\"\"\"\n\nfrom absl.testing import absltest\nfrom flax.errors import FlaxError, ScopeVariableNotFoundError\nimport pickle\n\nclass ErrorrsTest(absltest.TestCase):\n  def test_exception_can_be_pickled(self):\n    # tests the new __reduce__ method fixes bug reported in issue #4000\n    ex = ScopeVariableNotFoundError('varname', 'collection', 'scope')\n    pickled_ex = pickle.dumps(ex)\n    unpicked_ex = pickle.loads(pickled_ex)\n    self.assertIsInstance(unpicked_ex, FlaxError)\n    self.assertIn('varname', str(unpicked_ex))\n    self.assertIn('#flax.errors.ScopeVariableNotFoundError', str(unpicked_ex))\n    self.assertNotIn('#flax.errors.FlaxError', str(unpicked_ex))\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/run_all_tests.sh",
    "content": "#!/bin/bash\n\n# export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python\nPYTEST_OPTS=\nRUN_DOCTEST=false\nRUN_MYPY=false\nRUN_PYTEST=false\nRUN_PYTYPE=false\nGH_VENV=false\n\nfor flag in \"$@\"; do\ncase $flag in\n  --with-cov)\n  PYTEST_OPTS+=\"--cov=flax --cov-report=xml --cov-report=term --cov-config=pyproject.toml\"\n  ;;\n  --help)\n  echo \"Usage:\"\n  echo \"  --with-cov: Also generate pytest coverage.\"\n  exit\n  ;;\n  --only-doctest)\n  RUN_DOCTEST=true\n  ;;\n  --only-pytest)\n  RUN_PYTEST=true\n  ;;\n  --only-pytype)\n  RUN_PYTYPE=true\n  ;;\n  --only-mypy)\n  RUN_MYPY=true\n  ;;\n  --use-venv)\n  GH_VENV=true\n  ;;\n  *)\n  echo \"Unknown flag: $flag\"\n  exit 1\n  ;;\nesac\ndone\n\n# if neither --only-doctest, --only-pytest, --only-pytype, --only-mypy is set, run all tests\nif ! $RUN_DOCTEST && ! $RUN_PYTEST && ! $RUN_PYTYPE && ! $RUN_MYPY; then\n  RUN_DOCTEST=true\n  RUN_PYTEST=true\n  RUN_PYTYPE=true\n  RUN_MYPY=true\nfi\n\n# Activate cached virtual env for github CI\nif $GH_VENV; then\n  source $(dirname \"$0\")/../.venv/bin/activate\nfi\n\necho \"====== test config =======\"\necho \"PYTEST_OPTS: $PYTEST_OPTS\"\necho \"RUN_DOCTEST: $RUN_DOCTEST\"\necho \"RUN_PYTEST: $RUN_PYTEST\"\necho \"RUN_MYPY: $RUN_MYPY\"\necho \"RUN_PYTYPE: $RUN_PYTYPE\"\necho \"GH_VENV: $GH_VENV\"\necho \"WHICH PYTHON: $(which python)\"\necho \"jax: $(python -c 'import jax; print(jax.__version__)')\"\necho \"flax: $(python -c 'import flax; print(flax.__version__)')\"\necho \"==========================\"\necho \"\"\n\nsh $(dirname \"$0\")/download_dataset_metadata.sh || exit\n\n# Instead of using set -e, we have a manual error trap that\n# exits for any error code != 5 since pytest returns error code 5\n# for no found tests. (We may force minimal test coverage in examples\n# in the future!)\ntrap handle_errors ERR\nhandle_errors () {\n    ret=\"$?\"\n    if [[ \"$ret\" == 5 ]]; then\n      echo \"error code $ret == no tests found in $egd\"\n    else\n      echo \"error code $ret\"\n      exit 1\n    fi\n}\n\n# Run embedded tests inside docs\nif $RUN_DOCTEST; then\n  echo \"=== RUNNING DOCTESTS ===\"\n  # test doctest\n  sphinx-build -M doctest docs docs/_build -T\n  sphinx-build -M doctest docs_nnx docs_nnx/_build -T\n  # test build html\n  sphinx-build -M html docs docs/_build -T\n  sphinx-build -M html docs_nnx docs_nnx/_build -T\n  # test docstrings\n  pytest -n auto flax \\\n    --doctest-modules \\\n    --suppress-no-test-exit-code \\\n    --ignore=flax/nnx/examples\nfi\n\n# check that flax is running on editable mode\n# (i.e. no notebook installed flax from pypi)\necho \"=== CHECKING FLAX IS EDITABLE ===\"\nassert_error=\"flax is not running on editable mode.\"\n(cd docs; python -c \"import flax; assert 'site-packages' not in flax.__file__, \\\"$assert_error\\\"\")\n\n# env vars must be set after doctest\nexport JAX_NUMPY_RANK_PROMOTION=raise\nexport FLAX_PROFILE=1\n\nif $RUN_PYTEST; then\n  echo \"=== RUNNING PYTESTS ===\"\n  # Run battery of core FLAX API tests.\n  echo \"XLA_FLAGS='--xla_force_host_platform_device_count=4' pytest -n auto tests $PYTEST_OPTS\"\n  XLA_FLAGS='--xla_force_host_platform_device_count=4' pytest -n auto tests $PYTEST_OPTS\n  # Run nnx tests\n  pytest -n auto docs/_ext/codediff_test.py $PYTEST_OPTS $PYTEST_IGNORE\n  pytest -n auto docs_nnx/_ext/codediff_test.py $PYTEST_OPTS $PYTEST_IGNORE\n\n  # Per-example tests.\n  #\n  # we apply pytest within each example to avoid pytest's annoying test-filename collision.\n  # In pytest foo/bar/baz_test.py and baz/bleep/baz_test.py will collide and error out when\n  # /foo/bar and /baz/bleep aren't set up as packages.\n  for egd in $(find examples -maxdepth 1 -mindepth 1 -type d); do\n    # skip if folder starts with \"_\"\n    if [[ $egd == *\"_\"* ]]; then\n      continue\n    fi\n    # skiping examples until tfds issue is resolved\n    # pytest $egd\n  done\nfi\n\nif $RUN_PYTYPE; then\n  echo \"=== RUNNING PYTYPE ===\"\n  # Validate types in examples.\n  for egd in $(find examples -maxdepth 1 -mindepth 1 -type d); do\n    # skip if folder starts with \"_\" or is \"nnx_toy_examples\"\n    if [[ $egd == *\"_\"* ]] || [[ $egd == *\"nnx_toy_examples\"* ]]; then\n      continue\n    fi\n    # use cd to make sure pytype cache lives in example dir and doesn't name clash\n    # use *.py to avoid importing configs as a top-level import which leads to import errors\n    # because config files use relative imports (e.g. from config import ...).\n    (cd $egd ; pytype \"*.py\" --jobs auto --config ../../pyproject.toml)\n  done\n  # Validate types in library code.\n  pytype --jobs auto --config pyproject.toml flax/\nfi\n\nif $RUN_MYPY; then\n  echo \"=== RUNNING MYPY ===\"\n  # Validate types in library code.\n  mypy --config pyproject.toml flax/ --show-error-codes\nfi\n\n# Return error code 0 if no real failures happened.\necho \"finished all tests.\"\n"
  },
  {
    "path": "tests/serialization_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.struct and flax.serialization.\"\"\"\n\nimport collections\nimport platform\nfrom typing import Any, NamedTuple\n\nimport jax\nimport jax.numpy as jnp\nimport msgpack\nimport numpy as np\nimport optax\nimport pytest\nfrom absl.testing import absltest, parameterized\nfrom jax import random\nfrom jax.tree_util import Partial\n\nfrom flax import linen as nn\nfrom flax import serialization, struct\nfrom flax.core import freeze\nfrom flax.training import train_state\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\n@struct.dataclass\nclass Point:\n  x: float\n  y: float\n  meta: Any = struct.field(pytree_node=False)\n\n\n@struct.dataclass\nclass Box:\n  value: int\n\n\ndef to_state_dict(box: Box):\n  return box.value\n\n\ndef from_state_dict(box: Box, state: Any):\n  return box.replace(value=state)\n\n\nserialization.register_serialization_state(\n  Box, to_state_dict, from_state_dict, override=True\n)\n\n\nclass OriginalTuple(NamedTuple):\n  value: Any\n\n\nclass WrongTuple(NamedTuple):\n  wrong_field: Any\n\n\nclass OriginalModule(nn.Module):\n  @nn.compact\n  def __call__(self, x):\n    x = nn.Dense(10)(x)\n    return x\n\n\nclass WrongModule(nn.Module):\n  @nn.compact\n  def __call__(self, x):\n    x = nn.Dense(10)(x)\n    x = nn.Dense(10)(x)\n    return x\n\n\nclass SerializationTest(parameterized.TestCase):\n  def test_dataclass_serialization(self):\n    p = Point(x=1, y=2, meta={'dummy': True})\n    state_dict = serialization.to_state_dict(p)\n    self.assertEqual(\n      state_dict,\n      {\n        'x': 1,\n        'y': 2,\n      },\n    )\n    restored_p = serialization.from_state_dict(p, {'x': 3, 'y': 4})\n    expected_p = Point(x=3, y=4, meta={'dummy': True})\n    self.assertEqual(restored_p, expected_p)\n\n    with self.assertRaises(ValueError):  # invalid field\n      serialization.from_state_dict(p, {'z': 3})\n    with self.assertRaises(ValueError):  # missing field\n      serialization.from_state_dict(p, {'x': 3})\n\n  def test_pass_through_serialization(self):\n    p = Box(value=123)\n    state_dict = serialization.to_state_dict(p)\n    self.assertEqual(\n      state_dict,\n      123,\n    )\n    restored_box = serialization.from_state_dict(p, 123)\n    expected_box = Box(value=123)\n    self.assertEqual(restored_box, expected_box)\n\n  def test_model_serialization(self):\n    rng = random.key(0)\n    module = nn.Dense(features=1, kernel_init=nn.initializers.ones_init())\n    x = jnp.ones((1, 1), jnp.float32)\n    initial_params = module.init(rng, x)\n    state = serialization.to_state_dict(initial_params)\n    self.assertEqual(\n      state,\n      {\n        'params': {\n          'kernel': np.ones((1, 1)),\n          'bias': np.zeros((1,)),\n        }\n      },\n    )\n    state = {\n      'params': {\n        'kernel': np.zeros((1, 1)),\n        'bias': np.zeros((1,)),\n      }\n    }\n    restored_model = serialization.from_state_dict(initial_params, state)\n    self.assertEqual(restored_model, freeze(state))\n\n  def test_partial_serialization(self):\n    add_one = Partial(jnp.add, 1)\n    state = serialization.to_state_dict(add_one)\n    self.assertEqual(state, {'args': {'0': 1}, 'keywords': {}})\n    restored_add_one = serialization.from_state_dict(add_one, state)\n    self.assertEqual(add_one.args, restored_add_one.args)\n\n  def test_optimizer_serialization(self):\n    rng = random.key(0)\n    module = nn.Dense(features=1, kernel_init=nn.initializers.ones_init())\n    x = jnp.ones((1, 1), jnp.float32)\n    initial_params = module.init(rng, x)\n    tx = optax.sgd(0.1, momentum=0.1)\n    tx_state = tx.init(initial_params)\n    state = serialization.to_state_dict(tx_state)\n    expected_state = {\n      '0': {\n        'trace': {\n          'params': {\n            'bias': np.array([0.0], dtype=jnp.float32),\n            'kernel': np.array([[0.0]], dtype=jnp.float32),\n          }\n        }\n      },\n      '1': {},\n    }\n    self.assertEqual(state, expected_state)\n    state = jax.tree_util.tree_map(lambda x: x + 1, expected_state)\n    restored_tx_state = serialization.from_state_dict(tx_state, state)\n    tx_state_plus1 = jax.tree_util.tree_map(lambda x: x + 1, tx_state)\n    self.assertEqual(restored_tx_state, tx_state_plus1)\n\n  def test_collection_serialization(self):\n    @struct.dataclass\n    class DummyDataClass:\n      x: float\n\n      @classmethod\n      def initializer(cls, shape):\n        del shape\n        return cls(x=0.0)\n\n    class StatefulModule(nn.Module):\n      @nn.compact\n      def __call__(self):\n        state = self.variable('state', 'dummy', DummyDataClass.initializer, ())\n        state.value = state.value.replace(x=state.value.x + 1.0)\n\n    initial_variables = StatefulModule().init(random.key(0))\n    _, variables = StatefulModule().apply(initial_variables, mutable=['state'])\n    serialized_state_dict = serialization.to_state_dict(variables)\n    self.assertEqual(serialized_state_dict, {'state': {'dummy': {'x': 2.0}}})\n    deserialized_state = serialization.from_state_dict(\n      variables, serialized_state_dict\n    )\n    self.assertEqual(variables, deserialized_state)\n\n  @parameterized.parameters([\n      'byte',\n      'b',\n      'ubyte',\n      'short',\n      'h',\n      'ushort',\n      'i',\n      'uint',\n      'intp',\n      'p',\n      'uintp',\n      'long',\n      'l',\n      'longlong',\n      'q',\n      'ulonglong',\n      'half',\n      'e',\n      'f',\n      'double',\n      'd',\n      'longdouble',\n      'g',\n      'cdouble',\n      'clongdouble',\n      'm',\n      'b1',\n      'int64',\n      'i8',\n      'uint64',\n      'u8',\n      'float16',\n      'f2',\n      'float32',\n      'f4',\n      'float64',\n      'f8',\n      'float128',\n      'f16',\n      'complex64',\n      'c8',\n      'complex128',\n      'c16',\n      'complex256',\n      'c32',\n      'm8',\n      'int32',\n      'i4',\n      'uint32',\n      'u4',\n      'int16',\n      'i2',\n      'uint16',\n      'u2',\n      'int8',\n      'i1',\n      'uint8',\n      'u1',\n      'single',\n      'csingle',\n      'intc',\n      'uintc',\n      'int',\n      'float',\n      'complex',\n      'bool',\n  ])\n  def test_numpy_serialization(self, dtype):\n    np.random.seed(0)\n    if (\n      (dtype in {'float128', 'f16', 'complex256', 'c32'})\n      and (platform.system() == 'Darwin')\n      and (platform.machine() == 'arm64')\n    ):\n      pytest.skip(\n        f'Mac M1 does not support dtype {dtype}'\n      )  # skip testing these dtypes if user is on Mac M1\n\n    v = np.random.uniform(-100, 100, size=()).astype(dtype)[()]\n    restored_v = serialization.msgpack_restore(\n      serialization.msgpack_serialize(v)\n    )\n    self.assertEqual(restored_v.dtype, v.dtype)\n    np.testing.assert_array_equal(restored_v, v)\n\n    for shape in [(), (5,), (10, 10), (1, 20, 30, 1)]:\n      arr = np.random.uniform(-100, 100, size=shape).astype(dtype)\n      restored_arr = serialization.msgpack_restore(\n        serialization.msgpack_serialize(arr)\n      )\n      self.assertEqual(restored_arr.dtype, arr.dtype)\n      np.testing.assert_array_equal(restored_arr, arr)\n\n  def test_jax_numpy_serialization(self):\n    jax_dtypes = [\n      jnp.bool_,\n      jnp.uint8,\n      jnp.uint16,\n      jnp.uint32,\n      jnp.int8,\n      jnp.int16,\n      jnp.int32,\n      jnp.bfloat16,\n      jnp.float16,\n      jnp.float32,\n      jnp.complex64,\n    ]\n    for dtype in jax_dtypes:\n      v = jnp.array(np.random.uniform(-100, 100, size=())).astype(dtype)[()]\n      restored_v = serialization.msgpack_restore(\n        serialization.msgpack_serialize(v)\n      )\n      self.assertEqual(restored_v.dtype, v.dtype)\n      np.testing.assert_array_equal(restored_v, v)\n\n      for shape in [(), (5,), (10, 10), (1, 20, 30, 1)]:\n        arr = jnp.array(np.random.uniform(-100, 100, size=shape)).astype(dtype)\n        restored_arr = serialization.msgpack_restore(\n          serialization.msgpack_serialize(arr)\n        )\n        self.assertEqual(restored_arr.dtype, arr.dtype)\n        np.testing.assert_array_equal(restored_arr, arr)\n\n  def test_complex_serialization(self):\n    for x in [1j, 1 + 2j]:\n      restored_x = serialization.msgpack_restore(\n        serialization.msgpack_serialize(x)\n      )\n      self.assertEqual(x, restored_x)\n\n  def test_restore_chunked(self):\n    old_chunksize = serialization.MAX_CHUNK_SIZE\n    serialization.MAX_CHUNK_SIZE = 91 * 8\n    try:\n      tmp = np.random.uniform(-100, 100, size=(21, 37))\n      serialized = serialization.to_bytes(tmp)\n      restored = serialization.msgpack_restore(serialized)\n    finally:\n      serialization.MAX_CHUNK_SIZE = old_chunksize\n\n    np.testing.assert_array_equal(restored, tmp)\n\n  def test_restore_unchunked(self):\n    \"\"\"Check if mgspack_restore works for unchunked inputs.\"\"\"\n\n    def msgpack_serialize_legacy(pytree):\n      \"\"\"Old implementation that was not chunking.\"\"\"\n      return msgpack.packb(\n        pytree, default=serialization._msgpack_ext_pack, strict_types=True\n      )\n\n    tmp = np.random.uniform(-100, 100, size=(21, 37))\n    serialized = msgpack_serialize_legacy(tmp)\n    old_chunksize = serialization.MAX_CHUNK_SIZE\n    serialization.MAX_CHUNK_SIZE = 91 * 8\n    try:\n      restored = serialization.msgpack_restore(serialized)\n    finally:\n      serialization.MAX_CHUNK_SIZE = old_chunksize\n\n    np.testing.assert_array_equal(restored, tmp)\n\n  def test_namedtuple_serialization(self):\n    foo_class = collections.namedtuple('Foo', 'a b c')\n    x1 = foo_class(a=1, b=2, c=3)\n    x1_serialized = serialization.to_bytes(x1)\n    x2 = foo_class(a=0, b=0, c=0)\n    restored_x1 = serialization.from_bytes(x2, x1_serialized)\n    self.assertEqual(type(x1), type(restored_x1))\n    self.assertEqual(x1, restored_x1)\n\n  def test_namedtuple_restore_legacy(self):\n    foo_class = collections.namedtuple('Foo', 'a b c')\n    x1 = foo_class(a=1, b=2, c=3)\n    legacy_encoding = {\n      'name': 'Foo',\n      'fields': {'0': 'a', '1': 'b', '2': 'c'},\n      'values': {'0': 1, '1': 2, '2': 3},\n    }\n    x2 = foo_class(a=0, b=0, c=0)\n    restored_x1 = serialization.from_state_dict(x2, legacy_encoding)\n    self.assertEqual(type(x1), type(restored_x1))\n    self.assertEqual(x1, restored_x1)\n\n  def test_model_serialization_to_bytes(self):\n    rng = random.key(0)\n    module = nn.Dense(features=1, kernel_init=nn.initializers.ones_init())\n    initial_params = module.init(rng, jnp.ones((1, 1), jnp.float32))\n    serialized_bytes = serialization.to_bytes(initial_params)\n    restored_params = serialization.from_bytes(initial_params, serialized_bytes)\n    self.assertEqual(restored_params, initial_params)\n\n  def test_optimizer_serialization_to_bytes(self):\n    rng = random.key(0)\n    module = nn.Dense(features=1, kernel_init=nn.initializers.ones_init())\n    initial_params = module.init(rng, jnp.ones((1, 1), jnp.float32))\n    # model = nn.Model(module, initial_params)\n    tx = optax.sgd(0.1, momentum=0.1)\n    tx_state = tx.init(initial_params)\n    serialized_bytes = serialization.to_bytes(tx_state)\n    restored_tx_state = serialization.from_bytes(tx_state, serialized_bytes)\n    self.assertEqual(restored_tx_state, tx_state)\n\n  def test_serialization_chunking(self):\n    old_chunksize = serialization.MAX_CHUNK_SIZE\n    serialization.MAX_CHUNK_SIZE = 91 * 8\n    try:\n      tmp = {'a': np.ones((10, 10))}\n      tmp = serialization._chunk_array_leaves_in_place(tmp)\n    finally:\n      serialization.MAX_CHUNK_SIZE = old_chunksize\n    test = jax.tree_util.tree_map(jnp.shape, tmp)\n    ref = {\n      'a': {\n        '__msgpack_chunked_array__': (),\n        'chunks': {'0': (91,), '1': (9,)},\n        'shape': {'0': (), '1': ()},\n      }\n    }\n    self.assertEqual(test, ref)\n\n  def test_serialization_chunking2(self):\n    old_chunksize = serialization.MAX_CHUNK_SIZE\n    serialization.MAX_CHUNK_SIZE = 91 * 8\n    try:\n      tmp = {'a': np.ones((10, 10))}\n      tmpbytes = serialization.to_bytes(tmp)\n      newtmp = serialization.from_bytes(tmp, tmpbytes)\n    finally:\n      serialization.MAX_CHUNK_SIZE = old_chunksize\n    jax.tree_util.tree_map(np.testing.assert_array_equal, tmp, newtmp)\n\n  def test_serialization_chunking3(self):\n    old_chunksize = serialization.MAX_CHUNK_SIZE\n    serialization.MAX_CHUNK_SIZE = 91 * 8\n    try:\n      tmp = {'a': np.ones((10, 10))}\n      tmpbytes = serialization.msgpack_serialize(tmp)\n      newtmp = serialization.msgpack_restore(tmpbytes)\n    finally:\n      serialization.MAX_CHUNK_SIZE = old_chunksize\n\n    jax.tree_util.tree_map(np.testing.assert_array_equal, tmp, newtmp)\n\n  @parameterized.parameters(\n    {\n      'target': [[[1, 2, 3], [4, 5]]],\n      'wrong_target': [[[1, 2, 3], [4]]],\n      'msg': (\n        'The size of the list and the state dict do not match,'\n        ' got 1 and 2 at path ./0/1'\n      ),\n    },\n    {\n      'target': (((1, 2, 3), (4, 5)),),\n      'wrong_target': (((1, 2, 3), (4,)),),\n      'msg': (\n        'The size of the list and the state dict do not match,'\n        ' got 1 and 2 at path ./0/1'\n      ),\n    },\n    {\n      'target': (((1, 2, 3), (OriginalTuple([4, 5]), 6)),),\n      'wrong_target': (((1, 2, 3), (WrongTuple([4, 5]), 6)),),\n      'msg': (\n        'The field names of the state dict and the named tuple do '\n        \"not match, got {'value'} and {'wrong_field'} at path ./0/1/0\"\n      ),\n    },\n    {\n      'target': {'a': {'b': {'c': [1, 2, 3], 'd': [4, 5]}}},\n      'wrong_target': {'a': {'b': {'c': [1, 2, 3], 'd': [4]}}},\n      'msg': (\n        'The size of the list and the state dict do not match,'\n        ' got 1 and 2 at path ./a/b/d'\n      ),\n    },\n    {\n      'target': {'a': {'b': {'c': [1, 2, 3], 'd': [4, 5]}}},\n      'wrong_target': {'a': {'b': {'c': [1, 2, 3], 'e': [4, 5]}}},\n      'msg': (\n        'The target dict keys and state dict keys do not match, target'\n        \" dict contains keys {'e'} which are not present in state dict at\"\n        ' path ./a/b'\n      ),\n    },\n    {\n      'target': 'original_params',\n      'wrong_target': 'wrong_params',\n      'msg': (\n        'The target dict keys and state dict keys do not match, target'\n        \" dict contains keys {'Dense_1'} which are not present in state\"\n        ' dict at path ./params'\n      ),\n    },\n    {\n      'target': 'original_train_state',\n      'wrong_target': 'wrong_train_state',\n      'msg': (\n        'The target dict keys and state dict keys do not match, target'\n        \" dict contains keys {'Dense_1'} which are not present in state\"\n        ' dict at path ./params/params'\n      ),\n    },\n  )\n  def test_serialization_errors(self, target, wrong_target, msg):\n    if target == 'original_params':\n      x = jnp.ones((1, 28, 28, 1))\n      rng = jax.random.key(1)\n      original_module = OriginalModule()\n      target = original_module.init(rng, x)\n      wrong_module = WrongModule()\n      wrong_target = wrong_module.init(rng, x)\n\n    elif target == 'original_train_state':\n      x = jnp.ones((1, 28, 28, 1))\n      rng = jax.random.key(1)\n      original_module = OriginalModule()\n      original_params = original_module.init(rng, x)\n      wrong_module = WrongModule()\n      wrong_params = wrong_module.init(rng, x)\n\n      tx = optax.sgd(learning_rate=0.1, momentum=0.9)\n      target = train_state.TrainState.create(\n        apply_fn=original_module.apply, params=original_params, tx=tx\n      )\n      wrong_target = train_state.TrainState.create(\n        apply_fn=wrong_module.apply, params=wrong_params, tx=tx\n      )\n\n    encoded_bytes = serialization.to_bytes(target)\n    with self.assertRaisesWithLiteralMatch(ValueError, msg):\n      serialization.from_bytes(wrong_target, encoded_bytes)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/struct_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.struct.\"\"\"\n\nimport dataclasses\nfrom typing import Any, Generic, TypeVar\n\nimport jax\nfrom absl.testing import absltest, parameterized\nfrom jax._src.tree_util import prefix_errors\n\nfrom flax import struct\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\n@struct.dataclass\nclass Point:\n  x: float\n  y: float\n  meta: Any = struct.field(pytree_node=False)\n\n\nclass StructTest(parameterized.TestCase):\n  def test_no_extra_fields(self):\n    p = Point(x=1, y=2, meta={})\n    with self.assertRaises(dataclasses.FrozenInstanceError):\n      p.new_field = 1\n\n  def test_mutation(self):\n    p = Point(x=1, y=2, meta={})\n    new_p = p.replace(x=3)\n    self.assertEqual(new_p, Point(x=3, y=2, meta={}))\n    with self.assertRaises(dataclasses.FrozenInstanceError):\n      p.y = 3\n\n  def test_slots(self):\n\n    @struct.dataclass(frozen=False, slots=True)\n    class SlotsPoint:\n      x: float\n      y: float\n    p = SlotsPoint(x=1., y=2.)\n    p.x = 3.  # can assign to existing fields\n    self.assertEqual(p, SlotsPoint(x=3., y=2.))\n    with self.assertRaises(AttributeError):\n      p.z = 0.  # can't create new fields by accident.\n\n  def test_pytree_nodes(self):\n    p = Point(x=1, y=2, meta={'abc': True})\n    leaves = jax.tree_util.tree_leaves(p)\n    self.assertEqual(leaves, [1, 2])\n    new_p = jax.tree_util.tree_map(lambda x: x + x, p)\n    self.assertEqual(new_p, Point(x=2, y=4, meta={'abc': True}))\n\n  def test_keypath_error(self):\n    # TODO(mattjj): avoid using internal prefix_errors by testing vmap error msg\n    (e,) = prefix_errors(Point(1.0, [2.0], meta={}), Point(1.0, 2.0, meta={}))\n    with self.assertRaisesRegex(ValueError, r'in_axes\\.y'):\n      raise e('in_axes')\n\n  def test_double_wrap_no_op(self):\n    class A:\n      a: int\n\n    self.assertFalse(hasattr(A, '_flax_dataclass'))\n\n    A = struct.dataclass(A)\n    self.assertTrue(hasattr(A, '_flax_dataclass'))\n\n    A = struct.dataclass(A)  # no-op\n    self.assertTrue(hasattr(A, '_flax_dataclass'))\n\n  def test_wrap_pytree_node_no_error(self):\n    @struct.dataclass\n    class A(struct.PyTreeNode):\n      a: int\n\n  @parameterized.parameters(\n      {'mode': 'dataclass'},\n      {'mode': 'pytreenode'},\n  )\n  def test_kw_only(self, mode):\n    if mode == 'dataclass':\n      @struct.dataclass\n      class A:\n        a: int = 1\n\n      @struct.dataclass(kw_only=True)\n      class B(A):\n        b: int\n    elif mode == 'pytreenode':\n      class A(struct.PyTreeNode):\n        a: int = 1\n\n      class B(A, struct.PyTreeNode, kw_only=True):\n        b: int\n\n    obj = B(b=2)\n    self.assertEqual(obj.a, 1)\n    self.assertEqual(obj.b, 2)\n\n    with self.assertRaisesRegex(TypeError, \"non-default argument 'b' follows default argument\"):\n      if mode == 'dataclass':\n        @struct.dataclass\n        class B(A):\n          b: int\n      elif mode == 'pytreenode':\n        class B(A, struct.PyTreeNode):\n          b: int\n\n  def test_metadata_pass_through(self):\n    @struct.dataclass\n    class A:\n      foo: int = struct.field(default=9, metadata={'baz': 9})\n    assert A.__dataclass_fields__['foo'].metadata == {'baz': 9, 'pytree_node': True}\n\n  @parameterized.parameters(\n      {'mode': 'dataclass'},\n      {'mode': 'pytreenode'},\n  )\n  def test_mutable(self, mode):\n    if mode == 'dataclass':\n      @struct.dataclass\n      class A:\n        a: int = 1\n\n      @struct.dataclass(frozen=False)\n      class B:\n        b: int = 1\n    elif mode == 'pytreenode':\n      class A(struct.PyTreeNode):\n        a: int = 1\n\n      class B(struct.PyTreeNode, frozen=False):\n        b: int = 1\n\n    obj = A()\n    with self.assertRaisesRegex(dataclasses.FrozenInstanceError, \"cannot assign to field 'a'\"):\n      obj.a = 2\n\n    obj = B()\n    obj.b = 2\n    self.assertEqual(obj.b, 2)\n\n\n  def test_generic_pytreenode_base_order(self):\n    # PyTreeNode + Generic should work regardless of base order (#5233).\n    T = TypeVar('T')\n    U = TypeVar('U')\n\n    # Generic after PyTreeNode.\n    class A(struct.PyTreeNode, Generic[T, U]):\n      x: int = 0\n\n    self.assertEqual(A.__parameters__, (T, U))\n    A[int, int]  # should not raise\n\n    # Generic before PyTreeNode.\n    class B(Generic[T, U], struct.PyTreeNode):\n      x: int = 0\n\n    self.assertEqual(B.__parameters__, (T, U))\n    B[int, int]  # should not raise\n\n    # Subclassing a parameterized generic PyTreeNode.\n    class Base(struct.PyTreeNode, Generic[T, U]):\n      x: int = 0\n\n    class Sub(Base[int, str]):\n      y: int = 1\n\n    obj = Sub(x=1, y=2)\n    self.assertEqual(obj.x, 1)\n    self.assertEqual(obj.y, 2)\n    leaves = jax.tree_util.tree_leaves(obj)\n    self.assertEqual(leaves, [1, 2])\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/tensorboard_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.metrics.tensorboard.\"\"\"\nimport itertools\nimport pathlib\nimport tempfile\n\nimport numpy as np\nimport tensorflow as tf\nfrom absl.testing import absltest\nfrom tensorboard.backend.event_processing import (\n  directory_watcher,\n  event_file_loader,\n)\nfrom tensorboard.util import tensor_util\n\nfrom flax.metrics.tensorboard import SummaryWriter, _flatten_dict\n\n\ndef _process_event(event):\n  for value in event.summary.value:\n    yield {'wall_time': event.wall_time, 'step': event.step, 'value': value}\n\n\ndef _disk_usage(path: pathlib.Path):\n  \"\"\"Recursively computes the disk usage of a directory.\"\"\"\n  if path.is_file():\n    return path.stat().st_size\n  elif path.is_dir():\n    size_bytes = 0\n    for file in path.iterdir():\n      size_bytes += _disk_usage(file)\n    return size_bytes\n  else:\n    raise NotImplementedError('What filetype is {file}?')\n\n\nclass TensorboardTest(absltest.TestCase):\n  def parse_and_return_summary_value(self, path):\n    \"\"\"Parse the event file in the given path and return the\n    only summary value.\"\"\"\n    event_value_list = []\n    event_file_generator = directory_watcher.DirectoryWatcher(\n      path, event_file_loader.EventFileLoader\n    ).Load()\n    event_values = itertools.chain.from_iterable(\n      map(_process_event, event_file_generator)\n    )\n    for value_dict in event_values:\n      event_value_list.append(value_dict)\n\n    self.assertLen(event_value_list, 1)\n    self.assertEqual(event_value_list[0]['step'], 1)\n    self.assertGreater(event_value_list[0]['wall_time'], 0.0)\n    return event_value_list[0]['value']\n\n  def test_summarywriter_flush_after_close(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    summary_writer.close()\n    with self.assertRaises(AttributeError):\n      summary_writer.flush()\n\n  def test_summarywriter_scalar(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    # Write the scalar and check if the event exists and check data.\n    float_value = 99.1232\n    summary_writer.scalar(tag='scalar_test', value=float_value, step=1)\n\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n    self.assertEqual(summary_value.tag, 'scalar_test')\n    self.assertTrue(\n      np.allclose(\n        tensor_util.make_ndarray(summary_value.tensor).item(), float_value\n      )\n    )\n\n  def test_summarywriter_text(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    text = 'hello world.'\n    summary_writer.text(tag='text_test', textdata=text, step=1)\n\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n    self.assertEqual(summary_value.tag, 'text_test')\n    self.assertEqual(\n      tensor_util.make_ndarray(summary_value.tensor).item().decode('utf-8'),\n      text,\n    )\n\n  def test_summarywriter_image(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    expected_img = np.random.uniform(low=0.0, high=255.0, size=(30, 30, 3))\n    expected_img = expected_img.astype(np.uint8)\n    summary_writer.image(tag='image_test', image=expected_img, step=1)\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n\n    self.assertEqual(summary_value.tag, 'image_test')\n    actual_img = tf.image.decode_image(summary_value.tensor.string_val[2])\n    self.assertTrue(np.allclose(actual_img, expected_img))\n\n  def test_summarywriter_image_float_pixel_values(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    expected_img = np.random.uniform(low=0.0, high=1.0, size=(30, 30, 3))\n    summary_writer.image(tag='image_test', image=expected_img, step=1)\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n\n    # convert and scale expected_img appropriately to numpy uint8.\n    expected_img = tf.image.convert_image_dtype(\n      image=expected_img, dtype=np.uint8\n    )\n\n    self.assertEqual(summary_value.tag, 'image_test')\n    actual_img = tf.image.decode_image(summary_value.tensor.string_val[2])\n    self.assertTrue(np.allclose(actual_img, expected_img))\n\n  def test_summarywriter_2dimage_scaled(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    img = np.random.uniform(low=0.0, high=255.0, size=(30, 30))\n    img = img.astype(np.uint8)\n    summary_writer.image(tag='2dimage_test', image=img, step=1)\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n\n    self.assertEqual(summary_value.tag, '2dimage_test')\n    actual_img = tf.image.decode_image(summary_value.tensor.string_val[2])\n    # assert the image was increased in dimension\n    self.assertEqual(actual_img.shape, (30, 30, 3))\n\n  def test_summarywriter_single_channel_image_scaled(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    img = np.random.uniform(low=0.0, high=255.0, size=(30, 30, 1))\n    img = img.astype(np.uint8)\n    summary_writer.image(tag='2dimage_1channel_test', image=img, step=1)\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n\n    self.assertEqual(summary_value.tag, '2dimage_1channel_test')\n    actual_img = tf.image.decode_image(summary_value.tensor.string_val[2])\n    # assert the image was increased in dimension\n    self.assertEqual(actual_img.shape, (30, 30, 3))\n\n  def test_summarywriter_multiple_images(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    expected_img = np.random.uniform(low=0.0, high=255.0, size=(2, 30, 30, 3))\n    expected_img = expected_img.astype(np.uint8)\n    summary_writer.image(tag='multiple_images_test', image=expected_img, step=1)\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n\n    self.assertEqual(summary_value.tag, 'multiple_images_test')\n    actual_imgs = [\n      tf.image.decode_image(s) for s in summary_value.tensor.string_val[2:]\n    ]\n    self.assertTrue(np.allclose(np.stack(actual_imgs, axis=0), expected_img))\n\n  def test_summarywriter_multiple_2dimages_scaled(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    img = np.random.uniform(low=0.0, high=255.0, size=(2, 30, 30))\n    img = img.astype(np.uint8)\n    summary_writer.image(tag='multiple_2dimages_test', image=img, step=1)\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n\n    self.assertEqual(summary_value.tag, 'multiple_2dimages_test')\n    actual_imgs = [\n      tf.image.decode_image(s) for s in summary_value.tensor.string_val[2:]\n    ]\n    # assert the images were increased in dimension\n    self.assertEqual(np.stack(actual_imgs, axis=0).shape, (2, 30, 30, 3))\n\n  def test_summarywriter_audio(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    expected_audio_samples = np.random.uniform(\n      low=-1.0, high=1.0, size=(2, 48000, 2)\n    )\n    summary_writer.audio(\n      tag='audio_test', audiodata=expected_audio_samples, step=1\n    )\n\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n    self.assertEqual(summary_value.tag, 'audio_test')\n\n    # Assert two audio files are parsed.\n    self.assertLen(summary_value.tensor.string_val, 2)\n\n    # Assert values.\n    actual_audio_1 = tf.audio.decode_wav(\n      summary_value.tensor.string_val[0]\n    ).audio\n    self.assertTrue(\n      np.allclose(expected_audio_samples[0], actual_audio_1, atol=1e-04)\n    )\n\n    actual_audio_2 = tf.audio.decode_wav(\n      summary_value.tensor.string_val[1]\n    ).audio\n    self.assertTrue(\n      np.allclose(expected_audio_samples[1], actual_audio_2, atol=1e-04)\n    )\n\n  def test_summarywriter_audio_sampled_output(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    expected_audio_samples = np.random.uniform(\n      low=-1.0, high=1.0, size=(2, 48000, 2)\n    )\n    summary_writer.audio(\n      tag='audio_test',\n      audiodata=expected_audio_samples,\n      step=1,\n      max_outputs=1,\n    )\n\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n    self.assertEqual(summary_value.tag, 'audio_test')\n\n    # Assert only the first audio clip is available.\n    self.assertLen(summary_value.tensor.string_val, 1)\n\n    # Assert values.\n    actual_audio = tf.audio.decode_wav(summary_value.tensor.string_val[0]).audio\n\n    self.assertTrue(\n      np.allclose(expected_audio_samples[0], actual_audio, atol=1e-04)\n    )\n\n  def test_summarywriter_clipped_audio(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    expected_audio_samples = np.random.uniform(\n      low=-2.0, high=2.0, size=(2, 48000, 2)\n    )\n    summary_writer.audio(\n      tag='audio_test',\n      audiodata=expected_audio_samples,\n      step=1,\n      max_outputs=1,\n    )\n\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n    self.assertEqual(summary_value.tag, 'audio_test')\n\n    # Assert one audio files is parsed.\n    self.assertLen(summary_value.tensor.string_val, 1)\n\n    # actual_audio is clipped.\n    actual_audio = tf.audio.decode_wav(summary_value.tensor.string_val[0]).audio\n    self.assertFalse(\n      np.allclose(expected_audio_samples[0], actual_audio, atol=1e-04)\n    )\n\n    clipped_audio = np.clip(np.array(expected_audio_samples[0]), -1, 1)\n    self.assertTrue(np.allclose(clipped_audio, actual_audio, atol=1e-04))\n\n  def test_summarywriter_histogram_defaultbins(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    histogram = np.arange(1000)\n    # Histogram will be created for 30 (default) bins.\n    summary_writer.histogram(tag='histogram_test', values=histogram, step=1)\n\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n    self.assertEqual(summary_value.tag, 'histogram_test')\n    actual_histogram = tensor_util.make_ndarray(summary_value.tensor)\n    self.assertTrue(actual_histogram.shape, (30, 3))\n    self.assertTrue(\n      np.allclose(actual_histogram[0], (0.0, 33.3, 34.0), atol=1e-01)\n    )\n\n  def test_summarywriter_histogram_2bins(self):\n    log_dir = tempfile.mkdtemp()\n    summary_writer = SummaryWriter(log_dir=log_dir)\n    histogram = np.arange(1000)\n    summary_writer.histogram(\n      tag='histogram_test', values=histogram, step=1, bins=2\n    )\n\n    summary_value = self.parse_and_return_summary_value(path=log_dir)\n    self.assertEqual(summary_value.tag, 'histogram_test')\n    actual_histogram = tensor_util.make_ndarray(summary_value.tensor)\n    self.assertTrue(actual_histogram.shape, (2, 3))\n    self.assertTrue(\n      np.allclose(actual_histogram[0], (0.0, 499.5, 500.0), atol=1e-01)\n    )\n    self.assertTrue(\n      np.allclose(actual_histogram[1], (499.5, 999.0, 500.0), atol=1e-01)\n    )\n\n  def test_flatten_dict(self):\n    # Valid types according to https://github.com/tensorflow/tensorboard/blob/1204566da5437af55109f7a4af18f9f8b7c4f864/tensorboard/plugins/hparams/summary_v2.py\n    input_hparams = {\n      # Example Invalid Types\n      'None': None,\n      'List': [1, 2, 3],\n      'Tuple': (1, 2, 3),\n      'Complex': complex('1+1j'),\n      'np.complex_': np.complex128('1+1j'),\n      # Valid Python Types\n      'Bool': True,\n      'Int': 1,\n      'Float': 1.0,\n      'Str': 'test',\n      # Valid Numpy Types\n      'np.bool_': np.bool_(1),\n      'np.integer': np.int_(1),\n      'np.floating': np.float64(1.0),\n      'np.character': np.str_('test'),\n      # Nested dict to flatten\n      'Nested_Dict': {\n        'None': None,\n        'List': [1, 2, 3],\n        'Tuple': (1, 2, 3),\n        'Complex': complex('1+1j'),\n        'np.complex_': np.complex128('1+1j'),\n        'Bool': True,\n        'Int': 1,\n        'Float': 1.0,\n        'Str': 'test',\n        'np.bool_': np.bool_(1),\n        'np.integer': np.int_(1),\n        'np.floating': np.float64(1.0),\n        'np.character': np.str_('test'),\n      },\n    }\n\n    result_hparams = _flatten_dict(input_hparams)\n\n    expected_hparams = {\n      'None': 'None',\n      'List': '[1, 2, 3]',\n      'Tuple': '(1, 2, 3)',\n      'Complex': '(1+1j)',\n      'np.complex_': '(1+1j)',\n      # Valid Python Types\n      'Bool': True,\n      'Int': 1,\n      'Float': 1.0,\n      'Str': 'test',\n      # Valid Numpy Types\n      'np.bool_': np.bool_(1),\n      'np.integer': np.int_(1),\n      'np.floating': np.float64(1.0),\n      'np.character': np.str_('test'),\n      # Nested Dict\n      'Nested_Dict.None': 'None',\n      'Nested_Dict.List': '[1, 2, 3]',\n      'Nested_Dict.Tuple': '(1, 2, 3)',\n      'Nested_Dict.Complex': '(1+1j)',\n      'Nested_Dict.np.complex_': '(1+1j)',\n      'Nested_Dict.Bool': True,\n      'Nested_Dict.Int': 1,\n      'Nested_Dict.Float': 1.0,\n      'Nested_Dict.Str': 'test',\n      'Nested_Dict.np.bool_': np.bool_(1),\n      'Nested_Dict.np.integer': np.int_(1),\n      'Nested_Dict.np.floating': np.float64(1.0),\n      'Nested_Dict.np.character': np.str_('test'),\n    }\n\n    self.assertDictEqual(result_hparams, expected_hparams)\n\n  def test_auto_flush(self):\n    tmp_dir = pathlib.Path(self.create_tempdir().full_path)\n    summary_writer = SummaryWriter(tmp_dir, auto_flush=True)\n    summary_writer.scalar('metric', 123, 1)\n    filesize_before_flush = _disk_usage(tmp_dir)\n    summary_writer.flush()\n    filesize_after_flush = _disk_usage(tmp_dir)\n    self.assertEqual(filesize_before_flush, filesize_after_flush)\n\n  def test_no_auto_flush(self):\n    tmp_dir = pathlib.Path(self.create_tempdir().full_path)\n    summary_writer = SummaryWriter(tmp_dir, auto_flush=False)\n    summary_writer.scalar('metric', 123, 1)\n    filesize_before_flush = _disk_usage(tmp_dir)\n    summary_writer.flush()\n    filesize_after_flush = _disk_usage(tmp_dir)\n    self.assertLess(filesize_before_flush, filesize_after_flush)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/traceback_util_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.traceback_util.\"\"\"\n\nimport contextlib\nimport sys\nimport traceback\n\nimport jax\nfrom absl.testing import absltest\nfrom jax import numpy as jnp\nfrom jax import random\nfrom jax._src import traceback_util as jax_traceback_util\n\nfrom flax import linen as nn\nfrom flax import traceback_util\n\n# pylint: disable=arguments-differ,protected-access, g-wrong-blank-lines\n\n# __tracebackhide__ is a python >=3.7 feature.\nTRACEBACKHIDE_SUPPORTED = tuple(sys.version_info)[:3] >= (3, 7, 0)\n\nEXPECTED_FILES = (__file__, contextlib.__spec__.origin)\n\n\nclass TracebackTest(absltest.TestCase):\n  def test_exclusion_list(self):\n    traceback_util.show_flax_in_tracebacks()\n    exclusion_len_wo_flax = len(jax_traceback_util._exclude_paths)\n    traceback_util.hide_flax_in_tracebacks()\n    exclusion_len_w_flax = len(jax_traceback_util._exclude_paths)\n    self.assertLen(\n      traceback_util._flax_exclusions,\n      exclusion_len_w_flax - exclusion_len_wo_flax,\n    )\n\n  def test_simple_exclusion_tracebackhide(self):\n    if not TRACEBACKHIDE_SUPPORTED:\n      return\n\n    class Test1(nn.Module):\n      @nn.remat\n      @nn.compact\n      def __call__(self, x):\n        return Test2()(x)\n\n    class Test2(nn.Module):\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        raise ValueError('error here.')\n        return x  # pylint: disable=unreachable\n\n    traceback_util.hide_flax_in_tracebacks()\n    jax.config.update('jax_traceback_filtering', 'tracebackhide')\n\n    key = random.key(0)\n    try:\n      nn.jit(Test1)().init(key, jnp.ones((5, 3)))\n    except ValueError as e:\n      tb = e.__traceback__\n\n    filtered_frames = 0\n    unfiltered_frames = 0\n\n    for f, _ in traceback.walk_tb(tb):\n      if '__tracebackhide__' not in f.f_locals:\n        self.assertIn(f.f_code.co_filename, EXPECTED_FILES)\n        filtered_frames += 1\n      unfiltered_frames += 1\n\n    self.assertEqual(filtered_frames, 3)\n    self.assertGreater(unfiltered_frames, filtered_frames)\n\n  def test_simple_exclusion_remove_frames(self):\n    class Test1(nn.Module):\n      @nn.remat\n      @nn.compact\n      def __call__(self, x):\n        return Test2()(x)\n\n    class Test2(nn.Module):\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        raise ValueError('error here.')\n        return x  # pylint: disable=unreachable\n\n    traceback_util.hide_flax_in_tracebacks()\n    jax.config.update('jax_traceback_filtering', 'remove_frames')\n\n    key = random.key(0)\n    try:\n      nn.jit(Test1)().init(key, jnp.ones((5, 3)))\n    except ValueError as e:\n      tb_filtered = e.__traceback__\n      tb_unfiltered = e.__cause__.__traceback__\n      e_cause = e.__cause__\n\n    self.assertIsInstance(e_cause, jax_traceback_util.UnfilteredStackTrace)\n\n    filtered_frames = 0\n    for _, _ in traceback.walk_tb(tb_filtered):\n      filtered_frames += 1\n\n    unfiltered_frames = 0\n    for _, _ in traceback.walk_tb(tb_unfiltered):\n      unfiltered_frames += 1\n\n    self.assertEqual(filtered_frames, 3)\n    self.assertGreater(unfiltered_frames, filtered_frames)\n\n  def test_dynamic_exclusion(self):\n    if not TRACEBACKHIDE_SUPPORTED:\n      return\n\n    class Test1(nn.Module):\n      @nn.remat\n      @nn.compact\n      def __call__(self, x):\n        return Test2()(x)\n\n    class Test2(nn.Module):\n      @nn.jit\n      @nn.compact\n      def __call__(self, x):\n        raise ValueError('error here.')\n        return x  # pylint: disable=unreachable\n\n    key = random.key(0)\n\n    traceback_util.show_flax_in_tracebacks()\n    jax.config.update('jax_traceback_filtering', 'off')\n    try:\n      nn.jit(Test1)().init(key, jnp.ones((5, 3)))\n    except ValueError as e:\n      tb_all = e.__traceback__\n\n    traceback_util.hide_flax_in_tracebacks()\n    jax.config.update('jax_traceback_filtering', 'tracebackhide')\n    try:\n      nn.jit(Test1)().init(key, jnp.ones((5, 3)))\n    except ValueError as e:\n      tb_no_flax = e.__traceback__\n\n    traceback_util.show_flax_in_tracebacks()\n    jax.config.update('jax_traceback_filtering', 'tracebackhide')\n    try:\n      nn.jit(Test1)().init(key, jnp.ones((5, 3)))\n    except ValueError as e:\n      tb_w_flax = e.__traceback__\n\n    filtered_frames_all = 0\n    unfiltered_frames_all = 0\n    for f, _ in traceback.walk_tb(tb_all):\n      if '__tracebackhide__' not in f.f_locals:\n        unfiltered_frames_all += 1\n      else:\n        filtered_frames_all += 1\n\n    filtered_frames_no_flax = 0\n    unfiltered_frames_no_flax = 0\n    for f, _ in traceback.walk_tb(tb_no_flax):\n      if '__tracebackhide__' not in f.f_locals:\n        self.assertIn(f.f_code.co_filename, EXPECTED_FILES)\n        unfiltered_frames_no_flax += 1\n      else:\n        filtered_frames_no_flax += 1\n\n    filtered_frames_w_flax = 0\n    unfiltered_frames_w_flax = 0\n    for f, _ in traceback.walk_tb(tb_w_flax):\n      if '__tracebackhide__' not in f.f_locals:\n        unfiltered_frames_w_flax += 1\n      else:\n        filtered_frames_w_flax += 1\n\n    self.assertEqual(\n      unfiltered_frames_all + filtered_frames_all,\n      unfiltered_frames_w_flax + filtered_frames_w_flax,\n    )\n    self.assertEqual(\n      unfiltered_frames_all + filtered_frames_all,\n      unfiltered_frames_no_flax + filtered_frames_no_flax,\n    )\n    self.assertEqual(unfiltered_frames_no_flax, 3)\n    self.assertGreater(unfiltered_frames_all, unfiltered_frames_w_flax)\n    self.assertGreater(unfiltered_frames_w_flax, unfiltered_frames_no_flax)\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  },
  {
    "path": "tests/traverse_util_test.py",
    "content": "# Copyright 2024 The Flax Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Tests for flax.traverse_util.\"\"\"\n\n\nimport collections\n\nimport jax\nimport jax.numpy as jnp\nimport numpy as np\nimport optax\nfrom absl.testing import absltest\n\nimport flax\nfrom flax import traverse_util\nfrom flax.core import freeze\n\n# Parse absl flags test_srcdir and test_tmpdir.\njax.config.parse_flags_with_absl()\n\n\nclass Foo:\n  def __init__(self, foo, bar=None):\n    self.foo = foo\n    self.bar = bar\n\n  def __eq__(self, other):\n    return self.foo == other.foo and self.bar == other.bar\n\n\nPoint = collections.namedtuple('Point', ['x', 'y'])\n\n\nclass TraversalTest(absltest.TestCase):\n  def test_traversal_id(self):\n    x = 1\n    traversal = traverse_util.t_identity\n    self.assertEqual(list(traversal.iterate(x)), [1])\n    y = traversal.update(lambda x: x + x, x)\n    self.assertEqual(y, 2)\n\n  def test_traverse_item(self):\n    x = {'foo': 1}\n    traversal = traverse_util.t_identity['foo']\n    self.assertEqual(list(traversal.iterate(x)), [1])\n    y = traversal.update(lambda x: x + x, x)\n    self.assertEqual(y, {'foo': 2})\n\n  def test_traverse_tuple_item(self):\n    x = (1, 2, 3)\n    traversal = traverse_util.t_identity[1]\n    self.assertEqual(list(traversal.iterate(x)), [2])\n    y = traversal.update(lambda x: x + x, x)\n    self.assertEqual(y, (1, 4, 3))\n\n  def test_traverse_tuple_items(self):\n    x = (1, 2, 3, 4)\n    traversal = traverse_util.t_identity[1:3]\n    self.assertEqual(list(traversal.iterate(x)), [2, 3])\n    y = traversal.update(lambda x: x + x, x)\n    self.assertEqual(y, (1, 4, 6, 4))\n\n  def test_traverse_namedtuple_item(self):\n    x = Point(x=1, y=2)\n    traversal = traverse_util.t_identity[1]\n    self.assertEqual(list(traversal.iterate(x)), [2])\n    y = traversal.update(lambda x: x + x, x)\n    self.assertEqual(y, Point(x=1, y=4))\n\n  def test_traverse_attr(self):\n    x = Foo(foo=1)\n    traversal = traverse_util.t_identity.foo\n    self.assertEqual(list(traversal.iterate(x)), [1])\n    y = traversal.update(lambda x: x + x, x)\n    self.assertEqual(y, Foo(foo=2))\n\n  def test_traverse_namedtuple_attr(self):\n    x = Point(x=1, y=2)\n    traversal = traverse_util.t_identity.y\n    self.assertEqual(list(traversal.iterate(x)), [2])\n    y = traversal.update(lambda x: x + x, x)\n    self.assertEqual(y, Point(x=1, y=4))\n\n  def test_traverse_dataclass_attr(self):\n    x = Point(x=1, y=2)\n    traversal = traverse_util.t_identity.y\n    self.assertEqual(list(traversal.iterate(x)), [2])\n    y = traversal.update(lambda x: x + x, x)\n    self.assertEqual(y, Point(x=1, y=4))\n\n  def test_traverse_merge(self):\n    x = [{'foo': 1, 'bar': 2}, {'foo': 3, 'bar': 4}]\n    traversal_base = traverse_util.t_identity.each()\n    traversal = traversal_base.merge(\n      traverse_util.TraverseItem('foo'), traverse_util.TraverseItem('bar')\n    )\n    self.assertEqual(list(traversal.iterate(x)), [1, 2, 3, 4])\n    y = traversal.update(lambda x: x + x, x)\n    self.assertEqual(y, [{'foo': 2, 'bar': 4}, {'foo': 6, 'bar': 8}])\n\n  def test_traverse_each(self):\n    x = [{'foo': 1}, {'foo': 2}]\n    traversal = traverse_util.t_identity.each()['foo']\n    self.assertEqual(list(traversal.iterate(x)), [1, 2])\n    y = traversal.update(lambda x: x + x, x)\n    self.assertEqual(y, [{'foo': 2}, {'foo': 4}])\n\n  def test_traverse_each_dict(self):\n    x = {'foo': 1, 'bar': 2}\n    traversal = traverse_util.t_identity.each()\n    self.assertEqual(list(traversal.iterate(x)), [1, 2])\n    y = traversal.update(lambda x: x + x, x)\n    self.assertEqual(y, {'foo': 2, 'bar': 4})\n\n  def test_traverse_tree(self):\n    x = [{'foo': 1}, {'bar': 2}]\n    traversal = traverse_util.t_identity.tree()\n    self.assertEqual(list(traversal.iterate(x)), [1, 2])\n    y = traversal.update(lambda x: x + x, x)\n    self.assertEqual(y, [{'foo': 2}, {'bar': 4}])\n\n  def test_traverse_filter(self):\n    x = [1, -2, 3, -4]\n    traversal = traverse_util.t_identity.each().filter(lambda x: x < 0)\n    self.assertEqual(list(traversal.iterate(x)), [-2, -4])\n    y = traversal.update(lambda x: -x, x)\n    self.assertEqual(y, [1, 2, 3, 4])\n\n  def test_traversal_set(self):\n    x = {'foo': [1, 2]}\n    traversal = traverse_util.t_identity['foo'].each()\n    y = traversal.set([3, 4], x)\n    self.assertEqual(y, {'foo': [3, 4]})\n    with self.assertRaises(ValueError):\n      traversal.set([3], x)  # too few values\n    with self.assertRaises(ValueError):\n      traversal.set([3, 4, 5], x)  # too many values\n\n  def test_flatten_dict(self):\n    xs = {'foo': 1, 'bar': {'a': 2, 'b': {}}}\n    flat_xs = traverse_util.flatten_dict(xs)\n    self.assertEqual(\n      flat_xs,\n      {\n        ('foo',): 1,\n        ('bar', 'a'): 2,\n      },\n    )\n    flat_xs = traverse_util.flatten_dict(freeze(xs))\n    self.assertEqual(\n      flat_xs,\n      {\n        ('foo',): 1,\n        ('bar', 'a'): 2,\n      },\n    )\n    flat_xs = traverse_util.flatten_dict(xs, sep='/')\n    self.assertEqual(\n      flat_xs,\n      {\n        'foo': 1,\n        'bar/a': 2,\n      },\n    )\n\n  def test_unflatten_dict(self):\n    expected_xs = {'foo': 1, 'bar': {'a': 2}}\n    xs = traverse_util.unflatten_dict(\n      {\n        ('foo',): 1,\n        ('bar', 'a'): 2,\n      }\n    )\n    self.assertEqual(xs, expected_xs)\n    xs = traverse_util.unflatten_dict(\n      {\n        'foo': 1,\n        'bar/a': 2,\n      },\n      sep='/',\n    )\n    self.assertEqual(xs, expected_xs)\n\n  def test_flatten_dict_keep_empty(self):\n    xs = {'foo': 1, 'bar': {'a': 2, 'b': {}}}\n    flat_xs = traverse_util.flatten_dict(xs, keep_empty_nodes=True)\n    self.assertEqual(\n      flat_xs,\n      {\n        ('foo',): 1,\n        ('bar', 'a'): 2,\n        ('bar', 'b'): traverse_util.empty_node,\n      },\n    )\n    xs_restore = traverse_util.unflatten_dict(flat_xs)\n    self.assertEqual(xs, xs_restore)\n\n  def test_flatten_dict_is_leaf(self):\n    xs = {'foo': {'c': 4}, 'bar': {'a': 2, 'b': {}}}\n    flat_xs = traverse_util.flatten_dict(\n      xs, is_leaf=lambda k, x: len(k) == 1 and len(x) == 2\n    )\n    self.assertEqual(\n      flat_xs,\n      {\n        ('foo', 'c'): 4,\n        ('bar',): {'a': 2, 'b': {}},\n      },\n    )\n    xs_restore = traverse_util.unflatten_dict(flat_xs)\n    self.assertEqual(xs, xs_restore)\n\n\nclass ModelParamTraversalTest(absltest.TestCase):\n  def test_only_works_on_model_params(self):\n    traversal = traverse_util.ModelParamTraversal(lambda *_: True)\n    with self.assertRaises(ValueError):\n      list(traversal.iterate([]))\n\n  def test_param_selection(self):\n    params = {\n      'x': {\n        'kernel': 1,\n        'bias': 2,\n        'y': {\n          'kernel': 3,\n          'bias': 4,\n        },\n        'z': {},\n      },\n    }\n    expected_params = {\n      'x': {\n        'kernel': 2,\n        'bias': 2,\n        'y': {\n          'kernel': 6,\n          'bias': 4,\n        },\n        'z': {},\n      },\n    }\n    names = []\n\n    def filter_fn(name, _):\n      names.append(name)  # track names passed to filter_fn for testing\n      return 'kernel' in name\n\n    traversal = traverse_util.ModelParamTraversal(filter_fn)\n\n    values = list(traversal.iterate(params))\n    configs = [\n      (params, expected_params),\n      (flax.core.FrozenDict(params), flax.core.FrozenDict(expected_params)),\n    ]\n    for model, expected_model in configs:\n      self.assertEqual(values, [1, 3])\n      self.assertEqual(\n        set(names), {'/x/kernel', '/x/bias', '/x/y/kernel', '/x/y/bias'}\n      )\n      new_model = traversal.update(lambda x: x + x, model)\n      self.assertEqual(new_model, expected_model)\n\n  def test_path_value(self):\n    params_in = {'a': {'b': 10, 'c': 2}}\n    params_out = traverse_util.path_aware_map(\n      lambda path, x: x + 1 if 'b' in path else -x, params_in\n    )\n\n    self.assertEqual(params_out, {'a': {'b': 11, 'c': -2}})\n\n  def test_path_aware_map_with_multi_transform(self):\n    params = {\n      'linear_1': {'w': jnp.zeros((5, 6)), 'b': jnp.zeros(5)},\n      'linear_2': {'w': jnp.zeros((6, 1)), 'b': jnp.zeros(1)},\n    }\n    gradients = jax.tree_util.tree_map(jnp.ones_like, params)  # dummy gradients\n\n    param_labels = traverse_util.path_aware_map(\n      lambda path, x: 'kernel' if 'w' in path else 'bias', params\n    )\n    tx = optax.multi_transform(\n      {'kernel': optax.sgd(1.0), 'bias': optax.set_to_zero()}, param_labels\n    )\n    state = tx.init(params)\n    updates, new_state = tx.update(gradients, state, params)\n    new_params = optax.apply_updates(params, updates)\n\n    self.assertTrue(\n      np.allclose(new_params['linear_1']['b'], params['linear_1']['b'])\n    )\n    self.assertTrue(\n      np.allclose(new_params['linear_2']['b'], params['linear_2']['b'])\n    )\n    self.assertFalse(\n      np.allclose(new_params['linear_1']['w'], params['linear_1']['w'])\n    )\n    self.assertFalse(\n      np.allclose(new_params['linear_2']['w'], params['linear_2']['w'])\n    )\n\n  def test_path_aware_map_with_masked(self):\n    params = {\n      'linear_1': {'w': jnp.zeros((5, 6)), 'b': jnp.zeros(5)},\n      'linear_2': {'w': jnp.zeros((6, 1)), 'b': jnp.zeros(1)},\n    }\n    gradients = jax.tree_util.tree_map(jnp.ones_like, params)  # dummy gradients\n\n    params_mask = traverse_util.path_aware_map(\n      lambda path, x: 'w' in path, params\n    )\n    tx = optax.masked(optax.sgd(1.0), params_mask)\n    state = tx.init(params)\n    updates, new_state = tx.update(gradients, state, params)\n    new_params = optax.apply_updates(params, updates)\n\n    self.assertTrue(\n      np.allclose(new_params['linear_1']['b'], gradients['linear_1']['b'])\n    )\n    self.assertTrue(\n      np.allclose(new_params['linear_2']['b'], gradients['linear_2']['b'])\n    )\n    self.assertTrue(\n      np.allclose(new_params['linear_1']['w'], -gradients['linear_1']['w'])\n    )\n    self.assertTrue(\n      np.allclose(new_params['linear_2']['w'], -gradients['linear_2']['w'])\n    )\n\n  def test_path_aware_map_with_empty_nodes(self):\n    params_in = {'a': {'b': 10, 'c': 2}, 'b': {}}\n    params_out = traverse_util.path_aware_map(\n      lambda path, x: x + 1 if 'b' in path else -x, params_in\n    )\n\n    self.assertEqual(params_out, {'a': {'b': 11, 'c': -2}, 'b': {}})\n\n\nif __name__ == '__main__':\n  absltest.main()\n"
  }
]