Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/publish-huggingface.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: publish-huggingface
# After each successful publication on ollama.com, the same models are mirrored on Hugging Face:
# weights, vision projector, system prompt, sampling settings and the model card from docs/huggingface/.
# Needs one repository secret (Settings > Secrets and variables > Actions):
# HF_TOKEN a Hugging Face token with write access (https://huggingface.co/settings/tokens)
# and optionally one repository variable:
# HF_NAMESPACE the Hugging Face user or organization to publish under (default: the owner of HF_TOKEN)
on:
workflow_run:
workflows: [publish-ollama]
types: [completed]
branches: [main]
push:
branches: [main]
paths:
- 'docs/huggingface/**'
- 'scripts/publish-hf.sh'
- '.github/workflows/publish-huggingface.yml'
workflow_dispatch:
inputs:
models:
description: 'Models to publish (space-separated names, or "all")'
default: all
concurrency:
group: publish-huggingface
cancel-in-progress: false
jobs:
publish:
if: github.repository == 'Tromset/flycoder' && (github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success')
runs-on: ubuntu-latest
timeout-minutes: 180
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
HF_NAMESPACE: ${{ vars.HF_NAMESPACE }}
steps:
- uses: actions/checkout@v4

- name: Check the secret
id: secret
run: |
# Without a token the mirror is skipped, not failed: ollama.com stays the reference.
if [ -n "$HF_TOKEN" ]; then echo "ready=true" >> "$GITHUB_OUTPUT"; else
echo "::warning::HF_TOKEN is not set: nothing was published on Hugging Face. Add a write token from https://huggingface.co/settings/tokens."
echo "Skipped: add the HF_TOKEN secret to publish on Hugging Face." >> "$GITHUB_STEP_SUMMARY"
fi

- name: Free disk space for the largest model
if: steps.secret.outputs.ready == 'true'
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
sudo docker image prune --all --force >/dev/null
df -h /

- uses: actions/setup-node@v4
if: steps.secret.outputs.ready == 'true'
with:
node-version: 22

- uses: actions/setup-python@v5
if: steps.secret.outputs.ready == 'true'
with:
python-version: '3.12'

- name: Install huggingface_hub
if: steps.secret.outputs.ready == 'true'
run: python3 -m pip install --quiet 'huggingface_hub>=0.32,<2'

- name: Publish on Hugging Face
if: steps.secret.outputs.ready == 'true'
env:
REQUESTED: ${{ inputs.models }}
run: |
models=${REQUESTED:-all}
# models is a space-separated list of names: split it on purpose.
# shellcheck disable=SC2086
if [ "$models" = all ]; then sh scripts/publish-hf.sh "$HF_NAMESPACE"; else sh scripts/publish-hf.sh "$HF_NAMESPACE" $models; fi
29 changes: 27 additions & 2 deletions Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,30 @@ Updating a page uses the same request as the page's Edit button; Ollama does not

By hand, from any machine signed in to the account: `sh scripts/publish.sh` (all models) or `sh scripts/publish.sh Tromset flycoder0.3fast` (one model), then `OLLAMA_API_KEY=... sh scripts/update-ollama-pages.sh`.

For a new version, update the version in `package.json`, `install.sh`, `scripts/publish.sh`, the system prompt of the Modelfiles and the pages. `npm test` checks that they agree.
For a new version, update the version in `package.json`, `install.sh`, `scripts/publish.sh`, `scripts/publish-hf.sh`, the system prompt of the Modelfiles and the pages. `npm test` checks that they agree.

### 8.1 Publishing on Hugging Face

After each successful run of publish-ollama, the [publish-huggingface](.github/workflows/publish-huggingface.yml) workflow mirrors `flycoder0.3`, `flycoder0.3fast` and `flycoder0.3pro` on Hugging Face. It also runs when `docs/huggingface/` changes, and by hand from the Actions tab. For each model, `scripts/publish-hf.sh` downloads the published layers of `Tromset/<model>` from the ollama.com registry, checks their SHA-256, and uploads to `<namespace>/<model>`:

| File | Content |
|---|---|
| `<model>-Q4_K_M.gguf` | the weights, byte for byte the ollama.com file |
| `mmproj-<model>.gguf` | the vision projector |
| `system`, `params` | the system prompt and the sampling settings, which Ollama applies to `ollama run hf.co/<namespace>/<model>` |
| `LICENSE` | the base's Apache 2.0 license |
| `README.md` | the model card, from `docs/huggingface/<model>.md` (`@HF_NAMESPACE@` is replaced by the namespace) |

The template layer is not copied: on ollama.com it is a placeholder and Ollama formats Qwen with its built-in renderer. From Hugging Face, Ollama and llama.cpp (`--jinja`) use the chat template inside the GGUF; for tool calling and thinking in Ollama, `Tromset/<model>` stays the reference build.

One-time setup, in Settings > Secrets and variables > Actions:

| Name | Kind | Value |
|---|---|---|
| `HF_TOKEN` | secret | a token with write access, created at https://huggingface.co/settings/tokens |
| `HF_NAMESPACE` | variable, optional | the user or organization to publish under; by default, the owner of the token |

By hand: `pip install huggingface_hub`, then `HF_TOKEN=... sh scripts/publish-hf.sh` (all models, under the token's owner) or `HF_TOKEN=... sh scripts/publish-hf.sh <namespace> flycoder0.3fast`. The largest model needs 18 GB of free disk.

## 9. Troubleshooting

Expand All @@ -386,6 +409,7 @@ For a new version, update the version in `package.json`, `install.sh`, `scripts/
| Endless repetition | temperature changed to a value that is too low | go back to the Modelfile's settings |
| `npm run bench`: "Reference solution failed" | `python3` or `node` missing, or sandbox refused | `xcode-select --install`, Node 22, or `--no-sandbox` |
| The publish workflow fails at "Check the secrets" | `OLLAMA_KEY` is missing | add it (section 8) |
| publish-huggingface warns "HF_TOKEN is not set" and publishes nothing | `HF_TOKEN` is missing | add it (section 8.1) |

## 10. Development

Expand All @@ -400,11 +424,12 @@ For a new version, update the version in `package.json`, `install.sh`, `scripts/
| `install.sh` | one-command installer |
| `scripts/publish.sh`, `scripts/update-ollama-pages.sh` | publishing models and pages on ollama.com |
| `docs/ollama/` | the ollama.com pages, one file per model |
| `docs/huggingface/`, `scripts/publish-hf.sh` | the Hugging Face model cards and their publisher |
| `bench/bench.mjs`, `bench/problems.mjs` | benchmark and problems |
| `bench/baselines/` | 0.1 profile, for comparison |
| `docs/bench/` | reference results |
| `tests/` | tests for the bench, the Modelfiles, the scripts, the pages and FlyBrain |
| `.github/workflows/test.yml`, `.github/workflows/publish-ollama.yml` | CI on Linux and macOS, automatic publishing |
| `.github/workflows/test.yml`, `.github/workflows/publish-ollama.yml`, `.github/workflows/publish-huggingface.yml` | CI on Linux and macOS, automatic publishing |
| `training/`, `scripts/build_language_dataset.js`, `server/fly-language.js` | former FlyBrain voice pipeline, independent of FlyCoder; the voice speaks French by design |

### 10.2 Tests
Expand Down
46 changes: 46 additions & 0 deletions docs/huggingface/flycoder0.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
license: apache-2.0
base_model: Qwen/Qwen3.5-9B
base_model_relation: quantized
pipeline_tag: image-text-to-text
tags:
- gguf
- ollama
- llama.cpp
- code
- flycoder
---

# FlyCoder 0.3

A local coding model for Macs with 16 GB of memory. Complete, runnable code, a large context, and it answers in your language.

```sh
ollama run hf.co/@HF_NAMESPACE@/flycoder0.3
```

| Base | Size | Mac | Context |
|---|---|---|---|
| [Qwen3.5 9B](https://huggingface.co/Qwen/Qwen3.5-9B) (Apache 2.0) | 6.6 GB | 16 GB | 64K tokens |

- **Code that runs**: exact names and signatures, every import, edge cases handled, no "TODO", no invented APIs.
- **Large context**: 64K tokens out of the box, enough for whole files and coding agents.
- **Thinks first**: reasoning is on by default; `/set nothink` in Ollama for instant answers.

## Files

- `flycoder0.3-Q4_K_M.gguf`: the weights, the same file as [`Tromset/flycoder0.3`](https://ollama.com/Tromset/flycoder0.3) on ollama.com.
- `mmproj-flycoder0.3.gguf`: the vision projector, for images.
- `system`, `params`: FlyCoder's system prompt and sampling settings, applied by Ollama.

With llama.cpp, pass the same settings and the contents of `system` as the system message:

```sh
llama-server -hf @HF_NAMESPACE@/flycoder0.3 --jinja -c 65536 --temp 0.6 --top-k 20 --top-p 0.95 --min-p 0
```

For tool calling and thinking in Ollama, `ollama run Tromset/flycoder0.3` uses Ollama's built-in Qwen renderer and is the reference build.

Other sizes: [`flycoder0.3fast`](https://huggingface.co/@HF_NAMESPACE@/flycoder0.3fast) for 8 GB Macs, [`flycoder0.3pro`](https://huggingface.co/@HF_NAMESPACE@/flycoder0.3pro) for 32 GB Macs or more.

Source, benchmark and installer: https://github.com/Tromset/flycoder
46 changes: 46 additions & 0 deletions docs/huggingface/flycoder0.3fast.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
license: apache-2.0
base_model: Qwen/Qwen3.5-4B
base_model_relation: quantized
pipeline_tag: image-text-to-text
tags:
- gguf
- ollama
- llama.cpp
- code
- flycoder
---

# FlyCoder 0.3 fast

The fast FlyCoder, for Macs with 8 GB of memory or when speed matters most.

```sh
ollama run hf.co/@HF_NAMESPACE@/flycoder0.3fast
```

| Base | Size | Mac | Context |
|---|---|---|---|
| [Qwen3.5 4B](https://huggingface.co/Qwen/Qwen3.5-4B) (Apache 2.0) | 3.3 GB | 8 GB | 32K tokens |

- **Fast**: about twice the speed of `flycoder0.3`, with the same coding rules.
- **Code that runs**: exact names and signatures, every import, edge cases handled, no "TODO", no invented APIs.
- **Light**: fits next to your other apps on an 8 GB Mac.

## Files

- `flycoder0.3fast-Q4_K_M.gguf`: the weights, the same file as [`Tromset/flycoder0.3fast`](https://ollama.com/Tromset/flycoder0.3fast) on ollama.com.
- `mmproj-flycoder0.3fast.gguf`: the vision projector, for images.
- `system`, `params`: FlyCoder's system prompt and sampling settings, applied by Ollama.

With llama.cpp, pass the same settings and the contents of `system` as the system message:

```sh
llama-server -hf @HF_NAMESPACE@/flycoder0.3fast --jinja -c 32768 --temp 0.6 --top-k 20 --top-p 0.95 --min-p 0
```

For tool calling and thinking in Ollama, `ollama run Tromset/flycoder0.3fast` uses Ollama's built-in Qwen renderer and is the reference build.

Other sizes: [`flycoder0.3`](https://huggingface.co/@HF_NAMESPACE@/flycoder0.3) for 16 GB Macs, [`flycoder0.3pro`](https://huggingface.co/@HF_NAMESPACE@/flycoder0.3pro) for 32 GB Macs or more.

Source, benchmark and installer: https://github.com/Tromset/flycoder
46 changes: 46 additions & 0 deletions docs/huggingface/flycoder0.3pro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
license: apache-2.0
base_model: Qwen/Qwen3.8-27B
base_model_relation: quantized
pipeline_tag: image-text-to-text
tags:
- gguf
- ollama
- llama.cpp
- code
- flycoder
---

# FlyCoder 0.3 pro

The strongest FlyCoder, for hard coding work and coding agents, on Macs with 32 GB of memory or more.

```sh
ollama run hf.co/@HF_NAMESPACE@/flycoder0.3pro
```

| Base | Size | Mac | Context |
|---|---|---|---|
| [Qwen3.8 27B](https://huggingface.co/Qwen/Qwen3.8-27B) (Apache 2.0) | 18 GB | 32 GB | 64K tokens |

- **Code that runs**: exact names and signatures, every import, edge cases handled, a final check before answering, no "TODO", no invented APIs.
- **Built for agents**: reads files before editing, makes minimal changes, reports real test output, never runs a destructive command without asking.
- **Fast for its size**: multi-token prediction is on in Ollama, and 64K tokens of context cost only about 4.4 GB thanks to Qwen3.8's hybrid attention.

## Files

- `flycoder0.3pro-Q4_K_M.gguf`: the weights, the same file as [`Tromset/flycoder0.3pro`](https://ollama.com/Tromset/flycoder0.3pro) on ollama.com.
- `mmproj-flycoder0.3pro.gguf`: the vision projector, for images.
- `system`, `params`: FlyCoder's system prompt and sampling settings, applied by Ollama.

With llama.cpp, pass the same settings and the contents of `system` as the system message:

```sh
llama-server -hf @HF_NAMESPACE@/flycoder0.3pro --jinja -c 65536 --temp 0.6 --top-k 20 --top-p 0.95 --min-p 0
```

For tool calling and thinking in Ollama, `ollama run Tromset/flycoder0.3pro` uses Ollama's built-in Qwen renderer and is the reference build.

Other sizes: [`flycoder0.3`](https://huggingface.co/@HF_NAMESPACE@/flycoder0.3) for 16 GB Macs, [`flycoder0.3fast`](https://huggingface.co/@HF_NAMESPACE@/flycoder0.3fast) for 8 GB Macs.

Source, benchmark and installer: https://github.com/Tromset/flycoder
6 changes: 4 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,14 @@ Details: [Documentation.md, section 5.5](Documentation.md#55-flybrain-the-router

Every change to `main` that touches a Modelfile or a page in `docs/ollama/` is published on ollama.com by the [publish-ollama](.github/workflows/publish-ollama.yml) workflow: models first, then their pages. It needs two repository secrets, `OLLAMA_KEY` and `OLLAMA_API_KEY` ([Documentation.md, section 8](Documentation.md#8-publishing-on-ollamacom)).

Each publication is then mirrored on Hugging Face by the [publish-huggingface](.github/workflows/publish-huggingface.yml) workflow: the same GGUF weights, vision projector, system prompt and sampling settings, with a model card from `docs/huggingface/`, runnable with `ollama run hf.co/<namespace>/flycoder0.3` or llama.cpp. It needs the `HF_TOKEN` secret ([Documentation.md, section 8.1](Documentation.md#81-publishing-on-hugging-face)).

## Repository contents

- `Modelfile`, `Modelfile.fast`, `Modelfile.pro`: the three variants. `Modelfile.lite`, `Modelfile.router`: FlyBrain's experts.
- `docs/ollama/`: the ollama.com page of each model.
- `docs/ollama/`: the ollama.com page of each model. `docs/huggingface/`: the Hugging Face model card of each model.
- `brain/`: the FlyBrain server and its routing rules.
- `install.sh`: one-command install. `scripts/publish.sh`, `scripts/update-ollama-pages.sh`: publishing.
- `install.sh`: one-command install. `scripts/publish.sh`, `scripts/update-ollama-pages.sh`, `scripts/publish-hf.sh`: publishing.
- `bench/`: quality and speed benchmark. `tests/`: `npm test`.
- `training/`, `scripts/build_language_dataset.js`, `server/fly-language.js`: the former FlyBrain voice pipeline, independent of FlyCoder.

Expand Down
99 changes: 99 additions & 0 deletions scripts/publish-hf.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/bin/sh
# Publishes FlyCoder on Hugging Face: <namespace>/flycoder0.3, <namespace>/flycoder0.3fast, <namespace>/flycoder0.3pro.
# HF_TOKEN=... sh scripts/publish-hf.sh [<Hugging Face namespace>] [<name> ...]
# Default namespace: the owner of HF_TOKEN. Default names: every model below.
# Each repository mirrors the model published on ollama.com (Tromset/<name>), byte for byte:
# the GGUF weights, the vision projector, the system prompt, the sampling settings and the base's license,
# with docs/huggingface/<name>.md as the model card. Ollama, llama.cpp and LM Studio can run it from there.
# Prerequisites: curl, node, python3 with huggingface_hub (pip install huggingface_hub), a token with write access
# (https://huggingface.co/settings/tokens). Downloads one model at a time: the largest needs 18 GB of free disk.
# GitHub Actions runs this script after each publication on ollama.com (.github/workflows/publish-huggingface.yml).
set -eu

VERSION=0.3
OLLAMA_USER=Tromset
# Published name, quantization. Keep in sync with scripts/publish.sh and docs/huggingface/.
MODELS='flycoder0.3 Q4_K_M
flycoder0.3fast Q4_K_M
flycoder0.3pro Q4_K_M'
REGISTRY=https://registry.ollama.ai/v2

die() { printf 'FlyCoder: %s\n' "$*" >&2; exit 1; }

[ -n "${HF_TOKEN:-}" ] || die "HF_TOKEN is not set (create a write token at https://huggingface.co/settings/tokens)."
for tool in curl node python3; do command -v "$tool" >/dev/null 2>&1 || die "$tool is not installed."; done
python3 -c 'import huggingface_hub' 2>/dev/null || die "huggingface_hub is missing: pip install huggingface_hub"

here=$(cd "$(dirname "$0")/.." && pwd)
namespace=${1:-}
[ $# -gt 0 ] && shift
[ "${namespace#-}" = "$namespace" ] || die "usage: sh scripts/publish-hf.sh [<Hugging Face namespace>] [<name> ...]"
if [ -z "$namespace" ]; then
namespace=$(curl -fsS -H "Authorization: Bearer $HF_TOKEN" https://huggingface.co/api/whoami-v2 \
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).name))') \
|| die "Hugging Face refused HF_TOKEN."
fi

work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT

wanted() { # wanted <name> [<requested name> ...]: true when nothing was requested or <name> was
n=$1; shift
[ $# -eq 0 ] && return 0
for w in "$@"; do [ "$w" = "$n" ] && return 0; done
return 1
}

# layer <manifest> <media type>: the digest of the layer with that type, or nothing.
layer() {
node -e 'const m=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));
const l=m.layers.find(l=>l.mediaType==="application/vnd.ollama.image."+process.argv[2]); if(l) console.log(l.digest)' "$1" "$2"
}

# fetch <name> <digest> <file>: downloads a blob of Tromset/<name> and checks its digest.
fetch() {
curl -fsSL --retry 4 --retry-all-errors -o "$3" "$REGISTRY/$OLLAMA_USER/$1/blobs/$2"
[ "sha256:$(sha256sum "$3" | cut -d' ' -f1)" = "$2" ] || die "$3 does not match $2."
}

echo "$MODELS" | while read -r name quant; do
wanted "$name" "$@" || continue
card="$here/docs/huggingface/$name.md"
[ -f "$card" ] || die "$card is missing."
repo="$namespace/$name"
dir="$work/$name"
mkdir -p "$dir"
printf '\n==> https://huggingface.co/%s (from ollama.com/%s/%s)\n' "$repo" "$OLLAMA_USER" "$name"

curl -fsS -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
-o "$work/manifest.json" "$REGISTRY/$OLLAMA_USER/$name/manifests/latest" || die "Tromset/$name is not on ollama.com."
model=$(layer "$work/manifest.json" model)
[ -n "$model" ] || die "Tromset/$name has no weights."
fetch "$name" "$model" "$dir/$name-$quant.gguf"
[ "$(head -c 4 "$dir/$name-$quant.gguf")" = GGUF ] || die "the weights of Tromset/$name are not a GGUF file."
projector=$(layer "$work/manifest.json" projector)
[ -z "$projector" ] || fetch "$name" "$projector" "$dir/mmproj-$name.gguf"
# Ollama reads `system` and `params` from the repository when it runs hf.co/<repo>. The template layer is not
# copied: it is a placeholder, Ollama formats Qwen3.5 with its built-in renderer and llama.cpp with the GGUF's template.
for kind in system params license; do
digest=$(layer "$work/manifest.json" "$kind")
[ -n "$digest" ] || continue
file=$kind; [ "$kind" = license ] && file=LICENSE
fetch "$name" "$digest" "$dir/$file"
done
sed "s|@HF_NAMESPACE@|$namespace|g" "$card" > "$dir/README.md"

python3 - "$repo" "$dir" "FlyCoder $VERSION: $name from ollama.com/$OLLAMA_USER/$name" <<'EOF'
import sys
from huggingface_hub import HfApi
repo, folder, message = sys.argv[1:]
api = HfApi()
api.create_repo(repo, repo_type="model", exist_ok=True)
api.upload_folder(repo_id=repo, folder_path=folder, commit_message=message)
EOF
rm -rf "$dir"
done

printf '\nPublished FlyCoder %s on Hugging Face. Anyone can now run:\n' "$VERSION"
printf ' ollama run hf.co/%s/flycoder0.3\n' "$namespace"
printf ' llama-server -hf %s/flycoder0.3 --jinja\n' "$namespace"
Loading
Loading