atomwalk12 commited on
Commit
0dd6c2f
·
1 Parent(s): a9f4671

initial commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .devcontainer/devcontainer.json +32 -0
  2. .devcontainer/postCreateCommand.sh +10 -0
  3. .gemini/config.yaml +10 -0
  4. .gemini/styleguide.md +144 -0
  5. .github/actions/setup-python-env/action.yml +41 -0
  6. .github/dependabot.yml +11 -0
  7. .github/workflows/codeql.yml +100 -0
  8. .github/workflows/conventional-pr.yml +17 -0
  9. .github/workflows/main.yml +70 -0
  10. .github/workflows/on-release-main.yml +102 -0
  11. .github/workflows/validate-codecov-config.yml +15 -0
  12. .gitignore +155 -0
  13. .pre-commit-config.yaml +26 -0
  14. CHANGELOG.md +389 -0
  15. CONTRIBUTING.md +126 -0
  16. Dockerfile +21 -0
  17. LICENSE +21 -0
  18. Makefile +166 -0
  19. README.md +150 -7
  20. app.py +0 -7
  21. codecov.yaml +9 -0
  22. env/.env.distillation.example +14 -0
  23. env/.env.grpo.example +9 -0
  24. env/.env.grpo.runpod.example +16 -0
  25. linalg_zero/__init__.py +0 -0
  26. linalg_zero/config/cleaning_config.yaml +42 -0
  27. linalg_zero/config/data.py +341 -0
  28. linalg_zero/config/dataset/default.yml +0 -0
  29. linalg_zero/config/dataset/default_debug.yml +1 -0
  30. linalg_zero/config/distillation/env.example.sh +13 -0
  31. linalg_zero/config/distillation/llamacpp_qwen3_30b_A3B_instruct.yaml +49 -0
  32. linalg_zero/config/distillation/llamacpp_qwen3_30b_A3B_think.yaml +49 -0
  33. linalg_zero/config/distillation/llamacpp_qwen3_32b_instruct.yaml +49 -0
  34. linalg_zero/config/distillation/qwen3_think_vllm_debug.yaml +65 -0
  35. linalg_zero/config/distillation/vllm_qwen3_32b.yaml +46 -0
  36. linalg_zero/config/distillation/vllm_qwen3_32b_debug.yaml +46 -0
  37. linalg_zero/config/distillation/vllm_qwen3_4b_think.yaml +52 -0
  38. linalg_zero/config/grpo/Qwen/Qwen2.5-3B/eval/linalgzero-grpo-110.yaml +62 -0
  39. linalg_zero/config/grpo/Qwen/Qwen2.5-3B/eval/linalgzero-grpo.yaml +62 -0
  40. linalg_zero/config/grpo/Qwen/Qwen2.5-3B/eval/linalgzero-sft-110.yaml +62 -0
  41. linalg_zero/config/grpo/Qwen/Qwen2.5-3B/eval/linalgzero-sft.yaml +62 -0
  42. linalg_zero/config/grpo/Qwen/Qwen2.5-3B/local.yaml +95 -0
  43. linalg_zero/config/grpo/Qwen/Qwen2.5-3B/runpod.yaml +95 -0
  44. linalg_zero/config/sft/accelerate/zero2.yaml +21 -0
  45. linalg_zero/config/sft/accelerate/zero3.yaml +22 -0
  46. linalg_zero/config/sft/qwen2.5-3B/instruct.yaml +78 -0
  47. linalg_zero/config/sft/qwen2.5-3B/lora.yaml +97 -0
  48. linalg_zero/config/sft/qwen2.5-3B/merged.yaml +78 -0
  49. linalg_zero/config/sft/qwen2.5-3B/nst.yaml +99 -0
  50. linalg_zero/demo/app.py +968 -0
.devcontainer/devcontainer.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2
+ // README at: https://github.com/devcontainers/templates/tree/main/src/python
3
+ {
4
+ "name": "linalg-zero",
5
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6
+ "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye",
7
+ "runArgs": [
8
+ // avoid UID/GID remapping under rootless Podman
9
+ // This flag works with Podman but not with Docker:
10
+ // "--userns=keep-id",
11
+ "-v", "${localEnv:HOME}/.ssh:/home/vscode/.ssh:ro",
12
+ "--user", "1000:1000"
13
+ ],
14
+ "features": {},
15
+
16
+ // Use 'postCreateCommand' to run commands after the container is created.
17
+ "postCreateCommand": "./.devcontainer/postCreateCommand.sh",
18
+
19
+ // Configure tool-specific properties.
20
+ "customizations": {
21
+ "vscode": {
22
+ "extensions": ["ms-python.python", "editorconfig.editorconfig"],
23
+ "settings": {
24
+ "python.testing.pytestArgs": ["tests"],
25
+ "python.testing.unittestEnabled": false,
26
+ "python.testing.pytestEnabled": true,
27
+ "python.defaultInterpreterPath": "/workspaces/linalg-zero/.venv/bin/python",
28
+ "python.testing.pytestPath": "/workspaces/linalg-zero/.venv/bin/pytest"
29
+ }
30
+ }
31
+ }
32
+ }
.devcontainer/postCreateCommand.sh ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ #! /usr/bin/env bash
2
+
3
+ # Install uv
4
+ curl -LsSf https://astral.sh/uv/install.sh | sh
5
+
6
+ # Install Dependencies
7
+ uv sync
8
+
9
+ # Install pre-commit hooks
10
+ uv run pre-commit install --install-hooks
.gemini/config.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ have_fun: true
2
+ code_review:
3
+ disable: false
4
+ comment_severity_threshold: MEDIUM
5
+ max_review_comments: -1
6
+ pull_request_opened:
7
+ help: false
8
+ summary: true
9
+ code_review: true
10
+ ignore_patterns: []
.gemini/styleguide.md ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Project LinAlgZero Python Style Guide
2
+
3
+ # Introduction
4
+ This style guide outlines the coding conventions for Python code.
5
+ It's based on PEP 8, but with some modifications to address specific needs and
6
+ preferences within our organization.
7
+
8
+ # Key Principles
9
+ * **Readability:** Code should be easy to understand for all team members.
10
+ * **Maintainability:** Code should be easy to modify and extend.
11
+ * **Consistency:** Adhering to a consistent style across all projects improves
12
+ collaboration and reduces errors.
13
+ * **Performance:** While readability is paramount, code should be efficient.
14
+
15
+ # Deviations from PEP 8
16
+
17
+ ## Line Length
18
+ * **Maximum line length:** 119 characters (instead of PEP 8's 79).
19
+ * Modern screens allow for wider lines, improving code readability in many cases.
20
+ * Many common patterns in our codebase, like long strings or URLs, often exceed 79 characters.
21
+
22
+ ## Indentation
23
+ * **Use 4 spaces per indentation level.** (PEP 8 recommendation)
24
+
25
+ ## Imports
26
+ * **Group imports:**
27
+ * Standard library imports
28
+ * Related third party imports
29
+ * Local application/library specific imports
30
+ * **Absolute imports:** Always use absolute imports for clarity.
31
+ * **Import order within groups:** Sort alphabetically.
32
+
33
+ ## Naming Conventions
34
+
35
+ * **Variables:** Use lowercase with underscores (snake_case): `user_name`, `total_count`
36
+ * **Constants:** Use uppercase with underscores: `MAX_VALUE`, `DATABASE_NAME`
37
+ * **Functions:** Use lowercase with underscores (snake_case): `calculate_total()`, `process_data()`
38
+ * **Classes:** Use CapWords (CamelCase): `UserManager`, `PaymentProcessor`
39
+ * **Modules:** Use lowercase with underscores (snake_case): `user_utils`, `payment_gateway`
40
+
41
+ ## Docstrings
42
+ * **Use triple double quotes (`"""Docstring goes here."""`) for all docstrings.**
43
+ * **First line:** Concise summary of the object's purpose.
44
+ * **For complex functions/classes:** Include detailed descriptions of parameters, return values,
45
+ attributes, and exceptions.
46
+ * **Use Google style docstrings:** This helps with automated documentation generation.
47
+ ```python
48
+ def my_function(param1, param2):
49
+ """Single-line summary.
50
+
51
+ More detailed description, if necessary.
52
+
53
+ Args:
54
+ param1 (int): The first parameter.
55
+ param2 (str): The second parameter.
56
+
57
+ Returns:
58
+ bool: The return value. True for success, False otherwise.
59
+
60
+ Raises:
61
+ ValueError: If `param2` is invalid.
62
+ """
63
+ # function body here
64
+ ```
65
+
66
+ ## Type Hints
67
+ * **Use type hints:** Type hints improve code readability and help catch errors early.
68
+ * **Follow PEP 484:** Use the standard type hinting syntax.
69
+
70
+ ## Comments
71
+ * **Write clear and concise comments:** Explain the "why" behind the code, not just the "what".
72
+ * **Comment sparingly:** Well-written code should be self-documenting where possible.
73
+ * **Use complete sentences:** Start comments with a capital letter and use proper punctuation.
74
+
75
+ ## Logging
76
+ * **Use a standard logging framework:** Project LinAlgZero uses the built-in `logging` module.
77
+ * **Log at appropriate levels:** DEBUG, INFO, WARNING, ERROR, CRITICAL
78
+ * **Provide context:** Include relevant information in log messages to aid debugging.
79
+
80
+ ## Error Handling
81
+ * **Use specific exceptions:** Avoid using broad exceptions like `Exception`.
82
+ * **Handle exceptions gracefully:** Provide informative error messages and avoid crashing the program.
83
+ * **Use `try...except` blocks:** Isolate code that might raise exceptions.
84
+
85
+ # Tooling
86
+ * **Code formatter:** Ruff (ruff-format) - Enforces consistent formatting automatically.
87
+ * **Linter:** Ruff - Identifies potential issues and style violations.
88
+
89
+ # Example
90
+ ```python
91
+ """Module for user authentication."""
92
+
93
+ import hashlib
94
+ import logging
95
+ import os
96
+
97
+ from companyx.db import user_database
98
+
99
+ LOGGER = logging.getLogger(__name__)
100
+
101
+ def hash_password(password: str) -> str:
102
+ """Hashes a password using SHA-256.
103
+
104
+ Args:
105
+ password (str): The password to hash.
106
+
107
+ Returns:
108
+ str: The hashed password.
109
+ """
110
+ salt = os.urandom(16)
111
+ salted_password = salt + password.encode('utf-8')
112
+ hashed_password = hashlib.sha256(salted_password).hexdigest()
113
+ return f"{salt.hex()}:{hashed_password}"
114
+
115
+ def authenticate_user(username: str, password: str) -> bool:
116
+ """Authenticates a user against the database.
117
+
118
+ Args:
119
+ username (str): The user's username.
120
+ password (str): The user's password.
121
+
122
+ Returns:
123
+ bool: True if the user is authenticated, False otherwise.
124
+ """
125
+ try:
126
+ user = user_database.get_user(username)
127
+ if user is None:
128
+ LOGGER.warning("Authentication failed: User not found - %s", username)
129
+ return False
130
+
131
+ stored_hash = user.password_hash
132
+ salt, hashed_password = stored_hash.split(':')
133
+ salted_password = bytes.fromhex(salt) + password.encode('utf-8')
134
+ calculated_hash = hashlib.sha256(salted_password).hexdigest()
135
+
136
+ if calculated_hash == hashed_password:
137
+ LOGGER.info("User authenticated successfully - %s", username)
138
+ return True
139
+ else:
140
+ LOGGER.warning("Authentication failed: Incorrect password - %s", username)
141
+ return False
142
+ except Exception as e:
143
+ LOGGER.error("An error occurred during authentication: %s", e)
144
+ return False
.github/actions/setup-python-env/action.yml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "Setup Python Environment"
2
+ description: "Set up Python environment for the given Python version"
3
+
4
+ inputs:
5
+ python-version:
6
+ description: "Python version to use"
7
+ required: true
8
+ default: "3.12"
9
+ uv-version:
10
+ description: "uv version to use"
11
+ required: true
12
+ default: "0.6.14"
13
+ dependency-groups:
14
+ description: "Space-separated dependency groups to install (e.g. 'dev test')"
15
+ required: false
16
+ default: "dev"
17
+
18
+ runs:
19
+ using: "composite"
20
+ steps:
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: ${{ inputs.python-version }}
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v2
27
+ with:
28
+ version: ${{ inputs.uv-version }}
29
+ enable-cache: 'true'
30
+ cache-suffix: ${{ matrix.python-version }}
31
+
32
+ - name: Install Python dependencies
33
+ run: |
34
+ args=()
35
+ if [ -n "${{ inputs.dependency-groups }}" ]; then
36
+ for group in ${{ inputs.dependency-groups }}; do
37
+ args+=(--group "$group")
38
+ done
39
+ fi
40
+ uv sync --frozen "${args[@]}"
41
+ shell: bash
.github/dependabot.yml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "uv" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
.github/workflows/codeql.yml ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL Advanced"
13
+
14
+ on:
15
+ push:
16
+ branches: [ "main" ]
17
+ pull_request:
18
+ branches: [ "main" ]
19
+ schedule:
20
+ - cron: '42 21 * * 2'
21
+
22
+ jobs:
23
+ analyze:
24
+ name: Analyze (${{ matrix.language }})
25
+ # Runner size impacts CodeQL analysis time. To learn more, please see:
26
+ # - https://gh.io/recommended-hardware-resources-for-running-codeql
27
+ # - https://gh.io/supported-runners-and-hardware-resources
28
+ # - https://gh.io/using-larger-runners (GitHub.com only)
29
+ # Consider using larger runners or machines with greater resources for possible analysis time improvements.
30
+ runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
31
+ permissions:
32
+ # required for all workflows
33
+ security-events: write
34
+
35
+ # required to fetch internal or private CodeQL packs
36
+ packages: read
37
+
38
+ # only required for workflows in private repositories
39
+ actions: read
40
+ contents: read
41
+
42
+ strategy:
43
+ fail-fast: false
44
+ matrix:
45
+ include:
46
+ - language: actions
47
+ build-mode: none
48
+ - language: python
49
+ build-mode: none
50
+ # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
51
+ # Use `c-cpp` to analyze code written in C, C++ or both
52
+ # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
53
+ # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
54
+ # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
55
+ # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
56
+ # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
57
+ # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
58
+ steps:
59
+ - name: Checkout repository
60
+ uses: actions/checkout@v4
61
+
62
+ # Add any setup steps before running the `github/codeql-action/init` action.
63
+ # This includes steps like installing compilers or runtimes (`actions/setup-node`
64
+ # or others). This is typically only required for manual builds.
65
+ # - name: Setup runtime (example)
66
+ # uses: actions/setup-example@v1
67
+
68
+ # Initializes the CodeQL tools for scanning.
69
+ - name: Initialize CodeQL
70
+ uses: github/codeql-action/init@v3
71
+ with:
72
+ languages: ${{ matrix.language }}
73
+ build-mode: ${{ matrix.build-mode }}
74
+ # If you wish to specify custom queries, you can do so here or in a config file.
75
+ # By default, queries listed here will override any specified in a config file.
76
+ # Prefix the list here with "+" to use these queries and those in the config file.
77
+
78
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
79
+ # queries: security-extended,security-and-quality
80
+
81
+ # If the analyze step fails for one of the languages you are analyzing with
82
+ # "We were unable to automatically build your code", modify the matrix above
83
+ # to set the build mode to "manual" for that language. Then modify this step
84
+ # to build your code.
85
+ # ℹ️ Command-line programs to run using the OS shell.
86
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
87
+ - if: matrix.build-mode == 'manual'
88
+ shell: bash
89
+ run: |
90
+ echo 'If you are using a "manual" build mode for one or more of the' \
91
+ 'languages you are analyzing, replace this with the commands to build' \
92
+ 'your code, for example:'
93
+ echo ' make bootstrap'
94
+ echo ' make release'
95
+ exit 1
96
+
97
+ - name: Perform CodeQL Analysis
98
+ uses: github/codeql-action/analyze@v3
99
+ with:
100
+ category: "/language:${{matrix.language}}"
.github/workflows/conventional-pr.yml ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: conventional-pr
2
+ on:
3
+ pull_request:
4
+ types:
5
+ - opened
6
+ - edited
7
+ - synchronize
8
+ branches:
9
+ - main
10
+ jobs:
11
+ lint-pr:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+ - uses: CondeNast/conventional-pull-request-action@v0.2.0
16
+ env:
17
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
.github/workflows/main.yml ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Main
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ types: [opened, synchronize, reopened, ready_for_review]
9
+
10
+ jobs:
11
+ quality:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - name: Check out
15
+ uses: actions/checkout@v4
16
+
17
+ - uses: actions/cache@v4
18
+ with:
19
+ path: ~/.cache/pre-commit
20
+ key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
21
+
22
+ - name: Set up the environment
23
+ uses: ./.github/actions/setup-python-env
24
+
25
+ - name: Run checks
26
+ run: make check
27
+
28
+ tests-and-type-check:
29
+ runs-on: ubuntu-latest
30
+ strategy:
31
+ matrix:
32
+ python-version: ["3.10", "3.11", "3.12"]
33
+ fail-fast: false
34
+ defaults:
35
+ run:
36
+ shell: bash
37
+ steps:
38
+ - name: Check out
39
+ uses: actions/checkout@v4
40
+
41
+ - name: Set up the environment
42
+ uses: ./.github/actions/setup-python-env
43
+ with:
44
+ python-version: ${{ matrix.python-version }}
45
+ dependency-groups: "dev test"
46
+
47
+ - name: Run tests
48
+ run: uv run python -m pytest tests --cov --cov-config=pyproject.toml --cov-report=xml
49
+
50
+ - name: Check typing
51
+ run: uv run mypy
52
+
53
+
54
+ - name: Upload coverage reports to Codecov with GitHub Action on Python 3.11
55
+ uses: codecov/codecov-action@v4
56
+ if: ${{ matrix.python-version == '3.11' }}
57
+ env:
58
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
59
+
60
+ check-docs:
61
+ runs-on: ubuntu-latest
62
+ steps:
63
+ - name: Check out
64
+ uses: actions/checkout@v4
65
+
66
+ - name: Set up the environment
67
+ uses: ./.github/actions/setup-python-env
68
+
69
+ - name: Check if documentation can be built
70
+ run: uv run mkdocs build -s
.github/workflows/on-release-main.yml ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: release-main
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ semantic-release:
8
+ runs-on: ubuntu-latest
9
+ concurrency: release
10
+ permissions:
11
+ id-token: write
12
+ contents: write
13
+ outputs:
14
+ released: ${{ steps.release.outputs.released }}
15
+ tag: ${{ steps.release.outputs.tag }}
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ ref: ${{ github.ref_name }}
20
+ fetch-depth: 0
21
+ persist-credentials: false
22
+
23
+ - name: Setup | Force release branch to be at workflow sha
24
+ run: |
25
+ git reset --hard ${{ github.sha }}
26
+
27
+ - name: Action | Python Semantic Release
28
+ id: release
29
+ uses: python-semantic-release/python-semantic-release@v9.21.1
30
+ with:
31
+ github_token: ${{ secrets.GITHUB_TOKEN }}
32
+
33
+ # The following is done through the on-release-main.yml action.
34
+ # Leaving this here for reference.
35
+ # - name: Publish | Upload package to PyPI
36
+ # uses: pypa/gh-action-pypi-publish@release/v1
37
+ # # NOTE: DO NOT wrap the conditional in ${{ }} as it will always evaluate to true.
38
+ # # See https://github.com/actions/runner/issues/1173
39
+ # if: steps.release.outputs.released == 'true'
40
+
41
+ - name: Publish | Upload to GitHub Release Assets
42
+ uses: python-semantic-release/publish-action@v9.21.1
43
+ if: steps.release.outputs.released == 'true'
44
+ with:
45
+ github_token: ${{ secrets.GITHUB_TOKEN }}
46
+ tag: ${{ steps.release.outputs.tag }}
47
+
48
+
49
+ set-version:
50
+ needs: [semantic-release]
51
+ if: needs.semantic-release.outputs.released == 'true'
52
+ runs-on: ubuntu-24.04
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+
56
+ - name: Update project version
57
+ run: |
58
+ sed -i "s/^version = \".*\"/version = \"$RELEASE_VERSION\"/" pyproject.toml
59
+ env:
60
+ RELEASE_VERSION: ${{ needs.semantic-release.outputs.tag }}
61
+
62
+ - name: Upload updated pyproject.toml
63
+ uses: actions/upload-artifact@v4
64
+ with:
65
+ name: pyproject-toml
66
+ path: pyproject.toml
67
+
68
+ publish:
69
+ runs-on: ubuntu-latest
70
+ needs: [set-version]
71
+ steps:
72
+ - name: Check out
73
+ uses: actions/checkout@v4
74
+
75
+ - name: Set up the environment
76
+ uses: ./.github/actions/setup-python-env
77
+
78
+ - name: Download updated pyproject.toml
79
+ uses: actions/download-artifact@v4
80
+ with:
81
+ name: pyproject-toml
82
+
83
+ - name: Build package
84
+ run: uv build
85
+
86
+ - name: Publish package
87
+ run: uv publish
88
+ env:
89
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
90
+
91
+ deploy-docs:
92
+ needs: publish
93
+ runs-on: ubuntu-latest
94
+ steps:
95
+ - name: Check out
96
+ uses: actions/checkout@v4
97
+
98
+ - name: Set up the environment
99
+ uses: ./.github/actions/setup-python-env
100
+
101
+ - name: Deploy documentation
102
+ run: uv run mkdocs gh-deploy --force
.github/workflows/validate-codecov-config.yml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: validate-codecov-config
2
+
3
+ on:
4
+ pull_request:
5
+ paths: [codecov.yaml]
6
+ push:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ validate-codecov-config:
11
+ runs-on: ubuntu-22.04
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Validate codecov configuration
15
+ run: curl -sSL --fail-with-body --data-binary @codecov.yaml https://codecov.io/validate
.gitignore ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ docs/source
2
+
3
+ # From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore
4
+
5
+ # Byte-compiled / optimized / DLL files
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+
10
+ # C extensions
11
+ *.so
12
+
13
+ # Distribution / packaging
14
+ .Python
15
+ build/
16
+ develop-eggs/
17
+ dist/
18
+ downloads/
19
+ eggs/
20
+ .eggs/
21
+ lib/
22
+ lib64/
23
+ parts/
24
+ sdist/
25
+ var/
26
+ wheels/
27
+ share/python-wheels/
28
+ *.egg-info/
29
+ .installed.cfg
30
+ *.egg
31
+ MANIFEST
32
+
33
+ # PyInstaller
34
+ # Usually these files are written by a python script from a template
35
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
36
+ *.manifest
37
+ *.spec
38
+
39
+ # Installer logs
40
+ pip-log.txt
41
+ pip-delete-this-directory.txt
42
+
43
+ # Unit test / coverage reports
44
+ htmlcov/
45
+ .tox/
46
+ .nox/
47
+ .coverage
48
+ .coverage.*
49
+ .cache
50
+ nosetests.xml
51
+ coverage.xml
52
+ *.cover
53
+ *.py,cover
54
+ .hypothesis/
55
+ .pytest_cache/
56
+ cover/
57
+
58
+ # Translations
59
+ *.mo
60
+ *.pot
61
+
62
+ # Django stuff:
63
+ *.log
64
+ local_settings.py
65
+ db.sqlite3
66
+ db.sqlite3-journal
67
+
68
+ # Flask stuff:
69
+ instance/
70
+ .webassets-cache
71
+
72
+ # Scrapy stuff:
73
+ .scrapy
74
+
75
+ # Sphinx documentation
76
+ docs/_build/
77
+
78
+ # PyBuilder
79
+ .pybuilder/
80
+ target/
81
+
82
+ # Jupyter Notebook
83
+ .ipynb_checkpoints
84
+
85
+ # IPython
86
+ profile_default/
87
+ ipython_config.py
88
+
89
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
90
+ __pypackages__/
91
+
92
+ # Celery stuff
93
+ celerybeat-schedule
94
+ celerybeat.pid
95
+
96
+ # SageMath parsed files
97
+ *.sage.py
98
+
99
+ # Environments
100
+ .env
101
+ .venv
102
+ venv/
103
+
104
+ # Spyder project settings
105
+ .spyderproject
106
+ .spyproject
107
+
108
+ # Rope project settings
109
+ .ropeproject
110
+
111
+ # mkdocs documentation
112
+ /site
113
+
114
+ # mypy
115
+ .mypy_cache/
116
+ .dmypy.json
117
+ dmypy.json
118
+
119
+ # Pyre type checker
120
+ .pyre/
121
+
122
+ # pytype static type analyzer
123
+ .pytype/
124
+
125
+ # Cython debug symbols
126
+ cython_debug/
127
+
128
+ # Vscode config files
129
+ .vscode/
130
+
131
+ # PyCharm
132
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
133
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
134
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
135
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
136
+ #.idea/
137
+
138
+ logs
139
+ models
140
+ notebooks
141
+ *DE.md
142
+ wandb/
143
+ results/
144
+ *_debug.py
145
+ *_debug.sh
146
+ distilabel_cache/
147
+ hf-cache/
148
+ vllm-cache/
149
+ uv-cache/
150
+ unsloth_compiled_cache/
151
+ .*o
152
+ outputs
153
+ 500
154
+ distillation-cache
155
+ _unsloth_temporary_saved_buffers
.pre-commit-config.yaml ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: "v5.0.0"
4
+ hooks:
5
+ - id: check-case-conflict
6
+ - id: check-merge-conflict
7
+ - id: check-toml
8
+ - id: check-yaml
9
+ - id: check-json
10
+ exclude: ^.devcontainer/devcontainer.json
11
+ - id: pretty-format-json
12
+ exclude: ^.devcontainer/devcontainer.json
13
+ args: [--autofix, --no-sort-keys]
14
+ - id: end-of-file-fixer
15
+ exclude: ^linalg_zero/distillation/vllm/.*\.jinja$
16
+ - id: trailing-whitespace
17
+ exclude: ^linalg_zero/distillation/vllm/.*\.jinja$
18
+
19
+ - repo: https://github.com/astral-sh/ruff-pre-commit
20
+ rev: "v0.11.5"
21
+ hooks:
22
+ - id: ruff
23
+ args: [--exit-non-zero-on-fix]
24
+ exclude: ^linalg_zero/distillation/vllm/.*\.jinja$
25
+ - id: ruff-format
26
+ exclude: ^linalg_zero/distillation/vllm/.*\.jinja$
CHANGELOG.md ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CHANGELOG
2
+
3
+
4
+ ## v1.0.0 (2026-02-17)
5
+
6
+ ### Bug Fixes
7
+
8
+ - **generate**: Remove ambiguities in problem generation ([`5339d04`](https://github.com/atomwalk12/linalg-zero/commit/5339d04bd7f1a8253e744a209f47179f67a47394))
9
+ - **distillation**: Remove vllm chat templates ([`57be920`](https://github.com/atomwalk12/linalg-zero/commit/57be920fdb2ca9524593b67b735dfee8716affc0))
10
+ - **grpo**: Adjust launch script parameters ([`a40be0e`](https://github.com/atomwalk12/linalg-zero/commit/a40be0ebcf73657a4d0c695ba933d379a31eef2b))
11
+ - **grpo**: Log statistics ([`b983abd`](https://github.com/atomwalk12/linalg-zero/commit/b983abd1760b26b62c2e101ac76e1e3290359ec9))
12
+ - **sft**: Adjust optional accuracy callback ([`16b7d32`](https://github.com/atomwalk12/linalg-zero/commit/16b7d322f5a50fd830c1ea5acd51e3ca20702454))
13
+
14
+ ### Documentation
15
+
16
+ - **config**: Add sample config files ([`62290aa`](https://github.com/atomwalk12/linalg-zero/commit/62290aa84d011ef52078272d241204518a78cf16))
17
+ - **report**: Finalize project ([`ffbfcb2`](https://github.com/atomwalk12/linalg-zero/commit/ffbfcb24a0cc54bcd888b4b7c19148e75ffce4f7))
18
+
19
+ ### Features
20
+
21
+ - **distillation**: Add data generation configs ([`6aab283`](https://github.com/atomwalk12/linalg-zero/commit/6aab283a343c6273bfdc274e7397dff76bb3fa5c))
22
+ - **distillation**: Add validation script ([`8344dba`](https://github.com/atomwalk12/linalg-zero/commit/8344dbad775768a7a74ffd83dec56587514e1c49))
23
+ - **distillation**: Improve multi-turn generation with progress tracking ([`f58eadf`](https://github.com/atomwalk12/linalg-zero/commit/f58eadf0083f135f2fee2405b43eca1c2705d84e))
24
+ - **grpo**: Add environment abstraction layer with base classes ([`edc9615`](https://github.com/atomwalk12/linalg-zero/commit/edc9615163385df75ad18aa15f918e534c45f7fa))
25
+ - **grpo**: Add linear algebra environment with reward computation ([`4e46e7c`](https://github.com/atomwalk12/linalg-zero/commit/4e46e7c9e0040ee34e5fca060f652272f0c6ed1f))
26
+ - **grpo**: Add linear algebra tools for matrix operations ([`d2f08b1`](https://github.com/atomwalk12/linalg-zero/commit/d2f08b1231c2c0f493bbea1f3f6718cb6829c0fa))
27
+ - **grpo**: Add reward model and RL training utilities ([`aba4bae`](https://github.com/atomwalk12/linalg-zero/commit/aba4baefdbbe9c21c01ed82d2a062f788062f9d6))
28
+ - **grpo**: Add task selection and training infrastructure ([`d82f8c6`](https://github.com/atomwalk12/linalg-zero/commit/d82f8c69506056c227825741ae7098eabbcb2067))
29
+ - **grpo**: Add tool calling agent implementation ([`b95ec50`](https://github.com/atomwalk12/linalg-zero/commit/b95ec50ce7956f897a1bde16481633cb2395c77b))
30
+ - **grpo**: Add training and evaluation scripts ([`38b99fd`](https://github.com/atomwalk12/linalg-zero/commit/38b99fd20ecf08d0fa7ce185b6094b1d782d90d9))
31
+ - **grpo**: Add utility modules for training infrastructure ([`7362699`](https://github.com/atomwalk12/linalg-zero/commit/73626995bdcfd365b40fcd6813eca5ee99f2b8c0))
32
+ - **grpo**: Add yaml config files ([`bcb5b68`](https://github.com/atomwalk12/linalg-zero/commit/bcb5b68ef026110a8fcee14da23e5bf4736c8251))
33
+ - **sft**: Add model evaluation and dataset preparation scripts ([`5e8ee4c`](https://github.com/atomwalk12/linalg-zero/commit/5e8ee4c9d8fdf90e825a8a907216dadf78e8103b))
34
+ - **sft**: Add yaml training configs ([`65ff234`](https://github.com/atomwalk12/linalg-zero/commit/65ff2348dc592d790bef130ecf593ce157ebdaf3))
35
+ - **sft**: Adjust tool evaluation callback ([`c7f2c9d`](https://github.com/atomwalk12/linalg-zero/commit/c7f2c9daf2b8506e3687bd92d1d90d6599c99aa7))
36
+ - **sft**: Improve tool calling accuracy callback with weave logging ([`823b263`](https://github.com/atomwalk12/linalg-zero/commit/823b263a447470a30993c809264dbb20494d1331))
37
+ - **sft**: Refactor diagnostics and callbacks and improve evaluation logging ([`10ae836`](https://github.com/atomwalk12/linalg-zero/commit/10ae836eb0c7af9b7756bc0690c4461f2eaf3c8d))
38
+ - **system_prompts**: Add SFT system prompt and improve tool usage guidelines ([`061d21a`](https://github.com/atomwalk12/linalg-zero/commit/061d21aa6de9dc5e0f5ff3ac97a8f71be7a83c2e))
39
+
40
+ ### Refactoring
41
+
42
+ - Reorganize training entry points ([`5a65164`](https://github.com/atomwalk12/linalg-zero/commit/5a65164ceb460701ba946c745ddf5e8a1631b73e))
43
+ - **grpo**: Improve XML parser API and validation logic ([`fa80cba`](https://github.com/atomwalk12/linalg-zero/commit/fa80cba680bd6af515bc66a66b69a255b4e3f3d1))
44
+
45
+
46
+ ## v0.3.0 (2025-09-29)
47
+
48
+ ### Bug Fixes
49
+
50
+ - Normalize grpo dataset schema
51
+ ([`8590141`](https://github.com/atomwalk12/linalg-zero/commit/8590141149580635669ffcb40b3ad89c09849772))
52
+
53
+ - **context**: Modify entropy budget validation precision
54
+ ([`b27d237`](https://github.com/atomwalk12/linalg-zero/commit/b27d2376c8399c491c52ded7bfbf0b838824d306))
55
+
56
+ - **distillation**: Align parsed_messages size with the conversations size
57
+ ([`6a0a6bd`](https://github.com/atomwalk12/linalg-zero/commit/6a0a6bd173a48de5036700379c75da4ea51eb6a6))
58
+
59
+ - this ensures simpler reasoning since the two arrays contain similar data - fix small problems
60
+ around malformed messages, which are not silently skipped
61
+
62
+ - **distillation**: Solve accumulation problem with tool-use statistics
63
+ ([`523f2ec`](https://github.com/atomwalk12/linalg-zero/commit/523f2ecf0156b805bcb1b2d9aa14852de00e5acd))
64
+
65
+ - **entropy**: Allow data generation using varying precision levels
66
+ ([`08bbddd`](https://github.com/atomwalk12/linalg-zero/commit/08bbddd6bf699bfa0327381cc24da303afea9dd9))
67
+
68
+ - **generation**: Add difficulty rating based number of tool calls performed to reach a solution
69
+ ([`aa2bace`](https://github.com/atomwalk12/linalg-zero/commit/aa2bacea495661d2cbf23117bd751f079e1e3069))
70
+
71
+ - **generation**: Adjust matrices format and introduce better organization for the difficulty levels
72
+ ([`00bb08f`](https://github.com/atomwalk12/linalg-zero/commit/00bb08f44a4ac4a6bda4559a7ada28bc73084811))
73
+
74
+ - **generation**: Fix circular dependency by moving the library types to a separate file
75
+ ([`33ee32a`](https://github.com/atomwalk12/linalg-zero/commit/33ee32a3e2c26faa7da170b3adba812c1f6661ce))
76
+
77
+ - **generator**: Use Dirichlet distribution for entropy allocation
78
+ ([`0a16a52`](https://github.com/atomwalk12/linalg-zero/commit/0a16a52590aa038e3d2780e6378be275bd5e8b1b))
79
+
80
+ ### Documentation
81
+
82
+ - Improve generated statistics on tool execution
83
+ ([`7c62398`](https://github.com/atomwalk12/linalg-zero/commit/7c62398a065a2c746dd95ed5b2bd21314b2bab29))
84
+
85
+ ### Features
86
+
87
+ - Enhance distillation pipeline with new model configurations and diagnostics support
88
+ ([`0559239`](https://github.com/atomwalk12/linalg-zero/commit/05592398c9be27b9c364643ddc5b88cb19f4cdfe))
89
+
90
+ - Update distillation configurations
91
+ ([`b43e149`](https://github.com/atomwalk12/linalg-zero/commit/b43e149a411f3f4587d76fbdd67d135cac91896c))
92
+
93
+ - **base_generator**: Add ability to redirect arbitrarily matrix component across composition
94
+ contexts
95
+ ([`1c57a61`](https://github.com/atomwalk12/linalg-zero/commit/1c57a61ca13327ec2bde9be7539cb5e331452f9f))
96
+
97
+ - **distillation**: Add base script to run the distillation pipeline
98
+ ([`2189bb9`](https://github.com/atomwalk12/linalg-zero/commit/2189bb9303c0a2bb84331a81e200d9dae19724bd))
99
+
100
+ - simplifies the previous approach to use only 1 task for multi-turn conversations
101
+
102
+ - **generation**: Add entropy allocation using dirichlet distribution
103
+ ([`03d4bcb`](https://github.com/atomwalk12/linalg-zero/commit/03d4bcb88a8ad7f3c2cb9b6e89da3f30e71e816d))
104
+
105
+ - **generation**: Add matrix vector multiplication generator
106
+ ([`a1e0ff1`](https://github.com/atomwalk12/linalg-zero/commit/a1e0ff1c2f44a48aef44d53ec68ffa27f3494788))
107
+
108
+ - **generation**: Add result verification step involving sympy-primitive conversion
109
+ ([`07f8d51`](https://github.com/atomwalk12/linalg-zero/commit/07f8d517e886e46821fc6c8fa09a4bba00ab6e78))
110
+
111
+ - **generation**: Generate a fixed number of examples per class
112
+ ([`4eb251a`](https://github.com/atomwalk12/linalg-zero/commit/4eb251ac933e708520a46e22ae5f6e9dd47cb113))
113
+
114
+ - **generator**: Add composition infrastructure to allow mixture of problems
115
+ ([`d08820b`](https://github.com/atomwalk12/linalg-zero/commit/d08820bfa258f85cb5d533c9ccac5bb26940bcce))
116
+
117
+ - **generator**: Add constrained matrix generation for composed components
118
+ ([`76faaf1`](https://github.com/atomwalk12/linalg-zero/commit/76faaf10984ff94b911252147c77c9a41a633512))
119
+
120
+ - **generator**: Add difficulty data-class for managing difficulty parameters
121
+ ([`33eae33`](https://github.com/atomwalk12/linalg-zero/commit/33eae3355afb9255f5252eb9c661fecec612130d))
122
+
123
+ - **generator**: Add frobenius norm generator atomic operation
124
+ ([`c3c53b7`](https://github.com/atomwalk12/linalg-zero/commit/c3c53b7e6f036f99c8ae142dd0aad25c7113c1b4))
125
+
126
+ - refactor to differentiate between independent and dependent components - add tests for composing
127
+ linear-system -> matrix multiplication -> frobenius norm
128
+
129
+ - **generator**: Add frobenius norm solver
130
+ ([`7e346b3`](https://github.com/atomwalk12/linalg-zero/commit/7e346b36aad034a14d0a1ac71f6a2f25115a783d))
131
+
132
+ - **generator**: Add generator for solving linear systems
133
+ ([`37faf45`](https://github.com/atomwalk12/linalg-zero/commit/37faf45a86b1c0068d19a1391174c1618c7ca77b))
134
+
135
+ - **generator**: Add integer/rational number generators using on controllable complexity
136
+ ([`731100f`](https://github.com/atomwalk12/linalg-zero/commit/731100f609c6591a1fe2297ff0c64294195035d2))
137
+
138
+ - **generator**: Add matrix cofactor generator and related components
139
+ ([`347e8b3`](https://github.com/atomwalk12/linalg-zero/commit/347e8b3388ae2ed5858a3b78f1fadb2ec15a44ba))
140
+
141
+ - **generator**: Add matrix inverse generator and related components
142
+ ([`0154fef`](https://github.com/atomwalk12/linalg-zero/commit/0154fef50dfaa6a31dea0eeb78abd51144eca211))
143
+
144
+ - **generator**: Add matrix trace generator and related functionality
145
+ ([`6cee5cd`](https://github.com/atomwalk12/linalg-zero/commit/6cee5cdc31b82b0606f1b3655e3bd592831e2514))
146
+
147
+ - **generator**: Add matrix transpose generator
148
+ ([`32eae5c`](https://github.com/atomwalk12/linalg-zero/commit/32eae5cacb8b7472da3a3db7c3e5d890883b7df6))
149
+
150
+ - **generator**: Add new problem solver for finding matrix determinant
151
+ ([`c700f05`](https://github.com/atomwalk12/linalg-zero/commit/c700f0537f49b75e59793fbe26aa861d1a36598f))
152
+
153
+ - **generator**: Add rank generator
154
+ ([`6fdedf4`](https://github.com/atomwalk12/linalg-zero/commit/6fdedf41ec9437bc3500595fe21c36ca5dc603de))
155
+
156
+ - **generator**: Add SymPy problem generation base class with context management and template
157
+ handling
158
+ ([`c94e860`](https://github.com/atomwalk12/linalg-zero/commit/c94e860ad06ff8298a6fcc1233a3de04521187d3))
159
+
160
+ - **generator**: Add template engine to help with diverse problem generation
161
+ ([`d52b4eb`](https://github.com/atomwalk12/linalg-zero/commit/d52b4ebe9844c8ad808feef50fb0cfa64b020060))
162
+
163
+ - **generator**: Add validation and wrappers for generating dependent components
164
+ ([`d557dfe`](https://github.com/atomwalk12/linalg-zero/commit/d557dfefbeb2c421d3e9f13bf11612972680622e))
165
+
166
+ - **generator**: Allow dependencies between components to be referenced across problems
167
+ ([`ebf0d4a`](https://github.com/atomwalk12/linalg-zero/commit/ebf0d4adc3209be869a7bc88bf51aa2206ee8c7d))
168
+
169
+ - **generator**: Improve dataset generation with optimized registry and added constraints to integer
170
+ generation
171
+ ([`6352e35`](https://github.com/atomwalk12/linalg-zero/commit/6352e35b4550eb65778cdbaa82c8f6f3536d704f))
172
+
173
+ - **generator**: Improve factory registration API
174
+ ([`5a13a11`](https://github.com/atomwalk12/linalg-zero/commit/5a13a1144405f9add7b0a9e271871d53448b89e1))
175
+
176
+ - **generator**: Separate matrix-matrix multiplication from matrix-vector multiplication generators
177
+ ([`b07e5bb`](https://github.com/atomwalk12/linalg-zero/commit/b07e5bb23a8d709ff46f6bae88380747ec2f7c59))
178
+
179
+ - **generator**: Simplify component logic by transferring the responsibility to the base class for
180
+ question/answer generation and verification
181
+ ([`8f880f5`](https://github.com/atomwalk12/linalg-zero/commit/8f880f5d81232fe2361fac3669d1155adff0d754))
182
+
183
+ - **grpo**: Add GRPO configuration files and reward functions
184
+ ([`04557c4`](https://github.com/atomwalk12/linalg-zero/commit/04557c46114c56fb4ad4b7cd91cbf1a3403a763e))
185
+
186
+ - Added GRPO training scripts and configuration files. - Introduced new linear algebra functions and
187
+ tools for GRPO. - Implemented dataset preparation and processing for GRPO training. - Added
188
+ support for multi-turn interactions in training. - Created a comprehensive debug dataset for
189
+ testing and validation.
190
+
191
+ - **multi-turn**: Implement multi-turn generation with tool use and verification
192
+ ([`85f9314`](https://github.com/atomwalk12/linalg-zero/commit/85f93149fc0c45fd00ec9f2d806460b051f68979))
193
+
194
+ - **templates**: Adjust the templates to ensure a consistent format
195
+ ([`7cde039`](https://github.com/atomwalk12/linalg-zero/commit/7cde039a9dacaaf417c5e57443ef50290f52db16))
196
+
197
+ - **verification**: Add checks to ensure generated data is valid
198
+ ([`af961f8`](https://github.com/atomwalk12/linalg-zero/commit/af961f83090714757b7fe53f889835201c39d02f))
199
+
200
+ ### Refactoring
201
+
202
+ - Add enum for available problem types
203
+ ([`ca9bc90`](https://github.com/atomwalk12/linalg-zero/commit/ca9bc903188b9b6ba33d718548b5f24300e58eaa))
204
+
205
+ - Auto entropy allocation in base class based on difficulty
206
+ ([`6f56296`](https://github.com/atomwalk12/linalg-zero/commit/6f56296e69e39224017446808ffb56d71a981d4b))
207
+
208
+ - Centralise using a mixin class the template generation method to ensure consistent generation
209
+ across all components
210
+ ([`0b6b3c7`](https://github.com/atomwalk12/linalg-zero/commit/0b6b3c727674fbf6ab319c0196f26a99684789ba))
211
+
212
+ - Customise question layout
213
+ ([`d527db0`](https://github.com/atomwalk12/linalg-zero/commit/d527db0befed2da79a2cde57d0d894a05ebb13af))
214
+
215
+ - Minor tweaks
216
+ ([`d72ec79`](https://github.com/atomwalk12/linalg-zero/commit/d72ec79e23bf9049262ac05dd1207423f8a9aa35))
217
+
218
+ - Simplify tool accuracy callback
219
+ ([`8305221`](https://github.com/atomwalk12/linalg-zero/commit/8305221c2759d9b9df45c2e8c31baf2355b24e15))
220
+
221
+ - **composition**: Remove unnecessary code
222
+ ([`50f0258`](https://github.com/atomwalk12/linalg-zero/commit/50f02586d080b4edde21014d09d345d49ef5b959))
223
+
224
+ - **generator**: Add GenerationConstraints for matrix generation constraints
225
+ ([`a5f763f`](https://github.com/atomwalk12/linalg-zero/commit/a5f763f0f077345edb400b496f0e148ccd7b5942))
226
+
227
+ - **generator**: Encapsulate generation parameters in the config
228
+ ([`b89cabb`](https://github.com/atomwalk12/linalg-zero/commit/b89cabbc62c04fa44041cd5d5c56c1703410af8f))
229
+
230
+ - **generator**: Improve type safety and update function signatures and imports
231
+ ([`16cd9ea`](https://github.com/atomwalk12/linalg-zero/commit/16cd9eade56b3f44476af1ea3da0c820c71e6ba3))
232
+
233
+ - **generator**: Improve type safety and update function signatures and imports
234
+ ([`39f080e`](https://github.com/atomwalk12/linalg-zero/commit/39f080e4809ed96ed3b72f7a668d2893bdd1d1ef))
235
+
236
+ - **generator**: Sample entropy in a centralized place within the context
237
+ ([`d920fcf`](https://github.com/atomwalk12/linalg-zero/commit/d920fcfb1c81241c14eb3118e28df411fa87671a))
238
+
239
+ - **generator**: Unify problem types and improve entropy management
240
+ ([`fc309c2`](https://github.com/atomwalk12/linalg-zero/commit/fc309c247ac07d0ed05de3227a275c408db6bfea))
241
+
242
+ - **generator**: Update matrix operations to use centralized library calls
243
+ ([`bdd537b`](https://github.com/atomwalk12/linalg-zero/commit/bdd537be0b628b7b8f7f5da5528f0aaed00fa7ab))
244
+
245
+
246
+ ## v0.2.0 (2025-08-03)
247
+
248
+ ### Bug Fixes
249
+
250
+ - Add logging utilities for files and stdout
251
+ ([`d2692e6`](https://github.com/atomwalk12/linalg-zero/commit/d2692e68bbedabb5d110582737b3d995e26d64a2))
252
+
253
+ - Be granular about exceptions being thrown
254
+ ([`4c88f9f`](https://github.com/atomwalk12/linalg-zero/commit/4c88f9fc8a14effb11b49ce90fb323122e0c4916))
255
+
256
+ Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
257
+
258
+ - Ensure an exception is raised if the subprocess returns a non-zero exit code
259
+ ([`4695ab9`](https://github.com/atomwalk12/linalg-zero/commit/4695ab9c9afa4b56ec4167d99777b6b230d01ac7))
260
+
261
+ Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
262
+
263
+ - Finetune the number of Llama-cpp GPU layers offloaded to the GPU
264
+ ([`a37fc13`](https://github.com/atomwalk12/linalg-zero/commit/a37fc130ab9b04be0ecccfe1278c6e1fe68d3350))
265
+
266
+ - **dependencies**: Pin llama-cpp-python version to 0.3.13), update distilabel dependencies and add
267
+ lock file
268
+ ([`ef18d53`](https://github.com/atomwalk12/linalg-zero/commit/ef18d53a9fda569c6148ab8130557ce689d7c7ba))
269
+
270
+ - **distillation**: Add script to push debugging dataset to huggingface
271
+ ([`6d6c91a`](https://github.com/atomwalk12/linalg-zero/commit/6d6c91ac275aaaee1d145bbb2d99296da823c617))
272
+
273
+ - **inference**: Add hf_pretrained_model_name_or_path and remove redundant installation in launch
274
+ script
275
+ ([`75be580`](https://github.com/atomwalk12/linalg-zero/commit/75be58003bf3fd321deb185d117c7fc43ba11a57))
276
+
277
+ ### Build System
278
+
279
+ - Remove python 3.9 support
280
+ ([`e7421e6`](https://github.com/atomwalk12/linalg-zero/commit/e7421e611029b15f21ac9dcef2c4e14d97e99f9b))
281
+
282
+ ### Features
283
+
284
+ - Add configuration parameters, tasks for running distributed training and pin dependencies
285
+ ([`180fa5f`](https://github.com/atomwalk12/linalg-zero/commit/180fa5f4ccabf0f45d5c02371b80d0774a9cace5))
286
+
287
+ - Add dataset generator utility
288
+ ([`279b0bd`](https://github.com/atomwalk12/linalg-zero/commit/279b0bdcf867b586c3b201afe425da1e93f2bfea))
289
+
290
+ - Add workflow to generate a new dataset
291
+ ([`e0ba35d`](https://github.com/atomwalk12/linalg-zero/commit/e0ba35d7792309627dcca34293d11cb758ac3def))
292
+
293
+ - Implement question generation factories for arithmetic and linear algebra
294
+ ([`f20fb2c`](https://github.com/atomwalk12/linalg-zero/commit/f20fb2c97d825d059eb6612d1f7cd4504c41f74c))
295
+
296
+ - make use of a global registry to keep track of the various problem definitions
297
+
298
+ - **distillation**: Add centralised control for launching the inference server
299
+ ([`23a4078`](https://github.com/atomwalk12/linalg-zero/commit/23a4078dc59d1706989850f32d67786db32191ec))
300
+
301
+ - **distillation**: Add filter to easily track and discard incorrect results
302
+ ([`d991534`](https://github.com/atomwalk12/linalg-zero/commit/d991534a1f6eee0c691520f7d0a53bbdd0fbb341))
303
+
304
+ - **distillation**: Add generation pipeline
305
+ ([`d18317f`](https://github.com/atomwalk12/linalg-zero/commit/d18317f32e6e03dbb04e124560c58c731af57ba9))
306
+
307
+ - **distillation**: Add local script for llama.cpp inference server
308
+ ([`bf1c3e1`](https://github.com/atomwalk12/linalg-zero/commit/bf1c3e18ff1ff5cf39b47b8df3319898b2f41c3a))
309
+
310
+ - **distillation**: Add planning and tool selection
311
+ ([`6b9e3e2`](https://github.com/atomwalk12/linalg-zero/commit/6b9e3e2e952267ce9cd5796bcb0d448a48f98a3e))
312
+
313
+ - **distillation**: Add result synthesiser
314
+ ([`017a47c`](https://github.com/atomwalk12/linalg-zero/commit/017a47ca3204fb0a27c21532ba1e9fd480dae7a6))
315
+
316
+ - **distillation**: Add the argilla components for simpler result inspection
317
+ ([`20b7649`](https://github.com/atomwalk12/linalg-zero/commit/20b7649f88621cd5d5fea11624ce4e5fde8441de))
318
+
319
+ - **distillation**: Add the planner component
320
+ ([`ed5175a`](https://github.com/atomwalk12/linalg-zero/commit/ed5175aca518ed6d593ae863e00f6e84a02f6f4e))
321
+
322
+ - **distillation**: Add verl dependency for GRPO
323
+ ([`9d7ccb6`](https://github.com/atomwalk12/linalg-zero/commit/9d7ccb65c8488760e566cda5aabb345ef41a78f6))
324
+
325
+ - **distillation**: Code execution component
326
+ ([`24072e7`](https://github.com/atomwalk12/linalg-zero/commit/24072e75338875335d1efd226df6b72339f0c3c4))
327
+
328
+ - **distillation**: Customise the chat generation pipeline to preserve input/output results
329
+ ([`9973a10`](https://github.com/atomwalk12/linalg-zero/commit/9973a1009b1400a3e0cc0e0562b1464ffbd291b8))
330
+
331
+ - **distillation**: Demonstrate the tool selection component and update planner to use
332
+ ChatGeneration
333
+ ([`8fe4e0a`](https://github.com/atomwalk12/linalg-zero/commit/8fe4e0a840dfa665b80a89b2a62e3f07fb5806d5))
334
+
335
+ - **distillation**: Implement function calling pipeline using Llama-cpp
336
+ ([`f8776ce`](https://github.com/atomwalk12/linalg-zero/commit/f8776cedf82002f1c08a18f9252a929d70991dd1))
337
+
338
+ - **distillation**: Improve launch script to download models from the hf-hub and tune configuration
339
+ parameters
340
+ ([`6fc9503`](https://github.com/atomwalk12/linalg-zero/commit/6fc950382725f00268b4b9b671c90abed1f3ab02))
341
+
342
+ - **distillation**: Integrate all related components to generate new data (completed pipeline)
343
+ ([`c8532a4`](https://github.com/atomwalk12/linalg-zero/commit/c8532a4d5a6da3bfb7ce82e6de10f81f033ab826))
344
+
345
+ - **distillation**: Integrate math-verify for formal evaluation of the output
346
+ ([`a2c3757`](https://github.com/atomwalk12/linalg-zero/commit/a2c3757e216d462ed9915a179e517382c68639bd))
347
+
348
+ - **sft**: Add additional callbacks (i.e. evaluation, push revision to hub, early stopping)
349
+ ([`90e7f35`](https://github.com/atomwalk12/linalg-zero/commit/90e7f3532e0ca502f0edda571e5b33889cb41b89))
350
+
351
+ - **sft**: Complete evaluation with the ability to resume training, log results via wandb, create
352
+ model cards and save to the huggingface hub
353
+ ([`a7d86a3`](https://github.com/atomwalk12/linalg-zero/commit/a7d86a3e5c59ab527e9d3ebc5779f4d35065b2e7))
354
+
355
+ ### Testing
356
+
357
+ - Add dataset configuration file
358
+ ([`5372677`](https://github.com/atomwalk12/linalg-zero/commit/5372677debfcb91b17c0bbc5af665bd19872c3ba))
359
+
360
+ - Check default registry configuration
361
+ ([`47f190a`](https://github.com/atomwalk12/linalg-zero/commit/47f190a94e045650b3c1cffee3d4888d26b16164))
362
+
363
+
364
+ ## v0.1.0 (2025-07-08)
365
+
366
+ ### Bug Fixes
367
+
368
+ - Add container fix for Docker
369
+ ([`bf3884b`](https://github.com/atomwalk12/linalg-zero/commit/bf3884b10d03f6dfa253944c2497297bc91d32d2))
370
+
371
+ - Improve release pipeline to include semantic releases
372
+ ([`888738c`](https://github.com/atomwalk12/linalg-zero/commit/888738c4b914c65e001025ca3ba4473c1712f235))
373
+
374
+ ### Features
375
+
376
+ - Add additional tasks
377
+ ([`c036f31`](https://github.com/atomwalk12/linalg-zero/commit/c036f31ba63af43b205e7a73464935f98f602773))
378
+
379
+ - Add codeql support
380
+ ([`9b337c6`](https://github.com/atomwalk12/linalg-zero/commit/9b337c6eb7892943e2e6e1d004ec8611f868e0bd))
381
+
382
+ - Add gemini style guide
383
+ ([`0f65ed1`](https://github.com/atomwalk12/linalg-zero/commit/0f65ed1847f72f900cf3d37e0766e1d06269c451))
384
+
385
+ - Set up semantic release
386
+ ([`a97ecd3`](https://github.com/atomwalk12/linalg-zero/commit/a97ecd319d69c348b1a3a760c00ad6678a7b7339))
387
+
388
+ - Update mkdocs theme
389
+ ([`8dd0a40`](https://github.com/atomwalk12/linalg-zero/commit/8dd0a40dd627c64ce868d557b8c889c7c6a24d0d))
CONTRIBUTING.md ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to `linalg-zero`
2
+
3
+ Contributions are welcome, and they are greatly appreciated!
4
+ Every little bit helps, and credit will always be given.
5
+
6
+ You can contribute in many ways:
7
+
8
+ # Types of Contributions
9
+
10
+ ## Report Bugs
11
+
12
+ Report bugs at https://github.com/atomwalk12/linalg-zero/issues
13
+
14
+ If you are reporting a bug, please include:
15
+
16
+ - Your operating system name and version.
17
+ - Any details about your local setup that might be helpful in troubleshooting.
18
+ - Detailed steps to reproduce the bug.
19
+
20
+ ## Fix Bugs
21
+
22
+ Look through the GitHub issues for bugs.
23
+ Anything tagged with "bug" and "help wanted" is open to whoever wants to implement a fix for it.
24
+
25
+ ## Implement Features
26
+
27
+ Look through the GitHub issues for features.
28
+ Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it.
29
+
30
+ ## Write Documentation
31
+
32
+ linalg-zero could always use more documentation, whether as part of the official docs, in docstrings, or even on the web in blog posts, articles, and such.
33
+
34
+ ## Submit Feedback
35
+
36
+ The best way to send feedback is to file an issue at https://github.com/atomwalk12/linalg-zero/issues.
37
+
38
+ If you are proposing a new feature:
39
+
40
+ - Explain in detail how it would work.
41
+ - Keep the scope as narrow as possible, to make it easier to implement.
42
+ - Remember that this is a volunteer-driven project, and that contributions
43
+ are welcome :)
44
+
45
+ # Get Started!
46
+
47
+ Ready to contribute? Here's how to set up `linalg-zero` for local development.
48
+ Please note this documentation assumes you already have `uv` and `Git` installed and ready to go.
49
+
50
+ 1. Fork the `linalg-zero` repo on GitHub.
51
+
52
+ 2. Clone your fork locally:
53
+
54
+ ```bash
55
+ cd <directory_in_which_repo_should_be_created>
56
+ git clone git@github.com:YOUR_NAME/linalg-zero.git
57
+ ```
58
+
59
+ 3. Now we need to install the environment. Navigate into the directory
60
+
61
+ ```bash
62
+ cd linalg-zero
63
+ ```
64
+
65
+ Then, install and activate the environment with:
66
+
67
+ ```bash
68
+ uv sync --group dev --group test
69
+ ```
70
+
71
+ 4. Install pre-commit to run linters/formatters at commit time:
72
+
73
+ ```bash
74
+ uv run pre-commit install
75
+ ```
76
+
77
+ 5. Create a branch for local development:
78
+
79
+ ```bash
80
+ git checkout -b name-of-your-bugfix-or-feature
81
+ ```
82
+
83
+ Now you can make your changes locally.
84
+
85
+ 6. Don't forget to add test cases for your added functionality to the `tests` directory.
86
+
87
+ 7. When you're done making changes, check that your changes pass the formatting tests.
88
+
89
+ ```bash
90
+ make check
91
+ ```
92
+
93
+ Now, validate that all unit tests are passing:
94
+
95
+ ```bash
96
+ make test
97
+ ```
98
+
99
+ 9. Before raising a pull request you should also run tox.
100
+ This will run the tests across different versions of Python:
101
+
102
+ ```bash
103
+ tox
104
+ ```
105
+
106
+ This requires you to have multiple versions of python installed.
107
+ This step is also triggered in the CI/CD pipeline, so you could also choose to skip this step locally.
108
+
109
+ 10. Commit your changes and push your branch to GitHub:
110
+
111
+ ```bash
112
+ git add .
113
+ git commit -m "Your detailed description of your changes."
114
+ git push origin name-of-your-bugfix-or-feature
115
+ ```
116
+
117
+ 11. Submit a pull request through the GitHub website.
118
+
119
+ # Pull Request Guidelines
120
+
121
+ Before you submit a pull request, check that it meets these guidelines:
122
+
123
+ 1. The pull request should include tests.
124
+
125
+ 2. If the pull request adds functionality, the docs should be updated.
126
+ Put your new functionality into a function with a docstring, and add the feature to the list in `README.md`.
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Install uv
2
+ FROM python:3.12-slim
3
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
4
+
5
+ # Change the working directory to the `app` directory
6
+ WORKDIR /app
7
+
8
+ # Copy the lockfile and `pyproject.toml` into the image
9
+ COPY uv.lock /app/uv.lock
10
+ COPY pyproject.toml /app/pyproject.toml
11
+
12
+ # Install dependencies
13
+ RUN uv sync --frozen --no-install-project
14
+
15
+ # Copy the project into the image
16
+ COPY . /app
17
+
18
+ # Sync the project
19
+ RUN uv sync --frozen
20
+
21
+ CMD [ "python", "linalg_zero/generate.py" ]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Razvan Florian Vasile
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
Makefile ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## NOTE: the llama-cpp server is used to startup the inference server using `make distillation-server`
2
+ # Fixing the llama-cpp server version to 0.3.13 as the upstream repository gets updated frequently
3
+ # leading to incompatibility issues. If bumping the version don't forget to update pyproject.toml.
4
+ .PHONY: install-data-gen
5
+ install-data-gen: ## Install the virtual environment and install the pre-commit hooks.
6
+ @echo "🚀 Creating virtual environment using uv"
7
+ @uv sync --locked --group data-gen
8
+ @uv pip install setuptools flash-attn==2.7.3 --no-build-isolation
9
+ @uv run pre-commit install
10
+
11
+ .PHONY: install-sft
12
+ install-sft: ## Install the virtual environment and install the pre-commit hooks.
13
+ @echo "🚀 Creating virtual environment using uv"
14
+ @uv sync --locked --group sft
15
+ @uv pip install --upgrade setuptools ninja
16
+ @uv pip install --upgrade --torch-backend cu128 "xformers==0.0.33.post1"
17
+ @TORCH_CUDA_ARCH_LIST=8.9 MAX_JOBS=3 uv pip install --upgrade --no-build-isolation "flash-attn==2.8.3"
18
+ @uv run pre-commit install
19
+
20
+ .PHONY: install-grpo
21
+ install-grpo: ## Install the virtual environment and install the pre-commit hooks.
22
+ @echo "🚀 Creating virtual environment using uv"
23
+ @uv sync --locked --group grpo
24
+ @uv run pre-commit install
25
+
26
+ .PHONY: setup-dev
27
+ setup-dev: ## Setup the development environment
28
+ @echo "🚀 Setting up development environment"
29
+ @uv run linalg_zero/distillation/scripts/push_debug_dataset.py --dataset-name atomwalk12/linalg-debug --private
30
+
31
+ .PHONY: check
32
+ check: ## Run code quality tools.
33
+ @echo "🚀 Checking lock file consistency with 'pyproject.toml'"
34
+ @uv lock --locked
35
+ @echo "🚀 Linting code: Running pre-commit"
36
+ ifeq ($(CI),true)
37
+ @echo "🔍 CI detected: Running ruff in check mode"
38
+ @uv run ruff check .
39
+ @uv run ruff format --check .
40
+ @SKIP=ruff,ruff-format uv run pre-commit run -a
41
+ else
42
+ @uv run pre-commit run -a
43
+ endif
44
+ @echo "🚀 Static type checking: Running mypy"
45
+ @uv run mypy
46
+ @echo "🚀 Checking for obsolete dependencies: Running deptry"
47
+ @uv run deptry .
48
+
49
+ .PHONY: test
50
+ test: ## Test the code with pytest
51
+ @echo "🚀 Testing code: Running pytest"
52
+ @uv run python -m pytest --cov --cov-config=pyproject.toml --cov-report=xml
53
+
54
+ .PHONY: coverage-site
55
+ coverage-site: ## Generate coverage report in HTML format
56
+ @echo "🚀 Generating coverage report in HTML format"
57
+ @uv run coverage html
58
+
59
+ .PHONY: build
60
+ build: clean-build ## Build wheel file
61
+ @echo "🚀 Creating wheel file"
62
+ @uvx --from build pyproject-build --installer uv
63
+
64
+ .PHONY: clean-build
65
+ clean-build: ## Clean build artifacts
66
+ @echo "🚀 Removing build artifacts"
67
+ @uv run python -c "import shutil; import os; shutil.rmtree('dist') if os.path.exists('dist') else None"
68
+
69
+ .PHONY: publish
70
+ publish: ## Publish a release to PyPI.
71
+ @echo "🚀 Publishing."
72
+ @uvx twine upload --repository-url https://upload.pypi.org/legacy/ dist/*
73
+
74
+ .PHONY: build-and-publish
75
+ build-and-publish: build publish ## Build and publish.
76
+
77
+ .PHONY: docs-test
78
+ docs-test: ## Test if documentation can be built without warnings or errors
79
+ @echo "🚀 Testing documentation build"
80
+ @uv run mkdocs build -s
81
+
82
+ .PHONY: docs
83
+ docs: ## Build and serve the documentation
84
+ @echo "🚀 Building and serving documentation"
85
+ @uv run mkdocs serve
86
+
87
+ .PHONY: semantic-release
88
+ semantic-release: ## Test semantic release
89
+ @echo "🚀 Testing semantic release"
90
+ @uv run semantic-release -vv --noop version --print
91
+
92
+ .PHONY: gh-deploy
93
+ gh-deploy: ## Deploy the documentation to GitHub Pages
94
+ @echo "🚀 Deploying documentation to GitHub Pages"
95
+ @uv run mkdocs gh-deploy --force
96
+
97
+ LLAMACPP_CONFIG=linalg_zero/config/distillation/llamacpp_qwen3_32b_instruct.yaml
98
+ VLLM_CONFIG=linalg_zero/config/distillation/vllm_qwen3_32b.yaml
99
+
100
+ .PHONY: distillation-llamacpp
101
+ distillation-llamacpp: ## Start the llama.cpp server
102
+ @echo "🚀 Starting llama.cpp server"
103
+ @INFERENCE_BACKEND=llamacpp uv run python linalg_zero/distillation/launch_server.py --config $(LLAMACPP_CONFIG)
104
+
105
+ .PHONY: distillation-vllm
106
+ distillation-vllm: ## Start the vLLM server
107
+ @echo "🚀 Starting vLLM server"
108
+ @. ./env.sh && uv run python linalg_zero/distillation/launch_server.py --config ${VLLM_CONFIG}
109
+
110
+ .PHONY: distillation
111
+ distillation: ## Run the distillation pipeline using the vllm config
112
+ @echo "🚀 Running distillation pipeline"
113
+ @. ./env.sh && uv run python linalg_zero/distillation.py --config ${VLLM_CONFIG}
114
+
115
+ .PHONY: distillation-vllm-local
116
+ distillation-vllm-local: ## Start the vLLM server
117
+ @echo "🚀 Starting vLLM server"
118
+ @export USING_VLLM=true INFERENCE_BACKEND=vllm && uv run python linalg_zero/distillation/launch_server.py --config linalg_zero/config/distillation/vllm_qwen3_4b_think.yaml
119
+
120
+
121
+ .PHONY: distillation-local
122
+ distillation-debug: ## Start the vLLM server
123
+ @echo "🚀 Starting vLLM server"
124
+ @export USING_VLLM=true && uv run python linalg_zero/distillation.py --config linalg_zero/config/distillation/vllm_qwen3_4b_think.yaml
125
+
126
+
127
+ # SFT Training Commands
128
+ SFT_CONFIG=linalg_zero/config/sft/sft_debug_config.yaml
129
+ # SFT_CONFIG=linalg_zero/config/sft/sft_config.yaml
130
+ ACCELERATE_CONFIG=linalg_zero/config/sft/accelerate/zero3.yaml
131
+
132
+ .PHONY: sft-debug
133
+ sft-debug: ## Run SFT training on single GPU
134
+ @echo "🚀 Running SFT training on single GPU"
135
+ @uv run python linalg_zero/sft.py --config $(SFT_CONFIG)
136
+
137
+
138
+ .PHONY: sft-distributed
139
+ sft-distributed: ## Run SFT training with distributed setup using DeepSpeed ZeroStage 3
140
+ @echo "🚀 Running distributed SFT training with DeepSpeed"
141
+ @uv run accelerate launch --config_file=$(ACCELERATE_CONFIG) linalg_zero/sft.py --config $(SFT_CONFIG)
142
+
143
+ .PHONY: prepare-grpo-dataset
144
+ prepare-grpo-dataset: ## Prepare the GRPO dataset
145
+ @echo "🚀 Creating GRPO dataset"
146
+ @uv run linalg_zero/grpo/process_dataset.py
147
+
148
+ .PHONY: generate-optimised-config
149
+ generate-optimised-config: ## Generate the optimised config
150
+ @echo "🚀 Generating optimised config"
151
+ @uv run linalg_zero/generator/analysis/analyse.py
152
+
153
+ .PHONY: run-training
154
+ run-training: ## Run the training pipeline
155
+ @echo "🚀 Running training pipeline"
156
+ @$(MAKE) setup-dev
157
+ @$(MAKE) prepare-grpo-dataset
158
+ @echo "🚀 Training pipeline completed"
159
+
160
+
161
+ .PHONY: help
162
+ help:
163
+ @uv run python -c "import re; \
164
+ [[print(f'\033[36m{m[0]:<20}\033[0m {m[1]}') for m in re.findall(r'^([a-zA-Z_-]+):.*?## (.*)$$', open(makefile).read(), re.M)] for makefile in ('$(MAKEFILE_LIST)').strip().split()]"
165
+
166
+ .DEFAULT_GOAL := help
README.md CHANGED
@@ -1,12 +1,155 @@
1
  ---
2
- title: Linalg Zero
3
- emoji: 🐨
4
- colorFrom: pink
5
- colorTo: blue
6
  sdk: gradio
7
- sdk_version: 6.9.0
8
- app_file: app.py
 
9
  pinned: false
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Linalg-Zero
3
+ emoji: 🧠
4
+ colorFrom: indigo
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: "6.9.0"
8
+ python_version: "3.12.12"
9
+ app_file: linalg_zero/demo/app.py
10
  pinned: false
11
  ---
12
 
13
+ [![Release](https://img.shields.io/github/v/release/atomwalk12/linalg-zero)](https://img.shields.io/github/v/release/atomwalk12/linalg-zero)
14
+ [![Build status](https://img.shields.io/github/actions/workflow/status/atomwalk12/linalg-zero/main.yml?branch=main)](https://github.com/atomwalk12/linalg-zero/actions/workflows/main.yml?query=branch%3Amain)
15
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
16
+
17
+ # Linalg-Zero
18
+
19
+ ## Overview
20
+
21
+ <details>
22
+ <summary>Table of Contents</summary>
23
+ <ol>
24
+ <li><a href="#overview">Overview</a></li>
25
+ <li><a href="#main-phases">Main Phases</a></li>
26
+ <li><a href="#installation">Installation</a></li>
27
+ <li><a href="#quickstart">Quickstart</a></li>
28
+ <li><a href="#results">Results</a></li>
29
+ <li><a href="#artifacts">Artifacts</a></li>
30
+ <li><a href="#reproducibility">Reproducibility</a></li>
31
+ <li><a href="#acknowledgements">Acknowledgements</a></li>
32
+ </ol>
33
+ </details>
34
+
35
+ This repository offers tools for generating a linear algebra problem dataset and training an open-source base model (i.e. Qwen2.5-3B), aiming to explore planning and tool use using SFT and RL, distinct from Deepseek-R1's primary emphasis on reasoning.
36
+
37
+ The project is simple by design and mostly consists of:
38
+
39
+ - `linalg_zero/`: contains the scripts to train models as well as generate synthetic data:
40
+ - `generate.py`: generates the linear algebra dataset and splits.
41
+ - `distillation.py`: runs the distillation pipeline to create multi-turn tool-use data.
42
+ - `sft_train.py`: performs a simple SFT of a model on a dataset.
43
+ - `grpo_train.py`: trains a model with GRPO on a given dataset.
44
+ - `Makefile`: contains easy-to-run commands for the dataset and training workflows using previous scripts.
45
+
46
+ ## Main Phases
47
+
48
+ We use the DeepSeek-R1 [tech report](https://github.com/deepseek-ai/DeepSeek-R1) as a loose guide, but the project phases are:
49
+
50
+ * Step 1: generate a linear algebra dataset with controlled difficulty and tool-call metadata.
51
+ * Step 2: distill multi-turn tool-use data from a teacher model.
52
+ * Step 3: SFT the base model on the dataset to teach the tool-calling format.
53
+ * Step 4: GRPO fine-tune on the tool-use tasks, using a curriculum.
54
+
55
+
56
+ ## Installation
57
+
58
+ We use `uv` as the dependency management tool.
59
+ First, to install `uv`, follow the [UV Installation Guide](https://docs.astral.sh/uv/getting-started/installation/).
60
+
61
+ To run the experiments install the dependencies using:
62
+
63
+ * For generation/distillation: `make install-data-gen`
64
+ * For SFT: `make install-sft`
65
+ * For RL: `make install-grpo`
66
+
67
+ Next, log into your Hugging Face and Weights and Biases accounts as follows:
68
+
69
+ ```shell
70
+ huggingface-cli login
71
+ wandb login
72
+ ```
73
+
74
+ ## Quickstart
75
+
76
+ After installing dependencies above, run the commands below. For modifications, see the config files.
77
+
78
+ ```shell
79
+ # Phase 1: Generate dataset
80
+ uv run python linalg_zero/generate.py --dataset_name atomwalk12/linalgzero --push_dataset
81
+
82
+ # Phase 2: Distillation (setup once)
83
+ cp linalg_zero/config/distillation/env.example.sh env.sh
84
+ # Edit env.sh to set HF_TOKEN and ARGILLA_API_KEY.
85
+ source env.sh
86
+
87
+ # Terminal A
88
+ uv run python linalg_zero/distillation/launch_server.py --config linalg_zero/config/distillation/vllm_qwen3_32b.yaml
89
+
90
+ # Terminal B (new terminal; source env.sh again)
91
+ source env.sh
92
+ uv run python linalg_zero/distillation.py --config linalg_zero/config/distillation/vllm_qwen3_32b.yaml
93
+
94
+ # Phase 3: SFT
95
+ uv run python linalg_zero/sft_train.py --config linalg_zero/config/sft/qwen2.5-3B/lora.yaml
96
+
97
+ # Phase 4: GRPO
98
+ uv run python linalg_zero/grpo_train.py --config-name runpod.yaml
99
+ ```
100
+
101
+ Training requires the dataset to follow the strict OpenAI tool-calling format (see [this link](https://huggingface.co/docs/trl/en/dataset_formats#tool-calling)). We provide scripts to prepare and validate the data accordingly:
102
+
103
+ - `linalg_zero/`
104
+ - `sft/scripts/prepare_dataset.py`: prepares the SFT dataset.
105
+ - `grpo/scripts/prepare_dataset.py`: prepares and validates the GRPO dataset.
106
+
107
+ ## Results
108
+
109
+ We provide a recipe to encourage planning and tool-use capabilities in the [Qwen2.5-3B](https://huggingface.co/Qwen/Qwen2.5-3B) model, starting from a pre-trained (not instruction-tuned) base model.
110
+
111
+ This yields models like [Linalg-Zero-SFT](https://huggingface.co/atomwalk12/LinalgZero-SFT) and [Linalg-Zero-GRPO](https://huggingface.co/atomwalk12/LinalgZero-GRPO), with the following downstream performance on the test set:
112
+
113
+
114
+ | Metric | LinAlgZero-SFT | LinAlgZero-GRPO |
115
+ |--------------------|----------------|-----------------|
116
+ | Optimal Trajectory | 89.87% | 90.26% |
117
+ | Correctness | 91.86% | 92.63% |
118
+ | Format Validity | 96.15% | 96.66% |
119
+ | Tool Success | 100.00% | 100.00% |
120
+
121
+ ### Artifacts
122
+
123
+ | Artifact | Link |
124
+ |---|---|
125
+ | SFT checkpoint | [atomwalk12/LinalgZero-SFT](https://huggingface.co/atomwalk12/LinalgZero-SFT) |
126
+ | GRPO checkpoint | [atomwalk12/LinAlgZero-GRPO](https://huggingface.co/atomwalk12/LinAlgZero-GRPO) |
127
+ | Base dataset | [atomwalk12/linalgzero](https://huggingface.co/datasets/atomwalk12/linalgzero) |
128
+ | Distilled dataset (clean) | [atomwalk12/linalgzero-distilled-clean](https://huggingface.co/datasets/atomwalk12/linalgzero-distilled-clean) |
129
+ | SFT dataset | [atomwalk12/linalgzero-sft](https://huggingface.co/datasets/atomwalk12/linalgzero-sft) |
130
+ | GRPO dataset | [atomwalk12/linalgzero-grpo](https://huggingface.co/datasets/atomwalk12/linalgzero-grpo) |
131
+
132
+ ## Reproducibility
133
+ - **Distillation:** H100 80GB on [Runpod](https://www.runpod.io/) with [Qwen/Qwen3-32B-FP8](https://huggingface.co/Qwen/Qwen3-32B-FP8); 15 hours at $2.39/hr (~$25).
134
+ - **SFT:** Local 24GB RTX 4090 with [Qwen/Qwen2.5-3B](https://huggingface.co/Qwen/Qwen2.5-3B).
135
+ - **GRPO:** RTX 6000 Ada on [Runpod](https://www.runpod.io/), improving on the SFT baseline; 57 hours at $0.77/hr (~$50).
136
+ - **Total:** ~$75 using a mix of cloud GPUs and local training.
137
+
138
+ ## Acknowledgements
139
+ - We base our distillation pipeline on [distilabel](https://github.com/argilla-io/distilabel).
140
+ - We base the RL experiment on [ART](https://deepwiki.com/OpenPipe/ART).
141
+ - We use Qwen2.5 series base model [Qwen2.5](https://github.com/QwenLM/Qwen2.5).
142
+
143
+ ## Citation
144
+
145
+ If you find this project is useful in your own work, please consider citing as follows:
146
+
147
+ ```bibtex
148
+ @misc{linalg-zero,
149
+ title = {Linalg-Zero: Distilling Neurosymbolic Reasoning for Linear Algebra in Small Language Models},
150
+ url = {https://github.com/atomwalk12/linalg-zero},
151
+ author = {{Razvan F. Vasile}},
152
+ month = {March},
153
+ year = {2026}
154
+ }
155
+ ```
app.py DELETED
@@ -1,7 +0,0 @@
1
- import gradio as gr
2
-
3
- def greet(name):
4
- return "Hello " + name + "!!"
5
-
6
- demo = gr.Interface(fn=greet, inputs="text", outputs="text")
7
- demo.launch()
 
 
 
 
 
 
 
 
codecov.yaml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ coverage:
2
+ range: 70..100
3
+ round: down
4
+ precision: 1
5
+ status:
6
+ project:
7
+ default:
8
+ target: 90%
9
+ threshold: 0.5%
env/.env.distillation.example ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Distillation
4
+ export HF_HOME=/workspace/linalg-zero/hf-cache
5
+ export HUGGINGFACE_HUB_CACHE=/workspace/linalg-zero/hf-cache
6
+ export VLLM_WORKDIR=/workspace/linalg-zero/vllm-cache
7
+ export UV_CACHE_DIR=/workspace/linalg-zero/uv-cache
8
+ export XDG_CACHE_HOME=/workspace/linalg-zero/.cache
9
+ export PIP_CACHE_DIR=/workspace/linalg-zero/pip-cache
10
+ export USING_VLLM=true
11
+ export INFERENCE_BACKEND=vllm
12
+ export ARGILLA_API_URL=https://atomwalk12-linalgzero-distilled.hf.space
13
+ export ARGILLA_API_KEY=<...>
14
+ export HF_TOKEN=<...>
env/.env.grpo.example ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ OPENPIPE_API_KEY=<insert-key>
2
+ WANDB_API_KEY=<insert-key>
3
+ VLLM_API_KEY="my-secret-key"
4
+ ACCELERATE_MIXED_PRECISION=bf16
5
+ VLLM_NO_USAGE_STATS=1
6
+
7
+ HF_HUB_NAMESPACE=<...>
8
+ HF_TOKEN=<...>
9
+ HF_REPO_PRIVATE=1
env/.env.grpo.runpod.example ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export HF_HOME=/workspace/linalg-zero/hf-cache
2
+ export HUGGINGFACE_HUB_CACHE=/workspace/linalg-zero/hf-cache
3
+ export VLLM_WORKDIR=/workspace/linalg-zero/vllm-cache
4
+ export UV_CACHE_DIR=/workspace/linalg-zero/uv-cache
5
+ export XDG_CACHE_HOME=/workspace/linalg-zero/.cache
6
+ export PIP_CACHE_DIR=/workspace/linalg-zero/pip-cache
7
+
8
+ export OPENPIPE_API_KEY=<...>
9
+ export WANDB_API_KEY=<...>
10
+ export VLLM_API_KEY="my-secret-key"
11
+ export ACCELERATE_MIXED_PRECISION=bf16
12
+ export VLLM_NO_USAGE_STATS=1
13
+
14
+ export HF_HUB_NAMESPACE=<...>
15
+ export HF_TOKEN=<...>
16
+ export HF_REPO_PRIVATE=1
linalg_zero/__init__.py ADDED
File without changes
linalg_zero/config/cleaning_config.yaml ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Initial dirty: 49 entries
2
+
3
+ ##############
4
+ # Tool calls #
5
+ ##############
6
+ 120:
7
+ to_remove: [4, 5]
8
+ remove_reason:
9
+ "4,5": "exact msg/tool call with 2,3"
10
+ # Note that when counting these fields, they start from 0.
11
+ initial_msg_count: 9
12
+ expected_final_msg_count: 7
13
+
14
+
15
+ 419:
16
+ to_remove: [8, 9]
17
+ remove_reason:
18
+ "8,9": "exact msg/tool call with 6,7"
19
+ initial_msg_count: 11
20
+ expected_final_msg_count: 9
21
+
22
+
23
+ 1203:
24
+ to_remove: [4,5]
25
+ remove_reason:
26
+ "4,5": "exact msg/tool call with 2,3"
27
+ initial_msg_count: 7
28
+ expected_final_msg_count: 5
29
+
30
+ ################
31
+ # Final answer #
32
+ ################
33
+ 1052:
34
+ to_remove: [4,5]
35
+ remove_reason:
36
+ "4,5": "exact msg/tool call with 2,3"
37
+ to_replace:
38
+ 6:
39
+ "content":
40
+ "think": "<think>This tool call completes the task. Now, I can provide the final answer.</think>"
41
+ initial_msg_count: 7
42
+ expected_final_msg_count: 5
linalg_zero/config/data.py ADDED
@@ -0,0 +1,341 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass, field
2
+
3
+ from trl.scripts.utils import ScriptArguments as ScriptArgs
4
+ from trl.trainer.model_config import ModelConfig
5
+
6
+
7
+ @dataclass
8
+ class ScriptArguments(ScriptArgs):
9
+ """
10
+ Extended version of ScriptArguments with support for dataset mixtures.
11
+ """
12
+
13
+ dataset_name: str | None = field(
14
+ default=None, metadata={"help": "Training dataset name. Contains chain-of-thought solutions."}
15
+ )
16
+ eval_dataset_config: str | None = field(default=None, metadata={"help": "Evaluation dataset config."})
17
+ take_n: int | None = field(default=None, metadata={"help": "Number of examples to take from the dataset."})
18
+
19
+
20
+ @dataclass
21
+ class SFTModelConfig(ModelConfig):
22
+ enforce_eager: bool | None = field(default=None, metadata={"help": "Whether to enforce eager execution."})
23
+
24
+
25
+ @dataclass
26
+ class SFTRunConfig:
27
+ add_special_tokens: bool = field(
28
+ metadata={"help": "Whether to add special tokens to the model."},
29
+ )
30
+
31
+ early_stopping_patience: int = field(
32
+ default=3, metadata={"help": "The number of epochs to wait before early stopping."}
33
+ )
34
+
35
+ early_stopping_threshold: float = field(
36
+ default=0.0, metadata={"help": "Minimum improvement required to reset patience counter."}
37
+ )
38
+
39
+ benchmarks: list[str] = field(
40
+ default_factory=lambda: [],
41
+ metadata={"help": "The benchmarks to run after training."},
42
+ )
43
+ callbacks: list[str] = field(
44
+ default_factory=lambda: [],
45
+ metadata={"help": "The callbacks to run during training."},
46
+ )
47
+ chat_template: str | None = field(default=None, metadata={"help": "The chat template to use."})
48
+ system_prompt: str | None = field(
49
+ default=None,
50
+ metadata={"help": "The optional system prompt to use for benchmarking."},
51
+ )
52
+ hub_model_revision: str | None = field(
53
+ default="main",
54
+ metadata={"help": "The Hub model branch to push the model to."},
55
+ )
56
+ overwrite_hub_revision: bool = field(default=False, metadata={"help": "Whether to overwrite the Hub revision."})
57
+ push_to_hub_revision: bool = field(default=False, metadata={"help": "Whether to push to a Hub revision/branch."})
58
+ wandb_entity: str | None = field(
59
+ default=None,
60
+ metadata={"help": ("The entity to store runs under.")},
61
+ )
62
+ wandb_project: str | None = field(
63
+ default=None,
64
+ metadata={"help": ("The project to store runs under.")},
65
+ )
66
+ wandb_run_group: str | None = field(
67
+ default=None,
68
+ metadata={"help": ("The group to store runs under.")},
69
+ )
70
+ wandb_run_id: str | None = field(default=None, metadata={"help": {"The wandb run id."}})
71
+
72
+ eval_max_new_tokens: int | None = field(
73
+ default=None,
74
+ metadata={"help": "Max new tokens for evaluation callbacks (does not affect training)."},
75
+ )
76
+
77
+ max_seq_length: int | None = field(
78
+ default=None,
79
+ metadata={"help": "Max sequence length for evaluation callbacks (does not affect training)."},
80
+ )
81
+
82
+ gpu_memory_utilization: float | None = field(
83
+ default=0.95, metadata={"help": "Fraction of GPU memory to be used by vLLM (0-1)"}
84
+ )
85
+
86
+ # Evaluation sampling
87
+ max_eval_samples: int | None = field(
88
+ default=None,
89
+ metadata={"help": "Maximum number of eval samples for periodic evaluations. None/-1 for full dataset."},
90
+ )
91
+ final_eval_max_samples: int | None = field(
92
+ default=None,
93
+ metadata={"help": "Maximum number of eval samples for periodic evaluations. None/-1 for full dataset."},
94
+ )
95
+
96
+
97
+ @dataclass
98
+ class DatasetGenerationConfig:
99
+ """
100
+ Data class that stores the dataset generation parameters.
101
+
102
+ Args:
103
+ dataset_name (str): The name of the dataset to generate.
104
+ """
105
+
106
+ dataset_name: str | None = field(
107
+ metadata={"help": "Should be the name used to store the dataset on the Hugging Face Hub."},
108
+ )
109
+
110
+
111
+ @dataclass
112
+ class LlamaCppServerConfig:
113
+ """
114
+ Data class that stores LlamaCPP server parameters with llama_cpp_ prefix.
115
+ """
116
+
117
+ def __post_init__(self) -> None:
118
+ pass
119
+
120
+ # Server parameters
121
+ host: str = field(
122
+ metadata={"help": "Host address to bind to"},
123
+ )
124
+ port: int = field(
125
+ metadata={"help": "Port to listen on"},
126
+ )
127
+ n_ctx: int = field(
128
+ metadata={"help": "Context size"},
129
+ )
130
+ split_mode: int = field(
131
+ metadata={"help": "Split mode (0=none, 1=layer, 2=row)"},
132
+ )
133
+
134
+ # Model parameters
135
+ n_gpu_layers: int = field(
136
+ metadata={"help": "Number of GPU layers to offload"},
137
+ )
138
+
139
+ model: str = field(
140
+ metadata={"help": "Model URL to download (GGUF format)"},
141
+ )
142
+
143
+ hf_pretrained_model_name_or_path: str | None = field(
144
+ default=None,
145
+ metadata={"help": "Huggingface repository ID to ensure that the correct tokenizer is used."},
146
+ )
147
+
148
+ hf_model_repo_id: str | None = field(
149
+ default=None,
150
+ metadata={"help": "Path to the repository where the model is stored."},
151
+ )
152
+
153
+
154
+ @dataclass
155
+ class VllmServerConfig:
156
+ """
157
+ Data class that stores vLLM server parameters with vllm_ prefix.
158
+ """
159
+
160
+ # Model parameters
161
+ model: str = field(
162
+ metadata={"help": "Model name (HuggingFace format)"},
163
+ )
164
+
165
+ # Server parameters
166
+ host: str = field(
167
+ metadata={"help": "Host address to bind to"},
168
+ )
169
+ port: int = field(
170
+ metadata={"help": "Port to listen on"},
171
+ )
172
+ enable_auto_tool_choice: bool = field(
173
+ metadata={"help": "Enable automatic tool choice"},
174
+ )
175
+ tool_call_parser: str = field(
176
+ metadata={"help": "Tool call parser to use"},
177
+ )
178
+ chat_template: str | None = field(
179
+ default=None,
180
+ metadata={"help": "Chat template to use"},
181
+ )
182
+ quantization: str | None = field(
183
+ default=None,
184
+ metadata={"help": "Quantization to use"},
185
+ )
186
+ api_key: str = field(
187
+ default="not-used",
188
+ metadata={"help": "API key for authentication (use 'not-used' for local development)"},
189
+ )
190
+
191
+ # Memory / performance tuning parameters
192
+ dtype: str | None = field(
193
+ default=None,
194
+ metadata={"help": "Computation dtype for model weights and activations (e.g., float16)"},
195
+ )
196
+ kv_cache_dtype: str | None = field(
197
+ default=None,
198
+ metadata={"help": "KV cache dtype (auto, fp8, fp8_e4m3, fp8_e5m2)"},
199
+ )
200
+ max_model_len: int | None = field(
201
+ default=None,
202
+ metadata={"help": "Maximum model context length (tokens)"},
203
+ )
204
+ max_num_seqs: int | None = field(
205
+ default=None,
206
+ metadata={"help": "Maximum number of concurrent sequences"},
207
+ )
208
+ gpu_memory_utilization: float | None = field(
209
+ default=None,
210
+ metadata={"help": "Fraction of GPU memory to be used by vLLM (0-1)"},
211
+ )
212
+ enforce_eager: bool | None = field(
213
+ default=None,
214
+ metadata={"help": "Disable CUDA graphs to reduce memory usage"},
215
+ )
216
+ swap_space: int | None = field(
217
+ default=None,
218
+ metadata={"help": "CPU swap space in GB per GPU for paging KV cache"},
219
+ )
220
+ max_num_batched_tokens: int | None = field(
221
+ default=None,
222
+ metadata={"help": "Limit number of tokens processed per batch (prefill)"},
223
+ )
224
+ tensor_parallel_size: int | None = field(
225
+ default=None,
226
+ metadata={"help": "Tensor parallelism degree"},
227
+ )
228
+ enable_chunked_prefill: bool | None = field(
229
+ default=None,
230
+ metadata={"help": "Enable chunked prefill to reduce peak prefill memory"},
231
+ )
232
+
233
+ # Model parameters
234
+ reasoning_parser: str | None = field(
235
+ default=None,
236
+ metadata={"help": "Reasoning parser to use"},
237
+ )
238
+
239
+
240
+ @dataclass
241
+ class DistillationConfig:
242
+ """
243
+ Data class that stores the distillation pipeline parameters.
244
+ """
245
+
246
+ # Dataset parameters
247
+ dataset_name: str | None = field(
248
+ metadata={"help": "HuggingFace dataset to load"},
249
+ )
250
+
251
+ # Prompt parameters
252
+ prompt_column: str = field(
253
+ metadata={"help": "Column name for prompt data"},
254
+ )
255
+ prompt_template: str = field(
256
+ metadata={"help": "Template string for formatting prompts"},
257
+ )
258
+
259
+ # Generation parameters (non-defaults first)
260
+ model_type: str | None = field(metadata={"help": "Model type for generation"})
261
+ enable_reasoning: bool = field(metadata={"help": "Whether to enable thinking"})
262
+ max_new_tokens: int = field(
263
+ metadata={"help": "Maximum number of new tokens to generate"},
264
+ )
265
+ num_generations: int = field(
266
+ metadata={"help": "Number of generations per problem"},
267
+ )
268
+
269
+ # Processing parameters
270
+ input_batch_size: int = field(
271
+ metadata={"help": "Batch size for input processing"},
272
+ )
273
+ use_cache: bool = field(
274
+ metadata={"help": "Whether to use cache for the pipeline. This can enable error recovery."},
275
+ )
276
+
277
+ timeout: int = field(
278
+ metadata={"help": "Request timeout in seconds"},
279
+ )
280
+ retries: int = field(
281
+ metadata={"help": "Number of retries for failed requests"},
282
+ )
283
+
284
+ # Output parameters
285
+ hf_output_dataset: str | None = field(
286
+ metadata={"help": "HuggingFace repo to push results to"},
287
+ )
288
+ argilla_output_dataset: str | None = field(
289
+ metadata={"help": "Argilla dataset to push results to. This is used for manual annotation."},
290
+ )
291
+ private: bool = field(
292
+ metadata={"help": "Whether to make the output dataset private when pushing to HF Hub"},
293
+ )
294
+
295
+ # Generation parameters
296
+ n_turns: int = field(
297
+ metadata={"help": "Number of turns to generate"},
298
+ )
299
+ min_successful_completions: int = field(
300
+ default=-1,
301
+ metadata={"help": "Minimum number of successful completions to generate"},
302
+ )
303
+ strip_think_prefix: bool = field(
304
+ default=True,
305
+ metadata={"help": "Whether to strip the think prefix from the conversation. This is needed for Qwen3 models."},
306
+ )
307
+ # Optional stopping sequences (must come after non-default fields)
308
+ stop: list[str] | None = field(
309
+ default=None,
310
+ metadata={"help": "Stop sequences for generation (each string is a stop token)"},
311
+ )
312
+
313
+ debug_mode: bool = field(
314
+ default=False,
315
+ metadata={"help": "Whether to do evaluation"},
316
+ )
317
+
318
+ take_n: int | None = field(
319
+ default=None,
320
+ metadata={"help": "Number of examples to take from the dataset."},
321
+ )
322
+
323
+ structured_output: bool = field(
324
+ default=False,
325
+ metadata={"help": "Whether to use structured output"},
326
+ )
327
+
328
+ deterministic: bool = field(
329
+ default=True,
330
+ metadata={"help": "Make generation deterministic (temperature=0, top_p=1)"},
331
+ )
332
+
333
+ client_replicas: int | None = field(
334
+ default=None,
335
+ metadata={"help": "Number of client replicas for parallel processing"},
336
+ )
337
+
338
+ dataset_config: str | None = field(
339
+ default=None,
340
+ metadata={"help": "Dataset config to use"},
341
+ )
linalg_zero/config/dataset/default.yml ADDED
File without changes
linalg_zero/config/dataset/default_debug.yml ADDED
@@ -0,0 +1 @@
 
 
1
+ dataset_name: "linalg_zero"
linalg_zero/config/distillation/env.example.sh ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ export HF_HOME=/workspace/linalg-zero/hf-cache
4
+ export HUGGINGFACE_HUB_CACHE=/workspace/linalg-zero/hf-cache
5
+ export VLLM_WORKDIR=/workspace/linalg-zero/vllm-cache
6
+ export UV_CACHE_DIR=/workspace/linalg-zero/uv-cache
7
+ export XDG_CACHE_HOME=/workspace/linalg-zero/.cache
8
+ export PIP_CACHE_DIR=/workspace/linalg-zero/pip-cache
9
+ export USING_VLLM=true
10
+ export INFERENCE_BACKEND=vllm
11
+ export ARGILLA_API_URL=https://atomwalk12-linalgzero-distilled.hf.space
12
+ export ARGILLA_API_KEY=<token>
13
+ export HF_TOKEN=<token>
linalg_zero/config/distillation/llamacpp_qwen3_30b_A3B_instruct.yaml ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ######################
2
+ # DistillationConfig #
3
+ ######################
4
+
5
+ # Dataset parameters
6
+ dataset_name: "atomwalk12/linalgzero"
7
+ debug_mode: True
8
+ take_n: 20
9
+ n_turns: 6
10
+
11
+ # Prompt parameters
12
+ prompt_column: "prompt"
13
+ prompt_template: "{{ instruction }}"
14
+
15
+ # Generation parameters
16
+ model_type: "default"
17
+ deterministic: true
18
+ max_new_tokens: 8192
19
+ num_generations: 1
20
+
21
+ # Processing parameters
22
+ input_batch_size: 5
23
+ use_cache: true
24
+ client_replicas: 1
25
+ timeout: 600
26
+ retries: 2
27
+
28
+ # Output parameters
29
+ hf_output_dataset: "atomwalk12/linalgzero-distilled-debug"
30
+ argilla_output_dataset: "atomwalk12/linalgzero-distilled-debug"
31
+ private: false
32
+
33
+ ########################
34
+ # LlamaCppServerConfig #
35
+ ########################
36
+
37
+ # Server parameters
38
+ host: "localhost"
39
+ port: 8000
40
+
41
+ # Model
42
+ model: "Qwen3-30B-A3B-Instruct-2507-UD-Q5_K_XL.gguf"
43
+ hf_model_repo_id: "unsloth/Qwen3-30B-A3B-Instruct-2507-GGUF"
44
+ hf_pretrained_model_name_or_path: "Qwen/Qwen3-32B"
45
+ enable_reasoning: true
46
+
47
+ n_gpu_layers: 45
48
+ n_ctx: 8192
49
+ split_mode: 2
linalg_zero/config/distillation/llamacpp_qwen3_30b_A3B_think.yaml ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ######################
2
+ # DistillationConfig #
3
+ ######################
4
+
5
+ # Dataset parameters
6
+ dataset_name: "atomwalk12/linalgzero"
7
+ debug_mode: True
8
+ take_n: 20
9
+ n_turns: 6
10
+
11
+ # Prompt parameters
12
+ prompt_column: "prompt"
13
+ prompt_template: "{{ instruction }}"
14
+
15
+ # Generation parameters
16
+ model_type: "default"
17
+ deterministic: true
18
+ max_new_tokens: 8192
19
+ num_generations: 1
20
+
21
+ # Processing parameters
22
+ input_batch_size: 5
23
+ use_cache: true
24
+ client_replicas: 1
25
+ timeout: 600
26
+ retries: 2
27
+
28
+ # Output parameters
29
+ hf_output_dataset: "atomwalk12/linalgzero-distilled-debug"
30
+ argilla_output_dataset: "atomwalk12/linalgzero-distilled-debug"
31
+ private: false
32
+
33
+ ########################
34
+ # LlamaCppServerConfig #
35
+ ########################
36
+
37
+ # Server parameters
38
+ host: "localhost"
39
+ port: 8000
40
+
41
+ # Model
42
+ model: "Qwen3-30B-A3B-Thinking-2507-UD-Q5_K_XL.gguf"
43
+ hf_model_repo_id: "unsloth/Qwen3-30B-A3B-Thinking-2507-GGUF"
44
+ hf_pretrained_model_name_or_path: "Qwen/Qwen3-32B"
45
+ enable_reasoning: true
46
+
47
+ n_gpu_layers: 45
48
+ n_ctx: 8192
49
+ split_mode: 2
linalg_zero/config/distillation/llamacpp_qwen3_32b_instruct.yaml ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ######################
2
+ # DistillationConfig #
3
+ ######################
4
+
5
+ # Dataset parameters
6
+ dataset_name: "atomwalk12/linalgzero"
7
+ debug_mode: False
8
+ take_n: 20
9
+ n_turns: 6
10
+
11
+ # Prompt parameters
12
+ prompt_column: "prompt"
13
+ prompt_template: "{{ instruction }}"
14
+
15
+ # Generation parameters
16
+ model_type: "default"
17
+ deterministic: true
18
+ max_new_tokens: 8192
19
+ num_generations: 1
20
+
21
+ # Processing parameters
22
+ input_batch_size: 7
23
+ use_cache: true
24
+ client_replicas: 1
25
+ timeout: 600
26
+ retries: 2
27
+
28
+ # Output parameters
29
+ hf_output_dataset: "atomwalk12/linalgzero-distilled-debug"
30
+ argilla_output_dataset: "atomwalk12/linalgzero-distilled-debug"
31
+ private: false
32
+
33
+ ########################
34
+ # LlamaCppServerConfig #
35
+ ########################
36
+
37
+ # Server parameters
38
+ host: "localhost"
39
+ port: 8000
40
+
41
+ # Model
42
+ model: "Qwen3-32B-Q4_K_M.gguf"
43
+ hf_model_repo_id: "unsloth/Qwen3-32B-GGUF"
44
+ hf_pretrained_model_name_or_path: "Qwen/Qwen3-32B"
45
+ enable_reasoning: true
46
+
47
+ n_gpu_layers: 60
48
+ n_ctx: 8192
49
+ split_mode: 2
linalg_zero/config/distillation/qwen3_think_vllm_debug.yaml ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Distillation Configuration for vLLM inference
2
+
3
+ # Dataset parameters
4
+ dataset_name: "atomwalk12/linalgzero"
5
+ # dataset_name: "atomwalk12/linalg-distilled-debug"
6
+ debug_mode: True
7
+ dataset_config: null
8
+ take_n: 20
9
+
10
+ # Prompt parameters
11
+ prompt_column: "prompt"
12
+ prompt_template: "{{ instruction }}"
13
+
14
+ # Generation parameters
15
+ model_type: "default"
16
+ deterministic: true
17
+ max_new_tokens: 8192 # 32768
18
+ num_generations: 1
19
+
20
+ # Processing parameters
21
+ input_batch_size: 3
22
+ use_cache: true
23
+ client_replicas: 1
24
+ timeout: 600
25
+ retries: 2
26
+
27
+ # Output parameters
28
+ hf_output_dataset: "atomwalk12/linalgzero-distilled-debug"
29
+ argilla_output_dataset: "atomwalk12/linalgzero-distilled-debug"
30
+ private: false
31
+
32
+ # Server parameters
33
+ # To inspect the various model/chat template combinations:
34
+ # https://github.com/vllm-project/vllm/blob/main/docs/features/tool_calling.md
35
+
36
+ model: "Eslzzyl/Qwen3-4B-Thinking-2507-AWQ"
37
+ tool_call_parser: "hermes"
38
+ reasoning_parser: "qwen3" # "deepseek_r1"
39
+
40
+ #model: "lurker18/Llama_3.1_8B_Instruct_AWQ_4bit"
41
+ #tool_call_parser: "llama3_json"
42
+ #chat_template: "linalg_zero/distillation/vllm/tool_chat_template_llama3.1_json.jinja"
43
+
44
+ #model: "solidrust/Mistral-7B-Instruct-v0.3-AWQ"
45
+ #tool_call_parser: "mistral"
46
+ #chat_template: "linalg_zero/distillation/vllm/tool_chat_template_mistral_parallel.jinja"
47
+
48
+ enable_auto_tool_choice: true
49
+ quantization: "awq"
50
+ host: "localhost"
51
+ port: 8000
52
+
53
+ # Memory / performance tuning for 24GB GPU
54
+ dtype: "float16"
55
+ kv_cache_dtype: "fp8"
56
+ max_num_seqs: 1
57
+ gpu_memory_utilization: 0.85
58
+ enforce_eager: true
59
+ swap_space: 4
60
+ max_num_batched_tokens: 2048
61
+ max_model_len: 32768 # 131072
62
+ enable_chunked_prefill: true
63
+
64
+ # Distillation parameters
65
+ n_turns: 6
linalg_zero/config/distillation/vllm_qwen3_32b.yaml ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dataset parameters
2
+ dataset_name: "atomwalk12/linalgzero"
3
+ debug_mode: True
4
+ dataset_config: null
5
+ n_turns: 8
6
+ min_successful_completions: -1
7
+
8
+ # Prompt parameters
9
+ prompt_column: "prompt"
10
+ prompt_template: "{{ instruction }}"
11
+
12
+ # Generation parameters
13
+ model_type: "default"
14
+ deterministic: false
15
+ max_new_tokens: 4096
16
+ num_generations: 1
17
+ enable_reasoning: true
18
+
19
+ # Processing parameters
20
+ input_batch_size: 24
21
+ use_cache: true
22
+ timeout: 600
23
+ retries: 5
24
+
25
+ # Output parameters
26
+ hf_output_dataset: "atomwalk12/linalgzero-distilled"
27
+ argilla_output_dataset: "atomwalk12/linalgzero-distilled"
28
+ private: false
29
+
30
+ # Server parameters
31
+ model: "Qwen/Qwen3-32B-FP8"
32
+ tool_call_parser: "hermes"
33
+ reasoning_parser: "qwen3"
34
+
35
+ enable_auto_tool_choice: true
36
+ host: "localhost"
37
+ port: 8000
38
+
39
+ dtype: "bfloat16"
40
+ kv_cache_dtype: "auto"
41
+ max_num_seqs: 24
42
+ max_model_len: 24576
43
+ gpu_memory_utilization: 0.93
44
+ enable_chunked_prefill: true
45
+ swap_space: 8
46
+ max_num_batched_tokens: 1024
linalg_zero/config/distillation/vllm_qwen3_32b_debug.yaml ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dataset parameters
2
+ dataset_name: "atomwalk12/linalgzero"
3
+ debug_mode: False
4
+ dataset_config: null
5
+ n_turns: 8
6
+ take_n: 500
7
+
8
+ # Prompt parameters
9
+ prompt_column: "prompt"
10
+ prompt_template: "{{ instruction }}"
11
+
12
+ # Generation parameters
13
+ model_type: "default"
14
+ deterministic: false
15
+ max_new_tokens: 4096
16
+ num_generations: 1
17
+ enable_reasoning: true
18
+
19
+ # Processing parameters
20
+ input_batch_size: 24
21
+ use_cache: true
22
+ timeout: 600
23
+ retries: 5
24
+
25
+ # Output parameters
26
+ hf_output_dataset: "atomwalk12/linalgzero-distilled-500"
27
+ argilla_output_dataset: "atomwalk12/linalgzero-distilled-500"
28
+ private: false
29
+
30
+ # Server parameters
31
+ model: "Qwen/Qwen3-32B-FP8"
32
+ tool_call_parser: "hermes"
33
+ reasoning_parser: "qwen3"
34
+
35
+ enable_auto_tool_choice: true
36
+ host: "localhost"
37
+ port: 8000
38
+
39
+ dtype: "bfloat16"
40
+ kv_cache_dtype: "auto"
41
+ max_num_seqs: 24
42
+ max_model_len: 24576
43
+ gpu_memory_utilization: 0.93
44
+ enable_chunked_prefill: true
45
+ swap_space: 8
46
+ max_num_batched_tokens: 1024
linalg_zero/config/distillation/vllm_qwen3_4b_think.yaml ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dataset parameters
2
+ dataset_name: "atomwalk12/linalgzero"
3
+ debug_mode: true
4
+ dataset_config: null
5
+ n_turns: 6
6
+ take_n: 7
7
+ min_successful_completions: 8200
8
+
9
+ # Prompt parameters
10
+ prompt_column: "prompt"
11
+ prompt_template: "{{ instruction }}"
12
+
13
+ # Generation parameters
14
+ model_type: "default"
15
+ deterministic: false
16
+ max_new_tokens: 2048
17
+ num_generations: 1
18
+ enable_reasoning: true
19
+ strip_think_prefix: false
20
+
21
+ # Processing parameters
22
+ input_batch_size: 3
23
+ use_cache: true
24
+ client_replicas: 1
25
+ timeout: 600
26
+ retries: 2
27
+
28
+ # Output parameters
29
+ hf_output_dataset: "atomwalk12/linalgzero-distilled-debug"
30
+ argilla_output_dataset: "atomwalk12/linalgzero-distilled-debug"
31
+ private: false
32
+
33
+ # Server parameters
34
+ model: "Eslzzyl/Qwen3-4B-Thinking-2507-AWQ"
35
+ tool_call_parser: "hermes"
36
+ reasoning_parser: "qwen3"
37
+
38
+ enable_auto_tool_choice: true
39
+ quantization: "awq"
40
+ host: "localhost"
41
+ port: 8000
42
+
43
+ # Memory / performance tuning for 24GB GPU
44
+ dtype: "float16"
45
+ kv_cache_dtype: "auto"
46
+ max_num_seqs: 1
47
+ gpu_memory_utilization: 0.75
48
+ enforce_eager: true
49
+ swap_space: 4
50
+ max_num_batched_tokens: 2048
51
+ max_model_len: 8096
52
+ enable_chunked_prefill: true
linalg_zero/config/grpo/Qwen/Qwen2.5-3B/eval/linalgzero-grpo-110.yaml ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ init:
2
+ max_lora_rank: 32
3
+ dtype: bfloat16
4
+ load_in_4bit: false
5
+ load_in_8bit: false
6
+ max_seq_length: 6144
7
+
8
+ training:
9
+ trajectories_per_group: 4
10
+ groups_per_step: 8
11
+ learning_rate: 2e-6
12
+ beta: 0.03
13
+ eval_steps: 100
14
+ num_epochs: 2
15
+ train_mode: sync_rl
16
+ chat_template: "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
17
+
18
+ run:
19
+ project_id: 001-linalgzero-sft-110
20
+ project: linalgzero-eval
21
+ base_model: "results/LinalgZero-SFT-110-checkpoint-300/checkpoint-300"
22
+ model: "results/LinalgZero-SFT-110-checkpoint-300/checkpoint-300"
23
+ dataset_path: "atomwalk12/linalgzero-grpo"
24
+ env: linear_algebra
25
+ model_provider: hosted_vllm
26
+ user_model_provider: openai
27
+ user_strategy: mathematician
28
+ user_model: gpt-4o
29
+ agent_strategy: tool-calling-rl
30
+ temperature: 0.7
31
+ top_p: 0.8
32
+ repetition_penalty: 1.0
33
+ task_split: test
34
+ log_dir: rl_results
35
+ skip_eval: False
36
+ in_process: False
37
+ max_assistant_turns: 5
38
+ max_completion_tokens: 800
39
+ skip_special_tokens: False
40
+ stop: [
41
+ "<tool_response>",
42
+ "</tool_response>",
43
+ "User:",
44
+ ]
45
+ seed: 42
46
+
47
+ trainer:
48
+ max_prompt_length: 5000
49
+ max_completion_length: 800
50
+ report_to: ["wandb"]
51
+ seed: 42
52
+ data_seed: 42
53
+
54
+ engine:
55
+ gpu_memory_utilization: 0.6
56
+ max_lora_rank: 32
57
+ enable_sleep_mode: true
58
+ max_model_len: 6144
59
+ dtype: 'bfloat16'
60
+ quantization: null
61
+ load_format: 'auto'
62
+ seed: 42
linalg_zero/config/grpo/Qwen/Qwen2.5-3B/eval/linalgzero-grpo.yaml ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ init:
2
+ max_lora_rank: 32
3
+ dtype: bfloat16
4
+ load_in_4bit: false
5
+ load_in_8bit: false
6
+ max_seq_length: 10240
7
+
8
+ training:
9
+ trajectories_per_group: 4
10
+ groups_per_step: 8
11
+ learning_rate: 2e-6
12
+ beta: 0.03
13
+ eval_steps: 100
14
+ num_epochs: 2
15
+ train_mode: sync_rl
16
+ chat_template: "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
17
+
18
+ run:
19
+ project_id: LinAlgZero-GRPO-001
20
+ project: linalgzero-eval
21
+ base_model: "atomwalk12/LinAlgZero-GRPO"
22
+ model: "atomwalk12/LinAlgZero-GRPO"
23
+ dataset_path: "atomwalk12/linalgzero-grpo"
24
+ env: linear_algebra
25
+ model_provider: hosted_vllm
26
+ user_model_provider: openai
27
+ user_strategy: mathematician
28
+ user_model: gpt-4o
29
+ agent_strategy: tool-calling-rl
30
+ temperature: 0.0
31
+ top_p: null
32
+ repetition_penalty: 1.0
33
+ task_split: test
34
+ log_dir: rl_results
35
+ skip_eval: False
36
+ in_process: False
37
+ max_assistant_turns: 5
38
+ max_completion_tokens: 1536
39
+ skip_special_tokens: False
40
+ stop: [
41
+ "<tool_response>",
42
+ "</tool_response>",
43
+ "User:",
44
+ ]
45
+ seed: 42
46
+
47
+ trainer:
48
+ max_prompt_length: 8192
49
+ max_completion_length: 1536
50
+ report_to: ["wandb"]
51
+ seed: 42
52
+ data_seed: 42
53
+
54
+ engine:
55
+ gpu_memory_utilization: 0.6
56
+ max_lora_rank: 32
57
+ enable_sleep_mode: true
58
+ max_model_len: 10240
59
+ dtype: 'bfloat16'
60
+ quantization: null
61
+ load_format: 'auto'
62
+ seed: 42
linalg_zero/config/grpo/Qwen/Qwen2.5-3B/eval/linalgzero-sft-110.yaml ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ init:
2
+ max_lora_rank: 32
3
+ dtype: bfloat16
4
+ load_in_4bit: false
5
+ load_in_8bit: false
6
+ max_seq_length: 6144
7
+
8
+ training:
9
+ trajectories_per_group: 4
10
+ groups_per_step: 8
11
+ learning_rate: 2e-6
12
+ beta: 0.03
13
+ eval_steps: 100
14
+ num_epochs: 2
15
+ train_mode: sync_rl
16
+ chat_template: "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
17
+
18
+ run:
19
+ project_id: 001-linalgzero-sft-110
20
+ project: linalgzero-eval
21
+ base_model: "results/LinalgZero-SFT-110-checkpoint-300/checkpoint-300"
22
+ model: "results/LinalgZero-SFT-110-checkpoint-300/checkpoint-300"
23
+ dataset_path: "atomwalk12/linalgzero-grpo"
24
+ env: linear_algebra
25
+ model_provider: hosted_vllm
26
+ user_model_provider: openai
27
+ user_strategy: mathematician
28
+ user_model: gpt-4o
29
+ agent_strategy: tool-calling-rl
30
+ temperature: 0.7
31
+ top_p: 0.8
32
+ repetition_penalty: 1.0
33
+ task_split: test
34
+ log_dir: rl_results
35
+ skip_eval: False
36
+ in_process: False
37
+ max_assistant_turns: 5
38
+ max_completion_tokens: 800
39
+ skip_special_tokens: False
40
+ stop: [
41
+ "<tool_response>",
42
+ "</tool_response>",
43
+ "User:",
44
+ ]
45
+ seed: 42
46
+
47
+ trainer:
48
+ max_prompt_length: 5000
49
+ max_completion_length: 800
50
+ report_to: ["wandb"]
51
+ seed: 42
52
+ data_seed: 42
53
+
54
+ engine:
55
+ gpu_memory_utilization: 0.6
56
+ max_lora_rank: 32
57
+ enable_sleep_mode: true
58
+ max_model_len: 6144
59
+ dtype: 'bfloat16'
60
+ quantization: null
61
+ load_format: 'auto'
62
+ seed: 42
linalg_zero/config/grpo/Qwen/Qwen2.5-3B/eval/linalgzero-sft.yaml ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ init:
2
+ max_lora_rank: 32
3
+ dtype: bfloat16
4
+ load_in_4bit: false
5
+ load_in_8bit: false
6
+ max_seq_length: 6144
7
+
8
+ training:
9
+ trajectories_per_group: 4
10
+ groups_per_step: 8
11
+ learning_rate: 2e-6
12
+ beta: 0.03
13
+ eval_steps: 100
14
+ num_epochs: 2
15
+ train_mode: sync_rl
16
+ chat_template: "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
17
+
18
+ run:
19
+ project_id: 004-linalgzero-sft
20
+ project: linalgzero-eval
21
+ base_model: "atomwalk12/LinAlgZero-SFT"
22
+ model: "atomwalk12/LinAlgZero-SFT"
23
+ dataset_path: "atomwalk12/linalgzero-grpo"
24
+ env: linear_algebra
25
+ model_provider: hosted_vllm
26
+ user_model_provider: openai
27
+ user_strategy: mathematician
28
+ user_model: gpt-4o
29
+ agent_strategy: tool-calling-rl
30
+ temperature: 0.0
31
+ top_p: null
32
+ repetition_penalty: 1.0
33
+ task_split: test
34
+ log_dir: rl_results
35
+ skip_eval: False
36
+ in_process: False
37
+ max_assistant_turns: 5
38
+ max_completion_tokens: 800
39
+ skip_special_tokens: False
40
+ stop: [
41
+ "<tool_response>",
42
+ "</tool_response>",
43
+ "User:",
44
+ ]
45
+ seed: 42
46
+
47
+ trainer:
48
+ max_prompt_length: 5000
49
+ max_completion_length: 800
50
+ report_to: ["wandb"]
51
+ seed: 42
52
+ data_seed: 42
53
+
54
+ engine:
55
+ gpu_memory_utilization: 0.6
56
+ max_lora_rank: 32
57
+ enable_sleep_mode: true
58
+ max_model_len: 6144
59
+ dtype: 'bfloat16'
60
+ quantization: null
61
+ load_format: 'auto'
62
+ seed: 42
linalg_zero/config/grpo/Qwen/Qwen2.5-3B/local.yaml ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ - /training@training: training_schema
3
+ - /run@run: run_schema
4
+ - _self_
5
+
6
+
7
+ init:
8
+ max_lora_rank: 32
9
+ dtype: bfloat16
10
+ load_in_4bit: false
11
+ load_in_8bit: false
12
+ max_seq_length: 10240
13
+
14
+ training:
15
+ eval_retries: 3
16
+ trajectories_per_group: 8
17
+ groups_per_step: 16
18
+ learning_rate: 1e-6
19
+ beta: 0.0
20
+ eval_steps: 100
21
+ num_epochs: 3
22
+ train_mode: sync_rl
23
+ importance_sampling_level: sequence
24
+ scale_rewards: True
25
+ epsilon: 3e-4
26
+ epsilon_high: 4e-4
27
+ chat_template: "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
28
+
29
+ run:
30
+ project_id: test
31
+ project: linalgzero-temp
32
+ base_model: "atomwalk12/LinalgZero-SFT"
33
+ model: "atomwalk12/LinalgZero-SFT"
34
+ dataset_path: "atomwalk12/linalgzero-grpo"
35
+ env: linear_algebra
36
+ curriculum:
37
+ enabled: true
38
+ metric: tool_calls
39
+ sampling: mixture
40
+ initial_max_tool_calls: 1
41
+ final_max_tool_calls: 3
42
+ mixture_sigma: 0.5
43
+ mixture_min_prob_easiest: 0.1
44
+ fraction_at_start: 1.0
45
+ fraction_at_end: 1.0
46
+ min_total_tasks: 64
47
+ model_provider: hosted_vllm
48
+ user_strategy: mathematician
49
+ agent_strategy: tool-calling-rl
50
+ temperature: 1.0
51
+ top_p: 1.0
52
+ repetition_penalty: 1.0
53
+ task_split: test
54
+ log_dir: rl_results
55
+ skip_eval: False
56
+ in_process: False
57
+ max_assistant_turns: 5
58
+ max_completion_tokens: 1536
59
+ skip_special_tokens: False
60
+ stop: [
61
+ "<tool_response>",
62
+ "</tool_response>",
63
+ "User:",
64
+ ]
65
+ seed: 42
66
+
67
+ peft:
68
+ r: 32
69
+ lora_alpha: 64
70
+ use_gradient_checkpointing: "unsloth"
71
+ target_modules: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
72
+ random_state: 42
73
+
74
+ trainer:
75
+ learning_rate: 1e-6
76
+ per_device_train_batch_size: 8
77
+ gradient_accumulation_steps: 1 # if != 1, deadlock occurs
78
+ num_generations: 2
79
+ max_prompt_length: 8192
80
+ max_completion_length: 1536
81
+ report_to: ["wandb"]
82
+ save_total_limit: 10
83
+ seed: 42
84
+ data_seed: 42
85
+ max_grad_norm: 1.0
86
+
87
+ engine:
88
+ gpu_memory_utilization: 0.33
89
+ max_lora_rank: 32
90
+ enable_sleep_mode: true
91
+ max_model_len: 10240
92
+ dtype: 'bfloat16'
93
+ quantization: null
94
+ load_format: 'auto'
95
+ seed: 42
linalg_zero/config/grpo/Qwen/Qwen2.5-3B/runpod.yaml ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ - /training@training: training_schema
3
+ - /run@run: run_schema
4
+ - _self_
5
+
6
+
7
+ init:
8
+ max_lora_rank: 32
9
+ dtype: bfloat16
10
+ load_in_4bit: false
11
+ load_in_8bit: false
12
+ max_seq_length: 10240
13
+
14
+ training:
15
+ eval_retries: 3
16
+ trajectories_per_group: 8
17
+ groups_per_step: 16
18
+ learning_rate: 1e-6
19
+ beta: 0.0
20
+ eval_steps: 100
21
+ num_epochs: 3
22
+ train_mode: sync_rl
23
+ importance_sampling_level: sequence
24
+ scale_rewards: True
25
+ epsilon: 3e-4
26
+ epsilon_high: 4e-4
27
+ chat_template: "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
28
+
29
+ run:
30
+ project_id: linalgzero-3b-grpo-runpod
31
+ project: linalgzero-grpo
32
+ base_model: "atomwalk12/LinalgZero-SFT"
33
+ model: "atomwalk12/LinalgZero-SFT"
34
+ dataset_path: "atomwalk12/linalgzero-grpo"
35
+ env: linear_algebra
36
+ curriculum:
37
+ enabled: true
38
+ metric: tool_calls
39
+ sampling: mixture
40
+ initial_max_tool_calls: 1
41
+ final_max_tool_calls: 3
42
+ mixture_sigma: 0.5
43
+ mixture_min_prob_easiest: 0.1
44
+ fraction_at_start: 1.0
45
+ fraction_at_end: 1.0
46
+ min_total_tasks: 64
47
+ model_provider: hosted_vllm
48
+ user_strategy: mathematician
49
+ agent_strategy: tool-calling-rl
50
+ temperature: 1.0
51
+ top_p: 1.0
52
+ repetition_penalty: 1.0
53
+ task_split: test
54
+ log_dir: rl_results
55
+ skip_eval: False
56
+ in_process: False
57
+ max_assistant_turns: 5
58
+ max_completion_tokens: 1536
59
+ skip_special_tokens: False
60
+ stop: [
61
+ "<tool_response>",
62
+ "</tool_response>",
63
+ "User:",
64
+ ]
65
+ seed: 42
66
+
67
+ peft:
68
+ r: 32
69
+ lora_alpha: 64
70
+ use_gradient_checkpointing: "unsloth"
71
+ target_modules: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
72
+ random_state: 42
73
+
74
+ trainer:
75
+ learning_rate: 1e-6
76
+ per_device_train_batch_size: 8
77
+ gradient_accumulation_steps: 1 # if != 1, deadlock occurs
78
+ num_generations: 2
79
+ max_prompt_length: 8192
80
+ max_completion_length: 1536
81
+ report_to: ["wandb"]
82
+ save_total_limit: 10
83
+ seed: 42
84
+ data_seed: 42
85
+ max_grad_norm: 1.0
86
+
87
+ engine:
88
+ gpu_memory_utilization: 0.9
89
+ max_lora_rank: 32
90
+ enable_sleep_mode: true
91
+ max_model_len: 10240
92
+ dtype: 'bfloat16'
93
+ quantization: null
94
+ load_format: 'auto'
95
+ seed: 42
linalg_zero/config/sft/accelerate/zero2.yaml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ compute_environment: LOCAL_MACHINE
2
+ debug: false
3
+ deepspeed_config:
4
+ deepspeed_multinode_launcher: standard
5
+ offload_optimizer_device: none
6
+ offload_param_device: none
7
+ zero3_init_flag: false
8
+ zero_stage: 2
9
+ distributed_type: DEEPSPEED
10
+ downcast_bf16: 'no'
11
+ machine_rank: 0
12
+ main_training_function: main
13
+ mixed_precision: bf16
14
+ num_machines: 1
15
+ num_processes: 8
16
+ rdzv_backend: static
17
+ same_network: true
18
+ tpu_env: []
19
+ tpu_use_cluster: false
20
+ tpu_use_sudo: false
21
+ use_cpu: false
linalg_zero/config/sft/accelerate/zero3.yaml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ compute_environment: LOCAL_MACHINE
2
+ debug: false
3
+ deepspeed_config:
4
+ deepspeed_multinode_launcher: standard
5
+ offload_optimizer_device: none
6
+ offload_param_device: none
7
+ zero3_init_flag: true
8
+ zero3_save_16bit_model: true
9
+ zero_stage: 3
10
+ distributed_type: DEEPSPEED
11
+ downcast_bf16: 'no'
12
+ machine_rank: 0
13
+ main_training_function: main
14
+ mixed_precision: bf16
15
+ num_machines: 1
16
+ num_processes: 1 # NOTE: The number of GPUs on one node
17
+ rdzv_backend: static
18
+ same_network: true
19
+ tpu_env: []
20
+ tpu_use_cluster: false
21
+ tpu_use_sudo: false
22
+ use_cpu: false
linalg_zero/config/sft/qwen2.5-3B/instruct.yaml ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SFT config for a light touch-up of the Instruct model,
2
+ # targeting only the input/output layers (embed_tokens and lm_head).
3
+
4
+ # Model arguments
5
+ model_name_or_path: Qwen/Qwen2.5-3B-Instruct
6
+ model_revision: main
7
+ torch_dtype: bfloat16
8
+ attn_implementation: flash_attention_2
9
+
10
+ # Quantization / PEFT
11
+ load_in_4bit: false
12
+ load_in_8bit: false # Use 16bit training
13
+ bnb_4bit_quant_type: nf4
14
+ use_peft: false # IMPORTANT: do not create new LoRA adapters
15
+
16
+ # Data training arguments
17
+ dataset_name: atomwalk12/linalgzero-sft
18
+ dataset_config: default
19
+ eval_dataset_config: default
20
+ dataset_num_proc: 12
21
+ dataset_text_field: text
22
+ eos_token: <|im_end|>
23
+ add_special_tokens: true
24
+
25
+ # SFT trainer config
26
+ bf16: true
27
+
28
+ ## Evaluation parameters
29
+ do_eval: true # final evaluation at the end of training
30
+ eval_on_start: true # evaluation at the start of training
31
+ eval_strategy: 'steps'
32
+ eval_steps: 100
33
+ max_eval_samples: 520
34
+ final_eval_max_samples: 520
35
+
36
+ metric_for_best_model: eval_format_accuracy
37
+ greater_is_better: true
38
+ load_best_model_at_end: true
39
+ save_total_limit: 5
40
+ eval_max_new_tokens: 800
41
+
42
+ callbacks:
43
+ - tool_calling_accuracy
44
+
45
+ ## Training parameters
46
+ gradient_accumulation_steps: 16
47
+ gradient_checkpointing: true
48
+ gradient_checkpointing_kwargs:
49
+ use_reentrant: false
50
+ hub_model_id: atomwalk12/LinalgZero-SFT-Instruct
51
+ hub_strategy: every_save
52
+ learning_rate: 2e-5 # Alternatives: 5e-5/4e-5/3e-5/2e-5 and train for 2 epochs
53
+ log_level: info
54
+ logging_steps: 5
55
+ logging_strategy: steps
56
+ lr_scheduler_type: cosine_with_min_lr
57
+ lr_scheduler_kwargs:
58
+ min_lr_rate: 0.1
59
+ packing: false
60
+ max_grad_norm: 0.2
61
+ max_seq_length: 8192
62
+ max_length: 8192
63
+ max_steps: -1
64
+ num_train_epochs: 1
65
+ output_dir: results/LinalgZero-SFT-Instruct
66
+ overwrite_output_dir: true
67
+ per_device_eval_batch_size: 1
68
+ per_device_train_batch_size: 1
69
+ push_to_hub: true
70
+ report_to:
71
+ - wandb
72
+ save_strategy: steps
73
+ save_steps: 100
74
+ seed: 42
75
+ use_liger_kernel: false
76
+ warmup_ratio: 0.03
77
+ chat_template: "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
78
+ optim: adamw_torch_fused
linalg_zero/config/sft/qwen2.5-3B/lora.yaml ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # NOTE: ADJUST gradient_accumulation_steps, per_device_eval_batch_size, per_device_train_batch_size
2
+
3
+ # Model arguments
4
+ model_name_or_path: Qwen/Qwen2.5-3B
5
+ model_revision: main
6
+ torch_dtype: bfloat16
7
+ attn_implementation: flash_attention_2
8
+
9
+ # Model arguments
10
+ load_in_4bit: false
11
+ load_in_8bit: false # Use 16bit training
12
+ bnb_4bit_quant_type: nf4
13
+ use_peft: true
14
+ lora_r: 32 # NOTE: Alternatively 16 and 64 if underfitting
15
+ lora_alpha: 64
16
+ lora_dropout: 0.1
17
+ lora_target_modules: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
18
+ lora_task_type: "CAUSAL_LM"
19
+ # vllm parameters
20
+ # gpu_memory_utilization: 0.95
21
+ # enforce_eager: true
22
+ # fast_inference: true
23
+
24
+ # Data training arguments
25
+ dataset_name: atomwalk12/linalgzero-sft
26
+ dataset_config: default
27
+ eval_dataset_config: default
28
+ dataset_num_proc: 12
29
+ dataset_text_field: text
30
+ eos_token: <|im_end|>
31
+ add_special_tokens: true
32
+
33
+ # SFT trainer config
34
+ bf16: true
35
+
36
+ ## Evaluation parameters
37
+ do_eval: true # does a final evaluation step at the end of training
38
+ eval_on_start: true # does an evaluation step at the start of training
39
+ eval_strategy: 'steps' # can be "no","epoch","steps"
40
+ eval_steps: 100 # NOTE: Check also save_steps
41
+ max_eval_samples: 520 # use all evaluation samples (720 format checks -- 40*6*2+40*4*3+40*3*4 (520 samples))
42
+ final_eval_max_samples: 520
43
+
44
+ # NOTE: if we use a customized metric for best model selection, ensure to adjust:
45
+ # - metric_for_best_model: eval_format_accuracy or eval_loss
46
+ # - greater_is_better: true or false (if eval_loss is set)
47
+ # - load_best_model_at_end: true
48
+ metric_for_best_model: eval_format_accuracy
49
+ greater_is_better: true
50
+ load_best_model_at_end: true
51
+ save_total_limit: 5
52
+ eval_max_new_tokens: 800
53
+ # NOTE: for early stopping:
54
+ # - early_stopping_patience (3)
55
+ # - early_stopping_threshold (0.01)
56
+ # - callback (early_stopping)
57
+
58
+ callbacks:
59
+ - tool_calling_accuracy
60
+ # - push_to_hub_revision
61
+
62
+
63
+ ## Training parameters
64
+ # NOTE: adjust batch size, lr
65
+ gradient_accumulation_steps: 16 # Recommended batch size: 16
66
+ gradient_checkpointing: true
67
+ gradient_checkpointing_kwargs:
68
+ use_reentrant: false
69
+ hub_model_id: atomwalk12/LinalgZero-SFT-LoRA
70
+ hub_strategy: every_save
71
+ learning_rate: 2e-5 # Alternatives: 5e-5/4e-5/3e-5/2e-5 and train for 2 epochs
72
+ log_level: info
73
+ logging_steps: 5
74
+ logging_strategy: steps
75
+ lr_scheduler_type: cosine_with_min_lr
76
+ lr_scheduler_kwargs:
77
+ min_lr_rate: 0.1
78
+ packing: false
79
+ max_grad_norm: 0.2
80
+ max_seq_length: 8192
81
+ max_length: 8192
82
+ max_steps: -1
83
+ num_train_epochs: 1 # NOTE: Alternatively 2 if underfitting
84
+ output_dir: results/LinalgZero-SFT-LoRA
85
+ overwrite_output_dir: true
86
+ per_device_eval_batch_size: 1 # Overridden to 1 by unsloth
87
+ per_device_train_batch_size: 1
88
+ push_to_hub: true
89
+ report_to:
90
+ - wandb
91
+ save_strategy: steps
92
+ save_steps: 100
93
+ seed: 42
94
+ use_liger_kernel: false # Supported only for Llama, Mistral, Mixtral, and Gemma models
95
+ warmup_ratio: 0.03
96
+ chat_template: "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
97
+ optim: adamw_torch_fused
linalg_zero/config/sft/qwen2.5-3B/merged.yaml ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SFT config for a light touch-up of the merged model,
2
+ # targeting only the input/output layers (embed_tokens and lm_head).
3
+
4
+ # Model arguments
5
+ model_name_or_path: results/LinalgZero-SFT-merged
6
+ model_revision: main
7
+ torch_dtype: bfloat16
8
+ attn_implementation: flash_attention_2
9
+
10
+ # Quantization / PEFT
11
+ load_in_4bit: false
12
+ load_in_8bit: false # Use 16bit training
13
+ bnb_4bit_quant_type: nf4
14
+ use_peft: false # IMPORTANT: do not create new LoRA adapters
15
+
16
+ # Data training arguments
17
+ dataset_name: atomwalk12/linalgzero-sft
18
+ dataset_config: default
19
+ eval_dataset_config: default
20
+ dataset_num_proc: 12
21
+ dataset_text_field: text
22
+ eos_token: <|im_end|>
23
+ add_special_tokens: true
24
+
25
+ # SFT trainer config
26
+ bf16: true
27
+
28
+ ## Evaluation parameters
29
+ do_eval: true # final evaluation at the end of training
30
+ eval_on_start: true # evaluation at the start of training
31
+ eval_strategy: 'steps'
32
+ eval_steps: 100
33
+ max_eval_samples: 520
34
+ final_eval_max_samples: 520
35
+
36
+ metric_for_best_model: eval_format_accuracy
37
+ greater_is_better: true
38
+ load_best_model_at_end: true
39
+ save_total_limit: 5
40
+ eval_max_new_tokens: 800
41
+
42
+ callbacks:
43
+ - tool_calling_accuracy
44
+
45
+ ## Training parameters
46
+ gradient_accumulation_steps: 16
47
+ gradient_checkpointing: true
48
+ gradient_checkpointing_kwargs:
49
+ use_reentrant: false
50
+ hub_model_id: atomwalk12/LinalgZero-SFT
51
+ hub_strategy: every_save
52
+ learning_rate: 2e-5 # Alternatives: 5e-5/4e-5/3e-5/2e-5 and train for 2 epochs
53
+ log_level: info
54
+ logging_steps: 5
55
+ logging_strategy: steps
56
+ lr_scheduler_type: cosine_with_min_lr
57
+ lr_scheduler_kwargs:
58
+ min_lr_rate: 0.1
59
+ packing: false
60
+ max_grad_norm: 0.2
61
+ max_seq_length: 8192
62
+ max_length: 8192
63
+ max_steps: -1
64
+ num_train_epochs: 1
65
+ output_dir: results/LinalgZero-SFT
66
+ overwrite_output_dir: true
67
+ per_device_eval_batch_size: 1
68
+ per_device_train_batch_size: 1
69
+ push_to_hub: true
70
+ report_to:
71
+ - wandb
72
+ save_strategy: steps
73
+ save_steps: 100
74
+ seed: 42
75
+ use_liger_kernel: false
76
+ warmup_ratio: 0.03
77
+ chat_template: "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
78
+ optim: adamw_torch_fused
linalg_zero/config/sft/qwen2.5-3B/nst.yaml ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # NOTE: ADJUST gradient_accumulation_steps, per_device_eval_batch_size, per_device_train_batch_size
2
+
3
+ # Model arguments
4
+ model_name_or_path: Qwen/Qwen2.5-3B
5
+ model_revision: main
6
+ torch_dtype: bfloat16
7
+ attn_implementation: flash_attention_2
8
+ wandb_project: linalgzero-sft
9
+ wandb_run_id: run001-sft-nst
10
+
11
+ # Model arguments
12
+ load_in_4bit: false
13
+ load_in_8bit: false # Use 16bit training
14
+ bnb_4bit_quant_type: nf4
15
+ use_peft: true
16
+ lora_r: 32 # NOTE: Alternatively 16 and 64 if underfitting
17
+ lora_alpha: 64
18
+ lora_dropout: 0.1
19
+ lora_target_modules: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
20
+ lora_task_type: "CAUSAL_LM"
21
+ # vllm parameters
22
+ # gpu_memory_utilization: 0.95
23
+ # enforce_eager: true
24
+ # fast_inference: true
25
+
26
+ # Data training arguments
27
+ dataset_name: atomwalk12/linalgzero-sft-110
28
+ dataset_config: default
29
+ eval_dataset_config: default
30
+ dataset_num_proc: 12
31
+ dataset_text_field: text
32
+ eos_token: <|im_end|>
33
+
34
+ # SFT trainer config
35
+ bf16: true
36
+
37
+ ## Evaluation parameters
38
+ do_eval: true # does a final evaluation step at the end of training
39
+ eval_on_start: false # does an evaluation step at the start of training
40
+ eval_strategy: 'steps' # can be "no","epoch","steps"
41
+ eval_steps: 110 # NOTE: Check also save_steps
42
+ max_eval_samples: 260 # use all evaluation samples (720 format checks -- 40*6*2+40*4*3+40*3*4 (520 samples))
43
+ final_eval_max_samples: 260
44
+ add_special_tokens: False
45
+
46
+ # NOTE: if we use a customized metric for best model selection, ensure to adjust:
47
+ # - metric_for_best_model: eval_format_accuracy or eval_loss
48
+ # - greater_is_better: true or false (if eval_loss is set)
49
+ # - load_best_model_at_end: true
50
+ metric_for_best_model: eval_format_accuracy
51
+ greater_is_better: true
52
+ load_best_model_at_end: true
53
+ save_total_limit: 30
54
+ eval_max_new_tokens: 800
55
+ # NOTE: for early stopping:
56
+ # - early_stopping_patience (3)
57
+ # - early_stopping_threshold (0.01)
58
+ # - callback (early_stopping)
59
+
60
+ callbacks:
61
+ - tool_calling_accuracy
62
+ # - push_to_hub_revision
63
+
64
+
65
+ ## Training parameters
66
+ # NOTE: adjust batch size, lr
67
+ gradient_accumulation_steps: 16 # Recommended batch size: 16
68
+ gradient_checkpointing: true
69
+ gradient_checkpointing_kwargs:
70
+ use_reentrant: false
71
+ hub_model_id: atomwalk12/LinalgZero-SFT-LoRA-NST
72
+ hub_strategy: every_save
73
+ learning_rate: 2e-5 # Alternatives: 5e-5/4e-5/3e-5/2e-5 and train for 2 epochs
74
+ log_level: info
75
+ logging_steps: 5
76
+ logging_strategy: steps
77
+ lr_scheduler_type: cosine_with_min_lr
78
+ lr_scheduler_kwargs:
79
+ min_lr_rate: 0.1
80
+ packing: false
81
+ max_grad_norm: 0.2
82
+ max_seq_length: 8192
83
+ max_length: 8192
84
+ max_steps: -1
85
+ num_train_epochs: 1 # NOTE: Alternatively 2 if underfitting
86
+ output_dir: results/LinalgZero-SFT-LoRA-NST
87
+ overwrite_output_dir: true
88
+ per_device_eval_batch_size: 1 # Overridden to 1 by unsloth
89
+ per_device_train_batch_size: 1
90
+ push_to_hub: true
91
+ report_to:
92
+ - wandb
93
+ save_strategy: steps
94
+ save_steps: 110
95
+ seed: 42
96
+ use_liger_kernel: false # Supported only for Llama, Mistral, Mixtral, and Gemma models
97
+ warmup_ratio: 0.03
98
+ chat_template: "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
99
+ optim: adamw_torch_fused
linalg_zero/demo/app.py ADDED
@@ -0,0 +1,968 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import gc
3
+ import json
4
+ import os
5
+ import threading
6
+ from typing import Any
7
+
8
+ import gradio as gr
9
+ import spaces # Import spaces early to enable ZeroGPU support
10
+ import torch
11
+ from datasets import load_dataset
12
+ from huggingface_hub import snapshot_download
13
+ from linalg_zero.distillation.components.models import DefaultConfig
14
+ from linalg_zero.distillation.data import FunctionInvocationInfo, ThoughtSchema
15
+ from linalg_zero.grpo.verifiers.xml_parser import XMLParser
16
+ from linalg_zero.shared.lib import get_lib, get_tools
17
+ from linalg_zero.shared.system_prompts import (ANSWER_CLOSE, ANSWER_OPEN,
18
+ THINK_CLOSE, THINK_OPEN,
19
+ TOOL_CALL_CLOSE, TOOL_CALL_OPEN,
20
+ TOOL_RESPONSE_CLOSE,
21
+ TOOL_RESPONSE_OPEN,
22
+ get_math_system_prompt)
23
+ from torch.utils._pytree import tree_map
24
+ from transformers import (AutoTokenizer, StoppingCriteria,
25
+ StoppingCriteriaList, TextIteratorStreamer, pipeline)
26
+
27
+ # Global event to signal cancellation from the UI thread to the generation thread
28
+ cancel_event = threading.Event()
29
+
30
+ access_token = os.environ["HF_TOKEN"]
31
+ DEMO_DIR = os.path.dirname(__file__)
32
+ ASSISTANT_AVATAR_PATH = os.path.join(DEMO_DIR, "assets", "linalgzero-avatar.svg")
33
+
34
+ # Optional: Disable GPU visibility if you wish to force CPU usage
35
+ # os.environ["CUDA_VISIBLE_DEVICES"] = " "
36
+
37
+ # ------------------------------
38
+ # Allowed model definitions
39
+ # ------------------------------
40
+ MODELS = {
41
+ "Qwen3-1.7B": {
42
+ "repo_id": "Qwen/Qwen3-1.7B",
43
+ "description": "Dense causal language model with 1.7B parameters.",
44
+ "params_b": 1.7,
45
+ },
46
+ "atomwalk12/LinAlgZero-GRPO": {
47
+ "repo_id": "atomwalk12/LinAlgZero-GRPO-merged",
48
+ "description": "LinAlgZero GRPO fine-tuned model.",
49
+ "params_b": 3.0,
50
+ },
51
+ "atomwalk12/LinalgZero-SFT": {
52
+ "repo_id": "atomwalk12/LinalgZero-SFT",
53
+ "description": "LinAlgZero SFT fine-tuned model.",
54
+ "params_b": 3.0,
55
+ },
56
+ }
57
+ # Global cache for pipelines to avoid re-loading.
58
+ PIPELINES = {}
59
+ MODEL_SNAPSHOT_PATHS: dict[str, str] = {}
60
+ SYSTEM_PROMPT = get_math_system_prompt(include_examples=False)
61
+ TOOL_LIBRARY = get_lib()
62
+ TOOL_SCHEMAS = get_tools()
63
+ PARSER = XMLParser()
64
+ MODEL_MESSAGE_CONFIG = DefaultConfig()
65
+ TRACE_TITLES = {"💭 Thought", "🛠 Tool Call", "📦 Tool Response"}
66
+ KEEP_SPECIAL_TOKENS = {
67
+ ANSWER_OPEN,
68
+ ANSWER_CLOSE,
69
+ THINK_OPEN,
70
+ THINK_CLOSE,
71
+ TOOL_CALL_OPEN,
72
+ TOOL_CALL_CLOSE,
73
+ TOOL_RESPONSE_OPEN,
74
+ TOOL_RESPONSE_CLOSE,
75
+ }
76
+ SIMPLE_EXAMPLE_QUERIES = [
77
+ "What is the rank of matrix A = [[2, 3], [2, -4]]?",
78
+ "Step 1: find the transpose of A = [[1, 2], [3, 4]]. Step 2: find the trace of the result.",
79
+ "Find the determinant of [[3, 1], [2, 5]].",
80
+ "Find the Frobenius norm of [[3, -2], [-1, 5]].",
81
+ "Find the cofactor matrix of [[5, 2], [1, 3]].",
82
+ ]
83
+
84
+
85
+ def _parse_launch_args() -> argparse.Namespace:
86
+ parser = argparse.ArgumentParser(add_help=False)
87
+ parser.add_argument(
88
+ "--share",
89
+ action="store_true",
90
+ help="Enable a public Gradio share link.",
91
+ )
92
+ args, _ = parser.parse_known_args()
93
+ return args
94
+
95
+
96
+ def _extract_example_query(example: dict[str, Any]) -> str | None:
97
+ query = example.get("query")
98
+ if isinstance(query, str) and query.strip():
99
+ return query.strip()
100
+
101
+ messages = example.get("messages")
102
+ if isinstance(messages, str):
103
+ try:
104
+ messages = json.loads(messages)
105
+ except Exception:
106
+ return None
107
+
108
+ if isinstance(messages, list):
109
+ for message in reversed(messages):
110
+ if not isinstance(message, dict):
111
+ continue
112
+ if message.get("role") != "user":
113
+ continue
114
+ content = message.get("content")
115
+ if isinstance(content, str) and content.strip():
116
+ return content.strip()
117
+ return None
118
+
119
+
120
+ def _truncate_example_label(query: str, max_chars: int = 96) -> str:
121
+ one_line_query = " ".join(query.split())
122
+ if len(one_line_query) <= max_chars:
123
+ return one_line_query
124
+ return one_line_query[: max_chars - 3] + "..."
125
+
126
+
127
+ def _load_dataset_example_queries(limit: int = 10) -> tuple[list[str], str | None, str | None]:
128
+ dataset_candidates = {
129
+ "atomwalk12/linalgzero-grpo": ["test", "validation", "train"],
130
+ "atomwalk12/linalgzero": ["validation", "test", "train"],
131
+ "atomwalk12/linalgzero-sft": ["test", "validation", "train"],
132
+ }
133
+
134
+ for repo, splits in dataset_candidates.items():
135
+ for split in splits:
136
+ try:
137
+ dataset = load_dataset(repo, split=split, streaming=True)
138
+ except Exception:
139
+ continue
140
+
141
+ queries: list[str] = []
142
+ seen_queries: set[str] = set()
143
+ for example in dataset:
144
+ query = _extract_example_query(example)
145
+ if not query or query in seen_queries:
146
+ continue
147
+ seen_queries.add(query)
148
+ queries.append(query)
149
+ if len(queries) >= limit:
150
+ return queries, repo, split
151
+
152
+ if queries:
153
+ return queries, repo, split
154
+
155
+ return [], None, None
156
+
157
+
158
+ DATASET_EXAMPLE_QUERIES, DATASET_EXAMPLE_REPO, DATASET_EXAMPLE_SPLIT = _load_dataset_example_queries()
159
+ DATASET_EXAMPLE_CHOICES = [(_truncate_example_label(query), query) for query in DATASET_EXAMPLE_QUERIES]
160
+ SIMPLE_EXAMPLE_ROWS = [[query] for query in SIMPLE_EXAMPLE_QUERIES]
161
+ if DATASET_EXAMPLE_REPO is not None and DATASET_EXAMPLE_SPLIT is not None:
162
+ DATASET_EXAMPLE_INFO = f"Loaded from {DATASET_EXAMPLE_REPO} ({DATASET_EXAMPLE_SPLIT})"
163
+ else:
164
+ DATASET_EXAMPLE_INFO = "Dataset examples unavailable"
165
+
166
+ # Match the current GRPO/SFT evaluation configs:
167
+ # - temperature = 0.0
168
+ # - top_p = null (functionally ignored in deterministic mode)
169
+ # - repetition_penalty = 1.0
170
+ # In the actual GRPO eval agent, non-train splits force deterministic decoding
171
+ # (do_sample=False, temperature=0.0, top_p=None), so top_k is effectively unused.
172
+ EVAL_TEMPERATURE = 0.0
173
+ UI_TOP_K_DEFAULT = 40
174
+ UI_TOP_P_DEFAULT = 1.0
175
+ EVAL_REPETITION_PENALTY = 1.0
176
+ DEFAULT_MAX_TOKENS = 1024
177
+ ZERO_GPU_DURATION_SECONDS = 120
178
+
179
+ def prepare_model_artifacts(model_name: str, *, local_files_only: bool = False) -> str:
180
+ cached_path = MODEL_SNAPSHOT_PATHS.get(model_name)
181
+ if cached_path is not None and os.path.exists(cached_path):
182
+ return cached_path
183
+
184
+ repo = MODELS[model_name]["repo_id"]
185
+ model_path = snapshot_download(
186
+ repo_id=repo,
187
+ token=access_token,
188
+ local_files_only=local_files_only,
189
+ )
190
+ MODEL_SNAPSHOT_PATHS[model_name] = model_path
191
+ return model_path
192
+
193
+
194
+ def load_pipeline(model_name):
195
+ """
196
+ Load and cache a transformers pipeline for text generation.
197
+ Tries bfloat16, falls back to float16 or float32 if unsupported.
198
+ """
199
+ global PIPELINES
200
+ if model_name in PIPELINES:
201
+ return PIPELINES[model_name]
202
+ model_path = prepare_model_artifacts(model_name, local_files_only=True)
203
+ tokenizer = AutoTokenizer.from_pretrained(
204
+ model_path,
205
+ token=access_token,
206
+ local_files_only=True,
207
+ )
208
+ for dtype in (torch.bfloat16, torch.float16, torch.float32):
209
+ try:
210
+ pipe = pipeline(
211
+ task="text-generation",
212
+ model=model_path,
213
+ tokenizer=tokenizer,
214
+ trust_remote_code=True,
215
+ dtype=dtype, # Use `dtype` instead of deprecated `torch_dtype`
216
+ device_map="auto",
217
+ use_cache=True, # Enable past-key-value caching
218
+ token=access_token,
219
+ local_files_only=True,
220
+ )
221
+ PIPELINES[model_name] = pipe
222
+ return pipe
223
+ except Exception:
224
+ continue
225
+ # Final fallback
226
+ pipe = pipeline(
227
+ task="text-generation",
228
+ model=model_path,
229
+ tokenizer=tokenizer,
230
+ trust_remote_code=True,
231
+ device_map="auto",
232
+ use_cache=True,
233
+ local_files_only=True,
234
+ )
235
+ PIPELINES[model_name] = pipe
236
+ return pipe
237
+
238
+ def _extract_visible_title(message: dict[str, Any]) -> str | None:
239
+ metadata = message.get("metadata")
240
+ if isinstance(metadata, dict):
241
+ title = metadata.get("title")
242
+ if isinstance(title, str):
243
+ return title
244
+ return None
245
+
246
+
247
+ def _build_llm_messages(chat_history: list[dict[str, Any]] | None, user_msg: str) -> list[dict[str, Any]]:
248
+ messages = [{"role": "system", "content": SYSTEM_PROMPT}]
249
+ for msg in chat_history or []:
250
+ role = msg.get("role")
251
+ content = msg.get("content")
252
+ if role not in {"user", "assistant"} or not isinstance(content, str):
253
+ continue
254
+ if role == "assistant" and _extract_visible_title(msg) in TRACE_TITLES:
255
+ continue
256
+ messages.append({"role": role, "content": content})
257
+ messages.append({"role": "user", "content": user_msg})
258
+ return messages
259
+
260
+
261
+ def _render_prompt(messages: list[dict[str, Any]], tokenizer: AutoTokenizer) -> str:
262
+ if hasattr(tokenizer, "chat_template") and tokenizer.chat_template:
263
+ kwargs: dict[str, Any] = {
264
+ "tokenize": False,
265
+ "add_generation_prompt": True,
266
+ "tools": TOOL_SCHEMAS,
267
+ }
268
+ try:
269
+ return tokenizer.apply_chat_template(messages, enable_thinking=True, **kwargs)
270
+ except TypeError:
271
+ return tokenizer.apply_chat_template(messages, **kwargs)
272
+
273
+ prompt = messages[0]["content"].strip() + "\n"
274
+ for msg in messages[1:]:
275
+ if msg["role"] == "user":
276
+ prompt += "User: " + str(msg["content"]).strip() + "\n"
277
+ elif msg["role"] == "assistant":
278
+ prompt += "Assistant: " + str(msg.get("content", "")).strip() + "\n"
279
+ for tool_call in msg.get("tool_calls", []):
280
+ function_info = tool_call.get("function", {})
281
+ prompt += (
282
+ f"{TOOL_CALL_OPEN}"
283
+ + json.dumps({
284
+ "name": function_info.get("name"),
285
+ "arguments": function_info.get("arguments"),
286
+ })
287
+ + f"{TOOL_CALL_CLOSE}\n"
288
+ )
289
+ elif msg["role"] == "tool":
290
+ prompt += f"User: {TOOL_RESPONSE_OPEN}{msg['content']}{TOOL_RESPONSE_CLOSE}\n"
291
+ if not prompt.strip().endswith("Assistant:"):
292
+ prompt += "Assistant: "
293
+ return prompt
294
+
295
+
296
+ class StopOnSequences(StoppingCriteria):
297
+ def __init__(self, stop_token_sequences: list[list[int]]) -> None:
298
+ super().__init__()
299
+ self.stop_token_sequences = [seq for seq in stop_token_sequences if seq]
300
+
301
+ def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs: Any) -> bool:
302
+ if cancel_event.is_set():
303
+ return True
304
+
305
+ current_tokens = input_ids[0].tolist()
306
+ for seq in self.stop_token_sequences:
307
+ if len(current_tokens) >= len(seq) and current_tokens[-len(seq):] == seq:
308
+ return True
309
+ return False
310
+
311
+
312
+ def _get_stop_token_sequences(tokenizer: AutoTokenizer) -> list[list[int]]:
313
+ stop_strings = [TOOL_CALL_CLOSE, ANSWER_CLOSE, TOOL_RESPONSE_OPEN, TOOL_RESPONSE_CLOSE]
314
+ return [tokenizer.encode(stop_text, add_special_tokens=False) for stop_text in stop_strings]
315
+
316
+
317
+ def _decode_generated_tokens(tokenizer: AutoTokenizer, generated_tokens: torch.Tensor) -> str:
318
+ raw_output = tokenizer.decode(generated_tokens, skip_special_tokens=False)
319
+ cleaned_output = raw_output
320
+ for special_token in tokenizer.all_special_tokens:
321
+ if special_token not in KEEP_SPECIAL_TOKENS:
322
+ cleaned_output = cleaned_output.replace(special_token, "")
323
+ return cleaned_output
324
+
325
+
326
+ def _clean_decoded_text(tokenizer: AutoTokenizer, text: str) -> str:
327
+ cleaned_text = text
328
+ for special_token in tokenizer.all_special_tokens:
329
+ if special_token not in KEEP_SPECIAL_TOKENS:
330
+ cleaned_text = cleaned_text.replace(special_token, "")
331
+ return cleaned_text
332
+
333
+
334
+ def _extract_partial_tag_content(message: str, tag: str) -> str | None:
335
+ open_tag = f"<{tag}>"
336
+ close_tag = f"</{tag}>"
337
+ start_idx = message.rfind(open_tag)
338
+ if start_idx == -1:
339
+ return None
340
+ content = message[start_idx + len(open_tag):]
341
+ end_idx = content.find(close_tag)
342
+ if end_idx != -1:
343
+ content = content[:end_idx]
344
+ return content.strip()
345
+
346
+
347
+ def _extract_live_sections(message: str) -> dict[str, str | None]:
348
+ return {
349
+ "thought": _extract_partial_tag_content(message, "think"),
350
+ "tool_call": _extract_partial_tag_content(message, "tool_call"),
351
+ "answer": _extract_partial_tag_content(message, "answer"),
352
+ }
353
+
354
+
355
+ def _set_live_message(
356
+ history: list[dict[str, Any]],
357
+ live_indices: dict[str, int],
358
+ key: str,
359
+ content: str | None,
360
+ *,
361
+ title: str | None = None,
362
+ code_block_lang: str | None = None,
363
+ ) -> None:
364
+ if not content:
365
+ return
366
+
367
+ rendered_content = content
368
+ if code_block_lang is not None:
369
+ rendered_content = f"```{code_block_lang}\n{content}\n```"
370
+
371
+ idx = live_indices.get(key)
372
+ if idx is None:
373
+ message: dict[str, Any] = {"role": "assistant", "content": rendered_content}
374
+ if title is not None:
375
+ message["metadata"] = {"title": title}
376
+ history.append(message)
377
+ live_indices[key] = len(history) - 1
378
+ return
379
+
380
+ history[idx]["content"] = rendered_content
381
+
382
+
383
+ def _build_debug_info() -> str:
384
+ tool_lines = "\n".join(f"- `{tool_name}`" for tool_name in sorted(TOOL_LIBRARY.keys()))
385
+ return f"### Available Tools\n{tool_lines}\n\n### System Prompt\n```text\n{SYSTEM_PROMPT}\n```"
386
+
387
+
388
+ def _stream_assistant_turn(
389
+ pipe: Any,
390
+ messages: list[dict[str, Any]],
391
+ max_tokens: int,
392
+ temperature: float,
393
+ top_k: int,
394
+ top_p: float,
395
+ repeat_penalty: float,
396
+ ):
397
+ prompt = _render_prompt(messages, pipe.tokenizer)
398
+ inputs = pipe.tokenizer(
399
+ prompt,
400
+ return_tensors="pt",
401
+ truncation=True,
402
+ padding=bool(getattr(pipe.tokenizer, "pad_token_id", None)),
403
+ )
404
+ device = getattr(pipe, "device", None) or pipe.model.device
405
+ inputs = tree_map(lambda x: x.to(device) if hasattr(x, "to") else x, inputs)
406
+
407
+ streamer = TextIteratorStreamer(
408
+ pipe.tokenizer,
409
+ skip_prompt=True,
410
+ skip_special_tokens=False,
411
+ )
412
+
413
+ generation_kwargs: dict[str, Any] = {
414
+ "input_ids": inputs["input_ids"],
415
+ "attention_mask": inputs["attention_mask"],
416
+ "max_new_tokens": int(max_tokens),
417
+ "repetition_penalty": float(repeat_penalty),
418
+ "use_cache": True,
419
+ "pad_token_id": getattr(pipe.tokenizer, "pad_token_id", pipe.tokenizer.eos_token_id),
420
+ "stopping_criteria": StoppingCriteriaList([StopOnSequences(_get_stop_token_sequences(pipe.tokenizer))]),
421
+ "streamer": streamer,
422
+ }
423
+ eos_token_id = getattr(pipe.tokenizer, "eos_token_id", None)
424
+ if eos_token_id is not None:
425
+ generation_kwargs["eos_token_id"] = eos_token_id
426
+ if temperature > 0:
427
+ generation_kwargs.update({
428
+ "do_sample": True,
429
+ "temperature": float(temperature),
430
+ "top_k": int(top_k),
431
+ "top_p": float(top_p),
432
+ })
433
+ else:
434
+ generation_kwargs["do_sample"] = False
435
+
436
+ error_holder: dict[str, Exception] = {}
437
+
438
+ def _run_generation() -> None:
439
+ try:
440
+ with torch.inference_mode():
441
+ pipe.model.generate(**generation_kwargs)
442
+ except Exception as exc:
443
+ error_holder["error"] = exc
444
+ finally:
445
+ streamer.end()
446
+
447
+ generation_thread = threading.Thread(target=_run_generation, daemon=True)
448
+ generation_thread.start()
449
+
450
+ raw_output = ""
451
+ yield raw_output, prompt
452
+ for chunk in streamer:
453
+ raw_output = _clean_decoded_text(pipe.tokenizer, raw_output + chunk)
454
+ yield raw_output, prompt
455
+
456
+ generation_thread.join()
457
+ if "error" in error_holder:
458
+ raise error_holder["error"]
459
+
460
+
461
+ def _parse_assistant_output(raw_output: str, context: list[dict[str, Any]]) -> tuple[ThoughtSchema | None, dict[str, Any]]:
462
+ analysis = PARSER.analyze_message_in_context(
463
+ context,
464
+ message=raw_output,
465
+ tool_names=list(TOOL_LIBRARY.keys()),
466
+ )
467
+ if not bool(analysis["is_valid_think_then_tool_or_answer"]):
468
+ return None, analysis
469
+ if analysis["has_answer"] and not bool(analysis["answer_policy_valid"]):
470
+ return None, analysis
471
+
472
+ tool_call: FunctionInvocationInfo | None = None
473
+ tool_info = analysis["tool"]
474
+ if analysis["has_tool_call"]:
475
+ if not bool(tool_info["json_valid"]):
476
+ return None, analysis
477
+ tool_call = FunctionInvocationInfo(
478
+ name=str(tool_info["name"]),
479
+ arguments=dict(tool_info["arguments"]),
480
+ )
481
+
482
+ return ThoughtSchema(
483
+ thought=analysis["thought"] or "",
484
+ tool_call=tool_call,
485
+ final_answer=analysis["answer"],
486
+ completed=analysis["answer"] is not None,
487
+ ), analysis
488
+
489
+
490
+ def _execute_tool_call(message: ThoughtSchema) -> dict[str, str]:
491
+ if message.tool_call is None:
492
+ raise ValueError("Tool call is required")
493
+
494
+ name = message.tool_call.name
495
+ arguments = message.tool_call.arguments
496
+ try:
497
+ if name not in TOOL_LIBRARY:
498
+ return {
499
+ "function_name": name,
500
+ "execution_result": f"ERROR: Function '{name}' not found in library",
501
+ }
502
+ result = TOOL_LIBRARY[name](**arguments)
503
+ return {"function_name": name, "execution_result": str(result)}
504
+ except Exception as exc:
505
+ return {
506
+ "function_name": name,
507
+ "execution_result": f"ERROR: {type(exc).__name__}: {exc}",
508
+ }
509
+
510
+
511
+ # Keep a single GPU allocation for the whole multi-turn solve, with a fixed
512
+ # budget that is simpler and more predictable than a heuristic estimate.
513
+ @spaces.GPU(duration=ZERO_GPU_DURATION_SECONDS)
514
+ def chat_response(
515
+ user_msg,
516
+ chat_history,
517
+ show_tool_trace,
518
+ enable_streaming,
519
+ max_tool_turns,
520
+ model_name,
521
+ max_tokens,
522
+ temperature,
523
+ top_k,
524
+ top_p,
525
+ repeat_penalty,
526
+ ):
527
+ """
528
+ Generates responses by iteratively calling linear algebra tools until a final answer is produced.
529
+ """
530
+ cancel_event.clear()
531
+
532
+ history = list(chat_history or [])
533
+ history.append({"role": "user", "content": user_msg})
534
+ debug_info = _build_debug_info()
535
+
536
+ try:
537
+ pipe = load_pipeline(model_name)
538
+ context = _build_llm_messages(chat_history, user_msg)
539
+
540
+ yield history, debug_info
541
+
542
+ for step_idx in range(int(max_tool_turns)):
543
+ raw_output = ""
544
+ live_indices: dict[str, int] = {}
545
+
546
+ turn_stream = _stream_assistant_turn(
547
+ pipe=pipe,
548
+ messages=context,
549
+ max_tokens=max_tokens,
550
+ temperature=temperature,
551
+ top_k=top_k,
552
+ top_p=top_p,
553
+ repeat_penalty=repeat_penalty,
554
+ )
555
+
556
+ if enable_streaming:
557
+ for partial_output, _current_prompt in turn_stream:
558
+ raw_output = partial_output
559
+
560
+ live_sections = _extract_live_sections(raw_output)
561
+ if show_tool_trace:
562
+ _set_live_message(
563
+ history,
564
+ live_indices,
565
+ "thought",
566
+ live_sections["thought"],
567
+ title="💭 Thought",
568
+ )
569
+ _set_live_message(
570
+ history,
571
+ live_indices,
572
+ "tool_call",
573
+ live_sections["tool_call"],
574
+ title="🛠 Tool Call",
575
+ code_block_lang="json",
576
+ )
577
+ _set_live_message(
578
+ history,
579
+ live_indices,
580
+ "answer",
581
+ live_sections["answer"],
582
+ )
583
+
584
+ yield history, debug_info
585
+ else:
586
+ for partial_output, _current_prompt in turn_stream:
587
+ raw_output = partial_output
588
+
589
+ if cancel_event.is_set():
590
+ history.append({"role": "assistant", "content": "[Generation Canceled]"})
591
+ yield history, debug_info
592
+ break
593
+
594
+ parsed, analysis = _parse_assistant_output(raw_output, context)
595
+ if parsed is None:
596
+ history.append({
597
+ "role": "assistant",
598
+ "content": "I couldn't produce a valid tool call or final answer for this problem.",
599
+ })
600
+ yield history, debug_info
601
+ break
602
+
603
+ assistant_message = MODEL_MESSAGE_CONFIG.format_assistant_message(parsed)
604
+ if assistant_message is None:
605
+ history.append({"role": "assistant", "content": "The model returned an empty action."})
606
+ yield history, debug_info
607
+ break
608
+
609
+ context.append(assistant_message)
610
+
611
+ if show_tool_trace and parsed.thought:
612
+ _set_live_message(
613
+ history,
614
+ live_indices,
615
+ "thought",
616
+ parsed.thought,
617
+ title="💭 Thought",
618
+ )
619
+
620
+ if parsed.tool_call is not None:
621
+ if show_tool_trace:
622
+ _set_live_message(
623
+ history,
624
+ live_indices,
625
+ "tool_call",
626
+ json.dumps(
627
+ {
628
+ "name": parsed.tool_call.name,
629
+ "arguments": parsed.tool_call.arguments,
630
+ },
631
+ indent=2,
632
+ ),
633
+ title="🛠 Tool Call",
634
+ code_block_lang="json",
635
+ )
636
+
637
+ tool_result = _execute_tool_call(parsed)
638
+ context.append(MODEL_MESSAGE_CONFIG.create_tool_message(context, tool_result))
639
+
640
+ if show_tool_trace:
641
+ history.append({
642
+ "role": "assistant",
643
+ "content": tool_result["execution_result"],
644
+ "metadata": {"title": "📦 Tool Response"},
645
+ })
646
+
647
+ yield history, debug_info
648
+ continue
649
+
650
+ if parsed.final_answer is not None:
651
+ answer_idx = live_indices.get("answer")
652
+ if answer_idx is not None:
653
+ history[answer_idx]["content"] = parsed.final_answer
654
+ else:
655
+ history.append({"role": "assistant", "content": parsed.final_answer})
656
+ yield history, debug_info
657
+ break
658
+ else:
659
+ history.append({
660
+ "role": "assistant",
661
+ "content": "I hit the tool-turn limit before finishing the problem.",
662
+ })
663
+ yield history, debug_info
664
+ except GeneratorExit:
665
+ print("Chat response cancelled.")
666
+ return
667
+ except Exception as e:
668
+ history.append({"role": "assistant", "content": f"Error: {e}"})
669
+ yield history, debug_info
670
+ finally:
671
+ gc.collect()
672
+
673
+
674
+ demo_theme = gr.themes.Soft(
675
+ primary_hue="indigo",
676
+ secondary_hue="purple",
677
+ neutral_hue="slate",
678
+ radius_size="lg",
679
+ font=[gr.themes.GoogleFont("Inter"), "Arial", "sans-serif"]
680
+ )
681
+
682
+ demo_css = """
683
+ .chatbot { border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
684
+ button.primary { font-weight: 600; }
685
+ .gradio-accordion { margin-bottom: 12px; }
686
+ """
687
+
688
+ with gr.Blocks(title="LLM Inference with ZeroGPU") as demo:
689
+ # Header
690
+ gr.Markdown("""
691
+ # 🧠 LinAlgZero Demo
692
+ ### Multi-turn linear algebra solving with tool calling
693
+ """)
694
+
695
+ with gr.Row():
696
+ # Left Panel - Configuration
697
+ with gr.Column(scale=3):
698
+ # Core Settings (Always Visible)
699
+ with gr.Group():
700
+ gr.Markdown("### ⚙️ Core Settings")
701
+ model_dd = gr.Dropdown(
702
+ label="🤖 Model",
703
+ choices=list(MODELS.keys()),
704
+ value="atomwalk12/LinAlgZero-GRPO",
705
+ info="Select the language model to use"
706
+ )
707
+ trace_chk = gr.Checkbox(
708
+ label="🔍 Show Tool Trace",
709
+ value=True,
710
+ info="Show thoughts, tool calls, and tool responses in the chat"
711
+ )
712
+ stream_chk = gr.Checkbox(
713
+ label="⚡ Enable Live Streaming",
714
+ value=True,
715
+ info="Stream partial model output while each tool-calling turn is being generated"
716
+ )
717
+ max_tool_turns = gr.Slider(
718
+ 1, 12, value=6, step=1,
719
+ label="Max Tool Turns",
720
+ info="Maximum number of tool-calling turns before stopping"
721
+ )
722
+
723
+ example_dropdown = gr.Dropdown(
724
+ label="📚 Dataset Example",
725
+ choices=DATASET_EXAMPLE_CHOICES,
726
+ value=None,
727
+ info=DATASET_EXAMPLE_INFO,
728
+ allow_custom_value=False,
729
+ )
730
+
731
+ # Advanced Settings (Collapsible)
732
+ with gr.Accordion("🎛️ Advanced Generation Parameters", open=True):
733
+ max_tok = gr.Slider(
734
+ 64, 16384, value=DEFAULT_MAX_TOKENS, step=32,
735
+ label="Max Tokens",
736
+ info="Maximum length of generated response"
737
+ )
738
+ temp = gr.Slider(
739
+ 0.0, 2.0, value=EVAL_TEMPERATURE, step=0.1,
740
+ label="Temperature",
741
+ info="Higher = more creative, Lower = more focused"
742
+ )
743
+ with gr.Row():
744
+ k = gr.Slider(
745
+ 1, 100, value=UI_TOP_K_DEFAULT, step=1,
746
+ label="Top-K",
747
+ info="Number of top tokens to consider",
748
+ interactive=False,
749
+ )
750
+ p = gr.Slider(
751
+ 0.1, 1.0, value=UI_TOP_P_DEFAULT, step=0.05,
752
+ label="Top-P",
753
+ info="Nucleus sampling threshold",
754
+ interactive=False,
755
+ )
756
+ rp = gr.Slider(
757
+ 1.0, 2.0, value=EVAL_REPETITION_PENALTY, step=0.1,
758
+ label="Repetition Penalty",
759
+ info="Penalize repeated tokens"
760
+ )
761
+
762
+ # Actions
763
+ with gr.Column():
764
+ reset_params_btn = gr.Button("↺ Restore Default Params", variant="secondary")
765
+ clr = gr.Button("🗑️ Clear Chat", variant="secondary", scale=1)
766
+
767
+ # Right Panel - Chat Interface
768
+ with gr.Column(scale=7):
769
+ chat = gr.Chatbot(
770
+ height=1200,
771
+ label="💬 Conversation",
772
+ buttons=["copy"],
773
+ avatar_images=(None, ASSISTANT_AVATAR_PATH),
774
+ layout="bubble"
775
+ )
776
+
777
+ # Input Area
778
+ with gr.Row():
779
+ txt = gr.Textbox(
780
+ placeholder="💭 Type your message here... (Press Enter to send)",
781
+ scale=9,
782
+ container=False,
783
+ show_label=False,
784
+ lines=1,
785
+ max_lines=5
786
+ )
787
+ with gr.Column(scale=1, min_width=120):
788
+ submit_btn = gr.Button("📤 Send", variant="primary", size="lg", interactive=False)
789
+ cancel_btn = gr.Button("⏹️ Stop", variant="stop", visible=False, size="lg")
790
+
791
+ # Example Prompts
792
+ gr.Examples(
793
+ examples=SIMPLE_EXAMPLE_ROWS,
794
+ inputs=txt,
795
+ label="💡 Example Prompts"
796
+ )
797
+
798
+ # Debug/Status Info (Collapsible)
799
+ with gr.Accordion("🔍 Debug Info", open=False):
800
+ dbg = gr.Markdown(value=_build_debug_info())
801
+
802
+ # --- Event Listeners ---
803
+
804
+ # Group all inputs for cleaner event handling
805
+ chat_inputs = [txt, chat, trace_chk, stream_chk, max_tool_turns, model_dd, max_tok, temp, k, p, rp]
806
+ # Group all UI components that can be updated.
807
+ ui_components = [chat, dbg, txt, submit_btn, cancel_btn]
808
+
809
+ def submit_and_manage_ui(user_msg, chat_history, *args):
810
+ """
811
+ Orchestrator function that manages UI state and calls the backend chat function.
812
+ It uses a try...finally block to ensure the UI is always reset.
813
+ """
814
+ if not user_msg.strip():
815
+ # If the message is empty, do nothing.
816
+ # We yield an empty dict to avoid any state changes.
817
+ yield {}
818
+ return
819
+
820
+ # 1. Update UI to "generating" state.
821
+ # Crucially, we do NOT update the `chat` component here, as the backend
822
+ # will provide the correctly formatted history in the first response chunk.
823
+ yield {
824
+ txt: gr.update(value="", interactive=False),
825
+ submit_btn: gr.update(interactive=False),
826
+ cancel_btn: gr.update(visible=True),
827
+ }
828
+
829
+ cancelled = False
830
+ try:
831
+ model_name = args[3]
832
+ prepare_model_artifacts(model_name)
833
+
834
+ # 2. Call the backend and stream updates
835
+ backend_args = [user_msg, chat_history] + list(args)
836
+ for response_chunk in chat_response(*backend_args):
837
+ yield {
838
+ chat: response_chunk[0],
839
+ dbg: response_chunk[1],
840
+ }
841
+ except GeneratorExit:
842
+ # Mark as cancelled and re-raise to prevent "generator ignored GeneratorExit"
843
+ cancelled = True
844
+ print("Generation cancelled by user.")
845
+ raise
846
+ except Exception as e:
847
+ print(f"An error occurred during generation: {e}")
848
+ # If an error happens, add it to the chat history to inform the user.
849
+ error_history = (chat_history or []) + [
850
+ {'role': 'user', 'content': user_msg},
851
+ {'role': 'assistant', 'content': f"**An error occurred:** {str(e)}"}
852
+ ]
853
+ yield {chat: error_history}
854
+ finally:
855
+ # Only reset UI if not cancelled (to avoid "generator ignored GeneratorExit")
856
+ if not cancelled:
857
+ print("Resetting UI state.")
858
+ yield {
859
+ txt: gr.update(interactive=True),
860
+ submit_btn: gr.update(interactive=False),
861
+ cancel_btn: gr.update(visible=False),
862
+ }
863
+
864
+ def set_cancel_flag():
865
+ """Called by the cancel button, sets the global event."""
866
+ cancel_event.set()
867
+ print("Cancellation signal sent.")
868
+
869
+ def reset_ui_after_cancel():
870
+ """Reset UI components after cancellation."""
871
+ cancel_event.clear() # Clear the flag for next generation
872
+ print("UI reset after cancellation.")
873
+ return {
874
+ txt: gr.update(interactive=True),
875
+ submit_btn: gr.update(interactive=False),
876
+ cancel_btn: gr.update(visible=False),
877
+ }
878
+
879
+ def clear_chat_state():
880
+ """Clear the chat and restore default UI state."""
881
+ cancel_event.clear()
882
+ return {
883
+ chat: [],
884
+ dbg: _build_debug_info(),
885
+ txt: "",
886
+ submit_btn: gr.update(interactive=False),
887
+ cancel_btn: gr.update(visible=False),
888
+ }
889
+
890
+ def set_example_text(example_query: str | None) -> str:
891
+ return example_query or ""
892
+
893
+ def apply_example_selection(evt: gr.SelectData):
894
+ if evt is None or not evt.selected:
895
+ return gr.skip(), gr.skip()
896
+ example_text = set_example_text(evt.value)
897
+ return gr.update(value=example_text), toggle_submit_button(example_text)
898
+
899
+ def toggle_submit_button(text: str | None):
900
+ return gr.update(interactive=bool(text and text.strip()))
901
+
902
+ def toggle_sampling_controls(temperature: float):
903
+ sampling_enabled = float(temperature) > 0.0
904
+ return [
905
+ gr.update(interactive=sampling_enabled),
906
+ gr.update(interactive=sampling_enabled),
907
+ ]
908
+
909
+ def restore_generation_defaults():
910
+ return (
911
+ gr.update(value=DEFAULT_MAX_TOKENS),
912
+ gr.update(value=EVAL_TEMPERATURE),
913
+ gr.update(value=UI_TOP_K_DEFAULT, interactive=False),
914
+ gr.update(value=UI_TOP_P_DEFAULT, interactive=False),
915
+ gr.update(value=EVAL_REPETITION_PENALTY),
916
+ )
917
+
918
+ # Event for submitting text via Enter key or Submit button
919
+ submit_event = txt.submit(
920
+ fn=submit_and_manage_ui,
921
+ inputs=chat_inputs,
922
+ outputs=ui_components,
923
+ )
924
+ submit_click_event = submit_btn.click(
925
+ fn=submit_and_manage_ui,
926
+ inputs=chat_inputs,
927
+ outputs=ui_components,
928
+ )
929
+
930
+ # Event for the "Cancel" button.
931
+ # It sets the cancel flag, cancels the submit event, then resets the UI.
932
+ cancel_btn.click(
933
+ fn=set_cancel_flag,
934
+ cancels=[submit_event, submit_click_event]
935
+ ).then(
936
+ fn=reset_ui_after_cancel,
937
+ outputs=ui_components
938
+ )
939
+
940
+ txt.input(fn=toggle_submit_button, inputs=txt, outputs=submit_btn)
941
+ txt.change(fn=toggle_submit_button, inputs=txt, outputs=submit_btn)
942
+ example_dropdown.select(
943
+ fn=apply_example_selection,
944
+ outputs=[txt, submit_btn],
945
+ )
946
+ temp.change(fn=toggle_sampling_controls, inputs=temp, outputs=[k, p])
947
+ reset_params_btn.click(
948
+ fn=restore_generation_defaults,
949
+ outputs=[max_tok, temp, k, p, rp],
950
+ )
951
+
952
+ # Clear chat action
953
+ clr.click(
954
+ fn=set_cancel_flag,
955
+ cancels=[submit_event, submit_click_event]
956
+ ).then(
957
+ fn=clear_chat_state,
958
+ outputs=ui_components
959
+ )
960
+
961
+ if __name__ == "__main__":
962
+ launch_args = _parse_launch_args()
963
+ demo.launch(
964
+ theme=demo_theme,
965
+ css=demo_css,
966
+ share=launch_args.share,
967
+ ssr_mode=False,
968
+ )