Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ data/

# Tokenizer data files (small, needed for inference)
!tokenizer/data/
tokenizer/data/orion_*/
tokenizer/data/*_tok/
tokenizer/hf_*/

# Experiments (binaries only, source tracked)
experiments/*.o
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ INFERENCE_SRC = \
kernels/inference/prefill_ane.m \
kernels/inference/decode_ane.m \
kernels/inference/decode_cpu.m \
kernels/inference/kv_cache.m
kernels/inference/kv_cache.m \
kernels/inference/qwen_cpu_ops.m

TRAINING_SRC = \
kernels/training/stories_train.m \
kernels/training/stories_cpu_ops.m \
kernels/training/qwen_lora_cpu_ops.m \
kernels/training/qwen_lora_train.m \

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Qwen production sources are now part of every build, but none of the 33 added test_qwen*.m programs are included in TEST_NAMES or a separate test target. They all compile when requested manually, and the two frontend tests run successfully, but make test never discovers them. Would you add a focused target (for example test-qwen) and include the self-contained tests in the default verification path, with model/ANE-dependent probes clearly separated? That would make the close-out claims reproducible for reviewers and CI.

kernels/training/data_loader.m

MODEL_SRC = model/weight_loader.m
Expand Down Expand Up @@ -65,6 +68,7 @@ COMPILER_C_SRC = \
compiler/frontends/gpt2_prefill.c \
compiler/frontends/gpt2_decode.c \
compiler/frontends/gpt2_final.c \
compiler/frontends/qwen35_prefill.c \
compiler/frontends/classifier_softmax.c \
compiler/frontends/stories_train.c \
compiler/frontends/lora.c
Expand Down
131 changes: 131 additions & 0 deletions README_ORION_Q.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Orion-Q

Orion-Q is a Qwen-focused porting and diagnostics subset built on top of Orion.

It is not a separate engine. It is a curated extension of Orion that adds:

- Qwen model configs and blob conversion
- Qwen CPU inference path
- Qwen ANE or hybrid inference path
- Qwen LoRA training primitives
- Qwen diagnostics, including smoke, probe, parity, and diff tests

The boundary for this subset is defined in:

- `docs/orion_q/ADR-008-orion-q-boundary.md`

## What Orion-Q Is

Orion-Q is the part of the local Orion worktree that makes Qwen-family models runnable and verifiable inside Orion.

In practical terms, Orion-Q includes:

- Qwen frontend and model registration
- Qwen weight loading and export path
- Qwen-specific CPU and ANE execution support
- Qwen LoRA training path
- Qwen diagnostics and validation tests

## What Orion-Q Is Not

Orion-Q does not include:

- Silver accelerator work
- user-specific training tracks
- CRPG or other domain assets
- reports, logs, or generated tokenizer experiment outputs
- exported checkpoints or model weights

Those belong to downstream tracks or local runtime artifacts, not to Orion-Q itself.

## Current Status

Within the currently defined Orion-Q scope:

- Qwen porting core: complete
- binary judge diagnostics: close-out achieved
- target hybrid parity smoke scope: close-out achieved
- ANE training preparation line: complete

Supporting documents:

- `docs/orion_q/ORION_Q_PORT_CLOSEOUT.md`
- `docs/orion_q/ORION_Q_HYBRID_PARITY_CLOSEOUT.md`
- `docs/orion_q/ORION_Q_ANE_PREP_CLOSEOUT.md`

## Included Code Areas

The shared Orion-Q subset is expected to cover these groups:

- shared Orion core changes required by Qwen support
- `compiler/frontends/qwen35_*`
- `kernels/inference/qwen_*`
- `kernels/training/qwen_lora_*`
- `model/configs/qwen35_*`
- `model/convert/hf_to_blobs_qwen35.py`
- `tests/test_qwen35_*`
- `tests/test_qwen35_9b_*`

## Validation Philosophy

Orion-Q treats diagnostics as part of the product surface, not as throwaway experiments.

That means the following are first-class parts of the subset:

- smoke tests
- bridge-stage diffs
- layer diffs
- parity checks
- ANE training probes

## Recommended Share Mode

The recommended way to share Orion-Q is:

1. As an Orion-based fork or draft PR branch
2. With generated artifacts excluded
3. With a narrow, explicit scope

Suggested framing:

`Orion-Q: a Qwen-focused porting and diagnostics subset built on top of Orion`

## Excluded Artifacts

Do not publish these as part of Orion-Q:

- `tokenizer/data/orion_*`
- `tokenizer/data/*_tok`
- exported blobs and checkpoints
- local reports and logs
- user workflow assets

## Local Share Bundle

This workspace can generate a clean Orion-Q share bundle with:

```bash
python3 scripts/prepare_orion_q_share.py --clean
```

Default output:

```text
build/orion_q_share/Orion-Q
```

The bundle is driven by:

- `scripts/orion_q_share_manifest.txt`

## Relationship to Upstream Orion

Upstream Orion remains the execution core.

Orion-Q should be communicated as:

- Orion core
- plus Qwen-specific porting
- plus Qwen-specific diagnostics

It should not be presented as a replacement brand or a disconnected new project.
13 changes: 10 additions & 3 deletions apps/cli/commands/infer.m
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ int orion_cmd_infer(int argc, const char* argv[]) {
double t_load = time_ms() - t0;
fprintf(stderr, "Weights loaded in %.1f ms\n", t_load);

OrionModelConfig cfg = kGPT2_124M;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dynamic dimensions here are a useful start, but this command still loads OrionGPT2Weights, uses the GPT-2 tokenizer/EOS id, and calls only the GPT-2 prefill/decode functions. As a result, the Qwen execution paths added in this PR are not reachable through orion infer (or another registered CLI command). Could the PR add explicit model selection and dispatch through the Qwen manifest, tokenizer regex/special tokens, Qwen CPU path, and ANE/hybrid path? Alternatively, the documentation and PR scope should describe these as diagnostic scaffolding rather than a runnable Orion-Q integration.

cfg.n_layer = w->n_layer;
cfg.d_model = w->d_model;
cfg.hidden_dim = w->d_ff;
cfg.vocab = w->vocab;
cfg.max_seq = w->max_seq;

// Tokenize prompt
int prompt_tokens[1024];
int prompt_len = orion_gpt2_encode(tok, prompt, prompt_tokens, 1024);
Expand All @@ -134,7 +141,7 @@ int orion_cmd_infer(int argc, const char* argv[]) {

// Allocate
float* logits = (float*)malloc(w->vocab * sizeof(float));
OrionKVCache* kv = orion_kv_cache_create(&kGPT2_124M);
OrionKVCache* kv = orion_kv_cache_create(&cfg);
int gen_count = 0;

// Start profiler
Expand All @@ -148,12 +155,12 @@ int orion_cmd_infer(int argc, const char* argv[]) {
bool prefill_ok;
if (use_ane) {
prefill_ok = orion_ane_prefill(w, prompt_tokens, prompt_len,
&kGPT2_124M, weights_path, kv, logits);
&cfg, weights_path, kv, logits);
if (!prefill_ok) {
fprintf(stderr, "Warning: ANE prefill failed, falling back to CPU\n");
// Reset KV cache
orion_kv_cache_free(kv);
kv = orion_kv_cache_create(&kGPT2_124M);
kv = orion_kv_cache_create(&cfg);
orion_gpt2_prefill_kv(w, prompt_tokens, prompt_len, kv, logits);
prefill_ok = true;
}
Expand Down
51 changes: 47 additions & 4 deletions compiler/builder.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>

// Helper: create a node with basic fields
static OrionNode make_node(OrionOp op, const char* name, OrionDtype dtype, int shape[4]) {
Expand All @@ -17,6 +18,11 @@ static OrionNode make_node(OrionOp op, const char* name, OrionDtype dtype, int s
return n;
}

static bool orion_use_fp32_rrms_powchain(void) {
const char *mode = getenv("ORION_RMSNORM_RRMS_MODE");
return mode && (strcmp(mode, "nr1") == 0 || strcmp(mode, "fp32") == 0);
}

int orion_gb_input(OrionGraph* g, const char* name, OrionDtype dtype, int shape[4]) {
OrionNode n = make_node(ORION_OP_INPUT, name, dtype, shape);
int idx = orion_graph_add_node(g, &n);
Expand Down Expand Up @@ -335,8 +341,24 @@ int orion_gb_gelu(OrionGraph* g, int input, const char* prefix, int dim __attrib
int orion_gb_silu(OrionGraph* g, int input, const char* prefix, int dim __attribute__((unused)), int seq __attribute__((unused))) {
char buf[ORION_MAX_NAME];

// Use sigmoid(x) = 0.5 * (tanh(0.5 * x) + 1) to avoid the ANE builtin sigmoid drift.
snprintf(buf, sizeof(buf), "%s_half", prefix);
int half = orion_gb_const_scalar(g, buf, ORION_DTYPE_FP16, 0.5f);

snprintf(buf, sizeof(buf), "%s_hx", prefix);
int hx = orion_gb_mul(g, input, half, buf);

snprintf(buf, sizeof(buf), "%s_th", prefix);
int th = orion_gb_tanh(g, hx, buf);

snprintf(buf, sizeof(buf), "%s_one", prefix);
int one = orion_gb_const_scalar(g, buf, ORION_DTYPE_FP16, 1.0f);

snprintf(buf, sizeof(buf), "%s_onep", prefix);
int onep = orion_gb_add(g, th, one, buf);

snprintf(buf, sizeof(buf), "%s_sig", prefix);
int sig = orion_gb_sigmoid(g, input, buf);
int sig = orion_gb_mul(g, onep, half, buf);

snprintf(buf, sizeof(buf), "%s_out", prefix);
int out = orion_gb_mul(g, input, sig, buf);
Expand Down Expand Up @@ -366,6 +388,7 @@ int orion_gb_rmsnorm(OrionGraph* g, int input, int weight, float eps,
// / dim
float inv_dim = 1.0f / (float)dim;
snprintf(buf, sizeof(buf), "%s_invd", prefix);
snprintf(buf, sizeof(buf), "%s_invd", prefix);
int invd = orion_gb_const_scalar(g, buf, ORION_DTYPE_FP16, inv_dim);
snprintf(buf, sizeof(buf), "%s_ms", prefix);
int ms = orion_gb_mul(g, ss, invd, buf);
Expand All @@ -382,9 +405,29 @@ int orion_gb_rmsnorm(OrionGraph* g, int input, int weight, float eps,
snprintf(buf, sizeof(buf), "%s_rrms", prefix);
int rrms = orion_gb_pow(g, mse, nhalf, buf);

// x * rrms
snprintf(buf, sizeof(buf), "%s_xr", prefix);
int xr = orion_gb_mul(g, input, rrms, buf);
int xr = -1;
if (orion_use_fp32_rrms_powchain()) {
snprintf(buf, sizeof(buf), "%s_rrms_sq", prefix);
int rrms_sq = orion_gb_mul(g, rrms, rrms, buf);
snprintf(buf, sizeof(buf), "%s_nr_term", prefix);
int nr_term = orion_gb_mul(g, mse, rrms_sq, buf);
snprintf(buf, sizeof(buf), "%s_half_nr", prefix);
int half_nr = orion_gb_const_scalar(g, buf, ORION_DTYPE_FP16, 0.5f);
snprintf(buf, sizeof(buf), "%s_nr_half", prefix);
int nr_half = orion_gb_mul(g, nr_term, half_nr, buf);
snprintf(buf, sizeof(buf), "%s_threehalves", prefix);
int threehalves = orion_gb_const_scalar(g, buf, ORION_DTYPE_FP16, 1.5f);
snprintf(buf, sizeof(buf), "%s_nr_corr", prefix);
int nr_corr = orion_gb_sub(g, threehalves, nr_half, buf);
snprintf(buf, sizeof(buf), "%s_rrms_refined", prefix);
int rrms_refined = orion_gb_mul(g, rrms, nr_corr, buf);
snprintf(buf, sizeof(buf), "%s_xr", prefix);
xr = orion_gb_mul(g, input, rrms_refined, buf);
} else {
// x * rrms
snprintf(buf, sizeof(buf), "%s_xr", prefix);
xr = orion_gb_mul(g, input, rrms, buf);
}

// weight * normalized
snprintf(buf, sizeof(buf), "%s_out", prefix);
Expand Down
Loading