Downloads · 30 days
686
100% of all-time downloads
joshwolk/jwen
jwen is a text generation model from joshwolk. Use it when you need the model to write or continue text. It is set up for transformers. The card lists the license as other.
Jwen draws faces the way I doodle them in Apple Notes. It is Qwen 2.5 3B, fully fine-tuned to write Apple Notes drawings as pen strokes, then preference-tuned on 660 of my own yes/no verdicts. It predicts pen tool, co…
Downloads · 30 days
686
100% of all-time downloads
All-time downloads
686
Public
Parameters
3.1B
6.8 GB on disk
Likes
0
Public
Click a slice to open those files.
.safetensors6.8 GB · 100%
From the Hugging Face model README
Jwen draws faces the way I doodle them in Apple Notes. It is Qwen 2.5 3B, fully fine-tuned to write Apple Notes drawings as pen strokes, then preference-tuned on 660 of my own yes/no verdicts. It predicts pen tool, color, position and timing token by token, so a drawing comes out stroke by stroke in the order a hand would make it.
The live stream, and the write-up of how it was made, are at joshuawolk.com/jwen.

samples/example.json is that drawing's stroke file: 69 strokes, one blue pen, character group 2, both temperatures at 0.9. It is one of the thousand in the live stream, re-rendered here from its tokens.

Eight more from the same thousand, across pen, pencil, marker and crayon. Not every sample is this clean, see Limitations.
Built with Qwen. Released under the Qwen Research License: research and non-commercial use only.
| File | Purpose |
|---|---|
model-*.safetensors, config.json | the weights, bf16, a standard Qwen2ForCausalLM with the embedding matrix widened to 157,668 rows |
stroke_vocab.json | the 5,732 stroke tokens, in id order |
meta.json | old, the id of the first stroke token (151,936), and the format version |
tokenizer.json, tokenizer_config.json | the base Qwen tokenizer, for the text rows; the stroke tokens are not in it |
jwen/sample.py | grammar-constrained sampler that writes stroke JSON and a rough preview PNG |
jwen/stroke_tokens.py | the token format, grammar and stroke decoder |
pkrender/main.swift, pkrender/Info.plist | PencilKit renderer for macOS, the only way to see the real ink; the plist gives the process the bundle identity PencilKit expects |
samples/ | the example drawing's stroke file, and the two images above |
pip install torch transformers pillow
python jwen/sample.py --count 4 --group 3 --out samples
That writes samples/000.json (strokes), samples/000.tokens.txt and samples/000.png (a Pillow polyline preview). Sampling a face is a few thousand tokens; expect well under a minute per drawing on an H100 and three to four minutes on an Apple Silicon Mac with 16 GB or more.
Options: --group 0..82 picks a character group (the model chooses when omitted, but naming one helps), --palette mono|color and --inks one|many steer the look, --temp-struct and --temp-points set separate temperatures for the plan and the points (0.75 and 0.9 by default), --seed fixes the draw.
Strokes carry Apple pencil, pen, marker, crayon, watercolor and fountain pen tools with widths and pressure, and only PencilKit draws those the way Notes does. On macOS:
cd pkrender
xcrun --sdk macosx swiftc -O main.swift -o pkrender -framework PencilKit -framework AppKit \
-Xlinker -sectcreate -Xlinker __TEXT -Xlinker __info_plist -Xlinker Info.plist
./pkrender ../samples/000.json ../samples/000.pk.png 1080
Pass --pkrender pkrender/pkrender to sample.py to render every sample as it lands. --dark on pkrender renders the dark-mode ink.
One drawing is one sequence:
<face> <cluster:N|none> <type:face> <side:S> <palette:mono|color> <inks:one|many> <count:N>
<plan> { <s> <tool:T> <color:C> <x:X> <y:Y> <ex:X> <ey:Y> <bw:W> <bh:H> }* </plan>
{ <s> <gap:G> <dt:D> <tool:T> <color:C> <x:X> <y:Y> <ex:X> <ey:Y> <bw:W> <bh:H> <w:K> { [<w:K>] (<dx:D> <dy:D> | <x:X> <y:Y>) }* }*
</face>
The header names the character group, palette, ink count and stroke count. The plan lists every stroke's tool, color, start, end and bounding box before any point is drawn. Then each stroke repeats its header and gives its points, thinned to one every 24 ms, as deltas on a 1024 grid. Stroke token i in stroke_vocab.json has model id 151936 + i. jwen/sample.py masks illegal tokens at every step, copies each stroke header from the plan, and stops after the last planned stroke, so free-running generation is not needed and would produce malformed sequences.
<cluster:N> comes from.Total GPU time was about $103.
It draws faces and little else, in one person's hand. Feature placement is loose: eyes, nose and mouth land in plausible order more often than in plausible places. Sampling without the grammar produces broken sequences. The preview renderer is a stand-in; the ink only looks right through PencilKit.