diff --git a/.clang-format b/.clang-format
index f990767..f67a9c5 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,14 +1,27 @@
+---
+# Clang-Format Configuration File
+# ----------------------------------------------------
BasedOnStyle: LLVM
+Language: Cpp
+
+# Indentation and Tab Settings
IndentWidth: 6
UseTab: Never
TabWidth: 6
ContinuationIndentWidth: 6
+
+# Brace Wrapping
BreakBeforeBraces: Allman
+
+# Formatting Geometry & Constraints
ColumnLimit: 100
PointerAlignment: Right
ReferenceAlignment: Right
+
+# Include Management
SortIncludes: CaseInsensitive
IncludeBlocks: Regroup
+# Spacing rules
SpaceBeforeParens: ControlStatements
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
@@ -23,5 +36,7 @@ AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortBlocksOnASingleLine: Empty
+# Braced initializer list formatting (e.g. vector/array initialization)
Cpp11BracedListStyle: true
SpacesInAngles: Never
+...
\ No newline at end of file
diff --git a/.devops/Dockerfile.dev.frontend b/.devops/Dockerfile.dev.frontend
deleted file mode 100644
index de054d6..0000000
--- a/.devops/Dockerfile.dev.frontend
+++ /dev/null
@@ -1,12 +0,0 @@
-FROM node:20-alpine
-
-WORKDIR /app
-
-COPY frontend/package*.json ./
-RUN npm ci
-
-COPY frontend/ ./
-
-EXPOSE 5173
-
-CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index 0a9af06..a962c59 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -7,12 +7,16 @@ on:
- "**/*.py"
- "**/*.cpp"
- "**/*.c"
+ - "**/*.h"
+ - "**/*.hpp"
pull_request:
branches: [ "master" ]
paths:
- "**/*.py"
- "**/*.cpp"
- "**/*.c"
+ - "**/*.h"
+ - "**/*.hpp"
jobs:
syntax-check:
@@ -35,38 +39,32 @@ jobs:
- name: Lint Python Files
run: |
- # Only checks syntax and severe errors
- flake8 . --count --select=E999,F821,F822,F823 --show-source --statistics
-
- # --- C/C++ Syntax Check ---
- - name: Install C/C++ Compiler (GCC)
- run: |
- sudo apt-get update
- sudo apt-get install -y gcc g++
-
+ # E9, F63, F7, F82 checks syntax errors & undefined variables
+ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
- name: Check C/C++ Syntax
run: |
echo "Checking C and C++ files for syntax errors..."
errors=0
- # Using redirect loops to properly catch errors without subshell issues
+ # Loop through C files (-I. includes local root directory for header lookups)
while read -r file; do
if [ -z "$file" ]; then continue; fi
echo "Checking $file"
- if ! gcc -fsyntax-only "$file"; then
- echo "Syntax error in $file"
+ if ! gcc -fsyntax-only -I. "$file"; then
+ echo "::error file=$file::Syntax error in $file"
errors=$((errors + 1))
fi
- done < <(find . -name "*.c")
+ done < <(find . -type f -name "*.c")
+ # Loop through C++ files
while read -r file; do
if [ -z "$file" ]; then continue; fi
echo "Checking $file"
- if ! g++ -fsyntax-only "$file"; then
- echo "syntax error in $file"
+ if ! g++ -fsyntax-only -I. "$file"; then
+ echo "::error file=$file::Syntax error in $file"
errors=$((errors + 1))
fi
- done < <(find . -name "*.cpp")
+ done < <(find . -type f -name "*.cpp")
if [ $errors -ne 0 ]; then
echo "$errors file(s) failed syntax verification."
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 20b8142..570e77d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,6 +3,7 @@ name: Build & Test
on:
push:
branches: [ master, main, develop ]
+
jobs:
cpp-quality:
runs-on: ubuntu-latest
@@ -22,7 +23,7 @@ jobs:
- name: Clang-Format Check
id: clang-format
run: |
- echo "### π Clang-Format Code Style Check" >> $GITHUB_STEP_SUMMARY
+ echo "### Clang-Format Code Style Check" >> $GITHUB_STEP_SUMMARY
find . -name "*.cpp" -o -name "*.h" -o -name "*.hpp" | grep -v build > cpp_files.txt
if [ -s cpp_files.txt ]; then
@@ -32,10 +33,10 @@ jobs:
cat format_output.txt >> $GITHUB_STEP_SUMMARY
exit 1
else
- echo " Code formatting is correct" >> $GITHUB_STEP_SUMMARY
+ echo "Code formatting is correct" >> $GITHUB_STEP_SUMMARY
fi
else
- echo " No C++ files found" >> $GITHUB_STEP_SUMMARY
+ echo "No C++ files found" >> $GITHUB_STEP_SUMMARY
fi
continue-on-error: true
@@ -55,7 +56,7 @@ jobs:
echo "Issues detected:" >> $GITHUB_STEP_SUMMARY
cat cppcheck_output.txt >> $GITHUB_STEP_SUMMARY
else
- echo " No critical issues found" >> $GITHUB_STEP_SUMMARY
+ echo "No critical issues found" >> $GITHUB_STEP_SUMMARY
fi
continue-on-error: true
@@ -88,7 +89,7 @@ jobs:
fetch-depth: 0
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
@@ -101,10 +102,10 @@ jobs:
- name: Black Format Check
id: black
run: |
- echo "### Black Code Formatting" >> $GITHUB_STEP_SUMMARY
+ echo "### Black Code Formatting" >> $GITHUB_STEP_SUMMARY
black --check --diff . 2>&1 | tee black_output.txt || true
if [ $? -ne 0 ]; then
- echo " Formatting issues detected:" >> $GITHUB_STEP_SUMMARY
+ echo "Formatting issues detected:" >> $GITHUB_STEP_SUMMARY
head -30 black_output.txt >> $GITHUB_STEP_SUMMARY
else
echo "Black formatting passed" >> $GITHUB_STEP_SUMMARY
@@ -127,27 +128,27 @@ jobs:
- name: Flake8 Linting
id: flake8
run: |
- echo "### Flake8 Linting" >> $GITHUB_STEP_SUMMARY
+ echo "### Flake8 Linting" >> $GITHUB_STEP_SUMMARY
flake8 . --max-line-length=127 --count --statistics 2>&1 | tee flake8_output.txt || true
if [ $? -ne 0 ]; then
echo "Linting violations:" >> $GITHUB_STEP_SUMMARY
cat flake8_output.txt >> $GITHUB_STEP_SUMMARY
else
- echo " Flake8 passed" >> $GITHUB_STEP_SUMMARY
+ echo "Flake8 passed" >> $GITHUB_STEP_SUMMARY
fi
continue-on-error: true
- name: Pylint Analysis
id: pylint
run: |
- echo "### Pylint Code Analysis" >> $GITHUB_STEP_SUMMARY
+ echo "### Pylint Code Analysis" >> $GITHUB_STEP_SUMMARY
find . -name "*.py" -type f | grep -v __pycache__ | xargs pylint \
--fail-under=7.0 \
--disable=C0111,C0103,R0913,W0212 \
2>&1 | tee pylint_output.txt || true
if grep -q "rated at" pylint_output.txt; then
- echo " Pylint analysis complete" >> $GITHUB_STEP_SUMMARY
+ echo "Pylint analysis complete" >> $GITHUB_STEP_SUMMARY
tail -3 pylint_output.txt >> $GITHUB_STEP_SUMMARY
fi
continue-on-error: true
@@ -155,10 +156,10 @@ jobs:
- name: Bandit Security Scan
id: bandit
run: |
- echo "### Bandit Security Check" >> $GITHUB_STEP_SUMMARY
+ echo "### Bandit Security Check" >> $GITHUB_STEP_SUMMARY
bandit -r . -ll 2>&1 | tee bandit_output.txt || true
if grep -q "Issue:" bandit_output.txt; then
- echo " Security issues found:" >> $GITHUB_STEP_SUMMARY
+ echo "Security issues found:" >> $GITHUB_STEP_SUMMARY
grep -A1 "Issue:" bandit_output.txt >> $GITHUB_STEP_SUMMARY
else
echo "No security issues detected" >> $GITHUB_STEP_SUMMARY
@@ -171,6 +172,10 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
fail-fast: false
+
+ defaults:
+ run:
+ shell: bash
steps:
- uses: actions/checkout@v4
@@ -181,12 +186,7 @@ jobs:
if: runner.os == 'Linux'
run: |
sudo apt-get update
- sudo apt-get install -y build-essential g++
-
- - name: Install dependencies (macOS)
- if: runner.os == 'macOS'
- run: |
- brew install gcc
+ sudo apt-get install -y build-essential g++ libomp-dev
- name: Install dependencies (Windows)
if: runner.os == 'Windows'
@@ -196,39 +196,57 @@ jobs:
- name: Build inference executable
id: build-inference
run: |
- echo "### π¨ Building Inference Executable" >> $GITHUB_STEP_SUMMARY
+ echo "### Building Inference Executable" >> $GITHUB_STEP_SUMMARY
- if [ -f "main.cpp" ]; then
- g++ -std=c++17 -O2 -I. -Iinclude -o quadtrix_inference main.cpp 2>&1 | tee inference_build.txt || true
-
- if [ -f "quadtrix_inference" ] || [ -f "quadtrix_inference.exe" ]; then
- echo " Inference build successful" >> $GITHUB_STEP_SUMMARY
+ if [ "$RUNNER_OS" == "macOS" ]; then
+ echo "Building for macOS with Metal framework support..."
+ if [ -f "llm.mm" ] && [ -f "main.cpp" ]; then
+ clang++ -std=c++17 -O3 -I. -Iinclude main.cpp llm.mm -framework Foundation -framework Metal -framework MetalPerformanceShaders -o llm 2>&1 | tee inference_build.txt || true
+ elif [ -f "main.cpp" ]; then
+ clang++ -std=c++17 -O3 -I. -Iinclude main.cpp -o llm 2>&1 | tee inference_build.txt || true
+ fi
+
+ if [ -f "llm" ]; then
+ echo "Inference build successful (macOS)" >> $GITHUB_STEP_SUMMARY
else
- echo "Inference build failed" >> $GITHUB_STEP_SUMMARY
+ echo "Inference build failed (macOS)" >> $GITHUB_STEP_SUMMARY
cat inference_build.txt >> $GITHUB_STEP_SUMMARY
fi
+
else
- echo "main.cpp not found - skipping inference build" >> $GITHUB_STEP_SUMMARY
+ echo "Building for $RUNNER_OS with GCC..."
+ if [ -f "main.cpp" ]; then
+ g++ -std=c++17 -O3 -march=native -fopenmp -I. -Iinclude -o llm.exe main.cpp 2>&1 | tee inference_build.txt || true
+
+ if [ -f "llm.exe" ]; then
+ echo "Inference build successful ($RUNNER_OS)" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "Inference build failed ($RUNNER_OS)" >> $GITHUB_STEP_SUMMARY
+ cat inference_build.txt >> $GITHUB_STEP_SUMMARY
+ fi
+ else
+ echo "main.cpp not found - skipping inference build" >> $GITHUB_STEP_SUMMARY
+ fi
fi
continue-on-error: true
- name: Test inference executable
id: test-inference
run: |
- echo "### Testing Inference Executable" >> $GITHUB_STEP_SUMMARY
+ echo "### Testing Inference Executable" >> $GITHUB_STEP_SUMMARY
- if [ -f "quadtrix_inference" ] || [ -f "quadtrix_inference.exe" ]; then
- BINARY="quadtrix_inference"
- if [ ! -f "$BINARY" ]; then
- BINARY="quadtrix_inference.exe"
- fi
-
+ BINARY="llm.exe"
+ if [ "$RUNNER_OS" == "macOS" ]; then
+ BINARY="llm"
+ fi
+
+ if [ -f "$BINARY" ]; then
if [ -f "data/input.txt" ]; then
./$BINARY data/input.txt 2>&1 | tee inference_test.txt || true
echo "Inference test completed" >> $GITHUB_STEP_SUMMARY
head -20 inference_test.txt >> $GITHUB_STEP_SUMMARY
else
- echo " data/input.txt not found - skipping inference test" >> $GITHUB_STEP_SUMMARY
+ echo "data/input.txt not found - skipping inference test" >> $GITHUB_STEP_SUMMARY
fi
else
echo "Inference executable not found" >> $GITHUB_STEP_SUMMARY
@@ -244,29 +262,29 @@ jobs:
g++ -std=c++17 -O2 -I. -Iinclude llm.cpp train.cpp -o quadtrix_train 2>&1 | tee training_build.txt || true
if [ -f "quadtrix_train" ] || [ -f "quadtrix_train.exe" ]; then
- echo " Training build successful" >> $GITHUB_STEP_SUMMARY
+ echo "Training build successful" >> $GITHUB_STEP_SUMMARY
else
- echo " Training build failed" >> $GITHUB_STEP_SUMMARY
+ echo "Training build failed" >> $GITHUB_STEP_SUMMARY
cat training_build.txt >> $GITHUB_STEP_SUMMARY
fi
elif [ -f "train.cpp" ]; then
- echo " train.cpp found but llm.cpp not found - skipping training build" >> $GITHUB_STEP_SUMMARY
+ echo "train.cpp found but llm.cpp not found - skipping training build" >> $GITHUB_STEP_SUMMARY
else
- echo " train.cpp not found - skipping training build" >> $GITHUB_STEP_SUMMARY
+ echo "train.cpp not found - skipping training build" >> $GITHUB_STEP_SUMMARY
fi
continue-on-error: true
- name: Test training executable
id: test-training
run: |
- echo "### Testing Training Executable" >> $GITHUB_STEP_SUMMARY
+ echo "### Testing Training Executable" >> $GITHUB_STEP_SUMMARY
- if [ -f "quadtrix_train" ] || [ -f "quadtrix_train.exe" ]; then
- BINARY="quadtrix_train"
- if [ ! -f "$BINARY" ]; then
- BINARY="quadtrix_train.exe"
- fi
-
+ BINARY="quadtrix_train"
+ if [ -f "quadtrix_train.exe" ]; then
+ BINARY="quadtrix_train.exe"
+ fi
+
+ if [ -f "$BINARY" ]; then
echo "Running training executable (timeout 30s)..." >> $GITHUB_STEP_SUMMARY
timeout 30 ./$BINARY 2>&1 | tee training_test.txt || true
echo "Training test completed" >> $GITHUB_STEP_SUMMARY
@@ -289,7 +307,7 @@ jobs:
fetch-depth: 0
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
@@ -300,7 +318,7 @@ jobs:
pip install pytest pytest-cov
if [ -f "requirements.txt" ]; then
- pip install -r requirements.txt 2>/dev/null || echo " Some requirements failed to install"
+ pip install -r requirements.txt 2>/dev/null || echo "Some requirements failed to install"
fi
- name: Run Python tests
@@ -313,11 +331,11 @@ jobs:
if [ $? -eq 0 ]; then
echo "All tests passed" >> $GITHUB_STEP_SUMMARY
else
- echo " Some tests failed" >> $GITHUB_STEP_SUMMARY
+ echo "Some tests failed" >> $GITHUB_STEP_SUMMARY
fi
tail -30 pytest_output.txt >> $GITHUB_STEP_SUMMARY
else
- echo " No test files found" >> $GITHUB_STEP_SUMMARY
+ echo "No test files found" >> $GITHUB_STEP_SUMMARY
fi
continue-on-error: true
diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml
index 0c6456b..ca47c38 100644
--- a/.github/workflows/jekyll-gh-pages.yml
+++ b/.github/workflows/jekyll-gh-pages.yml
@@ -28,7 +28,7 @@ jobs:
source: ./
destination: ./_site
- name: Upload artifact
- uses: actions/upload-pages-artifact@v3
+ uses: actions/upload-pages-artifact@v5
deploy:
environment:
diff --git a/.github/workflows/lmgnu.yml b/.github/workflows/main.yml
similarity index 98%
rename from .github/workflows/lmgnu.yml
rename to .github/workflows/main.yml
index e284cd1..2a2d6c8 100644
--- a/.github/workflows/lmgnu.yml
+++ b/.github/workflows/main.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
environment:
- name: lmgnu
+ name: main
steps:
- name: Checkout Code with Full History
diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml
index 3c43c02..b318fbe 100644
--- a/.github/workflows/pr-check.yml
+++ b/.github/workflows/pr-check.yml
@@ -2,7 +2,7 @@ name: PR Comment Checks
on:
issue_comment:
- types: [created, edited]
+ types: [created]
permissions:
contents: read
@@ -13,41 +13,38 @@ permissions:
jobs:
check-trigger:
runs-on: ubuntu-latest
- if: |
- github.event.issue.pull_request &&
- contains(github.event.comment.body, '/run-checks') &&
- (
- github.event.comment.author_association == 'OWNER' ||
- github.event.comment.author_association == 'MEMBER'
- )
+ if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/run-checks') && (github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR') }}
outputs:
- pr_number: ${{ github.event.issue.number }}
+ pr_number: ${{ steps.pr-data.outputs.pr_number }}
+ comment_id: ${{ steps.pr-data.outputs.comment_id }}
steps:
- - name: Add reaction to comment
+ - name: Extract PR and Comment Metadata
+ id: pr-data
+ run: |
+ echo "pr_number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
+ echo "comment_id=${{ github.event.comment.id }}" >> $GITHUB_OUTPUT
+
+ - name: Add rocket reaction
uses: actions/github-script@v7
with:
script: |
- (async () => {
- await github.rest.reactions.createForIssueComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- comment_id: context.payload.comment.id,
- content: 'rocket'
- });
- })();
-
- - name: Update comment with status
+ await github.rest.reactions.createForIssueComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: context.payload.comment.id,
+ content: 'rocket'
+ });
+
+ - name: Acknowledge in comment
uses: actions/github-script@v7
with:
script: |
- (async () => {
- await github.rest.issues.updateComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- comment_id: context.payload.comment.id,
- body: `Running strict checks... (initiated by @${context.payload.comment.user.login})\n\nPlease wait for results...`
- });
- })();
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: context.payload.comment.id,
+ body: `${context.payload.comment.body}\n\n> π **Strict PR checks triggered by @${context.payload.comment.user.login}...** Running analysis and build steps.`
+ });
strict-cpp-checks:
runs-on: ubuntu-latest
@@ -56,31 +53,30 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- ref: refs/pull/${{ needs.check-trigger.outputs.pr_number }}/merge
+ ref: refs/pull/${{ needs.check-trigger.outputs.pr_number }}/head
- name: Install C++ tools
run: |
sudo apt-get update
- sudo apt-get install -y clang-format clang-tidy cppcheck build-essential cmake g++ clang
+ sudo apt-get install -y clang-format clang-tidy cppcheck build-essential g++ clang
- - name: Run clang-format check (strict)
- id: clang-format
+ - name: Clang-Format Check
run: |
- echo "### Clang Format Check" >> $GITHUB_STEP_SUMMARY
find . -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) | grep -v /build/ > cpp_files.txt || true
if [ -s cpp_files.txt ]; then
- clang-format --dry-run -Werror $(cat cpp_files.txt) 2>&1 | tee format_output.txt || true
- if [ -s format_output.txt ]; then
- echo "Format violations found" >> $GITHUB_STEP_SUMMARY
- cat format_output.txt >> $GITHUB_STEP_SUMMARY
- exit 1
- else
- echo "C++ formatting is correct" >> $GITHUB_STEP_SUMMARY
- fi
- else
- echo "No C++ files found" >> $GITHUB_STEP_SUMMARY
+ clang-format --dry-run -Werror $(cat cpp_files.txt)
fi
+ - name: CppCheck Analysis
+ run: |
+ cppcheck --enable=all --error-exitcode=1 --suppress=missingIncludeSystem --suppress=unusedFunction --std=c++17 .
+
+ - name: Clang-Tidy Analysis
+ run: |
+ find . -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) | grep -v /build/ | while read file; do
+ clang-tidy "$file" -checks="*,-fuchsia-*,-google-*,-llvm-*,-readability-magic-numbers" -- -I. -Iinclude || exit 1
+ done
+
strict-python-checks:
runs-on: ubuntu-latest
needs: check-trigger
@@ -88,13 +84,29 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- ref: refs/pull/${{ needs.check-trigger.outputs.pr_number }}/merge
+ ref: refs/pull/${{ needs.check-trigger.outputs.pr_number }}/head
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- cache: 'pip'
+
+ - name: Install Python Linters
+ run: |
+ python -m pip install --upgrade pip
+ pip install black isort flake8 pylint bandit
+
+ - name: Black Formatting
+ run: black --check .
+
+ - name: Isort Import Check
+ run: isort --check-only .
+
+ - name: Flake8 Syntax & Code Quality
+ run: flake8 . --max-line-length=127 --select=E9,F63,F7,F82
+
+ - name: Bandit Security Scan
+ run: bandit -r . -ll
build-test:
runs-on: ubuntu-latest
@@ -103,12 +115,25 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- ref: refs/pull/${{ needs.check-trigger.outputs.pr_number }}/merge
+ ref: refs/pull/${{ needs.check-trigger.outputs.pr_number }}/head
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
- - name: Install C++ dependencies
+ - name: Build C++ Inference Executable
run: |
- sudo apt-get update
- sudo apt-get install -y cmake build-essential g++ clang
+ if [ -f "main.cpp" ]; then
+ g++ -std=c++17 -O3 -fopenmp -I. -Iinclude -o llm.exe main.cpp
+ fi
+
+ - name: Run Python Pytest Suite
+ run: |
+ python -m pip install --upgrade pip
+ pip install pytest pytest-cov
+ if [ -f "requirements.txt" ]; then pip install -r requirements.txt || true; fi
+ if [ -d "tests" ] || [ -d "test" ]; then pytest; fi
summary:
runs-on: ubuntu-latest
@@ -121,27 +146,25 @@ jobs:
uses: actions/github-script@v7
with:
script: |
- (async () => {
- const cpp_status = '${{ needs.strict-cpp-checks.result }}' === 'success' ? 'β ' : 'β ISSUES FOUND';
- const python_status = '${{ needs.strict-python-checks.result }}' === 'success' ? 'β ' : 'β ISSUES FOUND';
- const build_status = '${{ needs.build-test.result }}' === 'success' ? 'β ' : 'β ISSUES FOUND';
- const overall = (cpp_status === 'β ' && python_status === 'β ' && build_status === 'β ') ? 'PASSED' : 'REVIEW REQUIRED';
-
- const comment = `## Strict Check Results
-
- C++ Checks: ${cpp_status}
- Python Checks: ${python_status}
- Build & Tests: ${build_status}
-
- Overall Status: ${overall}
-
- See full run: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}
- `;
-
- await github.rest.issues.createComment({
- issue_number: Number(process.env.PR_NUMBER),
- owner: context.repo.owner,
- repo: context.repo.repo,
- body: comment
- });
- })();
+ const cpp_status = '${{ needs.strict-cpp-checks.result }}' === 'success' ? 'β Passed' : 'β Failed';
+ const python_status = '${{ needs.strict-python-checks.result }}' === 'success' ? 'β Passed' : 'β Failed';
+ const build_status = '${{ needs.build-test.result }}' === 'success' ? 'β Passed' : 'β Failed';
+ const overall = (cpp_status.includes('β ') && python_status.includes('β ') && build_status.includes('β '))
+ ? 'β **ALL CHECKS PASSED**'
+ : 'β **CHECK FAILURES DETECTED**';
+
+ const comment = `## π Maintainer Check Results\n\n` +
+ `| Check Category | Status |\n` +
+ `| :--- | :--- |\n` +
+ `| **C++ Strict Quality** | ${cpp_status} |\n` +
+ `| **Python Strict Quality** | ${python_status} |\n` +
+ `| **Build & Tests** | ${build_status} |\n\n` +
+ `**Overall Result**: ${overall}\n\n` +
+ `[View Full Execution Logs](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`;
+
+ await github.rest.issues.createComment({
+ issue_number: Number(process.env.PR_NUMBER),
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: comment
+ });
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1fb1f49..6ee793a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -32,8 +32,6 @@ jobs:
tag_name="v${raw_tag}"
fi
echo "tag_name=${tag_name}" >> "$GITHUB_OUTPUT"
-
- # macOS Jobs
macos-arm64-cpu:
name: macOS Apple Silicon (arm64) CPU
needs: release-metadata
@@ -134,8 +132,6 @@ jobs:
path: llm.cpp-${{ needs.release-metadata.outputs.tag_name }}-ios-xcframework.tar.gz
if-no-files-found: error
retention-days: 30
-
- # Ubuntu Jobs
ubuntu-x64-cpu:
name: Ubuntu x64 (CPU)
needs: release-metadata
@@ -457,8 +453,6 @@ jobs:
path: llm.cpp-${{ needs.release-metadata.outputs.tag_name }}-bin-ubuntu-x64-sycl-fp16.tar.gz
if-no-files-found: error
retention-days: 30
-
- # Android Job
android-arm64-cpu:
name: Android arm64 (CPU)
needs: release-metadata
@@ -516,8 +510,6 @@ jobs:
path: llm.cpp-${{ needs.release-metadata.outputs.tag_name }}-bin-android-arm64-cpu.tar.gz
if-no-files-found: error
retention-days: 30
-
- # Windows Jobs
windows-x64-cpu:
name: Windows x64 (CPU)
needs: release-metadata
diff --git a/.gitignore b/.gitignore
index 0dfdee5..8df9a56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,3 +57,6 @@ Thumbs.db
*.swo
.idea
docker-compose.override.yml
+libs/lmgnu/build/
+libs/lmgnu/dist/
+libs/lmgnu/*.egg-info/
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..84e66c6
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,4 @@
+[submodule "libs/lmgnu"]
+ path = libs/lmgnu
+ url = https://github.com/LMGNU/LMGNU.git
+ branch = master
diff --git a/.pyhton-version b/.pyhton-version
new file mode 100644
index 0000000..c8cfe39
--- /dev/null
+++ b/.pyhton-version
@@ -0,0 +1 @@
+3.10
diff --git a/CITATION.cff b/CITATION.cff
new file mode 100644
index 0000000..770d3e8
--- /dev/null
+++ b/CITATION.cff
@@ -0,0 +1,56 @@
+# This CITATION.cff file was generated with cffinit.
+# Visit https://bit.ly/cffinit to generate yours today!
+
+cff-version: 1.2.0
+title: llm.cpp
+message: >-
+ If you use this software, please cite it using the
+ metadata from this file.
+type: software
+authors:
+ - given-names: Eamon
+ name-particle: 'Eamon Sippy '
+ email: eamon112009@gmail.com
+ family-names: EAMON
+identifiers:
+ - type: url
+ value: 'https://github.com/LMGNU/llm.cpp'
+ description: LLM training in C++ & Python.
+repository-code: 'https://github.com/LMGNU/llm.cpp'
+abstract: >-
+ LLM training in C++ & Python.
+
+ No third-party runtime dependency - it builds from
+ main.cpp, config/config.h, and include/*.h alone.
+
+
+ Character-level tokenizer built directly from the input
+ corpus
+
+ Train/validation split via DataLoader
+
+ Token + positional embeddings
+
+ Multi-head causal self-attention with explicit QKV
+ projections
+
+ Pre-layer-norm residual transformer blocks
+
+ Feed-forward MLP with ReLU
+
+ Cross-entropy loss
+
+ Fully analytical backward pass β-every gradient
+ (attention, layer norm, MLP, embeddings) is derived and
+ coded in include/backward.h, not autograd
+
+ AdamW optimizer (first/second moment estimates, weight
+ decay)
+
+ Checkpoint save/load
+
+ Autoregressive generation and terminal chat mode
+keywords:
+ - llm
+ - transformer
+license: GPL-3.0
diff --git a/Makefile b/Makefile
index ccb6702..4524d11 100644
--- a/Makefile
+++ b/Makefile
@@ -1,104 +1,44 @@
-# =============================================================================
-# Quadtrix.cpp β Makefile (llama.cpp-style convenience targets)
-# =============================================================================
+.PHONY: all release debug benchmark-bin clean-native format help
-.PHONY: all build clean run dev gpu train bench logs ps shell help
-
-SHELL := /bin/bash
-SCRIPT := ./scripts/build.sh
-
-# ββ Native C++ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+SHELL := /bin/bash
CC := g++
CFLAGS := -std=c++17 -O3 -march=native
IFLAGS := -I. -Iinclude
-TARGET := quadtrix
+TARGET := llm
SRCS := main.cpp
+HDRS := $(wildcard include/*.h)
all: $(TARGET)
-$(TARGET): $(SRCS)
- $(CC) $(CFLAGS) $(IFLAGS) -o $@ $^
- @echo "β Built $(TARGET)"
+$(TARGET): $(SRCS) $(HDRS)
+ $(CC) $(CFLAGS) $(IFLAGS) -o $@ $(SRCS)
+ @echo "Built $(TARGET)"
-# Optimised release (same flags, explicit target)
-release: $(SRCS)
- $(CC) $(CFLAGS) $(IFLAGS) -DNDEBUG -o $(TARGET) $^
+release: $(SRCS) $(HDRS)
+ $(CC) $(CFLAGS) $(IFLAGS) -DNDEBUG -o $(TARGET) $(SRCS)
strip $(TARGET)
+debug: $(SRCS) $(HDRS)
+ $(CC) -std=c++17 -O0 -g -fsanitize=address,undefined $(IFLAGS) -o $(TARGET)-debug $(SRCS)
-# Debug build
-debug: $(SRCS)
- $(CC) -std=c++17 -O0 -g -fsanitize=address,undefined \
- $(IFLAGS) -o $(TARGET)-debug $^
-
-benchmark-bin: benchmark.cpp
- $(CC) $(CFLAGS) $(IFLAGS) -o quadtrix-bench $^
+benchmark-bin: benchmark.cpp $(HDRS)
+ $(CC) $(CFLAGS) $(IFLAGS) -o llm-bench benchmark.cpp
clean-native:
- rm -f $(TARGET) $(TARGET)-debug quadtrix-bench
-
-# ββ Docker / Compose targets βββββββββββββββββββββββββββββββββββββββββββββββββ
-build:
- $(SCRIPT) up
-
-run: build
- @echo "Stack already started."
-
-dev:
- $(SCRIPT) dev
-
-gpu:
- $(SCRIPT) gpu
-
-train-cpp:
- $(SCRIPT) train-cpp
-
-train-torch:
- $(SCRIPT) train-torch
+ rm -f $(TARGET) $(TARGET)-debug llm-bench
-bench:
- $(SCRIPT) bench
-
-logs:
- $(SCRIPT) logs
-
-ps:
- $(SCRIPT) ps
-
-shell:
- $(SCRIPT) shell $(SERVICE)
-
-clean:
- $(SCRIPT) clean
-
-# ββ Misc βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
format:
find . \( -name "*.cpp" -o -name "*.h" \) \
! -path "./build/*" \
| xargs clang-format -i --style=LLVM
-lint-py:
- ruff check backend/ engine/
-
help:
@echo ""
- @echo " Quadtrix.cpp β make targets"
+ @echo " llm.cpp β native make targets"
@echo ""
- @echo " Native:"
@echo " make Build C++ binary (native)"
@echo " make release Stripped release binary"
@echo " make debug Debug binary with ASan/UBSan"
+ @echo " make benchmark-bin Build benchmark binary"
@echo " make clean-native Remove native build artifacts"
@echo " make format Run clang-format on all C++ files"
- @echo ""
- @echo " Docker:"
- @echo " make build docker compose up --build (CPU)"
- @echo " make dev Hot-reload dev stack"
- @echo " make gpu CUDA GPU stack"
- @echo " make train-cpp Train with C++ inside Docker"
- @echo " make train-torch Train with PyTorch inside Docker"
- @echo " make bench Run benchmark"
- @echo " make logs Tail all logs"
- @echo " make ps Show container status"
- @echo " make shell Shell into backend (SERVICE=frontend to change)"
- @echo " make clean Remove containers + volumes"
- @echo ""
+ @echo ""
\ No newline at end of file
diff --git a/README.md b/README.md
index 0e6bc79..3320459 100644
--- a/README.md
+++ b/README.md
@@ -1,35 +1,78 @@
# llm.cpp
-
+
+
-[](https://github.com/LMGNU/llm.cpp/actions/workflows/ci.yml) [](https://github.com/LMGNU/llm.cpp/actions/workflows/docker-publish.yml) [](https://github.com/LMGNU/llm.cpp/actions/workflows/release.yml) [](https://github.com/LMGNU/llm.cpp/actions/workflows/test.yml) [](https://github.com/LMGNU/llm.cpp/actions/workflows/ci-approval.yml)
+[](https://github.com/LMGNU/llm.cpp/releases) [](https://www.gnu.org/licenses/gpl-3.0) [](https://github.com/LMGNU/llm.cpp/actions/workflows/docker-publish.yml)
+
+
+This project implements language models in dependency-free C++, eliminating the need for PyTorch or Python to train a transformer locally. The core implementation is a decoder-only ***GPT architecture*** featuring custom tensors, embeddings, multi-head causal self-attention, layer normalization, cross-entropy loss, and an analytical backward pass with the AdamW optimizer - all contained within [main.cpp](main.cpp) ,[llm.mm](llm.mm) and the [include/](include) directory also a ***token level [BPE]*** [tokenizer.h](include/tokenizer.h) implementation inside [include](include). With no autograd engine or external frameworks, every gradient is explicitly derived and written out.
+The model achieves a validation loss of 1.6371 nats after 76 minutes of CPU training on 31.4 million characters, demonstrating that character-level language modeling at this scale is highly tractable on commodity hardware without external dependencies. On a GPU (CUDA/bfloat16), a validation loss of 2.3918 is reached in under 83 minutes, achieving a peak throughput of 19.6k tokens per second.
-Language models in dependency-free C++, with no need for PyTorch or Python to make a transformer actually works. The native path is a decoder-only GPT: tensors, embeddings, multi-head causal self-attention, layer norm, cross-entropy, and a analytical backward pass with AdamW, all in [main.cpp](main.cpp) and [include/](include/). No autograd, no framework - every gradient is derived and written out.
+## Board
+| S.No. | time | val_bpb / Metric | scale | Date | Contributors |
+|---|-------------|------------------|-------|------|--------------|
+| 1 | 168 hours | 29.41 PPL (~0.93 BPB) | 124M (32x TPU v3) | Feb 2019 | OpenAI (GPT-2 Small) |
+| 2 | 45 min | 3.28 Val Loss (~0.748 BPB) | 124M (8x H100) | May 2024 | Andrej Karpathy (llm.c) |
+| 3 | 2.98 min | 3.28 Val Loss (~0.748 BPB) | 124M (8x H100) | Feb 2025 | Keller Jordan et al. (Modded-NanoGPT) |
+| 4 | 72 hours | 22.7 PPL (~0.85 BPB) | 125M (32x A100) | Feb 2024 | Meta (MobileLLM-125M) |
+| 5 | ~24 hours | ~1.02 BPB | 135M (64x H100) | Jul 2024 | Hugging Face (SmolLM-135M) |
+| 6 | 61.3 min | 0.7176 | 10.82M (T4) | Mar 2026 | @Eamon2009 |
+| 7 | 6.1 min | 0.9250 | 1.99M (T4) | Feb 2026 | @Eamon2009 |
+| 8 | 39.4 min | 1.3145 | 0.82M (CPU) | July 2026 | @Eamon2009 |
+| 9 | 76.2 min | 1.6371 | 0.82M (CPU) | Jan 2026 | @Eamon2009 |
-Alongside it sits a parallel PyTorch implementation in [engine/main.py](engine/main.py) and [engine/inference.py](engine/inference.py), so you can train and generate the same architecture with `torch` + `tiktoken` when you want speed instead of transparency. There's also an experimental integrated-GPU path in [iGPU/](engine/iGPU/).
+More broadly, the primary contribution of this work lies in its absolute transparency. Every gradient in the backward pass is explicitly written and readable, and every tensor operation is a standard C++ function. By exposing exactly what frameworks like PyTorch compute under the hood, this implementation provides a clear educational pathway. We believe that this fundamental understanding is the true foundation of genuine expertise in deep learning.
+Alongside it sits a parallel PyTorch implementation in [engine/main.py](engine/main.py) and [engine/inference.py](engine/inference.py), so you can train and generate the same architecture with `torch` + `tiktoken` when you want speed instead of transparency. There's also an experimental integrated-GPU path in [iGPU/](engine/iGPU/). The point of this repo is the C++ core. The PyTorch exist to make the model usable, but if you're here to ***train a GPT without a framework*** doing the work for you, [include/backward.h](include/backward.h) is where to start reading.
+
+---
+
+## From CPU to GPU: The LibTorch Port
+The custom C++ backend is transparent but slow: a CPU executes scalar matrix multiplication at roughly 1-10 GFLOP/s. An NVIDIA RTX 4090 delivers βΌ80 TFLOP/s-an 8,000β80,000Γ speedup for the same computation.The LibTorch port replaces the custom backend with PyTorchβs C++ API, gaining cuBLAS-accelerated matrix operations. The transformer architecture remains unchanged only the compute layer is modified. A single line migrates
+the model to GPU:
+```cpp
+model->to(torch::kCUDA)
+```
+which transfers all parameters to GPU memory; all subsequent `torch::matmul` calls dispatch to cuBLAS automatically.
+
+---
+
+
+
+
+
+
-The point of this repo is the C++ core. The PyTorch, FastAPI, and frontend layers exist to make the model usable, but if you're here to learn how a GPT is actually built and trained without a framework doing the work for you, [include/backward.h](include/backward.h) is where to start reading.
***technical notes***: [docs](https://eamon2009.github.io/LLMs/)
+---
-## quick start (C++, train + chat)
+## quick start (CPU)
The fastest way to see the whole pipeline - tokenize, train, checkpoint, generate - using the bundled character-level corpus:
+get the data set first :
+
+``` shell
+cd data # you set the file size for data set
+python data_set.py # also get any dataset from hugging face datasets
+```
+
```bash
-g++ -std=c++17 -O2 -I. -Iinclude -o quadtrix.exe main.cpp
-./quadtrix.exe data/input.txt
+# run this
+g++ -std=c++17 -O3 -march=native -fopenmp -I. -Iinclude -o llm.exe main.cpp
+./llm.exe data/input.txt
```
This trains from scratch on `data/input.txt` and writes the best checkpoint to `best_model.bin`. Once you have a checkpoint, generate or chat with it:
```bash
-./quadtrix.exe data/input.txt --generate
-./quadtrix.exe data/input.txt --chat --chat-tokens 300
+./llm.exe data/input.txt --generate
+./llm.exe data/input.txt --chat --chat-tokens 300
```
debugging tip: drop `-O2` for `-g` when compiling if you want to step through `include/backward.h` or `include/gpt.h` in a debugger β the manual backward pass is much easier to follow one breakpoint at a time.
@@ -37,8 +80,124 @@ debugging tip: drop `-O2` for `-g` when compiling if you want to step through `i
### runtime arguments
```bash
-quadtrix.exe [data_path] [--generate] [--chat] [--chat-tokens N]
+llm.exe [data_path] [--generate] [--chat] [--chat-tokens N]
+```
+### PyTorch (single-GPU + CPU)
+```bash
+# to train
+cd engine
+python main.py
+# for inference stay in engine/
+python inference.py
+```
+### Multi-GPU
+Single GPU or CPU Mode (Default Fallback)
+If you do not pass any distributed environment parameters, the script automatically defaults to running locally on a single GPU (if available) or CPU.
+```bash
+cd engine
+cd distributed
+python train.py
+```
+### Single-Node Multi-GPU Training (DDP via torchrun)
+To train using multiple GPUs on a single machine, use PyTorch's torchrun launcher. Replace --nproc_per_node with the number of GPUs available on your machine (e.g., 2, 4, or 8).
+```bash
+cd engine
+cd distributed
+# Example: Running on 4 GPUs on a single machine
+torchrun --standalone --nproc_per_node=4 train.py
```
+---
+## File structure
+
+```text
+## π Project Structure
+
+```text
+.
+βββ .devops/ # DevOps, Docker, & proxy configurations
+β βββ docker-compose.dev.yml # Local development compose setup
+β βββ docker-compose.gpu.yml # Compose setup with GPU acceleration
+β βββ docker-compose.yml # Base Docker Compose file
+β βββ Dockerfile # Primary build setup
+β βββ Dockerfile.backend # Backend service Docker setup
+β βββ Dockerfile.cpp # Native C++ build container setup
+β βββ Dockerfile.frontend # Frontend UI Docker setup
+β βββ nginx.conf # Reverse proxy configuration
+β
+βββ .github/ # GitHub Actions CI/CD workflows & repository templates
+β βββ ISSUE_TEMPLATE/ # Bug report & feature request templates
+β β βββ bug_report.md
+β β βββ config.yml
+β β βββ feature_request.md
+β βββ workflows/ # CI/CD automation pipelines
+β β βββ check.yml
+β β βββ ci-approval.yml
+β β βββ ci.yml
+β β βββ docker-publish.yml
+β β βββ jekyll-gh-pages.yml
+β β βββ main.yml
+β β βββ pr-check.yml
+β β βββ release.yml
+β β βββ test.yml
+β βββ dependabot.yml # Automated dependency update configuration
+β βββ pull_request_template.md # Pull request contribution template
+β
+βββ assets/ # Visual execution benchmarks & screenshots
+β βββ run_2026-07-16 165731.png
+β βββ run_20260430_192930.png
+β βββ run_20260508_110726.png
+β βββ run_20260530_165216 (1).png
+β
+βββ benches/ # Performance benchmarking suite
+β βββ bench.cpp # C++ benchmark execution script
+β
+βββ config/ # Global project configurations
+β βββ config.h # C++ global configuration header
+β
+βββ data/ # Dataset ingestion scripts & raw samples
+β βββ data_set.py # Data loader preparation script
+β βββ input.txt # Sample raw dataset text
+β
+βββ docs/ # Documentation media & report graphics
+β βββ training_report.png # Model training performance chart
+β
+βββ engine/ # Core execution & inference engines
+β βββ distributed/ # Multi-node / distributed training & inference
+β β βββ infer.py # Distributed inference pipeline
+β β βββ train.py # Distributed training pipeline
+β βββ llm.cpp/ # Low-level CUDA/C++ runtime engine
+β β βββ config/ # Engine-specific configurations
+β β βββ include/ # CUDA kernels & C++ architecture headers
+β β βββ best_model.bin # Trained binary weights checkpoint
+β β βββ llm.cu # CUDA GPU execution source
+β β βββ llm.exe # Compiled engine binary executable
+β β βββ llm.py # Engine Python bindings/wrapper
+β β βββ Makefile # Engine build compilation setup
+β β βββ train.mm # Metal training harness
+β βββ logs/ # Execution log files
+β βββ inference.py # Python model inference entry point
+β βββ main.py # Primary Python execution entry point
+β βββ llm.pt # Pretrained PyTorch model checkpoint
+β
+βββ include/ # Core C++ neural network architecture headers
+β βββ attention.h # Multi-head attention implementation
+β βββ backward.h # Backpropagation & gradient calculation utilities
+β βββ block.h # Transformer block assembly
+β βββ embedding.h # Token & positional embedding logic
+β βββ feedforward.h # Feed-forward layer implementation
+β βββ gpt.h # Full GPT transformer architecture layout
+β βββ layernorm.h # Layer normalization operations
+β βββ linear.h # Fully connected dense layer
+β βββ lm.h # High-level language model interface
+β βββ sampler.h # Token sampling routines (Top-K, Top-P, Temperature)
+β βββ tensor.h # Multidimensional array data structures
+β βββ tokenizer.h # Text tokenization logic
+β βββ torch_bridge.h # PyTorch interoperability layer
+β
+βββ scripts/ # Automation & compilation scripts
+β
+```
+---
| Argument | Description |
|---|---|
@@ -52,47 +211,50 @@ quadtrix.exe [data_path] [--generate] [--chat] [--chat-tokens N]
| `GPT_DATA_PATH` | `data/input.txt` | Override the default training corpus |
| `GPT_MODEL_PATH` | `best_model.bin` | Override the checkpoint path |
-## what's actually implemented in C++
+## What's Actually Implemented in C++
-No third-party runtime dependency β it builds from `main.cpp`, `config/config.h`, and `include/*.h` alone.
+No third-party runtime dependency - it builds from `main.cpp`, `config/config.h`, and `include/*.h` alone.
-- Character-level tokenizer built directly from the input corpus
+- **Byte Pair Encoding (BPE) Tokenizer** - Built entirely from scratch. Compiles a custom vocabulary directly from the training corpus by running iterative token-pair merges until it hits a targeted vocabulary threshold.
- Train/validation split via `DataLoader`
- Token + positional embeddings
- Multi-head causal self-attention with explicit QKV projections
- Pre-layer-norm residual transformer blocks
- Feed-forward MLP with ReLU
- Cross-entropy loss
-- **Fully analytical backward pass** β every gradient (attention, layer norm, MLP, embeddings) is derived and coded in `include/backward.h`, not autograd
+- **Fully analytical backward pass** - every gradient (attention, layer norm, MLP, embeddings) is derived mathematically and coded explicitly in `include/backward.h`, not autograd
- AdamW optimizer (first/second moment estimates, weight decay)
- Checkpoint save/load
- Autoregressive generation and terminal chat mode
-Hyperparameters live in `config/config.h` and require a rebuild to take effect:
+Hyperparameters live in `engine/llm.cpp/config/config.h` and require a rebuild to take effect:
```cpp
-static const int BATCH_SIZE = 4;
-static const int BLOCK_SIZE = 64;
-static const int N_EMBD = 128;
-static const int N_HEAD = 4;
-static const int N_LAYER = 4;
-static const float DROPOUT = 0.2f;
-static const float LEARNING_RATE = 3e-4f;
-static const int MAX_ITERS = 3000;
+// note: The c++ version only runs on cpu not on GPU
+static const unsigned int SEED = 1337;
+static const double TRAIN_SPLIT = 0.9;
+static const int BATCH_SIZE = 32;
+static const int BLOCK_SIZE = 64;
+static const int MAX_ITERS = 5000;
+static const int EVAL_INTERVAL = 500;
+static const float LEARNING_RATE = 5e-4f;
+static const int EVAL_ITERS = 25;
+static const int N_EMBD = 128;
+static const int N_HEAD = 2;
+static const int N_LAYER = 4;
+static const float DROPOUT = 0.05f;
+static const int BPE_VOCAB_SIZE = 2048;
```
-For an optimized native build:
-
-```bash
-g++ -std=c++17 -O3 -march=native -I. -Iinclude -o quadtrix.exe main.cpp
-```
## the PyTorch reference path
-[engine/main.py](engine/main.py) trains the same architectural idea with `torch`, `torch.nn`, and GPT-2 BPE tokenization via `tiktoken`, useful when you want to scale past what C++ loops can comfortably train on CPU.
+[engine/main.py](engine/main.py) trains the same architectural idea with `torch`, `torch.nn`, and GPT-4 BPE tokenization via `tiktoken`, useful when you want to scale past what C++ loops can comfortably train on CPU.
```bash
-python engine/main.py
+cd engine
+python fineweb_dataset.py # you can also use data/input.txt also
+python main.py
```
It looks for `engine/input.txt` by default; point it elsewhere with `QUADTRIX_TRAIN_DATA` if needed. Run inference against a saved checkpoint:
@@ -101,22 +263,7 @@ It looks for `engine/input.txt` by default; point it elsewhere with `QUADTRIX_TR
python engine/inference.py --checkpoint engine/best_model.pt --prompt "Once upon a time" --max-new-tokens 100
```
-## results so far
-## Leaderboard
-
-Runs are ranked by validation loss. Lower is better.
-
-| # | Val Loss | Parameters | Time | Hardware | Description |
-|--:|----------|------------|----------|----------|------------------------------------------|
-| 1 | **0.7176** | 10.82M | 61.3 min | NVIDIA T4 | Large-scale run, coherent paragraphs, strong convergence |
-| 2 | 0.9250 | 1.99M | 6.1 min | NVIDIA T4 | Optimised run, fast training, stable learning |
-| 3 | 1.3145 | 0.82M | 39.4 min | x64 CPU | Baseline, small data |
-| 4 | 1.6371 | 0.82M | 76.2 min | x64 CPU | Extended CPU training, 3000 iterations |
-
-All runs: @Eamon2009, 2026.
-
## Benchmarks
-
### Runs at a Glance
| Metric | Character-Level | Small Scale | Large Scale |
@@ -141,15 +288,34 @@ See [run.md](run.md) and the leaderboard in the full docs for more configuration
|---|---|---|---|
| nanoGPT / minGPT | Minimal, educational GPT training | Python | PyTorch |
| llama2.c | Inference-only | C | None |
-| **llm.cpp** | Training *and* inference, manual backward pass, web UI | C++ / Python / TypeScript | Manual (C++) + PyTorch |
+| **llm.cpp** | Training *and* inference, manual backward pass | C++ / Python | Manual (C++) + PyTorch |
-I'd like the C++ core (`main.cpp`, `include/`, `config/`) to stay dependency-free and to stay the part of this repo that explin transformer internals directly. The PyTorch engine, FastAPI middleware, and React frontend are welcome to grow more features, integrations, and UI polish. If you build a port to another language or framework, I'm happy to link to it from a notable-forks section; just open an issue or PR.
+I'd like the C++ core (`main.cpp`, `include/`, `config/`) to stay dependency-free and to stay the part of this repo that explin transformer internals directly. The PyTorch engine, include, and ci are welcome to grow more features, integrations, and CI polish. If you build a port to another language or framework, I'm happy to link to it from a notable-forks section; just open an issue or PR.
## references
-- Vaswani et al., "Attention Is All You Need", 2017
-- Radford et al., GPT-2 technical work, 2019
-- nanoGPT and minGPT as educational reference points
+- Vaswani et al., ["Attention Is All You Need"](https://arxiv.org/abs/1706.03762), 2017
+- Radford et al., ["Language Models are Unsupervised Multitask Learners"](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf) (GPT-2 technical work), 2019
+- Brown et al., ["Language Models are Few-Shot Learners"](https://arxiv.org/abs/2005.14165) (GPT-3 paper), 2020
+- Meta AI, ["The Llama 3 Herd of Models"](https://arxiv.org/abs/2407.21783) (Llama 3 paper), 2024
+- Andrej Karpathy, [nanoGPT](https://github.com/karpathy/nanoGPT) repository as an educational reference point
+- [HuggingFace Datasets](https://huggingface.co/datasets) for FineWeb and other pretraining/fine-tuning datasets
+
+## Cite
+
+If you find llm.cpp helpful in your research cite as:
+```bibtex
+@misc{llm.cpp,
+ author = {Eamon Sippy},
+ title = {llm.cpp: LLM training in C++ \& Python},
+ year = {2026},
+ publisher = {GitHub},
+ journal = {GitHub repository},
+ url = {https://github.com/LMGNU/llm.cpp}
+}
+
+```
+
## license
diff --git a/assets/run_2026-07-16 165731.png b/assets/run_2026-07-16 165731.png
new file mode 100644
index 0000000..3372892
Binary files /dev/null and b/assets/run_2026-07-16 165731.png differ
diff --git a/benches/python_benchmark.py b/benches/python_benchmark.py
deleted file mode 100644
index 326a46d..0000000
--- a/benches/python_benchmark.py
+++ /dev/null
@@ -1,482 +0,0 @@
-"""Real PyTorch benchmark suite for llm.cpp.
-
-Measures the things an ML/AI engineer usually asks for:
-model metadata, tokenizer/data throughput, forward latency, training-step
-latency, autoregressive generation latency, memory, and JSON/CSV output.
-"""
-
-from __future__ import annotations
-
-import argparse
-import csv
-import gc
-import importlib.util
-import json
-import math
-import platform
-import statistics
-import sys
-import time
-from dataclasses import dataclass, asdict
-from pathlib import Path
-from typing import Any, Callable
-
-
-ROOT = Path(__file__).resolve().parents[1]
-ENGINE_INFERENCE = ROOT / "engine" / "inference.py"
-DEFAULT_DATA = ROOT / "engine" / "input.txt"
-DEFAULT_OUT = ROOT / "benchmark" / "results"
-
-
-def load_engine_module():
- spec = importlib.util.spec_from_file_location(
- "quadtrix_engine_inference", ENGINE_INFERENCE)
- if spec is None or spec.loader is None:
- raise RuntimeError(f"Cannot import {ENGINE_INFERENCE}")
- module = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(module)
- return module
-
-
-def now_iso() -> str:
- return time.strftime("%Y-%m-%dT%H:%M:%S%z")
-
-
-def percentile(values: list[float], pct: float) -> float:
- if not values:
- return 0.0
- ordered = sorted(values)
- pos = (len(ordered) - 1) * pct
- lo = math.floor(pos)
- hi = math.ceil(pos)
- if lo == hi:
- return ordered[lo]
- return ordered[lo] + (ordered[hi] - ordered[lo]) * (pos - lo)
-
-
-def summarize_ms(samples: list[float]) -> dict[str, float]:
- mean = statistics.fmean(samples)
- return {
- "avg_ms": mean,
- "median_ms": statistics.median(samples),
- "min_ms": min(samples),
- "max_ms": max(samples),
- "p90_ms": percentile(samples, 0.90),
- "p95_ms": percentile(samples, 0.95),
- "std_ms": statistics.pstdev(samples) if len(samples) > 1 else 0.0,
- }
-
-
-def sync(torch: Any, device: Any) -> None:
- if str(device).startswith("cuda"):
- torch.cuda.synchronize()
-
-
-def timed_samples(
- torch: Any,
- device: Any,
- fn: Callable[[], Any],
- runs: int,
- warmup: int,
-) -> tuple[list[float], Any]:
- last = None
- for _ in range(warmup):
- last = fn()
- sync(torch, device)
-
- samples: list[float] = []
- for _ in range(runs):
- start = time.perf_counter()
- last = fn()
- sync(torch, device)
- samples.append((time.perf_counter() - start) * 1000.0)
- return samples, last
-
-
-def cuda_memory(torch: Any, device: Any) -> dict[str, float]:
- if not str(device).startswith("cuda"):
- return {}
- return {
- "cuda_allocated_mb": torch.cuda.memory_allocated(device) / (1024**2),
- "cuda_reserved_mb": torch.cuda.memory_reserved(device) / (1024**2),
- "cuda_peak_allocated_mb": torch.cuda.max_memory_allocated(device) / (1024**2),
- }
-
-
-def process_rss_mb() -> float | None:
- try:
- import psutil
-
- return psutil.Process().memory_info().rss / (1024**2)
- except Exception:
- return None
-
-
-@dataclass
-class BenchRow:
- suite: str
- name: str
- backend: str
- batch_size: int = 0
- sequence_length: int = 0
- tokens: int = 0
- avg_ms: float = 0.0
- median_ms: float = 0.0
- min_ms: float = 0.0
- max_ms: float = 0.0
- p90_ms: float = 0.0
- p95_ms: float = 0.0
- std_ms: float = 0.0
- tokens_per_sec: float = 0.0
- samples: int = 0
- loss: float | None = None
- memory_mb: float | None = None
- notes: str = ""
-
-
-class QuadtrixPythonBenchmark:
- def __init__(self, args: argparse.Namespace):
- self.args = args
- self.engine = load_engine_module()
- self.torch = __import__("torch")
- self.torch.manual_seed(args.seed)
- self.device = self.engine.device
- self.rows: list[BenchRow] = []
-
- if str(self.device).startswith("cuda"):
- self.torch.cuda.reset_peak_memory_stats(self.device)
-
- self.model = self._make_model()
- self.model.eval()
-
- def _make_model(self):
- checkpoint = Path(
- self.args.checkpoint) if self.args.checkpoint else self.engine.default_checkpoint_path()
- if checkpoint.exists() and not self.args.random_weights:
- return self.engine.load_model(checkpoint)
-
- model = self.engine.GPTLanguageModel().to(self.device)
- model.eval()
- return model
-
- def _record(self, row: BenchRow) -> None:
- self.rows.append(row)
- print(
- f"{row.suite:<14} {row.name:<24} "
- f"avg={row.avg_ms:9.3f} ms "
- f"p95={row.p95_ms:9.3f} ms "
- f"tok/s={row.tokens_per_sec:10.1f}"
- )
-
- def run(self) -> dict[str, Any]:
- print("Benchmark")
- print(f"Device: {self.device}")
- print(f"Runs: {self.args.runs}, warmup: {self.args.warmup}")
-
- self.bench_tokenizer_and_data()
- self.bench_primitives()
- self.bench_forward()
- self.bench_training_step()
- self.bench_generation()
-
- return self.save()
-
- def bench_tokenizer_and_data(self) -> None:
- data_path = Path(self.args.data)
- text = data_path.read_text(
- encoding="utf-8") if data_path.exists() else "llm.cpp-benchmark text. " * 512
- if self.args.max_data_chars and len(text) > self.args.max_data_chars:
- text = text[: self.args.max_data_chars]
- tokenizer = self.engine.tokenizer
-
- samples, encoded = timed_samples(
- self.torch,
- self.device,
- lambda: tokenizer.encode(text),
- self.args.runs,
- self.args.warmup,
- )
- stats = summarize_ms(samples)
- self._record(
- BenchRow(
- suite="data",
- name="tokenizer_encode",
- backend="python",
- tokens=len(encoded),
- tokens_per_sec=len(encoded) / (stats["avg_ms"] / 1000.0),
- samples=len(samples),
- memory_mb=process_rss_mb(),
- **stats,
- )
- )
-
- tensor = self.torch.tensor(encoded, dtype=self.torch.long)
- max_block = min(self.engine.block_size, max(2, len(tensor) - 2))
- batch_size = min(self.args.batch_size, max(
- 1, len(tensor) - max_block - 1))
-
- def make_batch():
- ix = self.torch.randint(len(tensor) - max_block - 1, (batch_size,))
- x = self.torch.stack([tensor[i: i + max_block]
- for i in ix]).to(self.device)
- y = self.torch.stack([tensor[i + 1: i + max_block + 1]
- for i in ix]).to(self.device)
- return x, y
-
- samples, _ = timed_samples(
- self.torch, self.device, make_batch, self.args.runs, self.args.warmup)
- stats = summarize_ms(samples)
- self._record(
- BenchRow(
- suite="data",
- name="batch_sample_to_device",
- backend="python",
- batch_size=batch_size,
- sequence_length=max_block,
- tokens=batch_size * max_block,
- tokens_per_sec=(batch_size * max_block) /
- (stats["avg_ms"] / 1000.0),
- samples=len(samples),
- memory_mb=process_rss_mb(),
- **stats,
- )
- )
-
- def bench_primitives(self) -> None:
- torch = self.torch
- cases = [
- ("matmul_3d", (1, 16, self.engine.n_embd),
- (self.engine.n_embd, self.engine.n_embd)),
- ("matmul_3d", (self.args.batch_size, self.engine.block_size,
- self.engine.n_embd), (self.engine.n_embd, self.engine.n_embd)),
- ("attention_scores", (self.args.batch_size,
- self.engine.block_size, self.engine.n_embd), None),
- ]
- for name, x_shape, w_shape in cases:
- x = torch.randn(*x_shape, device=self.device)
- if name == "attention_scores":
- def fn(): return torch.softmax(
- (x @ x.transpose(-2, -1)) / math.sqrt(x_shape[-1]), dim=-1)
- else:
- w = torch.randn(*w_shape, device=self.device)
- def fn(): return x @ w
- samples, _ = timed_samples(
- torch, self.device, fn, self.args.runs, self.args.warmup)
- stats = summarize_ms(samples)
- tokens = x_shape[0] * x_shape[1]
- self._record(
- BenchRow(
- suite="primitive",
- name=f"{name}_{x_shape[0]}x{x_shape[1]}",
- backend="python",
- batch_size=x_shape[0],
- sequence_length=x_shape[1],
- tokens=tokens,
- tokens_per_sec=tokens / (stats["avg_ms"] / 1000.0),
- samples=len(samples),
- memory_mb=process_rss_mb(),
- **stats,
- )
- )
-
- def bench_forward(self) -> None:
- torch = self.torch
- cases = [(1, 8), (1, self.engine.block_size),
- (self.args.batch_size, self.engine.block_size)]
- self.model.eval()
- for batch_size, seq_len in cases:
- idx = torch.randint(self.engine.vocab_size,
- (batch_size, seq_len), device=self.device)
- targets = torch.randint(
- self.engine.vocab_size, (batch_size, seq_len), device=self.device)
-
- @torch.no_grad()
- def fn():
- return self.model(idx, targets)
-
- samples, last = timed_samples(
- torch, self.device, fn, self.args.runs, self.args.warmup)
- stats = summarize_ms(samples)
- loss = float(last[1].item())
- tokens = batch_size * seq_len
- self._record(
- BenchRow(
- suite="forward",
- name=f"batch{batch_size}_seq{seq_len}",
- backend="python",
- batch_size=batch_size,
- sequence_length=seq_len,
- tokens=tokens,
- tokens_per_sec=tokens / (stats["avg_ms"] / 1000.0),
- samples=len(samples),
- loss=loss,
- memory_mb=process_rss_mb(),
- **stats,
- )
- )
-
- def bench_training_step(self) -> None:
- torch = self.torch
- model = self.engine.GPTLanguageModel().to(self.device)
- model.train()
- optimizer = torch.optim.AdamW(
- model.parameters(), lr=self.args.learning_rate)
- batch_size = self.args.batch_size
- seq_len = self.engine.block_size
- idx = torch.randint(self.engine.vocab_size,
- (batch_size, seq_len), device=self.device)
- targets = torch.randint(self.engine.vocab_size,
- (batch_size, seq_len), device=self.device)
-
- def fn():
- optimizer.zero_grad(set_to_none=True)
- _, loss = model(idx, targets)
- loss.backward()
- optimizer.step()
- return loss.detach()
-
- samples, loss = timed_samples(
- torch, self.device, fn, self.args.train_steps, self.args.warmup)
- stats = summarize_ms(samples)
- tokens = batch_size * seq_len
- self._record(
- BenchRow(
- suite="training",
- name=f"adamw_step_b{batch_size}_s{seq_len}",
- backend="python",
- batch_size=batch_size,
- sequence_length=seq_len,
- tokens=tokens,
- tokens_per_sec=tokens / (stats["avg_ms"] / 1000.0),
- samples=len(samples),
- loss=float(loss.item()),
- memory_mb=process_rss_mb(),
- **stats,
- )
- )
- del model
- gc.collect()
-
- def bench_generation(self) -> None:
- torch = self.torch
- tokenizer = self.engine.tokenizer
- prompts = [
- ("empty", ""),
- ("short", "The future of local AI is"),
- ("long", "llama.cpp is a compact transformer benchmark that measures " * 4),
- ]
- self.model.eval()
- for label, prompt in prompts:
- encoded = tokenizer.encode(prompt) or [0]
- encoded = encoded[-self.engine.block_size:]
- idx = torch.tensor([encoded], dtype=torch.long, device=self.device)
-
- @torch.no_grad()
- def fn():
- return self.model.generate(idx, self.args.generate_tokens, temperature=1.0, top_k=self.args.top_k)
-
- samples, _ = timed_samples(
- torch, self.device, fn, self.args.runs, self.args.warmup)
- stats = summarize_ms(samples)
- self._record(
- BenchRow(
- suite="generation",
- name=label,
- backend="python",
- batch_size=1,
- sequence_length=len(encoded),
- tokens=self.args.generate_tokens,
- tokens_per_sec=self.args.generate_tokens /
- (stats["avg_ms"] / 1000.0),
- samples=len(samples),
- memory_mb=process_rss_mb(),
- **stats,
- )
- )
-
- def save(self) -> dict[str, Any]:
- out_dir = Path(self.args.out)
- out_dir.mkdir(parents=True, exist_ok=True)
- n_params = sum(p.numel() for p in self.model.parameters())
- result = {
- "schema_version": 1,
- "timestamp": now_iso(),
- "backend": "python",
- "system": {
- "platform": platform.platform(),
- "python": sys.version.split()[0],
- "torch": self.torch.__version__,
- "device": str(self.device),
- "cuda": getattr(self.torch.version, "cuda", None),
- "rss_mb": process_rss_mb(),
- **cuda_memory(self.torch, self.device),
- },
- "model": {
- "vocab_size": self.engine.vocab_size,
- "block_size": self.engine.block_size,
- "n_embd": self.engine.n_embd,
- "n_head": self.engine.n_head,
- "n_layer": self.engine.n_layer,
- "dropout": self.engine.dropout,
- "parameters": n_params,
- "parameter_mb_fp32": n_params * 4 / (1024**2),
- },
- "config": {key: str(value) if isinstance(value, Path) else value for key, value in vars(self.args).items()},
- "results": [asdict(row) for row in self.rows],
- }
- json_path = out_dir / "python_benchmark.json"
- csv_path = out_dir / "python_benchmark.csv"
- json_path.write_text(json.dumps(result, indent=2), encoding="utf-8")
- with csv_path.open("w", newline="", encoding="utf-8") as f:
- writer = csv.DictWriter(
- f, fieldnames=list(asdict(self.rows[0]).keys()))
- writer.writeheader()
- for row in self.rows:
- writer.writerow(asdict(row))
- print(f"Saved {json_path}")
- print(f"Saved {csv_path}")
- return result
-
-
-def parse_args() -> argparse.Namespace:
- parser = argparse.ArgumentParser(
- description="Benchmarks.")
- parser.add_argument("--data", type=Path, default=DEFAULT_DATA)
- parser.add_argument("--checkpoint", type=Path, default=None)
- parser.add_argument("--out", type=Path, default=DEFAULT_OUT)
- parser.add_argument("--runs", type=int, default=10)
- parser.add_argument("--warmup", type=int, default=3)
- parser.add_argument("--batch-size", type=int, default=4)
- parser.add_argument("--train-steps", type=int, default=5)
- parser.add_argument("--generate-tokens", type=int, default=32)
- parser.add_argument("--learning-rate", type=float, default=3e-4)
- parser.add_argument("--top-k", type=int, default=None)
- parser.add_argument("--seed", type=int, default=1337)
- parser.add_argument("--max-data-chars", type=int, default=1_000_000)
- parser.add_argument("--random-weights", action="store_true",
- help="Do not load a checkpoint even if one exists.")
- parser.add_argument("--quick", action="store_true",
- help="Short run for smoke tests.")
- args = parser.parse_args()
- if args.quick:
- args.runs = 2
- args.warmup = 1
- args.train_steps = 1
- args.generate_tokens = 4
- args.max_data_chars = min(args.max_data_chars, 50_000)
- return args
-
-
-def main() -> int:
- try:
- benchmark = QuadtrixPythonBenchmark(parse_args())
- benchmark.run()
- return 0
- except ImportError as exc:
- print(f"Missing Python benchmark dependency: {exc}", file=sys.stderr)
- print("Install the engine requirements, including torch and tiktoken.", file=sys.stderr)
- return 2
-
-
-if __name__ == "__main__":
- raise SystemExit(main())
diff --git a/config/config.h b/config/config.h
index b88622d..58403e6 100644
--- a/config/config.h
+++ b/config/config.h
@@ -5,15 +5,15 @@ static const std::string DEFAULT_CLEANED_PATH = "data/input.txt";
static const std::string DATA_PATH_ENV_VAR = "GPT_DATA_PATH";
static const unsigned int SEED = 1337;
static const double TRAIN_SPLIT = 0.9; // 90% train, 10% val
-static const int BATCH_SIZE = 16;
-static const int BLOCK_SIZE = 64; // Context length
-static const int MAX_ITERS = 50;
-static const int EVAL_INTERVAL = 250;
-static const float LEARNING_RATE = 5e-4f;
-static const int EVAL_ITERS = 100;
+static const int BATCH_SIZE = 16;
+static const int BLOCK_SIZE = 64; // Context length
+static const int MAX_ITERS = 50;
+static const int EVAL_INTERVAL = 250;
+static const float LEARNING_RATE = 1e-4f;
+static const int EVAL_ITERS = 100;
static const int N_EMBD = 128;
static const int N_HEAD = 4;
static const int N_LAYER = 4;
-static const float DROPOUT = 0.05f;
+static const float DROPOUT = 0.05f;
static const std::string BEST_MODEL_PATH = "best_model.bin";
static const std::string MODEL_PATH_ENV_VAR = "GPT_MODEL_PATH";
diff --git a/data/data_set.py b/data/data_set.py
index dd10640..069e97d 100644
--- a/data/data_set.py
+++ b/data/data_set.py
@@ -2,9 +2,9 @@
import os
# Settings
-target_size_mb = 30
+target_size_mb = 10_000
target_size_bytes = target_size_mb * 1024 * 1024
-output_file = "cleaned.txt"
+output_file = "input.txt"
print(f"Streaming TinyStories until {target_size_mb} MB is reached...")
@@ -15,14 +15,14 @@
with open(output_file, "w", encoding="utf-8") as f:
for entry in dataset:
story_text = entry["text"] + "\n\n"
-
+
# Calculate size of this story in bytes
story_bytes = len(story_text.encode('utf-8'))
-
+
if current_size + story_bytes > target_size_bytes:
break
-
+
f.write(story_text)
current_size += story_bytes
-print(f"Done! Created '{output_file}' ({current_size / (1024*1024):.2f} MB)")
\ No newline at end of file
+print(f"Done! Created '{output_file}' ({current_size / (1024*1024):.2f} MB)")
diff --git a/docs/run.md b/docs/run.md
deleted file mode 100644
index a2c0e65..0000000
--- a/docs/run.md
+++ /dev/null
@@ -1,492 +0,0 @@
-# Quadtrix.cpp
-
-Quadtrix.cpp is a local GPT-style language model project with multiple runtime paths:
-
-- Native C++ inference and training through `Quadtrix.exe` / `main.cpp`
-- PyTorch checkpoint inference through `engine/inference.py` and `engine/best_model .pt`
-- FastAPI middleware in `backend/`
-- React + TypeScript chat UI in `frontend/`
-
-The web interface can chat with both model backends:
-
-- `C++`: calls the C++ HTTP server on port `8080`
-- `.pt`: loads the PyTorch checkpoint directly from `engine/best_model .pt`
-
-## Project Layout
-
-```text
-Quadtrix.cpp/
- Quadtrix.exe
- main.cpp
- config/
- include/
- data/
- engine/
- inference.py
- main.py
- fine-tune/main.py
- best_model .pt
- fineweb_30mb.txt
- backend/
- main.py
- inference.py
- requirements.txt
- frontend/
- package.json
- src/
-```
-
-## Requirements
-
-- Python 3.10+
-- Node.js 18+
-- npm
-- C++17 compiler if you want to rebuild the C++ executable
-
-## 1. Python Setup
-
-From the repo root:
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp
-python -m venv .venv
-.\.venv\Scripts\python.exe -m pip install --upgrade pip
-```
-
-Install backend and PyTorch inference dependencies:
-
-```powershell
-cd backend
-..\.venv\Scripts\python.exe -m pip install -r requirements.txt
-```
-
-## 2. Frontend Setup
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp\frontend
-npm.cmd install
-npm.cmd run build
-```
-
-Run the frontend:
-
-```powershell
-npm.cmd run dev
-```
-
-Frontend URL:
-
-```text
-http://localhost:5173
-```
-
-## Install as a Web App
-
-The frontend is configured as an installable PWA. It includes:
-
-- `frontend/manifest.webmanifest`
-- `frontend/sw.js`
-- `frontend/public/manifest.webmanifest`
-- `frontend/public/sw.js`
-- service worker registration in `frontend/src/registerServiceWorker.ts`
-
-For the clean installable version, build and preview the frontend:
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp\frontend
-npm.cmd run build
-npm.cmd run preview
-```
-
-Open the preview URL, usually:
-
-```text
-http://localhost:4173
-```
-
-Then install from the browser:
-
-- Chrome / Edge: click the install icon in the address bar
-- Or open browser menu -> Apps -> Install this site as an app
-
-The installed app still talks to the backend at:
-
-```text
-http://localhost:3001
-```
-
-So keep the FastAPI backend running when chatting.
-
-## 3. Run the PyTorch `.pt` Model in the Web UI
-
-The `.pt` model does not need a separate model server. The FastAPI backend loads it directly from:
-
-```text
-engine/best_model .pt
-```
-
-Start the backend:
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp\backend
-..\.venv\Scripts\python.exe -m uvicorn main:app --host 127.0.0.1 --port 3001
-```
-
-Start the frontend in another terminal:
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp\frontend
-npm.cmd run dev
-```
-
-Open:
-
-```text
-http://localhost:5173
-```
-
-Select `.pt` in the top bar.
-
-## 4. Run the C++ Model in the Web UI
-
-Start the C++ inference server:
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp
-.\Quadtrix.exe data\input.txt --server --port 8080
-```
-
-Start the backend:
-
-```powershell
-cd backend
-..\.venv\Scripts\python.exe -m uvicorn main:app --host 127.0.0.1 --port 3001
-```
-
-Start the frontend:
-
-```powershell
-cd ..\frontend
-npm.cmd run dev
-```
-
-Open:
-
-```text
-http://localhost:5173
-```
-
-Select `C++` in the top bar.
-
-## 5. Run Both Backends Together
-
-Use three terminals.
-
-Terminal 1:
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp
-.\Quadtrix.exe data\input.txt --server --port 8080
-```
-
-Terminal 2:
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp\backend
-..\.venv\Scripts\python.exe -m uvicorn main:app --host 127.0.0.1 --port 3001
-```
-
-Terminal 3:
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp\frontend
-npm.cmd run dev
-```
-
-Open:
-
-```text
-http://localhost:5173
-```
-
-Switch between `C++` and `.pt` from the model selector.
-
-## 6. Backend API
-
-Base URL:
-
-```text
-http://localhost:3001
-```
-
-Routes:
-
-```text
-GET /api/health
-GET /api/stats
-POST /api/chat
-GET /api/sessions
-POST /api/sessions
-DELETE /api/sessions/{id}
-GET /api/sessions/{id}/messages
-POST /api/feedback
-```
-
-Example `.pt` chat request:
-
-```powershell
-Invoke-RestMethod `
- -Uri http://localhost:3001/api/chat `
- -Method Post `
- -ContentType "application/json" `
- -Body '{
- "session_id": null,
- "prompt": "Once upon a time",
- "max_tokens": 100,
- "temperature": 1.0,
- "stream": false,
- "model_backend": "torch"
- }'
-```
-
-Example C++ chat request:
-
-```powershell
-Invoke-RestMethod `
- -Uri http://localhost:3001/api/chat `
- -Method Post `
- -ContentType "application/json" `
- -Body '{
- "session_id": null,
- "prompt": "Once upon a time",
- "max_tokens": 100,
- "temperature": 1.0,
- "stream": false,
- "model_backend": "cpp"
- }'
-```
-
-## 7. Environment Variables
-
-Backend defaults are in `backend/.env.example`:
-
-```text
-API_PORT=3001
-CORS_ORIGINS=http://localhost:5173
-REDIS_URL=
-LOG_LEVEL=INFO
-MAX_SESSIONS=1000
-SESSION_TTL_HOURS=24
-CPP_SERVER_URL=http://localhost:8080
-TORCH_CHECKPOINT_PATH=../engine/best_model .pt
-REQUEST_TIMEOUT_SECONDS=60
-```
-
-Create `backend/.env` if you want overrides.
-
-Frontend defaults are in `frontend/.env.example`:
-
-```text
-VITE_API_BASE_URL=http://localhost:3001
-```
-
-## 8. PyTorch CLI Inference
-
-Interactive chat:
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp
-.\.venv\Scripts\python.exe engine\inference.py --checkpoint "engine\best_model .pt"
-```
-
-Generate once:
-
-```powershell
-.\.venv\Scripts\python.exe engine\inference.py --checkpoint "engine\best_model .pt" --prompt "Hello" --max-new-tokens 100 --temperature 1.0
-```
-
-## 9. PyTorch Training
-
-Main training:
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp
-.\.venv\Scripts\python.exe engine\main.py
-```
-
-Fine-tuning:
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp
-.\.venv\Scripts\python.exe engine\fine-tune\main.py
-```
-
-## 10. C++ Build and Run
-
-Build manually:
-
-```powershell
-g++ -std=c++17 -O2 -I. -Iinclude -o Quadtrix.exe main.cpp
-```
-
-Train from scratch:
-
-```powershell
-.\Quadtrix.exe data\input.txt
-```
-
-Terminal chat:
-
-```powershell
-.\Quadtrix.exe data\input.txt --chat
-```
-
-Raw generation:
-
-```powershell
-.\Quadtrix.exe data\input.txt --generate
-```
-
-HTTP server:
-
-```powershell
-.\Quadtrix.exe data\input.txt --server --port 8080
-```
-
-## 11. Health Checks
-
-Backend:
-
-```powershell
-Invoke-RestMethod http://localhost:3001/api/health
-```
-
-C++ server:
-
-```powershell
-Invoke-RestMethod http://localhost:8080/health
-```
-
-Frontend:
-
-```text
-http://localhost:5173
-```
-
-When only `.pt` is available, backend health should show:
-
-```json
-{
- "status": "degraded",
- "api": "ok",
- "cpp_server": "unreachable",
- "torch_model": "ok"
-}
-```
-
-When both are available, backend health should show:
-
-```json
-{
- "status": "ok",
- "api": "ok",
- "cpp_server": "ok",
- "torch_model": "ok"
-}
-```
-
-## 12. Troubleshooting
-
-### PowerShell blocks `npm`
-
-Use `npm.cmd`:
-
-```powershell
-npm.cmd run dev
-npm.cmd run build
-```
-
-### `.pt` model is unavailable
-
-Check that this file exists:
-
-```text
-engine/best_model .pt
-```
-
-Then check Python dependencies:
-
-```powershell
-cd backend
-..\.venv\Scripts\python.exe -c "import torch, tiktoken; print(torch.__version__)"
-```
-
-### Backend cannot import FastAPI
-
-Install dependencies into the repo venv:
-
-```powershell
-cd backend
-..\.venv\Scripts\python.exe -m pip install -r requirements.txt
-```
-
-### C++ option is offline
-
-Start the C++ server:
-
-```powershell
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp
-.\Quadtrix.exe data\input.txt --server --port 8080
-```
-
-### Frontend cannot reach backend
-
-Check:
-
-```text
-http://localhost:3001/api/health
-```
-
-Make sure frontend config points to:
-
-```text
-VITE_API_BASE_URL=http://localhost:3001
-```
-
-### Port already in use
-
-```powershell
-Get-NetTCPConnection -LocalPort 3001
-Get-NetTCPConnection -LocalPort 5173
-Get-NetTCPConnection -LocalPort 8080
-```
-
-## Recommended Daily Run
-
-```powershell
-# Terminal 1
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp
-.\Quadtrix.exe data\input.txt --server --port 8080
-```
-
-```powershell
-# Terminal 2
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp\backend
-..\.venv\Scripts\python.exe -m uvicorn main:app --host 127.0.0.1 --port 3001
-```
-
-```powershell
-# Terminal 3
-cd C:\Users\Admin\Documents\GitHub\Quadtrix.cpp\frontend
-npm.cmd run dev
-```
-
-Open:
-
-```text
-http://localhost:5173
-```
-
-## License
-
-MIT
diff --git a/docs/quadtrix_training_report.png b/docs/training_report.png
similarity index 100%
rename from docs/quadtrix_training_report.png
rename to docs/training_report.png
diff --git a/engine/distributed/infer.py b/engine/distributed/infer.py
new file mode 100644
index 0000000..8112c10
--- /dev/null
+++ b/engine/distributed/infer.py
@@ -0,0 +1,189 @@
+import os
+import sys
+import tiktoken
+import torch
+import torch.distributed as dist
+import torch.nn as nn
+from torch.nn import functional as F
+BLOCK_SIZE = 20
+N_EMBD = 6
+N_HEAD = 4
+N_LAYER = 4
+DROPOUT = 0.0
+MODEL_WEIGHTS_PATH = "llm.pt"
+ddp = int(os.environ.get("RANK", -1)) != -1
+if ddp:
+ dist.init_process_group(backend="nccl")
+ ddp_rank = int(os.environ.get("RANK"))
+ ddp_local_rank = int(os.environ.get("LOCAL_RANK"))
+ ddp_world_size = int(os.environ.get("WORLD_SIZE"))
+ device = f"cuda:{ddp_local_rank}"
+ torch.cuda.set_device(device)
+ master_process = ddp_rank == 0
+else:
+ ddp_rank = 0
+ ddp_local_rank = 0
+ ddp_world_size = 1
+ master_process = True
+ device = "cuda" if torch.cuda.is_available() else "cpu"
+torch.manual_seed(1337 + ddp_rank)
+
+
+class MiniQuadtrixHead(nn.Module):
+ def __init__(self, head_size):
+ super().__init__()
+ self.key = nn.Linear(N_EMBD, head_size, bias=False)
+ self.query = nn.Linear(N_EMBD, head_size, bias=False)
+ self.value = nn.Linear(N_EMBD, head_size, bias=False)
+ self.register_buffer("tril", torch.tril(
+ torch.ones(BLOCK_SIZE, BLOCK_SIZE)))
+ self.dropout = nn.Dropout(DROPOUT)
+
+ def forward(self, x):
+ B, T, C = x.shape
+ k = self.key(x)
+ q = self.query(x)
+ wei = q @ k.transpose(-2, -1) * k.shape[-1] ** -0.5
+ wei = wei.masked_fill(self.tril[:T, :T] == 0, float("-inf"))
+ wei = F.softmax(wei, dim=-1)
+ wei = self.dropout(wei)
+ return wei @ self.value(x)
+
+
+class MiniQuadtrixMHA(nn.Module):
+ def __init__(self, num_heads, head_size):
+ super().__init__()
+ self.heads = nn.ModuleList(
+ [MiniQuadtrixHead(head_size) for _ in range(num_heads)])
+ self.proj = nn.Linear(head_size * num_heads, N_EMBD)
+ self.dropout = nn.Dropout(DROPOUT)
+
+ def forward(self, x):
+ out = torch.cat([h(x) for h in self.heads], dim=-1)
+ return self.dropout(self.proj(out))
+
+
+class MiniQuadtrixFFN(nn.Module):
+ def __init__(self, n_embd):
+ super().__init__()
+ self.net = nn.Sequential(
+ nn.Linear(n_embd, 4 * n_embd),
+ nn.ReLU(),
+ nn.Linear(4 * n_embd, n_embd),
+ nn.Dropout(DROPOUT),
+ )
+
+ def forward(self, x):
+ return self.net(x)
+
+
+class MiniQuadtrixBlock(nn.Module):
+ def __init__(self, n_embd, n_head):
+ super().__init__()
+ head_size = n_embd // n_head
+ self.sa = MiniQuadtrixMHA(n_head, head_size)
+ self.ffwd = MiniQuadtrixFFN(n_embd)
+ self.ln1 = nn.LayerNorm(n_embd)
+ self.ln2 = nn.LayerNorm(n_embd)
+
+ def forward(self, x):
+ x = x + self.sa(self.ln1(x))
+ x = x + self.ffwd(self.ln2(x))
+ return x
+
+
+class MiniQuadtrix(nn.Module):
+ def __init__(self, vocab_size):
+ super().__init__()
+ self.token_embedding_table = nn.Embedding(vocab_size, N_EMBD)
+ self.position_embedding_table = nn.Embedding(BLOCK_SIZE, N_EMBD)
+ self.blocks = nn.Sequential(
+ *[MiniQuadtrixBlock(N_EMBD, n_head=N_HEAD) for _ in range(N_LAYER)]
+ )
+ self.ln_f = nn.LayerNorm(N_EMBD)
+ self.lm_head = nn.Linear(N_EMBD, vocab_size)
+
+ def forward(self, idx):
+ B, T = idx.shape
+ tok_emb = self.token_embedding_table(idx)
+ pos_emb = self.position_embedding_table(
+ torch.arange(T, device=idx.device))
+ x = tok_emb + pos_emb
+ x = self.blocks(x)
+ x = self.ln_f(x)
+ logits = self.lm_head(x)
+ return logits
+
+ def generate(self, idx, max_new_tokens):
+ for _ in range(max_new_tokens):
+ idx_cond = idx[:, -BLOCK_SIZE:]
+ logits = self(idx_cond)
+ logits = logits[:, -1, :]
+ probs = F.softmax(logits, dim=-1)
+ idx_next = torch.multinomial(probs, num_samples=1)
+ idx = torch.cat((idx, idx_next), dim=1)
+ return idx
+
+
+@torch.inference_mode()
+def main():
+ tokenizer = tiktoken.get_encoding("o200k_base")
+ vocab_size = tokenizer.n_vocab
+ model = MiniQuadtrix(vocab_size).to(device)
+ model.load_state_dict(torch.load(MODEL_WEIGHTS_PATH,
+ map_location=device, weights_only=True))
+ model.eval()
+
+ if master_process:
+ print(f"Cluster inference online. Total GPUs active: {ddp_world_size}")
+
+ while True:
+ prompt_str = ""
+ if master_process:
+ try:
+ prompt_str = input("user > ").strip()
+ if not prompt_str:
+ prompt_str = "IGNORE_EMPTY"
+ except (KeyboardInterrupt, EOFError):
+ prompt_str = "EXIT_ENGINE"
+ if ddp:
+ str_len = torch.tensor(
+ [len(prompt_str)], dtype=torch.long, device=device)
+ dist.broadcast(str_len, src=0)
+ char_tensor = torch.tensor(
+ [ord(c) for c in prompt_str], dtype=torch.long, device=device)
+ if not master_process:
+ char_tensor = torch.zeros(
+ str_len.item(), dtype=torch.long, device=device)
+ dist.broadcast(char_tensor, src=0)
+ prompt_str = "".join([chr(x) for x in char_tensor.tolist()])
+
+ if prompt_str in ("exit", "quit", "q", "EXIT_ENGINE"):
+ break
+ if prompt_str == "IGNORE_EMPTY":
+ continue
+
+ tokens = tokenizer.encode(prompt_str)
+ context = torch.tensor([tokens], dtype=torch.long, device=device)
+ output_ids = model.generate(context, max_new_tokens=100)
+ new_tokens = output_ids[0][len(tokens):].tolist()
+ local_response = tokenizer.decode(new_tokens).strip()
+ if ddp:
+
+ gather_objects = [None] * ddp_world_size
+ dist.all_gather_object(gather_objects, local_response)
+ else:
+ gather_objects = [local_response]
+
+ if master_process:
+ print(f"\n--- Outputs across {ddp_world_size} GPUs ---")
+ for rank_id, resp in enumerate(gather_objects):
+ print(f"[GPU {rank_id}] > {resp}")
+ print("-" * 40 + "\n")
+
+ if ddp:
+ dist.destroy_process_group()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/engine/distributed/train.py b/engine/distributed/train.py
new file mode 100644
index 0000000..55510c8
--- /dev/null
+++ b/engine/distributed/train.py
@@ -0,0 +1,332 @@
+import os
+import sys
+import time
+from pathlib import Path
+import tiktoken
+import torch
+import torch.distributed as dist
+import torch.nn as nn
+from torch.nn import functional as F
+from torch.nn.parallel import DistributedDataParallel as DDP
+
+SCRIPT_DIR = Path(__file__).resolve().parent
+
+# -------------------------------------
+ddp = int(os.environ.get("RANK", -1)) != -1
+if ddp:
+ dist.init_process_group(backend="nccl")
+ ddp_rank = int(os.environ.get("RANK"))
+ ddp_local_rank = int(os.environ.get("LOCAL_RANK"))
+ ddp_world_size = int(os.environ.get("WORLD_SIZE"))
+ device = f"cuda:{ddp_local_rank}"
+ torch.cuda.set_device(device)
+ master_process = ddp_rank == 0
+else:
+ # Fallback to local single-device execution
+ ddp_rank = 0
+ ddp_local_rank = 0
+ ddp_world_size = 1
+ master_process = True
+ device = "cuda" if torch.cuda.is_available() else "cpu"
+
+if master_process:
+ print("[llm] - Distributed Cluster Mode Enabled")
+ print(f"PyTorch Version: {torch.__version__}")
+ print(f"Total Cluster GPUs (World Size): {ddp_world_size if ddp else 1}")
+
+start = time.time()
+cleaned_path = Path(os.environ.get("data", SCRIPT_DIR / "input.txt"))
+train_split = 0.9
+seed = 1337
+batch_size = 2
+block_size = 20
+max_iters = 10000
+eval_interval = 50
+learning_rate = 3e-4
+eval_iters = 20
+n_embd = 6
+n_head = 4
+n_layer = 4
+dropout = 0.0
+torch.manual_seed(seed + ddp_rank)
+
+
+def get_miniq_tokenizer(encoding_name="o200k_base"):
+ miniq_tokenizer = tiktoken.get_encoding(encoding_name)
+ miniq_vocab_size = miniq_tokenizer.n_vocab
+ return miniq_tokenizer, miniq_vocab_size
+
+
+def miniq_encode(text, tokenizer):
+ return tokenizer.encode(text)
+
+
+def miniq_decode(tokens, tokenizer):
+ return tokenizer.decode(tokens)
+
+
+# All processes load the dataset
+with open(cleaned_path, "r", encoding="utf-8") as f:
+ text = f.read()
+
+miniq_tokenizer, vocab_size = get_miniq_tokenizer("o200k_base")
+encoded_data = miniq_encode(text, miniq_tokenizer)
+data = torch.tensor(encoded_data, dtype=torch.long)
+n = int(train_split * len(data))
+train_data = data[:n]
+val_data = data[n:]
+
+
+def get_batch(split):
+ data_split = train_data if split == "train" else val_data
+ ix = torch.randint(len(data_split) - block_size, (batch_size,))
+ x = torch.stack([data_split[i: i + block_size] for i in ix])
+ y = torch.stack([data_split[i + 1: i + block_size + 1] for i in ix])
+ x, y = x.to(device), y.to(device)
+ return x, y
+
+
+@torch.no_grad()
+def estimate_loss():
+ out = {}
+ raw_model.eval() # Use un-wrapped model instance references
+ for split in ["train", "val"]:
+ losses = torch.zeros(eval_iters)
+ for k in range(eval_iters):
+ X, Y = get_batch(split)
+ _, loss = miniq_model(X, Y)
+ losses[k] = loss.item()
+ out[split] = losses.mean()
+ raw_model.train()
+ return out
+
+
+class MiniQuadtrixHead(nn.Module):
+
+ def __init__(self, head_size):
+ super().__init__()
+ self.key = nn.Linear(n_embd, head_size, bias=False)
+ self.query = nn.Linear(n_embd, head_size, bias=False)
+ self.value = nn.Linear(n_embd, head_size, bias=False)
+ self.register_buffer(
+ "tril", torch.tril(torch.ones(block_size, block_size))
+ )
+ self.dropout = nn.Dropout(dropout)
+
+ def forward(self, x):
+ B, T, C = x.shape
+ k = self.key(x)
+ q = self.query(x)
+ wei = q @ k.transpose(-2, -1) * k.shape[-1] ** -0.5
+ wei = wei.masked_fill(self.tril[:T, :T] == 0, float("-inf"))
+ wei = F.softmax(wei, dim=-1)
+ wei = self.dropout(wei)
+ return wei @ self.value(x)
+
+
+class MiniQuadtrixMHA(nn.Module):
+
+ def __init__(self, num_heads, head_size):
+ super().__init__()
+ self.heads = nn.ModuleList(
+ [MiniQuadtrixHead(head_size) for _ in range(num_heads)]
+ )
+ self.proj = nn.Linear(head_size * num_heads, n_embd)
+ self.dropout = nn.Dropout(dropout)
+
+ def forward(self, x):
+ out = torch.cat([h(x) for h in self.heads], dim=-1)
+ return self.dropout(self.proj(out))
+
+
+class MiniQuadtrixFFN(nn.Module):
+
+ def __init__(self, n_embd):
+ super().__init__()
+ self.net = nn.Sequential(
+ nn.Linear(n_embd, 4 * n_embd),
+ nn.ReLU(),
+ nn.Linear(4 * n_embd, n_embd),
+ nn.Dropout(dropout),
+ )
+
+ def forward(self, x):
+ return self.net(x)
+
+
+class MiniQuadtrixBlock(nn.Module):
+
+ def __init__(self, n_embd, n_head):
+ super().__init__()
+ head_size = n_embd // n_head
+ self.sa = MiniQuadtrixMHA(n_head, head_size)
+ self.ffwd = MiniQuadtrixFFN(n_embd)
+ self.ln1 = nn.LayerNorm(n_embd)
+ self.ln2 = nn.LayerNorm(n_embd)
+
+ def forward(self, x):
+ x = x + self.sa(self.ln1(x))
+ x = x + self.ffwd(self.ln2(x))
+ return x
+
+
+class MiniQuadtrix(nn.Module):
+
+ def __init__(self):
+ super().__init__()
+ self.token_embedding_table = nn.Embedding(vocab_size, n_embd)
+ self.position_embedding_table = nn.Embedding(block_size, n_embd)
+ self.blocks = nn.Sequential(
+ *[
+ MiniQuadtrixBlock(n_embd, n_head=n_head)
+ for _ in range(n_layer)
+ ]
+ )
+ self.ln_f = nn.LayerNorm(n_embd)
+ self.lm_head = nn.Linear(n_embd, vocab_size)
+ self.apply(self._init_weights)
+
+ def _init_weights(self, module):
+ if isinstance(module, nn.Linear):
+ torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
+ if module.bias is not None:
+ torch.nn.init.zeros_(module.bias)
+ elif isinstance(module, nn.Embedding):
+ torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
+
+ def forward(self, idx, targets=None):
+ B, T = idx.shape
+ tok_emb = self.token_embedding_table(idx)
+ pos_emb = self.position_embedding_table(
+ torch.arange(T, device=idx.device)
+ )
+ x = tok_emb + pos_emb
+ x = self.blocks(x)
+ x = self.ln_f(x)
+ logits = self.lm_head(x)
+
+ if targets is None:
+ loss = None
+ else:
+ B, T, C = logits.shape
+ logits = logits.view(B * T, C)
+ targets = targets.view(B * T)
+ loss = F.cross_entropy(logits, targets)
+ return logits, loss
+
+ def generate(self, idx, max_new_tokens):
+ for _ in range(max_new_tokens):
+ idx_cond = idx[:, -block_size:]
+ logits, _ = self(idx_cond)
+ logits = logits[:, -1, :]
+ probs = F.softmax(logits, dim=-1)
+ idx_next = torch.multinomial(probs, num_samples=1)
+ idx = torch.cat((idx, idx_next), dim=1)
+ return idx
+
+
+miniq_model = MiniQuadtrix().to(device)
+raw_model = miniq_model
+
+if ddp:
+ miniq_model = DDP(miniq_model, device_ids=[ddp_local_rank])
+ raw_model = miniq_model.module
+
+miniq_n_params = sum(p.numel() for p in raw_model.parameters())
+miniq_optimizer = torch.optim.AdamW(miniq_model.parameters(), lr=learning_rate)
+
+if master_process:
+ print("CONFIG")
+ print(f"Seed Configuration: {seed}")
+ print(f"Per-GPU Batch size: {batch_size}")
+ print(f"Effective Cluster Batch size: {batch_size * ddp_world_size}")
+ print(f"Block size: {block_size}")
+ print(f"Learning rate: {learning_rate}")
+ print(f"Parameters: {miniq_n_params:,}")
+ print(f"Train tokens: {len(train_data):,}")
+ print()
+
+best_val_loss = float("inf")
+train_start = time.time()
+
+for iter in range(max_iters):
+ if iter % eval_interval == 0 or iter == max_iters - 1:
+ losses = estimate_loss()
+ elapsed = time.time() - train_start
+ total_norm = 0
+ for p in miniq_model.parameters():
+ if p.grad is not None:
+ param_norm = p.grad.detach().data.norm(2)
+ total_norm += param_norm.item() ** 2
+ total_norm = total_norm ** 0.5
+
+ tokens_per_sec = (
+ (iter + 1) * batch_size * ddp_world_size * block_size / elapsed
+ if elapsed > 0
+ else 0
+ )
+ if master_process:
+ is_best = losses["val"] < best_val_loss
+ if is_best:
+ best_val_loss = losses["val"]
+ torch.save(raw_model.state_dict(), "llm.pt")
+
+ print(
+ f"step {iter} | loss: {losses['train']:.6f} | val_loss: {losses['val']:.6f} | norm: {total_norm:.4f} | dt: {elapsed*1000:.2f}ms | tok/sec: {tokens_per_sec:.2f}"
+ )
+ sys.stdout.flush()
+
+ xb, yb = get_batch("train")
+ logits, loss = miniq_model(xb, yb)
+ miniq_optimizer.zero_grad(set_to_none=True)
+ loss.backward()
+ miniq_optimizer.step()
+
+total_time = time.time() - train_start
+
+if master_process:
+ print("\n" + "=" * 50)
+ print(
+ f"Training Duration: {int(total_time // 60)}m {int(total_time % 60):02d}s")
+ print(f"Best validation loss recorded: {best_val_loss:.4f}")
+ print("=" * 50 + "\n")
+ try:
+ raw_model.load_state_dict(
+ torch.load("llm.pt", map_location=device, weights_only=True)
+ )
+ raw_model.eval()
+
+ print("INFERENCE TERMINAL CHAT MODE")
+ print("Type 'exit' to terminate session.\n")
+
+ while True:
+ prompt = input("user > ").strip()
+ if prompt.lower() in ("quit", "exit", "q"):
+ print("\nSession ended.")
+ break
+ if not prompt:
+ continue
+
+ encoded_prompt = miniq_encode(prompt, miniq_tokenizer)
+ context = torch.tensor(
+ [encoded_prompt], dtype=torch.long, device=device
+ )
+
+ with torch.no_grad():
+ output_ids = raw_model.generate(context, max_new_tokens=100)
+
+ new_tokens = output_ids[0][len(encoded_prompt):].tolist()
+ response = miniq_decode(new_tokens, miniq_tokenizer).strip()
+
+ print(f"\nModel > {response}\n")
+
+ except (KeyboardInterrupt, FileNotFoundError):
+ print("\nInference interrupted or weight metrics file bypassed.")
+
+ wall_clock = time.time() - start
+ print(
+ f"\nTotal process lifecycle runtime: {int(wall_clock // 60)}m {int(wall_clock % 60):02d}s\n")
+
+# Shutdown distributed worker nodes gracefully
+if ddp:
+ dist.destroy_process_group()
diff --git a/engine/fineweb_dataset.py b/engine/fineweb_dataset.py
deleted file mode 100644
index 9a0107f..0000000
--- a/engine/fineweb_dataset.py
+++ /dev/null
@@ -1,61 +0,0 @@
-"""
-Download 30MB of FineWeb dataset from Hugging Face.
-FineWeb is a large-scale web text dataset for language model training.
-"""
-
-from datasets import load_dataset
-import os
-
-def download_fineweb_sample(output_dir="engine", target_size_mb=30):
- """
- Download approximately 30MB of FineWeb dataset.-
- """
- print(f"Downloading ~{target_size_mb}MB of FineWeb dataset...")
- print("This may take a few minutes depending on your connection speed.\n")
- os.makedirs(output_dir, exist_ok=True)
- dataset = load_dataset(
- "HuggingFaceFW/fineweb",
- name="sample-10BT",
- split="train",
- streaming=True
- )
-
- # Download and save data until we reach ~30MB
- target_bytes = target_size_mb * 1024 * 1024
- current_bytes = 0
- samples = []
-
- print("Streaming and collecting samples...")
- for i, sample in enumerate(dataset):
- sample_size = len(sample['text'].encode('utf-8'))
-
- if current_bytes + sample_size > target_bytes:
- break
-
- samples.append(sample)
- current_bytes += sample_size
-
- if (i + 1) % 100 == 0:
- print(f"Collected {i + 1} samples ({current_bytes / (1024*1024):.2f} MB)")
-
- print(f"\nDownloaded {len(samples)} samples ({current_bytes / (1024*1024):.2f} MB)")
- output_file = os.path.join(output_dir, "input.txt")
- with open(output_file, 'w', encoding='utf-8') as f:
- for sample in samples:
- f.write(sample['text'])
- f.write('\n\n' + '='*80 + '\n\n') # Separator between documents
-
- print(f"Data saved to: {output_file}")
- print(f"Total samples: {len(samples)}")
- print(f"Total size: {current_bytes / (1024*1024):.2f} MB")
-
- return output_file
-
-if __name__ == "__main__":
- try:
- download_fineweb_sample()
- print("\nDownload completed successfully!")
- except Exception as e:
- print(f"\ Error: {e}")
- print("\nMake sure you have the 'datasets' library installed:")
- print(" pip install datasets")
\ No newline at end of file
diff --git a/engine/iGPU/inference.py b/engine/iGPU/inference.py
deleted file mode 100644
index 7f56f38..0000000
--- a/engine/iGPU/inference.py
+++ /dev/null
@@ -1,298 +0,0 @@
-import argparse
-from pathlib import Path
-import time
-
-import torch
-import torch.nn as nn
-from torch.nn import functional as F
-import tiktoken
-
-try:
- import torch_directml
-except ImportError:
- torch_directml = None
-
-
-W = 78
-DOUBLE = "=" * W
-SINGLE = "-" * W
-ARROW = "->"
-
-block_size = 32
-n_embd = 64
-n_head = 4
-n_layer = 4
-dropout = 0.1
-
-
-def header(title, subtitle=""):
- print(f"\n{DOUBLE}")
- print(f" {title}")
- if subtitle:
- print(f" {subtitle}")
- print(DOUBLE)
-
-
-def row(label, value="", unit="", note=""):
- label_col = f" {label:<28}"
- value_col = f"{str(value):<20}"
- unit_col = f"{unit:<8}"
- note_col = f" {note}" if note else ""
- print(f"{label_col}{value_col}{unit_col}{note_col}")
-
-
-def rule():
- print(f" {SINGLE}")
-
-
-def blank():
- print()
-
-
-def get_device():
- if torch_directml is not None:
- return torch_directml.device()
- if torch.cuda.is_available():
- return torch.device("cuda")
- return torch.device("cpu")
-
-
-def get_tokenizer(encoding_name="gpt2"):
- tokenizer = tiktoken.get_encoding(encoding_name)
- return tokenizer, tokenizer.n_vocab
-
-
-def encode(text, tokenizer):
- return tokenizer.encode(text)
-
-
-def decode(tokens, tokenizer):
- return tokenizer.decode(tokens)
-
-
-tokenizer, vocab_size = get_tokenizer("gpt2")
-device = get_device()
-
-
-class Head(nn.Module):
- def __init__(self, head_size):
- super().__init__()
- self.key = nn.Linear(n_embd, head_size, bias=False)
- self.query = nn.Linear(n_embd, head_size, bias=False)
- self.value = nn.Linear(n_embd, head_size, bias=False)
- self.register_buffer("tril", torch.tril(torch.ones(block_size, block_size)))
- self.dropout = nn.Dropout(dropout)
-
- def forward(self, x):
- _, T, _ = x.shape
- k = self.key(x)
- q = self.query(x)
- wei = q @ k.transpose(-2, -1) * k.shape[-1] ** -0.5
- wei = wei.masked_fill(self.tril[:T, :T] == 0, float("-inf"))
- wei = F.softmax(wei, dim=-1)
- wei = self.dropout(wei)
- return wei @ self.value(x)
-
-
-class MultiHeadAttention(nn.Module):
- def __init__(self, num_heads, head_size):
- super().__init__()
- self.heads = nn.ModuleList([Head(head_size) for _ in range(num_heads)])
- self.proj = nn.Linear(head_size * num_heads, n_embd)
- self.dropout = nn.Dropout(dropout)
-
- def forward(self, x):
- out = torch.cat([h(x) for h in self.heads], dim=-1)
- return self.dropout(self.proj(out))
-
-
-class FeedForward(nn.Module):
- def __init__(self, n_embd):
- super().__init__()
- self.net = nn.Sequential(
- nn.Linear(n_embd, 4 * n_embd),
- nn.ReLU(),
- nn.Linear(4 * n_embd, n_embd),
- nn.Dropout(dropout),
- )
-
- def forward(self, x):
- return self.net(x)
-
-
-class Block(nn.Module):
- def __init__(self, n_embd, n_head):
- super().__init__()
- head_size = n_embd // n_head
- self.sa = MultiHeadAttention(n_head, head_size)
- self.ffwd = FeedForward(n_embd)
- self.ln1 = nn.LayerNorm(n_embd)
- self.ln2 = nn.LayerNorm(n_embd)
-
- def forward(self, x):
- x = x + self.sa(self.ln1(x))
- x = x + self.ffwd(self.ln2(x))
- return x
-
-
-class GPTLanguageModel(nn.Module):
- def __init__(self):
- super().__init__()
- self.token_embedding_table = nn.Embedding(vocab_size, n_embd)
- self.position_embedding_table = nn.Embedding(block_size, n_embd)
- self.blocks = nn.Sequential(*[Block(n_embd, n_head=n_head) for _ in range(n_layer)])
- self.ln_f = nn.LayerNorm(n_embd)
- self.lm_head = nn.Linear(n_embd, vocab_size)
-
- def forward(self, idx, targets=None):
- B, T = idx.shape
- tok_emb = self.token_embedding_table(idx)
- pos_emb = self.position_embedding_table(torch.arange(T, device=device))
- x = tok_emb + pos_emb
- x = self.blocks(x)
- x = self.ln_f(x)
- logits = self.lm_head(x)
-
- if targets is None:
- loss = None
- else:
- B, T, C = logits.shape
- logits = logits.view(B * T, C)
- targets = targets.view(B * T)
- loss = F.cross_entropy(logits, targets)
- return logits, loss
-
- def generate(self, idx, max_new_tokens, temperature=1.0, top_k=None):
- for _ in range(max_new_tokens):
- idx_cond = idx[:, -block_size:]
- logits, _ = self(idx_cond)
- logits = logits[:, -1, :] / max(temperature, 1e-6)
-
- if top_k is not None:
- values, _ = torch.topk(logits, min(top_k, logits.size(-1)))
- logits[logits < values[:, [-1]]] = float("-inf")
-
- probs = F.softmax(logits, dim=-1)
- idx_next = torch.multinomial(probs, num_samples=1)
- idx = torch.cat((idx, idx_next), dim=1)
- return idx
-
-
-def default_checkpoint_path():
- script_dir = Path(__file__).resolve().parent
- candidates = [
- script_dir / "best_model.pt",
- Path.cwd() / "best_model.pt",
- Path.cwd() / "iGPU" / "best_model.pt",
- ]
- for candidate in candidates:
- if candidate.exists():
- return candidate
- return script_dir / "best_model.pt"
-
-
-def load_model(checkpoint_path):
- checkpoint_path = Path(checkpoint_path)
- if not checkpoint_path.exists():
- raise FileNotFoundError(
- f"Checkpoint not found: {checkpoint_path}\n"
- "Train first with iGPU/main.py, or pass --checkpoint path/to/best_model.pt"
- )
-
- model = GPTLanguageModel().to(device)
- state_dict = torch.load(checkpoint_path, map_location=device)
- model.load_state_dict(state_dict)
- model.eval()
- return model
-
-
-def generate_response(model, prompt, max_new_tokens, temperature, top_k):
- encoded_prompt = encode(prompt, tokenizer)
- context = torch.tensor([encoded_prompt], dtype=torch.long, device=device)
-
- with torch.no_grad():
- output_ids = model.generate(
- context,
- max_new_tokens=max_new_tokens,
- temperature=temperature,
- top_k=top_k,
- )
-
- new_tokens = output_ids[0][len(encoded_prompt):].tolist()
- return decode(new_tokens, tokenizer).strip()
-
-
-def chat(model, args):
- header("INFERENCE", "quit / exit / q -> end session")
- blank()
-
- while True:
- prompt = input(f" user {ARROW} ").strip()
- if prompt.lower() in ("quit", "exit", "q"):
- blank()
- print(" Session ended.")
- break
- if not prompt:
- continue
-
- response = generate_response(
- model,
- prompt,
- args.max_new_tokens,
- args.temperature,
- args.top_k,
- )
- blank()
- print(f" Model {ARROW} {response}")
- blank()
-
-
-def parse_args():
- parser = argparse.ArgumentParser(description="Run inference from an iGPU trained .pt checkpoint.")
- parser.add_argument(
- "--checkpoint",
- type=Path,
- default=default_checkpoint_path(),
- help="Path to the .pt file generated by iGPU/main.py.",
- )
- parser.add_argument("--prompt", type=str, default=None, help="Generate once from this prompt.")
- parser.add_argument("--max-new-tokens", type=int, default=200)
- parser.add_argument("--temperature", type=float, default=1.0)
- parser.add_argument("--top-k", type=int, default=None)
- return parser.parse_args()
-
-
-def main():
- args = parse_args()
- start = time.time()
-
- print(f"{'Quadtrix-v1.0':^{W}}")
- blank()
- row("Started", time.strftime("%Y-%m-%d %H:%M:%S"))
- row("Device", str(device))
- row("PyTorch", torch.__version__)
- row("Checkpoint", args.checkpoint)
- rule()
-
- model = load_model(args.checkpoint)
-
- if args.prompt:
- response = generate_response(
- model,
- args.prompt,
- args.max_new_tokens,
- args.temperature,
- args.top_k,
- )
- blank()
- print(response)
- else:
- chat(model, args)
-
- blank()
- row("Total", f"{time.time() - start:.2f}s")
- print(DOUBLE)
-
-
-if __name__ == "__main__":
- main()
diff --git a/engine/iGPU/main.py b/engine/iGPU/main.py
deleted file mode 100644
index d0db7a7..0000000
--- a/engine/iGPU/main.py
+++ /dev/null
@@ -1,360 +0,0 @@
-import torch
-import torch_directml
-import torch.nn as nn
-from torch.nn import functional as F
-import time
-import sys
-from pathlib import Path
-import tiktoken
-
-# LOGGING UTILITIES
-W = 78
-DOUBLE = "=" * W
-SINGLE = "-" * W
-TICK = "best"
-ARROW = ">"
-
-LOG_DIR = Path(__file__).resolve().parent / "logs"
-LOG_DIR.mkdir(parents=True, exist_ok=True)
-LOG_PATH = LOG_DIR / f"run_{time.strftime('%Y%m%d_%H%M%S')}.txt"
-
-def log(message=""):
- line = "" if message == "" else f"{time.strftime('%Y-%m-%d %H:%M:%S')} | {message}"
- print(line)
- with open(LOG_PATH, "a", encoding="utf-8") as f:
- f.write(f"{line}\n")
-
-def header(title, subtitle=""):
- log()
- log(DOUBLE)
- log(f" {title}")
- if subtitle:
- log(f" {subtitle}")
- log(DOUBLE)
-
-def row(label, value="", unit="", note=""):
- label_col = f" {label:<28}"
- value_col = f"{str(value):<20}"
- unit_col = f"{unit:<8}"
- note_col = f" {note}" if note else ""
- log(f"{label_col}{value_col}{unit_col}{note_col}")
-
-def rule(): log(f" {SINGLE}")
-def blank(): log()
-def info(msg): log(f" {ARROW} {msg}")
-def success(msg): log(f" ok {msg}")
-
-
-# SESSION
-
-
-
-log(f"{'Quadtrix-v1.0':^{W}}")
-blank()
-row("Started", time.strftime('%Y-%m-%d %H:%M:%S'))
-row("Device", str(torch_directml.device()))
-row("PyTorch", torch.__version__)
-row("Log file", str(LOG_PATH))
-
-start = time.time()
-
-# CONFIGURATION
-
-
-cleaned_path = "engine\data\cleaned.txt"
-train_split = 0.9
-seed = 1337
-
-batch_size = 16
-block_size = 32
-max_iters = 3000
-eval_interval = 100
-learning_rate = 1e-3
-device = torch_directml.device()
-eval_iters = 20
-n_embd = 64
-n_head = 4
-n_layer = 4
-dropout = 0.1
-
-torch.manual_seed(seed)
-
-
-# tokenizer
-
-def get_tokenizer(encoding_name="gpt2"):
- tokenizer = tiktoken.get_encoding(encoding_name)
- vocab_size = tokenizer.n_vocab
- return tokenizer, vocab_size
-
-def encode(text, tokenizer): return tokenizer.encode(text)
-def decode(tokens, tokenizer): return tokenizer.decode(tokens)
-
-
-
-# DATA
-
-with open(cleaned_path, 'r', encoding='utf-8') as f:
- text = f.read()
-
-tokenizer, vocab_size = get_tokenizer("gpt2")
-encoded_data = encode(text, tokenizer)
-
-data = torch.tensor(encoded_data, dtype=torch.long)
-n = int(train_split * len(data))
-train_data = data[:n]
-val_data = data[n:]
-
-# Batch and LOSS
-
-def get_batch(split):
- data_split = train_data if split == 'train' else val_data
- ix = torch.randint(len(data_split) - block_size, (batch_size,))
- x = torch.stack([data_split[i:i + block_size] for i in ix])
- y = torch.stack([data_split[i + 1:i + block_size + 1] for i in ix])
- x, y = x.to(device), y.to(device)
- return x, y
-
-@torch.no_grad()
-def estimate_loss():
- out = {}
- model.eval()
- for split in ['train', 'val']:
- losses = torch.zeros(eval_iters)
- for k in range(eval_iters):
- X, Y = get_batch(split)
- _, loss = model(X, Y)
- losses[k] = loss.item()
- out[split] = losses.mean()
- model.train()
- return out
-
-# model
-
-class Head(nn.Module):
- def __init__(self, head_size):
- super().__init__()
- self.key = nn.Linear(n_embd, head_size, bias=False)
- self.query = nn.Linear(n_embd, head_size, bias=False)
- self.value = nn.Linear(n_embd, head_size, bias=False)
- self.register_buffer('tril', torch.tril(torch.ones(block_size, block_size)))
- self.dropout = nn.Dropout(dropout)
-
- def forward(self, x):
- B, T, C = x.shape
- k = self.key(x)
- q = self.query(x)
- wei = q @ k.transpose(-2, -1) * k.shape[-1] ** -0.5
- wei = wei.masked_fill(self.tril[:T, :T] == 0, float('-inf'))
- wei = F.softmax(wei, dim=-1)
- wei = self.dropout(wei)
- return wei @ self.value(x)
-
-class MultiHeadAttention(nn.Module):
- def __init__(self, num_heads, head_size):
- super().__init__()
- self.heads = nn.ModuleList([Head(head_size) for _ in range(num_heads)])
- self.proj = nn.Linear(head_size * num_heads, n_embd)
- self.dropout = nn.Dropout(dropout)
-
- def forward(self, x):
- out = torch.cat([h(x) for h in self.heads], dim=-1)
- return self.dropout(self.proj(out))
-
-class FeedFoward(nn.Module):
- def __init__(self, n_embd):
- super().__init__()
- self.net = nn.Sequential(
- nn.Linear(n_embd, 4 * n_embd),
- nn.ReLU(),
- nn.Linear(4 * n_embd, n_embd),
- nn.Dropout(dropout),
- )
-
- def forward(self, x):
- return self.net(x)
-
-class Block(nn.Module):
- def __init__(self, n_embd, n_head):
- super().__init__()
- head_size = n_embd // n_head
- self.sa = MultiHeadAttention(n_head, head_size)
- self.ffwd = FeedFoward(n_embd)
- self.ln1 = nn.LayerNorm(n_embd)
- self.ln2 = nn.LayerNorm(n_embd)
-
- def forward(self, x):
- x = x + self.sa(self.ln1(x))
- x = x + self.ffwd(self.ln2(x))
- return x
-
-class GPTLanguageModel(nn.Module):
- def __init__(self):
- super().__init__()
- self.token_embedding_table = nn.Embedding(vocab_size, n_embd)
- self.position_embedding_table = nn.Embedding(block_size, n_embd)
- self.blocks = nn.Sequential(*[Block(n_embd, n_head=n_head) for _ in range(n_layer)])
- self.ln_f = nn.LayerNorm(n_embd)
- self.lm_head = nn.Linear(n_embd, vocab_size)
- self.apply(self._init_weights)
-
- def _init_weights(self, module):
- if isinstance(module, nn.Linear):
- torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
- if module.bias is not None:
- torch.nn.init.zeros_(module.bias)
- elif isinstance(module, nn.Embedding):
- torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
-
- def forward(self, idx, targets=None):
- B, T = idx.shape
- tok_emb = self.token_embedding_table(idx)
- pos_emb = self.position_embedding_table(torch.arange(T, device=device))
- x = tok_emb + pos_emb
- x = self.blocks(x)
- x = self.ln_f(x)
- logits = self.lm_head(x)
-
- if targets is None:
- loss = None
- else:
- B, T, C = logits.shape
- logits = logits.view(B * T, C)
- targets = targets.view(B * T)
- loss = F.cross_entropy(logits, targets)
- return logits, loss
-
- def generate(self, idx, max_new_tokens):
- for _ in range(max_new_tokens):
- idx_cond = idx[:, -block_size:]
- logits, _ = self(idx_cond)
- logits = logits[:, -1, :]
- probs = F.softmax(logits, dim=-1)
- idx_next = torch.multinomial(probs, num_samples=1)
- idx = torch.cat((idx, idx_next), dim=1)
- return idx
-
-
-
-# INITIALISE
-
-model = GPTLanguageModel().to(device)
-n_params = sum(p.numel() for p in model.parameters())
-optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
-
-header("CONFIG")
-row("Seed", seed)
-row("Batch size", batch_size)
-row("Block size", block_size)
-row("Learning rate", learning_rate)
-row("Layers", n_layer)
-row("Heads", n_head)
-row("Embedding dim", n_embd)
-row("Dropout", dropout)
-row("Parameters", f"{n_params:,}")
-row("Train tokens", f"{len(train_data):,}")
-row("Val tokens", f"{len(val_data):,}")
-
-
-#training
-header("TRAINING", f"{max_iters:,} steps | eval every {eval_interval} | checkpoint on improvement")
-blank()
-
-log(" training loop started")
-
-best_val_loss = float('inf')
-train_start = time.time()
-
-for iter in range(max_iters):
-
- if iter % eval_interval == 0 or iter == max_iters - 1:
- losses = estimate_loss()
- elapsed = time.time() - train_start
- pct = 100 * iter / max_iters
- eta_secs = (elapsed / (iter + 1)) * (max_iters - iter - 1) if iter > 0 else 0
- is_best = losses['val'] < best_val_loss
- status = f"{TICK} saved" if is_best else "-"
- elapsed_fmt = f"{int(elapsed // 60)}m {int(elapsed % 60):02d}s"
- eta_fmt = f"{int(eta_secs // 60)}m {int(eta_secs % 60):02d}s"
-
- if is_best:
- best_val_loss = losses['val']
- torch.save(model.state_dict(), 'best_model.pt')
- log(f" ckpt path=best_model.pt val_loss={best_val_loss:.4f} step={iter}")
-
- log(
- f" train step={iter}/{max_iters} pct={pct:.1f}% "
- f"loss_train={losses['train']:.4f} loss_val={losses['val']:.4f} "
- f"elapsed={elapsed_fmt} eta={eta_fmt} status={status}"
- )
- sys.stdout.flush()
-
- xb, yb = get_batch('train')
- logits, loss = model(xb, yb)
- optimizer.zero_grad(set_to_none=True)
- loss.backward()
- optimizer.step()
-
-total_time = time.time() - train_start
-blank()
-rule()
-row("Duration", f"{int(total_time // 60)}m {int(total_time % 60):02d}s")
-row("Best val loss", f"{best_val_loss:.4f}", "", TICK)
-row("Checkpoint", "best_model.pt", "", TICK)
-rule()
-
-
-
-# RESTORE CHECKPOIN
-blank()
-model.load_state_dict(torch.load('best_model.pt', map_location=device))
-model.eval()
-success(f"Restored best_model.pt | val loss {best_val_loss:.4f}")
-
-# INFERENCE
-
-
-header("INFERENCE", "quit / exit / q -> end session")
-blank()
-
-try:
- while True:
- prompt = input(f" user {ARROW} ").strip()
- log(f" user {ARROW} {prompt}")
-
- if prompt.lower() in ("quit", "exit", "q"):
- blank()
- success("Session ended.")
- break
-
- if not prompt:
- continue
-
- encoded_prompt = encode(prompt, tokenizer)
- context = torch.tensor([encoded_prompt], dtype=torch.long, device=device)
-
- with torch.no_grad():
- output_ids = model.generate(context, max_new_tokens=200)
-
- new_tokens = output_ids[0][len(encoded_prompt):].tolist()
- response = decode(new_tokens, tokenizer).strip()
-
- blank()
- log(f" Model {ARROW} {response}")
- blank()
-
-except KeyboardInterrupt:
- blank()
- success("Interrupted.")
-
-
-end = time.time()
-wall_clock = end - start
-
-blank()
-rule()
-row("Training", f"{int(total_time // 60)}m {int(total_time % 60):02d}s")
-row("Total", f"{int(wall_clock // 60)}m {int(wall_clock % 60):02d}s", "", TICK)
-rule()
-blank()
-log(f"{DOUBLE}\n")
diff --git a/engine/inference.py b/engine/inference.py
index d4ce649..c4d7bc9 100644
--- a/engine/inference.py
+++ b/engine/inference.py
@@ -1,74 +1,41 @@
-import argparse
-from pathlib import Path
-import time
-
+import os
+import sys
+import tiktoken
import torch
import torch.nn as nn
from torch.nn import functional as F
-import tiktoken
-
-
-ARROW = "->"
-
-block_size = 32
-n_embd = 64
-n_head = 4
-n_layer = 4
-dropout = 0.1
-
-device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
-
-
-def header(title, subtitle=""):
- print(f" {title}")
- if subtitle:
- print(f" {subtitle}")
-
-
-def row(label, value="", unit="", note=""):
- label_col = f" {label:<28}"
- value_col = f"{str(value):<20}"
- unit_col = f"{unit:<8}"
- note_col = f" {note}" if note else ""
- print(f"{label_col}{value_col}{unit_col}{note_col}")
-
-
-def rule():
- print(f"")
+# hyperparameters (Must match your trained model exactly)
+BLOCK_SIZE = 20
+N_EMBD = 6
+N_HEAD = 4
+N_LAYER = 4
+DROPOUT = 0.0 # Set to 0 for deterministic inference evaluation
+MODEL_WEIGHTS_PATH = "llm.pt"
+# -----------------------------------
+DEVICE = (
+ "cuda"
+ if torch.cuda.is_available()
+ else "mps"
+ if torch.backends.mps.is_available()
+ else "cpu"
+)
+
+
+class MiniQuadtrixHead(nn.Module):
-def blank():
- print()
-
-
-def get_tokenizer(encoding_name="gpt2"):
- tokenizer = tiktoken.get_encoding(encoding_name)
- return tokenizer, tokenizer.n_vocab
-
-
-def encode(text, tokenizer):
- return tokenizer.encode(text)
-
-
-def decode(tokens, tokenizer):
- return tokenizer.decode(tokens)
-
-
-tokenizer, vocab_size = get_tokenizer("gpt2")
-
-
-class Head(nn.Module):
def __init__(self, head_size):
super().__init__()
- self.key = nn.Linear(n_embd, head_size, bias=False)
- self.query = nn.Linear(n_embd, head_size, bias=False)
- self.value = nn.Linear(n_embd, head_size, bias=False)
- self.register_buffer("tril", torch.tril(
- torch.ones(block_size, block_size)))
- self.dropout = nn.Dropout(dropout)
+ self.key = nn.Linear(N_EMBD, head_size, bias=False)
+ self.query = nn.Linear(N_EMBD, head_size, bias=False)
+ self.value = nn.Linear(N_EMBD, head_size, bias=False)
+ self.register_buffer(
+ "tril", torch.tril(torch.ones(BLOCK_SIZE, BLOCK_SIZE))
+ )
+ self.dropout = nn.Dropout(DROPOUT)
def forward(self, x):
- _, T, _ = x.shape
+ B, T, C = x.shape
k = self.key(x)
q = self.query(x)
wei = q @ k.transpose(-2, -1) * k.shape[-1] ** -0.5
@@ -78,38 +45,43 @@ def forward(self, x):
return wei @ self.value(x)
-class MultiHeadAttention(nn.Module):
+class MiniQuadtrixMHA(nn.Module):
+
def __init__(self, num_heads, head_size):
super().__init__()
- self.heads = nn.ModuleList([Head(head_size) for _ in range(num_heads)])
- self.proj = nn.Linear(head_size * num_heads, n_embd)
- self.dropout = nn.Dropout(dropout)
+ self.heads = nn.ModuleList(
+ [MiniQuadtrixHead(head_size) for _ in range(num_heads)]
+ )
+ self.proj = nn.Linear(head_size * num_heads, N_EMBD)
+ self.dropout = nn.Dropout(DROPOUT)
def forward(self, x):
out = torch.cat([h(x) for h in self.heads], dim=-1)
return self.dropout(self.proj(out))
-class FeedForward(nn.Module):
+class MiniQuadtrixFFN(nn.Module):
+
def __init__(self, n_embd):
super().__init__()
self.net = nn.Sequential(
nn.Linear(n_embd, 4 * n_embd),
nn.ReLU(),
nn.Linear(4 * n_embd, n_embd),
- nn.Dropout(dropout),
+ nn.Dropout(DROPOUT),
)
def forward(self, x):
return self.net(x)
-class Block(nn.Module):
+class MiniQuadtrixBlock(nn.Module):
+
def __init__(self, n_embd, n_head):
super().__init__()
head_size = n_embd // n_head
- self.sa = MultiHeadAttention(n_head, head_size)
- self.ffwd = FeedForward(n_embd)
+ self.sa = MiniQuadtrixMHA(n_head, head_size)
+ self.ffwd = MiniQuadtrixFFN(n_embd)
self.ln1 = nn.LayerNorm(n_embd)
self.ln2 = nn.LayerNorm(n_embd)
@@ -119,164 +91,115 @@ def forward(self, x):
return x
-class GPTLanguageModel(nn.Module):
- def __init__(self):
+class MiniQuadtrix(nn.Module):
+
+ def __init__(self, vocab_size):
super().__init__()
- self.token_embedding_table = nn.Embedding(vocab_size, n_embd)
- self.position_embedding_table = nn.Embedding(block_size, n_embd)
+ self.token_embedding_table = nn.Embedding(vocab_size, N_EMBD)
+ self.position_embedding_table = nn.Embedding(BLOCK_SIZE, N_EMBD)
self.blocks = nn.Sequential(
- *[Block(n_embd, n_head=n_head) for _ in range(n_layer)])
- self.ln_f = nn.LayerNorm(n_embd)
- self.lm_head = nn.Linear(n_embd, vocab_size)
+ *[
+ MiniQuadtrixBlock(N_EMBD, n_head=N_HEAD)
+ for _ in range(N_LAYER)
+ ]
+ )
+ self.ln_f = nn.LayerNorm(N_EMBD)
+ self.lm_head = nn.Linear(N_EMBD, vocab_size)
- def forward(self, idx, targets=None):
+ def forward(self, idx):
B, T = idx.shape
tok_emb = self.token_embedding_table(idx)
- pos_emb = self.position_embedding_table(torch.arange(T, device=device))
+ pos_emb = self.position_embedding_table(
+ torch.arange(T, device=idx.device)
+ )
x = tok_emb + pos_emb
x = self.blocks(x)
x = self.ln_f(x)
logits = self.lm_head(x)
+ return logits
- if targets is None:
- loss = None
- else:
- B, T, C = logits.shape
- logits = logits.view(B * T, C)
- targets = targets.view(B * T)
- loss = F.cross_entropy(logits, targets)
- return logits, loss
-
- def generate(self, idx, max_new_tokens, temperature=1.0, top_k=None):
+ def generate(self, idx, max_new_tokens):
+ # idx shape: (Batch_Size, Context_Length)
for _ in range(max_new_tokens):
- idx_cond = idx[:, -block_size:]
- logits, _ = self(idx_cond)
- logits = logits[:, -1, :] / max(temperature, 1e-6)
-
- if top_k is not None:
- values, _ = torch.topk(logits, min(top_k, logits.size(-1)))
- logits[logits < values[:, [-1]]] = float("-inf")
-
+ idx_cond = idx[:, -BLOCK_SIZE:]
+ logits = self(idx_cond)
+ # Extract last token slice for all batches
+ logits = logits[:, -1, :]
probs = F.softmax(logits, dim=-1)
idx_next = torch.multinomial(probs, num_samples=1)
idx = torch.cat((idx, idx_next), dim=1)
- yield idx_next.item()
-
-
-def default_checkpoint_path():
- script_dir = Path(__file__).resolve().parent
- candidates = [
- script_dir / "best_model.pt",
- Path.cwd() / "best_model.pt",
- Path.cwd() / "engine" / "best_model.pt",
- ]
- for candidate in candidates:
- if candidate.exists():
- return candidate
- return script_dir / "best_model.pt"
-
-
-def load_model(checkpoint_path):
- checkpoint_path = Path(checkpoint_path)
- if not checkpoint_path.exists():
- raise FileNotFoundError(
- f"Checkpoint not found: {checkpoint_path}\n"
- "Train first with engine/main.py, or pass --checkpoint path/to/best_model.pt"
- )
-
- model = GPTLanguageModel().to(device)
- state_dict = torch.load(checkpoint_path, map_location=device)
- model.load_state_dict(state_dict)
- model.eval()
- return model
+ return idx
-def stream_response(model, prompt, max_new_tokens, temperature, top_k):
- encoded_prompt = encode(prompt, tokenizer)
- context = torch.tensor([encoded_prompt], dtype=torch.long, device=device)
-
- with torch.no_grad():
- for token_id in model.generate(
- context,
- max_new_tokens=max_new_tokens,
- temperature=temperature,
- top_k=top_k,
- ):
- word = decode([token_id], tokenizer)
- yield word
-
+def load_tokenizer(encoding_name="o200k_base"):
+ tokenizer = tiktoken.get_encoding(encoding_name)
+ return tokenizer, tokenizer.n_vocab
-def generate_response(model, prompt, max_new_tokens, temperature, top_k):
- return "".join(stream_response(model, prompt, max_new_tokens, temperature, top_k)).strip()
+@torch.inference_mode()
+def main():
+ print("=" * 50)
+ print("llm Inference Engine")
+ print(f"Target Device: {DEVICE.upper()}")
+ print("=" * 50)
+
+ tokenizer, vocab_size = load_tokenizer("o200k_base")
+ if not os.path.exists(MODEL_WEIGHTS_PATH):
+ print(
+ f"Error: Weights file '{MODEL_WEIGHTS_PATH}' not found. Please train the model first."
+ )
+ sys.exit(1)
-def chat(model, args):
- header("INFERENCE", "quit / exit / q -> end session")
- blank()
+ model = MiniQuadtrix(vocab_size).to(DEVICE)
+ model.load_state_dict(
+ torch.load(MODEL_WEIGHTS_PATH, map_location=DEVICE, weights_only=True)
+ )
+ model.eval()
+ try:
+ # Fuses operations and parallelizes execution sub-graphs natively
+ model = torch.compile(model)
+ print("Graph compilation successful (torch.compile applied).")
+ except Exception:
+ print("Proceeding without torch.compile graph optimizations.")
+
+ print("\nEntering Chat Mode. Type 'exit' or 'quit' to stop.")
+ print(
+ "Parallel Generation Mode: Returns 3 alternative paths simultaneously.\n"
+ )
while True:
- prompt = input(f" >> ").strip()
- if prompt.lower() in ("quit", "exit", "q"):
- blank()
- print(" Session ended.")
+ try:
+ prompt = input("User > ").strip()
+ if prompt.lower() in ("quit", "exit", "q"):
+ print("\nGoodbye.")
+ break
+ if not prompt:
+ continue
+
+ # Tokenize input
+ tokens = tokenizer.encode(prompt)
+
+ # Leverage Batch Parallelism: Replicate the prompt tensor across dimension 0
+ # This forces the GPU/CPU to execute 3 generations in parallel operations
+ num_parallel_samples = 3
+ context = (
+ torch.tensor([tokens], dtype=torch.long, device=DEVICE)
+ .repeat(num_parallel_samples, 1)
+ )
+
+ output_ids = model.generate(context, max_new_tokens=60)
+
+ print(
+ f"\nModel Responses ({num_parallel_samples} Parallel Paths):")
+ for i in range(num_parallel_samples):
+ new_tokens = output_ids[i][len(tokens):].tolist()
+ response = tokenizer.decode(new_tokens).strip()
+ print(f" Path {i+1} -> {response}")
+ print("-" * 50)
+
+ except KeyboardInterrupt:
+ print("\nSession interrupted.")
break
- if not prompt:
- continue
-
- blank()
- print(f" ", end="", flush=True)
- for word in stream_response(
- model,
- prompt,
- args.max_new_tokens,
- args.temperature,
- args.top_k,
- ):
- print(word, end="", flush=True)
- blank()
- blank()
-
-
-def parse_args():
- parser = argparse.ArgumentParser(
- description="Run inference from an engine trained .pt checkpoint.")
- parser.add_argument(
- "--checkpoint",
- type=Path,
- default=default_checkpoint_path(),
- help="Path to the .pt file generated by engine/main.py.",
- )
- parser.add_argument("--prompt", type=str, default=None,
- help="Generate once from this prompt.")
- parser.add_argument("--max-new-tokens", type=int, default=200)
- parser.add_argument("--temperature", type=float, default=1.0)
- parser.add_argument("--top-k", type=int, default=None)
- return parser.parse_args()
-
-
-def main():
- args = parse_args()
- start = time.time()
- blank()
- rule()
-
- model = load_model(args.checkpoint)
-
- if args.prompt:
- for word in stream_response(
- model,
- args.prompt,
- args.max_new_tokens,
- args.temperature,
- args.top_k,
- ):
- print(word, end="", flush=True)
- blank()
- else:
- chat(model, args)
-
- blank()
- row("Total", f"{time.time() - start:.2f}s")
if __name__ == "__main__":
diff --git a/engine/llm.cpp/Makefile b/engine/llm.cpp/Makefile
new file mode 100644
index 0000000..87d3ee2
--- /dev/null
+++ b/engine/llm.cpp/Makefile
@@ -0,0 +1,77 @@
+.PHONY: all build clean run dev gpu train bench logs ps shell help release debug benchmark-bin clean-native format
+
+SHELL := /bin/bash
+SCRIPT := ./scripts/build.sh
+CC := g++
+CFLAGS := -std=c++17 -O3 -march=native -fopenmp
+IFLAGS := -I. -Iinclude
+TARGET := llm
+SRCS := llm.cpp
+HDRS := $(wildcard include/*.h)
+
+all: $(TARGET)
+$(TARGET): $(SRCS) $(HDRS)
+ $(CC) $(CFLAGS) $(IFLAGS) -o $@ $(SRCS)
+ @echo "Built $(TARGET) with OpenMP parallel support."
+
+release: $(SRCS) $(HDRS)
+ $(CC) $(CFLAGS) $(IFLAGS) -DNDEBUG -o $(TARGET) $(SRCS)
+ strip $(TARGET)
+debug: $(SRCS) $(HDRS)
+ $(CC) -std=c++17 -O0 -g -fsanitize=address,undefined $(IFLAGS) -o $(TARGET)-debug $(SRCS)
+
+benchmark-bin: benchmark.cpp $(HDRS)
+ $(CC) $(CFLAGS) $(IFLAGS) -o llm-bench benchmark.cpp
+
+clean-native:
+ rm -f $(TARGET) $(TARGET)-debug llm-bench
+
+build:
+ $(SCRIPT) up
+
+run: build
+ @echo "Stack already started."
+
+dev:
+ $(SCRIPT) dev
+
+gpu:
+ $(SCRIPT) gpu
+
+train-cpp:
+ $(SCRIPT) train-cpp
+
+train-torch:
+ $(SCRIPT) train-torch
+
+bench:
+ $(SCRIPT) bench
+
+logs:
+ $(SCRIPT) logs
+
+ps:
+ $(SCRIPT) ps
+
+shell:
+ $(SCRIPT) shell $(SERVICE)
+
+clean:
+ $(SCRIPT) clean
+
+format:
+ find . \( -name "*.cpp" -o -name "*.h" \) \
+ ! -path "./build/*" \
+ | xargs clang-format -i --style=LLVM
+
+help:
+ @echo ""
+ @echo " llm.cpp β make targets"
+ @echo ""
+ @echo " Native:"
+ @echo " make Build C++ binary with OpenMP parallel support"
+ @echo " make release Stripped release binary"
+ @echo " make debug Debug binary with ASan/UBSan"
+ @echo " make clean-native Remove native build artifacts"
+ @echo " make format Run clang-format on all C++ files"
+ @echo ""
\ No newline at end of file
diff --git a/llm.cpp/include/attention.h b/engine/llm.cpp/include/attention.h
similarity index 100%
rename from llm.cpp/include/attention.h
rename to engine/llm.cpp/include/attention.h
diff --git a/llm.cpp/include/backward.h b/engine/llm.cpp/include/backward.h
similarity index 90%
rename from llm.cpp/include/backward.h
rename to engine/llm.cpp/include/backward.h
index 2528314..3076960 100644
--- a/llm.cpp/include/backward.h
+++ b/engine/llm.cpp/include/backward.h
@@ -1,24 +1,17 @@
-#pragma once
-// ============================================================
// include/backward.h β Full analytical backpropagation
// Each backward_* function:
// - receives dOut (gradient of loss w.r.t. this layer's output)
// - receives saved activations from the forward pass
// - fills gradients into Grad structs (mirroring weight layout)
// - returns dX (gradient of loss w.r.t. this layer's input)
-// ============================================================
+#pragma once
#include "config/config.h"
#include "tensor.h"
#include
#include
#include
-
-// =============================================================
-// Gradient stores (one per learnable weight tensour)
-// ============================================================
-
struct GradLinear
{
Tensor dW; // same shape as weight [in, out]
@@ -155,12 +148,6 @@ struct Grads
lm_head.zero();
}
};
-
-// ============================================================
-// Saved activations from the forward pass
-// (we need these to compute gradients)
-// ============================================================
-
struct SavedHead
{
Tensor x; // input [B, T, n_embd]
@@ -192,9 +179,9 @@ struct SavedFFN
struct SavedLN
{
- Tensor x; // input [B, T, C]
+ Tensor x; // inpu [B, T, C]
Tensor xhat; // normalized, before gamma/beta [B,T,C]
- Tensor inv_std; // 1/sqrt(var+eps) per row stored as [B, T, 1] β flat
+ Tensor inv_std; // 1/sqrt(var+eps) per row stored as [B, T, 1] flat
std::vector mu_vec, invstd_vec; // [B*T] each
};
@@ -227,11 +214,9 @@ struct SavedForward
std::vector targets;
};
-// ============================================================
-// Primitive backward ops
-// ============================================================
+// Primitive backward opss
-// ---- cross-entropy backward ---------------------------------
+// ---- cross-entropy backwadrd ---------------------------------
// dlogits [B*T, V]: softmax(logits) - one_hot(targets) / BT
inline Tensor backward_cross_entropy(const Tensor &logits2d, const std::vector &targets)
{
@@ -260,7 +245,7 @@ inline Tensor backward_cross_entropy(const Tensor &logits2d, const std::vector backward_bmm(const Tensor &dOut, // [B, T, T2]
const Tensor &a, // [B, T, D]
@@ -427,7 +412,7 @@ inline Tensor backward_softmax3d(const Tensor &dwei, // [B, T, T]
return dpre;
}
-// ---- cat_last backward [B,T,D_total] β slice per head ------
+// ---- cat_last backward [B,T,D_total] slice per head ------
inline std::vector backward_cat_last(const Tensor &dConcat,
const std::vector &head_sizes)
{
@@ -447,10 +432,6 @@ inline std::vector backward_cat_last(const Tensor &dConcat,
return out;
}
-// ============================================================
-// Forward pass WITH activation saving
-// ============================================================
-
// ---- saved layernorm forward --------------------------------
inline Tensor forward_ln_save(const Tensor &x,
const Tensor &gamma,
@@ -621,10 +602,8 @@ inline Tensor forward_ffn_save(const Tensor &x,
return out;
}
-// ============================================================
// Full model forward with all activations saved
-// ============================================================
-#include "quadtrix.h" // for GPTLanguageModel layout
+#include "lm.h"
inline SavedForward forward_save(GPTLanguageModel &model,
const std::vector &idx,
@@ -640,8 +619,6 @@ inline SavedForward forward_save(GPTLanguageModel &model,
s.targets = targets;
int C = model.n_embd;
int V = model.vocab_size;
-
- // ββ Embeddings βββββββββββββββββββββββββββββββββββββββββββ
s.tok_out = model.token_emb.forward(idx, B, T);
s.pos_out = model.pos_emb.forward_pos(T);
s.emb_sum = Tensor({B, T, C});
@@ -650,7 +627,7 @@ inline SavedForward forward_save(GPTLanguageModel &model,
for (int d = 0; d < C; ++d)
s.emb_sum.at(b, t, d) = s.tok_out.at(b, t, d) + s.pos_out.at(0, t, d);
- // ββ Transformer blocks βββββββββββββββββββββββββββββββββββ
+ // Transformer
s.blocks.resize(model.n_layer);
Tensor x = s.emb_sum;
for (int l = 0; l < model.n_layer; ++l)
@@ -700,12 +677,12 @@ inline SavedForward forward_save(GPTLanguageModel &model,
x = add(sb.x_after_mha, ffn);
}
- // ββ Final LN + lm_head βββββββββββββββββββββββββββββββββββ
+ // Final LN + lm_head
s.lm_in = forward_ln_save(x, model.ln_f.gamma, model.ln_f.beta, s.ln_f);
s.logits3d = matmul(s.lm_in, model.lm_head.weight);
s.logits3d = add_bias(s.logits3d, model.lm_head.bias);
- // reshape [B,T,V] β [B*T, V]
+ // reshape [B,T,V] [B*T, V]
s.logits2d = Tensor({B * T, V});
for (int i = 0; i < B * T; ++i)
for (int v = 0; v < V; ++v)
@@ -714,9 +691,7 @@ inline SavedForward forward_save(GPTLanguageModel &model,
return s;
}
-// ============================================================
// Full backward pass
-// ============================================================
inline Grads backward(GPTLanguageModel &model, const SavedForward &s)
{
@@ -725,8 +700,6 @@ inline Grads backward(GPTLanguageModel &model, const SavedForward &s)
int n_head = model.n_head, hs = C / n_head;
Grads g(V, C, n_head, model.n_layer, model.block_size);
-
- // ββ dLoss / dLogits [B*T, V] ββββββββββββββββββββββββββββ
Tensor dlogits2d = backward_cross_entropy(s.logits2d, s.targets);
// reshape to [B, T, V]
@@ -735,28 +708,23 @@ inline Grads backward(GPTLanguageModel &model, const SavedForward &s)
for (int v = 0; v < V; ++v)
dlogits3d.data[i * V + v] = dlogits2d.at(i, v);
- // ββ lm_head backward [B,T,V] β [B,T,C] ββββββββββββββββββ
Tensor dx = backward_linear(dlogits3d, s.lm_in, model.lm_head.weight, g.lm_head);
-
- // ββ final layernorm backward ββββββββββββββββββββββββββββββ
dx = backward_layernorm(dx, s.ln_f, model.ln_f.gamma, g.ln_f);
-
- // ββ transformer blocks (reverse order) βββββββββββββββββββ
for (int l = model.n_layer - 1; l >= 0; --l)
{
auto &blk = model.blocks[l];
auto &sb = s.blocks[l];
auto &gb = g.blocks[l];
- // ---- FFN residual: dx is d(x_after_mha + ffn_out) ββ
+ // ---- FFN residual: dx is d(x_after_mha + ffn_out) -
// dffn = dx (residual, pass-through)
// dx += dx (will be added after LN2 backward below)
Tensor dffn_out = dx; // gradient to the ffn output branch
// residual adds straight through:
// d(x_after_mha) gets dx directly (accumulated below)
- // ---- LN2 + FFN backward βββββββββββββββββββββββββββββ
- // dffn_out β (dropout bwd) β fc2 bwd β relu bwd β fc1 bwd β dx_ln2
+ // LN2 + FFN backward
+ // dffn_out (dropout bwd) fc2 bwd relu bwd fc1 bwd dx_ln2
Tensor dffn = dffn_out;
if (sb.ffn.used_dropout)
dffn = backward_dropout(dffn, sb.ffn.dropout_mask, DROPOUT);
@@ -768,21 +736,21 @@ inline Grads backward(GPTLanguageModel &model, const SavedForward &s)
// fc1 backward
Tensor dx_ln2 = backward_linear(dh_pre, sb.ffn.x, blk.ffwd.fc1.weight, gb.ffwd.dfc1);
- // LN2 backward β d(x_after_mha) from FFN branch
+ // LN2 backward d(x_after_mha) from FFN branch
Tensor dx_after_mha_ffn = backward_layernorm(dx_ln2, sb.ln2, blk.ln2.gamma, gb.ln2);
// total d(x_after_mha) = residual-pass + LN2 branch
Tensor dx_after_mha = add(dx, dx_after_mha_ffn);
- // ---- MHA residual: x_after_mha = x_in + attn_out βββ
+ // ---- MHA residual: x_after_mha = x_in + attn_out
Tensor dattn_out = dx_after_mha; // gradient to attn branch
// d(x_in) from this residual = dx_after_mha (passed through)
- // ---- MHA backward ββββββββββββββββββββββββββββββββββββ
+ // ---- MHA backward
Tensor dmha = dattn_out;
if (sb.mha.used_dropout)
dmha = backward_dropout(dmha, sb.mha.dropout_mask, DROPOUT);
- // proj backward [B,T,n_embd] β [B,T,n_head*hs]
+ // proj backward [B,T,n_embd] [B,T,n_head*hs]
Tensor dconcat = backward_linear(dmha, sb.mha.concat, blk.sa.proj.weight, gb.sa.proj);
// split concat grad back to each head
@@ -819,18 +787,18 @@ inline Grads backward(GPTLanguageModel &model, const SavedForward &s)
: sh.wei;
// d(out_h) = dhead_outs[h]
- // out_h = wei_used @ v β backward of bmm
- // da = dOut @ b^T β d_wei_used = dout_h @ v^T [B,T,T]
- // db = a^T @ dOut β dv = wei_used^T @ dout_h [B,T,hs]
+ // out_h = wei_used @ v backward of bmm
+ // da = dOut @ b^T d_wei_used = dout_h @ v^T [B,T,T]
+ // db = a^T @ dOut dv = wei_used^T @ dout_h [B,T,hs]
Tensor vT = transpose23(sh.v); // [B, hs, T]
// d_wei_drop [B,T,T] = dout_h @ vT (but vT is [B,hs,T], need [B,T,hs]@... )
// Use bmm with v transposed
- // dout_h [B,T,hs], v [B,T,hs] β vT [B,hs,T]
+ // dout_h [B,T,hs], v [B,T,hs] vT [B,hs,T]
// d_wei_drop = bmm(dout_h, vT) = [B,T,hs]@[B,hs,T] = [B,T,T]
Tensor d_wei_drop = bmm(dout_h, vT); // [B,T,T]
// dv = wei_used^T @ dout_h = [B,T,T]^T @ [B,T,hs] = [B,T,hs]
- Tensor wei_usedT = transpose23(wei_used); // [B, T, T] transposed β [B, T, T]
+ Tensor wei_usedT = transpose23(wei_used); // [B, T, T] transposed [B, T, T]
// bmm needs [B,T,T] x [B,T,hs]: wei_usedT [B,T,T] x dout_h [B,T,hs]
// but bmm signature is [B,T,D]x[B,D,T2]
// wei_usedT as [B,T,T] x dout_h [B,T,hs]: first transpose wei_used to get [B,T,T]
@@ -838,7 +806,7 @@ inline Grads backward(GPTLanguageModel &model, const SavedForward &s)
// back
Tensor dv = bmm(transpose23(sh.wei), dout_h); // [B,T,hs]
- // attention dropout backward on d_wei_drop β d_wei
+ // attention dropout backward on d_wei_drop d_wei
Tensor d_wei = d_wei_drop;
if (sh.used_dropout)
{
@@ -866,13 +834,13 @@ inline Grads backward(GPTLanguageModel &model, const SavedForward &s)
// dq = d_wei_pre @ k [B,T,T]@[B,T,hs]... need k [B,hs,T]^T = k
// actual: d_wei_pre[b,i,j] = sum over... it's q[b,i,:] Β· k[b,j,:]
// dq[b,i,d] = sum_j d_wei_pre[b,i,j] * k[b,j,d]
- // = bmm(d_wei_pre, k) where k is [B,T,hs] β need [B,T,hs] directly
+ // = bmm(d_wei_pre, k) where k is [B,T,hs] need [B,T,hs] directly
// = bmm with b=[B,hs,T]? No: bmm([B,T,T], [B,T,hs]) needs second arg [B,T,T]
// Use: dq = d_wei_pre @ k with k as [B, hs, T]... no.
- // dq[b,i,d] = sum_j d_pre[b,i,j] * k[b,j,d] β this IS bmm(d_pre, k) if
+ // dq[b,i,d] = sum_j d_pre[b,i,j] * k[b,j,d] this IS bmm(d_pre, k) if
// k were [B,T,hs]... but bmm expects [B,D,T2]. So treat as matmul style:
// bmm(d_pre [B,T,T], k [B,T,hs]) β but bmm signature needs [B,D,T2]:
- // interpret as B batches, each [T,T] @ [T,hs] = [T,hs]: b's D=T, T2=hs β valid!
+ // interpret as B batches, each [T,T] @ [T,hs] = [T,hs]: b's D=T, T2=hs valid!
Tensor dq = bmm(d_wei_pre, sh.k); // [B,T,hs]
// dk[b,j,d] = sum_i d_pre[b,i,j] * q[b,i,d]
@@ -898,7 +866,7 @@ inline Grads backward(GPTLanguageModel &model, const SavedForward &s)
}
}
- // LN1 backward β d(x_in)
+ // LN1 backward d(x_in)
Tensor dx_in_mha = backward_layernorm(dx_ln1, sb.ln1, blk.ln1.gamma, gb.ln1);
// total dx for this block: residual pass-through + LN1/MHA branch + LN2/FFN branch
@@ -907,7 +875,7 @@ inline Grads backward(GPTLanguageModel &model, const SavedForward &s)
dx = add(dx_after_mha, dx_in_mha);
}
- // ββ Embedding backward βββββββββββββββββββββββββββββββββββ
+ // Embedding backward
// dx is now d(emb_sum) = d(tok_emb + pos_emb)
// pos_emb grad: sum over batch
for (int b = 0; b < B; ++b)
@@ -927,9 +895,7 @@ inline Grads backward(GPTLanguageModel &model, const SavedForward &s)
return g;
}
-// ============================================================
-// Apply Grads β model weights (AdamW update)
-// ============================================================
+// AdamW update
struct AdamWState
{
diff --git a/engine/llm.cpp/include/bpe.h b/engine/llm.cpp/include/bpe.h
new file mode 100644
index 0000000..c96b0b5
--- /dev/null
+++ b/engine/llm.cpp/include/bpe.h
@@ -0,0 +1,289 @@
+// @Eamon2009
+#pragma once
+#include "config/config.h"
+
+#include
+#include
+#include
+#include