Image-Text-to-Text
Transformers
Safetensors
PEFT
qwen3_5_moe
classification
structured-prediction
multimodal
lora
Instructions to use harshatheg/GPC-1 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use harshatheg/GPC-1 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="harshatheg/GPC-1")# pip install -U transformers accelerate # Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("harshatheg/GPC-1") model = AutoModelForMultimodalLM.from_pretrained("harshatheg/GPC-1", device_map="auto") - PEFT
How to use harshatheg/GPC-1 with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use harshatheg/GPC-1 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "harshatheg/GPC-1" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "harshatheg/GPC-1", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/harshatheg/GPC-1
- SGLang
How to use harshatheg/GPC-1 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "harshatheg/GPC-1" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "harshatheg/GPC-1", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "harshatheg/GPC-1" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "harshatheg/GPC-1", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use harshatheg/GPC-1 with Docker Model Runner:
docker model run hf.co/harshatheg/GPC-1
Commit ·
c87a0aa
0
Parent(s):
Release v1
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +9 -0
- .gitattributes +39 -0
- .gitignore +7 -0
- API.md +118 -0
- Dockerfile +21 -0
- LICENSE +202 -0
- MODEL_CARD.md +30 -0
- NOTICE +16 -0
- README.md +132 -0
- adapter/adapter_config.json +43 -0
- adapter/adapter_model.safetensors +3 -0
- assets/base_files.sha256.json +32 -0
- assets/codebook255.json +1532 -0
- assets/examples/bounding-box.png +3 -0
- assets/examples/intent-classification.png +0 -0
- assets/examples/visual-coordinates.png +3 -0
- assets/runtime_manifest.json +37 -0
- config.json +122 -0
- demo/README.md +49 -0
- demo/app.py +150 -0
- demo/assets/person.jpg +3 -0
- demo/client.py +166 -0
- demo/presets.py +23 -0
- demo/requirements.txt +3 -0
- docs/ARCHITECTURE.md +34 -0
- docs/DATA_AND_LICENSES.md +50 -0
- docs/DOWNLOADS.md +60 -0
- docs/HUB_METRICS.md +11 -0
- download.py +181 -0
- examples/atomic_request.json +1 -0
- examples/numeric_request.json +7 -0
- gpc1_server/__init__.py +3 -0
- gpc1_server/api.py +315 -0
- gpc1_server/assets.py +38 -0
- gpc1_server/bootstrap.py +23 -0
- gpc1_server/compiler.py +205 -0
- gpc1_server/errors.py +16 -0
- gpc1_server/native_onepass.py +122 -0
- gpc1_server/runtime.py +246 -0
- gpc1_server/schemas.py +50 -0
- gpc1_server/vision_cache.py +98 -0
- model/LICENSE +202 -0
- model/chat_template.jinja +154 -0
- model/config.json +115 -0
- model/generation_config.json +13 -0
- model/merges.txt +0 -0
- model/model-00001-of-00016.safetensors +3 -0
- model/model-00002-of-00016.safetensors +3 -0
- model/model-00003-of-00016.safetensors +3 -0
- model/model-00004-of-00016.safetensors +3 -0
.dockerignore
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
**/__pycache__
|
| 2 |
+
**/*.py[cod]
|
| 3 |
+
**/.pytest_cache
|
| 4 |
+
**/.cache
|
| 5 |
+
.git
|
| 6 |
+
.env
|
| 7 |
+
.env.*
|
| 8 |
+
base
|
| 9 |
+
model
|
.gitattributes
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
assets/examples/visual-coordinates.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
assets/examples/bounding-box.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
demo/assets/person.jpg filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
model/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.py[cod]
|
| 3 |
+
.pytest_cache/
|
| 4 |
+
.cache/
|
| 5 |
+
.env
|
| 6 |
+
.env.*
|
| 7 |
+
base/
|
API.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GPC-1 API
|
| 2 |
+
|
| 3 |
+
The NVIDIA server exposes `GET /v1/models` and `POST /v1/chat/completions`. Requests use OpenAI-style `messages` and `response_format`; responses contain `choices` and a `gpc1` result object. This is a structured-prediction interface, not a complete Chat Completions implementation.
|
| 4 |
+
|
| 5 |
+
## Requests and responses
|
| 6 |
+
|
| 7 |
+
Use `model: "gpc-1"` and a strict JSON schema:
|
| 8 |
+
|
| 9 |
+
```json
|
| 10 |
+
{"type":"json_schema","json_schema":{"name":"result","strict":true,"schema":{}}}
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
Replace `schema` with an explicit object schema. Require every property and set `additionalProperties: false`. Supported types are `object`, `array`, `string`, `number`, `integer`, `boolean`, and `null`. References, unions, recursion, and patterns are unsupported.
|
| 14 |
+
|
| 15 |
+
Put mode-specific options in `gpc1`. OpenAI Python clients can pass them through `extra_body={"gpc1": ...}`.
|
| 16 |
+
|
| 17 |
+
- `choices[0].message.content`: JSON-encoded selected values.
|
| 18 |
+
- `gpc1`: probability distributions, numeric summaries, and execution accounting.
|
| 19 |
+
- `usage`: `null`; token usage is not reported.
|
| 20 |
+
- Errors: `{"error":{"message": "...", "type": "...", "param": null, "code": "..."}}`.
|
| 21 |
+
|
| 22 |
+
## Interpreting scores
|
| 23 |
+
|
| 24 |
+
Probabilities describe the model's distribution over the choices, numeric grid, or complete records supplied in the request. Changing that output space can change the scores. A probability of 0.9 is not a measured 90% accuracy rate; choose decision thresholds using representative examples from your application.
|
| 25 |
+
|
| 26 |
+
## Atomic categorical
|
| 27 |
+
|
| 28 |
+
Set `mode: "atomic"`, `field_key`, and an ordered `choices` array of 2–255 unique JSON values. The schema must contain exactly that field with a matching `enum`.
|
| 29 |
+
|
| 30 |
+
The response includes `value`, `selected_index`, and `probabilities` in choice order.
|
| 31 |
+
|
| 32 |
+
[Complete categorical request](examples/atomic_request.json)
|
| 33 |
+
|
| 34 |
+
## Numeric101
|
| 35 |
+
|
| 36 |
+
Set `mode: "numeric101"` and `numeric_fields`. Each field requires `key`, `description`, `minimum`, `maximum`, and `unit`. Optional `aliases` and `reference` clarify meaning. Schema properties must use `type: "number"` with matching bounds.
|
| 37 |
+
|
| 38 |
+
Each field uses `value(i) = minimum + (maximum - minimum) × i / 100`, for `i = 0…100`.
|
| 39 |
+
|
| 40 |
+
| Response field | Meaning |
|
| 41 |
+
| --- | --- |
|
| 42 |
+
| `choices[0].message.content` | Most likely grid value for each key |
|
| 43 |
+
| `gpc1.fields[key].map_value` | Most likely grid value |
|
| 44 |
+
| `gpc1.fields[key].expected_value` | Probability-weighted mean in the declared units |
|
| 45 |
+
| `gpc1.fields[key].probabilities` | Probabilities in ascending grid order |
|
| 46 |
+
| `gpc1.fields[key].normalized_grid` | Grid positions from 0 to 1 |
|
| 47 |
+
|
| 48 |
+
The mean may fall between grid positions. Numeric fields are separate marginals, not a full joint distribution.
|
| 49 |
+
|
| 50 |
+
[Complete numeric request](examples/numeric_request.json)
|
| 51 |
+
|
| 52 |
+
## Finite joint
|
| 53 |
+
|
| 54 |
+
Set `mode: "finite_joint"` and `allowed_records` containing unique complete JSON records that satisfy the schema. The response includes the selected `value`, candidate `probabilities`, and `log_scores`.
|
| 55 |
+
|
| 56 |
+
The schema accepts 2–255 records, subject to stricter runtime memory and row limits. The default runtime limit is 32 records. Probabilities are conditional on the supplied records; this mode does not generate arbitrary JSON.
|
| 57 |
+
|
| 58 |
+
## Images
|
| 59 |
+
|
| 60 |
+
Include one `image_url` content part containing a base64 PNG, JPEG, or WebP data URI:
|
| 61 |
+
|
| 62 |
+
```json
|
| 63 |
+
{"type":"image_url","image_url":{"url":"data:image/jpeg;base64,..."}}
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
Images are supported only in `numeric101`. HTTP(S) and file URLs are rejected. Describe the target, coordinate origin, axes, and units explicitly. For boxes, request `x_min`, `y_min`, `x_max`, and `y_max`; multiply normalized horizontal values by image width and vertical values by image height. Check coordinate ordering before drawing.
|
| 67 |
+
|
| 68 |
+
The visual README examples use `expected_value`, not the MAP values in message content.
|
| 69 |
+
|
| 70 |
+
## Deployment
|
| 71 |
+
|
| 72 |
+
Set `GPC1_MODEL_PATH` to the packaged `model/` directory and `GPC1_API_KEY` to a secret. Send `Authorization: Bearer YOUR_KEY` to the model endpoints. Startup verifies the packaged base and adapter; serving does not fetch models or images from URLs.
|
| 73 |
+
|
| 74 |
+
Run one server worker per model on a GPU. Model requests execute one at a time; additional requests wait in a bounded queue. Use separate replicas to serve parallel traffic. `GET /healthz` returns `{"status":"ok"}`. `GET /readyz` returns `{"status":"ready"}` after the model backend is selected, or HTTP 503 with `{"status":"not_ready"}` before then. These two health endpoints do not require the bearer key and do not return model metadata.
|
| 75 |
+
|
| 76 |
+
Build the container from the downloaded package, then mount its model weights read-only. Set `GPC1_API_KEY` in your environment before launching:
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
docker build -t gpc-1 .
|
| 80 |
+
docker run --gpus all --rm -p 127.0.0.1:8000:8000 \
|
| 81 |
+
-e GPC1_API_KEY -e GPC1_MODEL_PATH=/models/gpc-1 \
|
| 82 |
+
-v "$PWD/model:/models/gpc-1:ro" gpc-1
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
For internet-facing deployment, put an HTTPS proxy in front of the server. Keep the bearer key server-side and configure request-size limits at the proxy too.
|
| 86 |
+
|
| 87 |
+
### Context window
|
| 88 |
+
|
| 89 |
+
The release server admits up to **256K tokens (262,144)** per compiled input, matching the backbone's configured context capacity. This includes prompt formatting, schema, choices, numeric field definitions, and image tokens, not just the supplied text. Inputs are never silently truncated.
|
| 90 |
+
|
| 91 |
+
This is the configured ceiling, not a completed 256K GPU validation or a guarantee that every request fits on one GPU. Memory and latency depend on input length, images, and candidate count. Set `GPC1_MAX_INPUT_TOKENS` lower for your hardware. The hosted ZeroGPU demo uses a separate 8,192-token limit.
|
| 92 |
+
|
| 93 |
+
Finite-joint requests also have an aggregate padded-token budget, defaulting to twice the input ceiling (524,288 tokens). Larger candidate sets may reach that budget before the per-input ceiling. Request-body and output-memory limits remain independent.
|
| 94 |
+
|
| 95 |
+
| Setting | Default |
|
| 96 |
+
| --- | --- |
|
| 97 |
+
| `GPC1_MAX_INPUT_TOKENS` | 262,144; configurable from 1 to 262,144 |
|
| 98 |
+
| `GPC1_MAX_CONCURRENCY` | 1, required; use replicas for parallel traffic |
|
| 99 |
+
| `GPC1_MAX_PENDING_REQUESTS` | 4 waiting requests; one additional request may be active |
|
| 100 |
+
| `GPC1_MAX_QUEUE_WAIT_SECONDS` | 2 |
|
| 101 |
+
| `GPC1_MAX_BODY_BYTES` | 16 MiB |
|
| 102 |
+
| `GPC1_MAX_IMAGE_BYTES` | 3 MiB |
|
| 103 |
+
| `GPC1_MAX_IMAGE_PIXELS` | 16,777,216 |
|
| 104 |
+
| `GPC1_IMAGE_CACHE_MB` | 64; one image's vision features, or 0 to disable |
|
| 105 |
+
| `GPC1_MAX_JOINT_ROWS` | 32 |
|
| 106 |
+
| `GPC1_MAX_PADDED_TOKENS` | `max(131072, 2 × GPC1_MAX_INPUT_TOKENS)`; 524,288 by default |
|
| 107 |
+
| `GPC1_MAX_LOGIT_VECTORS` | 512 |
|
| 108 |
+
| `GPC1_MAX_LOGITS_BYTES` | 512 MiB |
|
| 109 |
+
|
| 110 |
+
Over-limit inputs are rejected, never truncated. POST requests require `Content-Length`. A full queue or an expired queue wait returns HTTP 429 with code `server_busy`; body and image size limits return 413. Set `GPC1_ADMIT_VISION_NUMERIC=disabled` or `GPC1_ADMIT_FINITE_JOINT=disabled` to disable those modes.
|
| 111 |
+
|
| 112 |
+
Repeated requests for the same processed image can reuse its vision features without changing the prediction math. A new image still requires encoding. The cache stays in memory, holds at most one image's features, and can be disabled with `GPC1_IMAGE_CACHE_MB=0`.
|
| 113 |
+
|
| 114 |
+
## Unsupported
|
| 115 |
+
|
| 116 |
+
Streaming, tools, audio, remote image fetching, atomic image classification, image-conditioned finite-joint output, and unrestricted JSON generation. Omit `temperature` and `max_completion_tokens`; they are rejected rather than ignored.
|
| 117 |
+
|
| 118 |
+
[Setup](README.md#get-started) · [Downloads and CI](docs/DOWNLOADS.md) · [Architecture](docs/ARCHITECTURE.md) · [Model card](MODEL_CARD.md)
|
Dockerfile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM nvidia/cuda:13.0.1-cudnn-runtime-ubuntu24.04
|
| 2 |
+
|
| 3 |
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 4 |
+
PYTHONUNBUFFERED=1 \
|
| 5 |
+
HF_HUB_OFFLINE=1 \
|
| 6 |
+
TRANSFORMERS_OFFLINE=1 \
|
| 7 |
+
GPC1_MAX_CONCURRENCY=1
|
| 8 |
+
WORKDIR /srv/gpc1
|
| 9 |
+
RUN apt-get update && apt-get install -y --no-install-recommends python3.12 python3-pip \
|
| 10 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 11 |
+
COPY requirements.txt ./
|
| 12 |
+
RUN python3.12 -m pip install --no-cache-dir --break-system-packages \
|
| 13 |
+
--index-url https://download.pytorch.org/whl/cu130 torch==2.9.1 torchvision==0.24.1 \
|
| 14 |
+
&& python3.12 -m pip install --no-cache-dir --break-system-packages -r requirements.txt
|
| 15 |
+
COPY gpc1_server ./gpc1_server
|
| 16 |
+
COPY assets ./assets
|
| 17 |
+
COPY adapter ./adapter
|
| 18 |
+
RUN useradd --create-home --uid 10001 gpc1 && chown -R gpc1:gpc1 /srv/gpc1
|
| 19 |
+
USER gpc1
|
| 20 |
+
EXPOSE 8000
|
| 21 |
+
CMD ["python3.12", "-m", "uvicorn", "gpc1_server.api:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1", "--no-access-log"]
|
LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright 2026 Alibaba Cloud
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
MODEL_CARD.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GPC-1 model card
|
| 2 |
+
|
| 3 |
+
GPC-1 is a structured prediction model based on Qwen3.5-35B-A3B. It supports categorical choices, bounded numeric estimates, image-conditioned coordinates, and complete records selected from a caller-defined set.
|
| 4 |
+
|
| 5 |
+
## Model details
|
| 6 |
+
|
| 7 |
+
| Property | Value |
|
| 8 |
+
| --- | --- |
|
| 9 |
+
| API model ID | `gpc-1` |
|
| 10 |
+
| Backbone family | `Qwen/Qwen3.5-35B-A3B` |
|
| 11 |
+
| Architecture | Multimodal mixture of experts |
|
| 12 |
+
| Distribution | GPC-1 backbone and matching adapter |
|
| 13 |
+
| Runtime | NVIDIA, BF16, Transformers / PyTorch |
|
| 14 |
+
| Categorical support | 2–255 caller-defined choices |
|
| 15 |
+
| Numeric support | 101 positions per declared range |
|
| 16 |
+
| Joint output | Caller-enumerated complete records |
|
| 17 |
+
| Server input ceiling | 256K tokens (262,144), including compiled request overhead |
|
| 18 |
+
| License | Apache-2.0, with applicable third-party notices |
|
| 19 |
+
|
| 20 |
+
The release package contains GPC-1's backbone weights and matching adapter. The runtime loads and verifies both. See [setup](README.md#get-started), [download options](docs/DOWNLOADS.md), and [context configuration](API.md#context-window).
|
| 21 |
+
|
| 22 |
+
## Intended use
|
| 23 |
+
|
| 24 |
+
Document and message classification, bounded numerical estimates, image-coordinate annotation, and structured workflow decisions. Define labels, units, and reference frames explicitly in each request.
|
| 25 |
+
|
| 26 |
+
Validate application-specific accuracy before using predictions in medical, legal, financial, or safety-critical decisions. The [API guide](API.md) defines score interpretation and supported output structures.
|
| 27 |
+
|
| 28 |
+
## License
|
| 29 |
+
|
| 30 |
+
GPC-1 inference code and the Qwen base use Apache-2.0. Preserve applicable upstream notices. Example media retain their own licenses. See [LICENSE](LICENSE), [NOTICE](NOTICE), and [attribution](docs/DATA_AND_LICENSES.md).
|
NOTICE
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
GPC-1
|
| 2 |
+
|
| 3 |
+
This distribution includes original GPC-1 inference software licensed under
|
| 4 |
+
the Apache License, Version 2.0.
|
| 5 |
+
|
| 6 |
+
GPC-1 is designed for and may be distributed with an adapter for
|
| 7 |
+
Qwen/Qwen3.5-35B-A3B at revision
|
| 8 |
+
59d61f3ce65a6d9863b86d2e96597125219dc754. The upstream Qwen model is
|
| 9 |
+
licensed under the Apache License, Version 2.0. Preserve all applicable
|
| 10 |
+
upstream copyright, patent, trademark, and attribution notices.
|
| 11 |
+
|
| 12 |
+
Qwen model source: https://huggingface.co/Qwen/Qwen3.5-35B-A3B
|
| 13 |
+
Pinned license: https://huggingface.co/Qwen/Qwen3.5-35B-A3B/blob/59d61f3ce65a6d9863b86d2e96597125219dc754/LICENSE
|
| 14 |
+
|
| 15 |
+
Third-party attributions and example-media licenses are documented in
|
| 16 |
+
docs/DATA_AND_LICENSES.md.
|
README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
base_model: Qwen/Qwen3.5-35B-A3B
|
| 4 |
+
library_name: transformers
|
| 5 |
+
base_model_relation: adapter
|
| 6 |
+
inference: false
|
| 7 |
+
tags:
|
| 8 |
+
- classification
|
| 9 |
+
- structured-prediction
|
| 10 |
+
- multimodal
|
| 11 |
+
- peft
|
| 12 |
+
- lora
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
# GPC-1
|
| 16 |
+
|
| 17 |
+
Introducing **GPC-1**, a general-purpose classifier based on **Qwen3.5-35B-A3B**, post-trained for categorical decisions, bounded numerical estimates, and image-conditioned coordinates.
|
| 18 |
+
|
| 19 |
+
Define your labels, numeric ranges, or allowed JSON records through one API. Build document routing, scoring, visual annotation, and structured workflows around outputs your application can use directly. Multiple numeric fields are evaluated in one model pass.
|
| 20 |
+
|
| 21 |
+
[Demo](https://huggingface.co/spaces/harshatheg/GPC-1-Demo) · [Get started](#get-started) · [Download options](docs/DOWNLOADS.md) · [API guide](API.md) · [Local demo](demo/README.md) · [Model card](MODEL_CARD.md)
|
| 22 |
+
|
| 23 |
+
## Key features
|
| 24 |
+
|
| 25 |
+
- **Categorical classification:** supply your own labels and receive a selected class with probabilities over the available choices.
|
| 26 |
+
- **Continuous range selections:** specify bounds and a field description to obtain a numerical estimate.
|
| 27 |
+
- **Image-conditioned numerical outputs:** ask for coordinates using the image and a natural-language description of the target.
|
| 28 |
+
- **Dependent structured choices:** score complete allowed JSON records together to keep related categorical decisions consistent.
|
| 29 |
+
- **OpenAI-style serving:** use familiar messages and JSON schemas on NVIDIA hardware.
|
| 30 |
+
|
| 31 |
+
## Continuous range selections
|
| 32 |
+
|
| 33 |
+
Define the range, units, and reference frame for each numeric field. The same interface can represent a position, angle, distance, or score.
|
| 34 |
+
|
| 35 |
+
For example, `0` to `1` can describe a horizontal image coordinate, while `0` to `360` can describe an angle in degrees.
|
| 36 |
+
|
| 37 |
+
```json
|
| 38 |
+
{
|
| 39 |
+
"key": "x_coord",
|
| 40 |
+
"description": "Horizontal position of the requested point; 0 is the left edge and 1 is the right edge",
|
| 41 |
+
"minimum": 0,
|
| 42 |
+
"maximum": 1,
|
| 43 |
+
"unit": "image-width fraction"
|
| 44 |
+
}
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
Each field uses 101 evenly spaced positions. The API returns the most likely position and a probability-weighted mean that can fall between positions. These are finite-resolution estimates. [Output semantics](API.md#numeric101).
|
| 48 |
+
|
| 49 |
+
## Examples
|
| 50 |
+
|
| 51 |
+
### Bounding boxes
|
| 52 |
+
|
| 53 |
+
Four normalized coordinates define two corners: `x_min`, `y_min`, `x_max`, and `y_max`, with the origin at the image’s top-left. [Image request format](API.md#images).
|
| 54 |
+
|
| 55 |
+

|
| 56 |
+
|
| 57 |
+
### Human pose and keypoints
|
| 58 |
+
|
| 59 |
+
Describe a landmark and request its coordinates, such as `right_hip_x` and `right_hip_y`. Each coordinate is a separate numeric estimate; a complete skeleton is not enforced.
|
| 60 |
+
|
| 61 |
+

|
| 62 |
+
|
| 63 |
+
### Intent classification
|
| 64 |
+
|
| 65 |
+
Define intent labels in the request and receive a selected label with probabilities over those labels.
|
| 66 |
+
|
| 67 |
+

|
| 68 |
+
|
| 69 |
+
### Numerical request
|
| 70 |
+
|
| 71 |
+
> “A tank holds 200 liters. It is 35% full. Return the current volume of liquid in liters.”
|
| 72 |
+
|
| 73 |
+
Define `volume` over `0–200`, with units `liters`. GPC-1 returns `70` as the most likely value. The same numeric interface can express a rating, distance, fraction, or image coordinate; the prompt supplies the interpretation.
|
| 74 |
+
|
| 75 |
+
### Dependent decisions
|
| 76 |
+
|
| 77 |
+
Instead of independently choosing a department and an action, provide the complete records your workflow permits. GPC-1 scores those records together, so an invalid combination cannot appear in the returned support. Joint scoring is bounded by the records you enumerate, not arbitrary JSON generation.
|
| 78 |
+
|
| 79 |
+
See the [API guide](API.md) for request payloads and response fields.
|
| 80 |
+
|
| 81 |
+
## Get started
|
| 82 |
+
|
| 83 |
+
Run GPC-1 on compatible NVIDIA hardware with enough memory for the BF16 model. The complete release includes the GPC-1 backbone, its matching adapter, and serving code. The server loads both weight components automatically.
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
python3 -m pip install "huggingface-hub>=1.7,<2"
|
| 87 |
+
hf download harshatheg/GPC-1 --local-dir gpc-1
|
| 88 |
+
python gpc-1/download.py verify gpc-1
|
| 89 |
+
cd gpc-1
|
| 90 |
+
python3 -m pip install --index-url https://download.pytorch.org/whl/cu130 torch==2.9.1 torchvision==0.24.1
|
| 91 |
+
python3 -m pip install -r requirements.txt
|
| 92 |
+
export GPC1_MODEL_PATH="$PWD/model"
|
| 93 |
+
export GPC1_API_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
|
| 94 |
+
python3 -m uvicorn gpc1_server.api:app \
|
| 95 |
+
--host 127.0.0.1 --port 8000 --no-access-log
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
Keep the server running. In another terminal, open `gpc-1`, set `GPC1_API_KEY` to the same key used by the server, and send an OpenAI-style request to `/v1/chat/completions`:
|
| 99 |
+
|
| 100 |
+
```bash
|
| 101 |
+
curl http://127.0.0.1:8000/v1/chat/completions \
|
| 102 |
+
-H "Authorization: Bearer $GPC1_API_KEY" \
|
| 103 |
+
-H "Content-Type: application/json" \
|
| 104 |
+
--data @examples/atomic_request.json
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
The API uses familiar `messages` and `response_format` fields, with a `gpc1` extension for the prediction mode. See [serving and examples](API.md) for numeric ranges, image inputs, joint records, and deployment configuration. The [download guide](docs/DOWNLOADS.md) covers pinned revisions, Python, CI, and offline deployment.
|
| 108 |
+
|
| 109 |
+
## At a glance
|
| 110 |
+
|
| 111 |
+
| Model detail | GPC-1 |
|
| 112 |
+
| --- | --- |
|
| 113 |
+
| Backbone family | Qwen3.5-35B-A3B |
|
| 114 |
+
| Packaged weights | GPC-1 backbone + matching adapter |
|
| 115 |
+
| Architecture | Multimodal mixture of experts |
|
| 116 |
+
| Categorical choices | 2–255 per request |
|
| 117 |
+
| Numeric outputs | 101-point support over a range you define |
|
| 118 |
+
| Image interface | Image-conditioned numeric fields |
|
| 119 |
+
| Dependent outputs | Enumerated joint records |
|
| 120 |
+
| Server input ceiling | 256K tokens (262,144), including compiled schema and image tokens |
|
| 121 |
+
| Serving | NVIDIA / PyTorch, OpenAI-style HTTP API |
|
| 122 |
+
| License | Apache-2.0 |
|
| 123 |
+
|
| 124 |
+
See [context and deployment](API.md#context-window) for hardware requirements and the hosted demo's separate limits.
|
| 125 |
+
|
| 126 |
+
## Under the hood
|
| 127 |
+
|
| 128 |
+
GPC-1 uses schema-bound scoring to return typed predictions from its multimodal backbone and matching adapter. Numeric fields share one model pass; dependent categorical decisions are scored as complete allowed records.
|
| 129 |
+
|
| 130 |
+
[Architecture](docs/ARCHITECTURE.md) · [Model card](MODEL_CARD.md) · [API documentation](API.md) · [Local demo](demo/README.md) · [License](LICENSE) · [Notices](NOTICE)
|
| 131 |
+
|
| 132 |
+
GPC-1 inference code is Apache-2.0. The Qwen base retains its upstream license and notices; [third-party attribution](docs/DATA_AND_LICENSES.md) is included with the package.
|
adapter/adapter_config.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"alora_invocation_tokens": null,
|
| 3 |
+
"alpha_pattern": {},
|
| 4 |
+
"arrow_config": null,
|
| 5 |
+
"auto_mapping": null,
|
| 6 |
+
"base_model_name_or_path": "model",
|
| 7 |
+
"bias": "none",
|
| 8 |
+
"corda_config": null,
|
| 9 |
+
"ensure_weight_tying": false,
|
| 10 |
+
"eva_config": null,
|
| 11 |
+
"exclude_modules": null,
|
| 12 |
+
"fan_in_fan_out": false,
|
| 13 |
+
"inference_mode": true,
|
| 14 |
+
"init_lora_weights": true,
|
| 15 |
+
"kasa_config": null,
|
| 16 |
+
"layer_replication": null,
|
| 17 |
+
"layers_pattern": null,
|
| 18 |
+
"layers_to_transform": null,
|
| 19 |
+
"loftq_config": {},
|
| 20 |
+
"lora_alpha": 16,
|
| 21 |
+
"lora_bias": false,
|
| 22 |
+
"lora_dropout": 0.0,
|
| 23 |
+
"lora_ga_config": null,
|
| 24 |
+
"megatron_config": null,
|
| 25 |
+
"megatron_core": "megatron.core",
|
| 26 |
+
"modules_to_save": null,
|
| 27 |
+
"monteclora_config": null,
|
| 28 |
+
"peft_type": "LORA",
|
| 29 |
+
"peft_version": "0.21.0",
|
| 30 |
+
"qalora_group_size": 16,
|
| 31 |
+
"r": 16,
|
| 32 |
+
"rank_pattern": {},
|
| 33 |
+
"revision": null,
|
| 34 |
+
"target_modules": ".*language_model.*\\.(q_proj|k_proj|v_proj|o_proj|in_proj_qkv|out_proj)$",
|
| 35 |
+
"target_parameters": null,
|
| 36 |
+
"task_type": "CAUSAL_LM",
|
| 37 |
+
"trainable_token_indices": null,
|
| 38 |
+
"use_bdlora": null,
|
| 39 |
+
"use_dora": false,
|
| 40 |
+
"use_qalora": false,
|
| 41 |
+
"use_rslora": false,
|
| 42 |
+
"velora_config": null
|
| 43 |
+
}
|
adapter/adapter_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2b2521cff2a98afdd42e3051c8c6de60ad928b1268244f2c13b4193059c6e1f4
|
| 3 |
+
size 45250376
|
assets/base_files.sha256.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"files": {
|
| 3 |
+
"chat_template.jinja": "a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715",
|
| 4 |
+
"config.json": "5e4d7f74fec2f360eb9cfbfcd6ec0c4c76e684d3a11caaed259d9fd9bfbc7944",
|
| 5 |
+
"generation_config.json": "4f25002776b741773666203dcea8f54619f177ace3ae483d311102092a4658e0",
|
| 6 |
+
"merges.txt": "a9d356d7bdf1ef4949e3e748e95b8e10ad9d4e2e838eddc38a0a7b6b94d1db8d",
|
| 7 |
+
"model-00001-of-00016.safetensors": "43a908b3d324d59b66c73a33e5c182809eb2df7da474b2fd2cbdabb16baeee38",
|
| 8 |
+
"model-00002-of-00016.safetensors": "4de54e39c2ecc6f3fb5e79e752f84b65c96c316d0d848c9b55d36a062df69e70",
|
| 9 |
+
"model-00003-of-00016.safetensors": "847472ab2e3e10da0baea3993890ce393cc1b364f53674a00a92ac6ed856ec0d",
|
| 10 |
+
"model-00004-of-00016.safetensors": "81b2b46905d62b66c6a0d2360050c266ea9416f913708e9a1447b28465f458f0",
|
| 11 |
+
"model-00005-of-00016.safetensors": "3a13dfef35c76070c92e7a303640f90b11795dd1ecc6e501d0e00d8310b0e3a2",
|
| 12 |
+
"model-00006-of-00016.safetensors": "baeb2742c637cb517e6410c1b7dc53ea69312078a1f0b708d43d7a62dffa22a9",
|
| 13 |
+
"model-00007-of-00016.safetensors": "5cc9bcd2e0a8e17f1319f8d808fd6595d9944f2a0e01e9e7cb8d550b9646e413",
|
| 14 |
+
"model-00008-of-00016.safetensors": "f294ac742f1f1762b8f05963812b33dd3bff1701b0ec887fe46717c7d66a638e",
|
| 15 |
+
"model-00009-of-00016.safetensors": "2198de19b6c6eda23f6b72f787f861ee4495ff9f29551f3b40cef72f1afbd96d",
|
| 16 |
+
"model-00010-of-00016.safetensors": "3fecc834fc976ae9205039b01c53049f18ca5b68d973f0977bfe90335faf0918",
|
| 17 |
+
"model-00011-of-00016.safetensors": "4e253c10600a5368df08bdd92e97c69c4b3b59fc4e7d8326a5f33b0888358779",
|
| 18 |
+
"model-00012-of-00016.safetensors": "420af5489aecba4d174c875520e3fcf5180ead42c9c6d8bc903ce8cb910f996c",
|
| 19 |
+
"model-00013-of-00016.safetensors": "a2b5627ba8a9b4564a795388ee90c757abb0f386aa6bef813ff3b6c52a9a85f7",
|
| 20 |
+
"model-00014-of-00016.safetensors": "e52489ba1847e0b1d89a445fa4f3b26dd1f2e957c70b35657e6333ea9be8f12a",
|
| 21 |
+
"model-00015-of-00016.safetensors": "7ef51deb1d96fe34a37d8bec88b4f7151c0ac762b802fe446c990ed4580bbb67",
|
| 22 |
+
"model-00016-of-00016.safetensors": "4df5299dd7e8033e6821336584fd5b8830c116cdea7385e64e3b0c2b14609ea8",
|
| 23 |
+
"model.safetensors.index.json": "f8448aa8b2fbf3519723b0c2965fc94ed64cafdd463381636a1df351952b3f1a",
|
| 24 |
+
"preprocessor_config.json": "27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516",
|
| 25 |
+
"tokenizer.json": "5f9e4d4901a92b997e463c1f46055088b6cca5ca61a6522d1b9f64c4bb81cb42",
|
| 26 |
+
"tokenizer_config.json": "316230d6a809701f4db5ea8f8fc862bc3a6f3229c937c174e674ff3ca0a64ac8",
|
| 27 |
+
"video_preprocessor_config.json": "7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13",
|
| 28 |
+
"vocab.json": "ce99b4cb2983d118806ce0a8b777a35b093e2000a503ebde25853284c9dfa003"
|
| 29 |
+
},
|
| 30 |
+
"model_id": "harshatheg/GPC-1",
|
| 31 |
+
"revision": "base-278124e95f96d21b"
|
| 32 |
+
}
|
assets/codebook255.json
ADDED
|
@@ -0,0 +1,1532 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"code": "A",
|
| 4 |
+
"position": 0,
|
| 5 |
+
"token_id": 32,
|
| 6 |
+
"utf8_hex": "41"
|
| 7 |
+
},
|
| 8 |
+
{
|
| 9 |
+
"code": "B",
|
| 10 |
+
"position": 1,
|
| 11 |
+
"token_id": 33,
|
| 12 |
+
"utf8_hex": "42"
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"code": "C",
|
| 16 |
+
"position": 2,
|
| 17 |
+
"token_id": 34,
|
| 18 |
+
"utf8_hex": "43"
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"code": "D",
|
| 22 |
+
"position": 3,
|
| 23 |
+
"token_id": 35,
|
| 24 |
+
"utf8_hex": "44"
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"code": "E",
|
| 28 |
+
"position": 4,
|
| 29 |
+
"token_id": 36,
|
| 30 |
+
"utf8_hex": "45"
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"code": "F",
|
| 34 |
+
"position": 5,
|
| 35 |
+
"token_id": 37,
|
| 36 |
+
"utf8_hex": "46"
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"code": "G",
|
| 40 |
+
"position": 6,
|
| 41 |
+
"token_id": 38,
|
| 42 |
+
"utf8_hex": "47"
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"code": "H",
|
| 46 |
+
"position": 7,
|
| 47 |
+
"token_id": 39,
|
| 48 |
+
"utf8_hex": "48"
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"code": "I",
|
| 52 |
+
"position": 8,
|
| 53 |
+
"token_id": 40,
|
| 54 |
+
"utf8_hex": "49"
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"code": "J",
|
| 58 |
+
"position": 9,
|
| 59 |
+
"token_id": 41,
|
| 60 |
+
"utf8_hex": "4a"
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"code": "K",
|
| 64 |
+
"position": 10,
|
| 65 |
+
"token_id": 42,
|
| 66 |
+
"utf8_hex": "4b"
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"code": "L",
|
| 70 |
+
"position": 11,
|
| 71 |
+
"token_id": 43,
|
| 72 |
+
"utf8_hex": "4c"
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"code": "M",
|
| 76 |
+
"position": 12,
|
| 77 |
+
"token_id": 44,
|
| 78 |
+
"utf8_hex": "4d"
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
"code": "N",
|
| 82 |
+
"position": 13,
|
| 83 |
+
"token_id": 45,
|
| 84 |
+
"utf8_hex": "4e"
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"code": "O",
|
| 88 |
+
"position": 14,
|
| 89 |
+
"token_id": 46,
|
| 90 |
+
"utf8_hex": "4f"
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"code": "P",
|
| 94 |
+
"position": 15,
|
| 95 |
+
"token_id": 47,
|
| 96 |
+
"utf8_hex": "50"
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"code": "Q",
|
| 100 |
+
"position": 16,
|
| 101 |
+
"token_id": 48,
|
| 102 |
+
"utf8_hex": "51"
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"code": "R",
|
| 106 |
+
"position": 17,
|
| 107 |
+
"token_id": 49,
|
| 108 |
+
"utf8_hex": "52"
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"code": "S",
|
| 112 |
+
"position": 18,
|
| 113 |
+
"token_id": 50,
|
| 114 |
+
"utf8_hex": "53"
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
"code": "T",
|
| 118 |
+
"position": 19,
|
| 119 |
+
"token_id": 51,
|
| 120 |
+
"utf8_hex": "54"
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"code": "U",
|
| 124 |
+
"position": 20,
|
| 125 |
+
"token_id": 52,
|
| 126 |
+
"utf8_hex": "55"
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"code": "V",
|
| 130 |
+
"position": 21,
|
| 131 |
+
"token_id": 53,
|
| 132 |
+
"utf8_hex": "56"
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
"code": "W",
|
| 136 |
+
"position": 22,
|
| 137 |
+
"token_id": 54,
|
| 138 |
+
"utf8_hex": "57"
|
| 139 |
+
},
|
| 140 |
+
{
|
| 141 |
+
"code": "X",
|
| 142 |
+
"position": 23,
|
| 143 |
+
"token_id": 55,
|
| 144 |
+
"utf8_hex": "58"
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
"code": "Y",
|
| 148 |
+
"position": 24,
|
| 149 |
+
"token_id": 56,
|
| 150 |
+
"utf8_hex": "59"
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"code": "Z",
|
| 154 |
+
"position": 25,
|
| 155 |
+
"token_id": 57,
|
| 156 |
+
"utf8_hex": "5a"
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"code": "AA",
|
| 160 |
+
"position": 26,
|
| 161 |
+
"token_id": 5840,
|
| 162 |
+
"utf8_hex": "4141"
|
| 163 |
+
},
|
| 164 |
+
{
|
| 165 |
+
"code": "AB",
|
| 166 |
+
"position": 27,
|
| 167 |
+
"token_id": 1803,
|
| 168 |
+
"utf8_hex": "4142"
|
| 169 |
+
},
|
| 170 |
+
{
|
| 171 |
+
"code": "AC",
|
| 172 |
+
"position": 28,
|
| 173 |
+
"token_id": 1646,
|
| 174 |
+
"utf8_hex": "4143"
|
| 175 |
+
},
|
| 176 |
+
{
|
| 177 |
+
"code": "AD",
|
| 178 |
+
"position": 29,
|
| 179 |
+
"token_id": 1745,
|
| 180 |
+
"utf8_hex": "4144"
|
| 181 |
+
},
|
| 182 |
+
{
|
| 183 |
+
"code": "AE",
|
| 184 |
+
"position": 30,
|
| 185 |
+
"token_id": 13276,
|
| 186 |
+
"utf8_hex": "4145"
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
"code": "AF",
|
| 190 |
+
"position": 31,
|
| 191 |
+
"token_id": 8018,
|
| 192 |
+
"utf8_hex": "4146"
|
| 193 |
+
},
|
| 194 |
+
{
|
| 195 |
+
"code": "AG",
|
| 196 |
+
"position": 32,
|
| 197 |
+
"token_id": 1825,
|
| 198 |
+
"utf8_hex": "4147"
|
| 199 |
+
},
|
| 200 |
+
{
|
| 201 |
+
"code": "AH",
|
| 202 |
+
"position": 33,
|
| 203 |
+
"token_id": 28946,
|
| 204 |
+
"utf8_hex": "4148"
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"code": "AI",
|
| 208 |
+
"position": 34,
|
| 209 |
+
"token_id": 15015,
|
| 210 |
+
"utf8_hex": "4149"
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"code": "AJ",
|
| 214 |
+
"position": 35,
|
| 215 |
+
"token_id": 29595,
|
| 216 |
+
"utf8_hex": "414a"
|
| 217 |
+
},
|
| 218 |
+
{
|
| 219 |
+
"code": "AK",
|
| 220 |
+
"position": 36,
|
| 221 |
+
"token_id": 11568,
|
| 222 |
+
"utf8_hex": "414b"
|
| 223 |
+
},
|
| 224 |
+
{
|
| 225 |
+
"code": "AL",
|
| 226 |
+
"position": 37,
|
| 227 |
+
"token_id": 939,
|
| 228 |
+
"utf8_hex": "414c"
|
| 229 |
+
},
|
| 230 |
+
{
|
| 231 |
+
"code": "AM",
|
| 232 |
+
"position": 38,
|
| 233 |
+
"token_id": 1354,
|
| 234 |
+
"utf8_hex": "414d"
|
| 235 |
+
},
|
| 236 |
+
{
|
| 237 |
+
"code": "AN",
|
| 238 |
+
"position": 39,
|
| 239 |
+
"token_id": 1058,
|
| 240 |
+
"utf8_hex": "414e"
|
| 241 |
+
},
|
| 242 |
+
{
|
| 243 |
+
"code": "AO",
|
| 244 |
+
"position": 40,
|
| 245 |
+
"token_id": 18183,
|
| 246 |
+
"utf8_hex": "414f"
|
| 247 |
+
},
|
| 248 |
+
{
|
| 249 |
+
"code": "AP",
|
| 250 |
+
"position": 41,
|
| 251 |
+
"token_id": 2456,
|
| 252 |
+
"utf8_hex": "4150"
|
| 253 |
+
},
|
| 254 |
+
{
|
| 255 |
+
"code": "AQ",
|
| 256 |
+
"position": 42,
|
| 257 |
+
"token_id": 88898,
|
| 258 |
+
"utf8_hex": "4151"
|
| 259 |
+
},
|
| 260 |
+
{
|
| 261 |
+
"code": "AR",
|
| 262 |
+
"position": 43,
|
| 263 |
+
"token_id": 905,
|
| 264 |
+
"utf8_hex": "4152"
|
| 265 |
+
},
|
| 266 |
+
{
|
| 267 |
+
"code": "AS",
|
| 268 |
+
"position": 44,
|
| 269 |
+
"token_id": 1846,
|
| 270 |
+
"utf8_hex": "4153"
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
"code": "AT",
|
| 274 |
+
"position": 45,
|
| 275 |
+
"token_id": 802,
|
| 276 |
+
"utf8_hex": "4154"
|
| 277 |
+
},
|
| 278 |
+
{
|
| 279 |
+
"code": "AU",
|
| 280 |
+
"position": 46,
|
| 281 |
+
"token_id": 33869,
|
| 282 |
+
"utf8_hex": "4155"
|
| 283 |
+
},
|
| 284 |
+
{
|
| 285 |
+
"code": "AV",
|
| 286 |
+
"position": 47,
|
| 287 |
+
"token_id": 7839,
|
| 288 |
+
"utf8_hex": "4156"
|
| 289 |
+
},
|
| 290 |
+
{
|
| 291 |
+
"code": "AW",
|
| 292 |
+
"position": 48,
|
| 293 |
+
"token_id": 14006,
|
| 294 |
+
"utf8_hex": "4157"
|
| 295 |
+
},
|
| 296 |
+
{
|
| 297 |
+
"code": "AX",
|
| 298 |
+
"position": 49,
|
| 299 |
+
"token_id": 2860,
|
| 300 |
+
"utf8_hex": "4158"
|
| 301 |
+
},
|
| 302 |
+
{
|
| 303 |
+
"code": "AY",
|
| 304 |
+
"position": 50,
|
| 305 |
+
"token_id": 2926,
|
| 306 |
+
"utf8_hex": "4159"
|
| 307 |
+
},
|
| 308 |
+
{
|
| 309 |
+
"code": "AZ",
|
| 310 |
+
"position": 51,
|
| 311 |
+
"token_id": 22828,
|
| 312 |
+
"utf8_hex": "415a"
|
| 313 |
+
},
|
| 314 |
+
{
|
| 315 |
+
"code": "BA",
|
| 316 |
+
"position": 52,
|
| 317 |
+
"token_id": 6844,
|
| 318 |
+
"utf8_hex": "4241"
|
| 319 |
+
},
|
| 320 |
+
{
|
| 321 |
+
"code": "BB",
|
| 322 |
+
"position": 53,
|
| 323 |
+
"token_id": 9798,
|
| 324 |
+
"utf8_hex": "4242"
|
| 325 |
+
},
|
| 326 |
+
{
|
| 327 |
+
"code": "BC",
|
| 328 |
+
"position": 54,
|
| 329 |
+
"token_id": 4738,
|
| 330 |
+
"utf8_hex": "4243"
|
| 331 |
+
},
|
| 332 |
+
{
|
| 333 |
+
"code": "BD",
|
| 334 |
+
"position": 55,
|
| 335 |
+
"token_id": 9265,
|
| 336 |
+
"utf8_hex": "4244"
|
| 337 |
+
},
|
| 338 |
+
{
|
| 339 |
+
"code": "BE",
|
| 340 |
+
"position": 56,
|
| 341 |
+
"token_id": 11261,
|
| 342 |
+
"utf8_hex": "4245"
|
| 343 |
+
},
|
| 344 |
+
{
|
| 345 |
+
"code": "BF",
|
| 346 |
+
"position": 57,
|
| 347 |
+
"token_id": 19278,
|
| 348 |
+
"utf8_hex": "4246"
|
| 349 |
+
},
|
| 350 |
+
{
|
| 351 |
+
"code": "BG",
|
| 352 |
+
"position": 58,
|
| 353 |
+
"token_id": 36513,
|
| 354 |
+
"utf8_hex": "4247"
|
| 355 |
+
},
|
| 356 |
+
{
|
| 357 |
+
"code": "BH",
|
| 358 |
+
"position": 59,
|
| 359 |
+
"token_id": 93801,
|
| 360 |
+
"utf8_hex": "4248"
|
| 361 |
+
},
|
| 362 |
+
{
|
| 363 |
+
"code": "BI",
|
| 364 |
+
"position": 60,
|
| 365 |
+
"token_id": 8335,
|
| 366 |
+
"utf8_hex": "4249"
|
| 367 |
+
},
|
| 368 |
+
{
|
| 369 |
+
"code": "BJ",
|
| 370 |
+
"position": 61,
|
| 371 |
+
"token_id": 14544,
|
| 372 |
+
"utf8_hex": "424a"
|
| 373 |
+
},
|
| 374 |
+
{
|
| 375 |
+
"code": "BK",
|
| 376 |
+
"position": 62,
|
| 377 |
+
"token_id": 85266,
|
| 378 |
+
"utf8_hex": "424b"
|
| 379 |
+
},
|
| 380 |
+
{
|
| 381 |
+
"code": "BL",
|
| 382 |
+
"position": 63,
|
| 383 |
+
"token_id": 9110,
|
| 384 |
+
"utf8_hex": "424c"
|
| 385 |
+
},
|
| 386 |
+
{
|
| 387 |
+
"code": "BM",
|
| 388 |
+
"position": 64,
|
| 389 |
+
"token_id": 28000,
|
| 390 |
+
"utf8_hex": "424d"
|
| 391 |
+
},
|
| 392 |
+
{
|
| 393 |
+
"code": "BN",
|
| 394 |
+
"position": 65,
|
| 395 |
+
"token_id": 15137,
|
| 396 |
+
"utf8_hex": "424e"
|
| 397 |
+
},
|
| 398 |
+
{
|
| 399 |
+
"code": "BO",
|
| 400 |
+
"position": 66,
|
| 401 |
+
"token_id": 4525,
|
| 402 |
+
"utf8_hex": "424f"
|
| 403 |
+
},
|
| 404 |
+
{
|
| 405 |
+
"code": "BP",
|
| 406 |
+
"position": 67,
|
| 407 |
+
"token_id": 25261,
|
| 408 |
+
"utf8_hex": "4250"
|
| 409 |
+
},
|
| 410 |
+
{
|
| 411 |
+
"code": "BR",
|
| 412 |
+
"position": 68,
|
| 413 |
+
"token_id": 12717,
|
| 414 |
+
"utf8_hex": "4252"
|
| 415 |
+
},
|
| 416 |
+
{
|
| 417 |
+
"code": "BS",
|
| 418 |
+
"position": 69,
|
| 419 |
+
"token_id": 7116,
|
| 420 |
+
"utf8_hex": "4253"
|
| 421 |
+
},
|
| 422 |
+
{
|
| 423 |
+
"code": "BT",
|
| 424 |
+
"position": 70,
|
| 425 |
+
"token_id": 17078,
|
| 426 |
+
"utf8_hex": "4254"
|
| 427 |
+
},
|
| 428 |
+
{
|
| 429 |
+
"code": "BU",
|
| 430 |
+
"position": 71,
|
| 431 |
+
"token_id": 14497,
|
| 432 |
+
"utf8_hex": "4255"
|
| 433 |
+
},
|
| 434 |
+
{
|
| 435 |
+
"code": "BV",
|
| 436 |
+
"position": 72,
|
| 437 |
+
"token_id": 57339,
|
| 438 |
+
"utf8_hex": "4256"
|
| 439 |
+
},
|
| 440 |
+
{
|
| 441 |
+
"code": "BW",
|
| 442 |
+
"position": 73,
|
| 443 |
+
"token_id": 74909,
|
| 444 |
+
"utf8_hex": "4257"
|
| 445 |
+
},
|
| 446 |
+
{
|
| 447 |
+
"code": "BX",
|
| 448 |
+
"position": 74,
|
| 449 |
+
"token_id": 52072,
|
| 450 |
+
"utf8_hex": "4258"
|
| 451 |
+
},
|
| 452 |
+
{
|
| 453 |
+
"code": "BY",
|
| 454 |
+
"position": 75,
|
| 455 |
+
"token_id": 19305,
|
| 456 |
+
"utf8_hex": "4259"
|
| 457 |
+
},
|
| 458 |
+
{
|
| 459 |
+
"code": "CA",
|
| 460 |
+
"position": 76,
|
| 461 |
+
"token_id": 4887,
|
| 462 |
+
"utf8_hex": "4341"
|
| 463 |
+
},
|
| 464 |
+
{
|
| 465 |
+
"code": "CB",
|
| 466 |
+
"position": 77,
|
| 467 |
+
"token_id": 12607,
|
| 468 |
+
"utf8_hex": "4342"
|
| 469 |
+
},
|
| 470 |
+
{
|
| 471 |
+
"code": "CC",
|
| 472 |
+
"position": 78,
|
| 473 |
+
"token_id": 3580,
|
| 474 |
+
"utf8_hex": "4343"
|
| 475 |
+
},
|
| 476 |
+
{
|
| 477 |
+
"code": "CD",
|
| 478 |
+
"position": 79,
|
| 479 |
+
"token_id": 6281,
|
| 480 |
+
"utf8_hex": "4344"
|
| 481 |
+
},
|
| 482 |
+
{
|
| 483 |
+
"code": "CE",
|
| 484 |
+
"position": 80,
|
| 485 |
+
"token_id": 2036,
|
| 486 |
+
"utf8_hex": "4345"
|
| 487 |
+
},
|
| 488 |
+
{
|
| 489 |
+
"code": "CF",
|
| 490 |
+
"position": 81,
|
| 491 |
+
"token_id": 9362,
|
| 492 |
+
"utf8_hex": "4346"
|
| 493 |
+
},
|
| 494 |
+
{
|
| 495 |
+
"code": "CG",
|
| 496 |
+
"position": 82,
|
| 497 |
+
"token_id": 8533,
|
| 498 |
+
"utf8_hex": "4347"
|
| 499 |
+
},
|
| 500 |
+
{
|
| 501 |
+
"code": "CH",
|
| 502 |
+
"position": 83,
|
| 503 |
+
"token_id": 2080,
|
| 504 |
+
"utf8_hex": "4348"
|
| 505 |
+
},
|
| 506 |
+
{
|
| 507 |
+
"code": "CI",
|
| 508 |
+
"position": 84,
|
| 509 |
+
"token_id": 10911,
|
| 510 |
+
"utf8_hex": "4349"
|
| 511 |
+
},
|
| 512 |
+
{
|
| 513 |
+
"code": "CK",
|
| 514 |
+
"position": 85,
|
| 515 |
+
"token_id": 2925,
|
| 516 |
+
"utf8_hex": "434b"
|
| 517 |
+
},
|
| 518 |
+
{
|
| 519 |
+
"code": "CL",
|
| 520 |
+
"position": 86,
|
| 521 |
+
"token_id": 3040,
|
| 522 |
+
"utf8_hex": "434c"
|
| 523 |
+
},
|
| 524 |
+
{
|
| 525 |
+
"code": "CM",
|
| 526 |
+
"position": 87,
|
| 527 |
+
"token_id": 9690,
|
| 528 |
+
"utf8_hex": "434d"
|
| 529 |
+
},
|
| 530 |
+
{
|
| 531 |
+
"code": "CN",
|
| 532 |
+
"position": 88,
|
| 533 |
+
"token_id": 27731,
|
| 534 |
+
"utf8_hex": "434e"
|
| 535 |
+
},
|
| 536 |
+
{
|
| 537 |
+
"code": "CO",
|
| 538 |
+
"position": 89,
|
| 539 |
+
"token_id": 8023,
|
| 540 |
+
"utf8_hex": "434f"
|
| 541 |
+
},
|
| 542 |
+
{
|
| 543 |
+
"code": "CP",
|
| 544 |
+
"position": 90,
|
| 545 |
+
"token_id": 6901,
|
| 546 |
+
"utf8_hex": "4350"
|
| 547 |
+
},
|
| 548 |
+
{
|
| 549 |
+
"code": "CR",
|
| 550 |
+
"position": 91,
|
| 551 |
+
"token_id": 8702,
|
| 552 |
+
"utf8_hex": "4352"
|
| 553 |
+
},
|
| 554 |
+
{
|
| 555 |
+
"code": "CS",
|
| 556 |
+
"position": 92,
|
| 557 |
+
"token_id": 6211,
|
| 558 |
+
"utf8_hex": "4353"
|
| 559 |
+
},
|
| 560 |
+
{
|
| 561 |
+
"code": "CT",
|
| 562 |
+
"position": 93,
|
| 563 |
+
"token_id": 1123,
|
| 564 |
+
"utf8_hex": "4354"
|
| 565 |
+
},
|
| 566 |
+
{
|
| 567 |
+
"code": "CU",
|
| 568 |
+
"position": 94,
|
| 569 |
+
"token_id": 16307,
|
| 570 |
+
"utf8_hex": "4355"
|
| 571 |
+
},
|
| 572 |
+
{
|
| 573 |
+
"code": "CV",
|
| 574 |
+
"position": 95,
|
| 575 |
+
"token_id": 18990,
|
| 576 |
+
"utf8_hex": "4356"
|
| 577 |
+
},
|
| 578 |
+
{
|
| 579 |
+
"code": "CW",
|
| 580 |
+
"position": 96,
|
| 581 |
+
"token_id": 64045,
|
| 582 |
+
"utf8_hex": "4357"
|
| 583 |
+
},
|
| 584 |
+
{
|
| 585 |
+
"code": "CX",
|
| 586 |
+
"position": 97,
|
| 587 |
+
"token_id": 63037,
|
| 588 |
+
"utf8_hex": "4358"
|
| 589 |
+
},
|
| 590 |
+
{
|
| 591 |
+
"code": "CY",
|
| 592 |
+
"position": 98,
|
| 593 |
+
"token_id": 33380,
|
| 594 |
+
"utf8_hex": "4359"
|
| 595 |
+
},
|
| 596 |
+
{
|
| 597 |
+
"code": "DA",
|
| 598 |
+
"position": 99,
|
| 599 |
+
"token_id": 6151,
|
| 600 |
+
"utf8_hex": "4441"
|
| 601 |
+
},
|
| 602 |
+
{
|
| 603 |
+
"code": "DB",
|
| 604 |
+
"position": 100,
|
| 605 |
+
"token_id": 3392,
|
| 606 |
+
"utf8_hex": "4442"
|
| 607 |
+
},
|
| 608 |
+
{
|
| 609 |
+
"code": "DC",
|
| 610 |
+
"position": 101,
|
| 611 |
+
"token_id": 5449,
|
| 612 |
+
"utf8_hex": "4443"
|
| 613 |
+
},
|
| 614 |
+
{
|
| 615 |
+
"code": "DD",
|
| 616 |
+
"position": 102,
|
| 617 |
+
"token_id": 3967,
|
| 618 |
+
"utf8_hex": "4444"
|
| 619 |
+
},
|
| 620 |
+
{
|
| 621 |
+
"code": "DE",
|
| 622 |
+
"position": 103,
|
| 623 |
+
"token_id": 1113,
|
| 624 |
+
"utf8_hex": "4445"
|
| 625 |
+
},
|
| 626 |
+
{
|
| 627 |
+
"code": "DF",
|
| 628 |
+
"position": 104,
|
| 629 |
+
"token_id": 5095,
|
| 630 |
+
"utf8_hex": "4446"
|
| 631 |
+
},
|
| 632 |
+
{
|
| 633 |
+
"code": "DG",
|
| 634 |
+
"position": 105,
|
| 635 |
+
"token_id": 51923,
|
| 636 |
+
"utf8_hex": "4447"
|
| 637 |
+
},
|
| 638 |
+
{
|
| 639 |
+
"code": "DH",
|
| 640 |
+
"position": 106,
|
| 641 |
+
"token_id": 49600,
|
| 642 |
+
"utf8_hex": "4448"
|
| 643 |
+
},
|
| 644 |
+
{
|
| 645 |
+
"code": "DI",
|
| 646 |
+
"position": 107,
|
| 647 |
+
"token_id": 17099,
|
| 648 |
+
"utf8_hex": "4449"
|
| 649 |
+
},
|
| 650 |
+
{
|
| 651 |
+
"code": "DJ",
|
| 652 |
+
"position": 108,
|
| 653 |
+
"token_id": 51483,
|
| 654 |
+
"utf8_hex": "444a"
|
| 655 |
+
},
|
| 656 |
+
{
|
| 657 |
+
"code": "DK",
|
| 658 |
+
"position": 109,
|
| 659 |
+
"token_id": 17756,
|
| 660 |
+
"utf8_hex": "444b"
|
| 661 |
+
},
|
| 662 |
+
{
|
| 663 |
+
"code": "DL",
|
| 664 |
+
"position": 110,
|
| 665 |
+
"token_id": 16037,
|
| 666 |
+
"utf8_hex": "444c"
|
| 667 |
+
},
|
| 668 |
+
{
|
| 669 |
+
"code": "DM",
|
| 670 |
+
"position": 111,
|
| 671 |
+
"token_id": 8135,
|
| 672 |
+
"utf8_hex": "444d"
|
| 673 |
+
},
|
| 674 |
+
{
|
| 675 |
+
"code": "DN",
|
| 676 |
+
"position": 112,
|
| 677 |
+
"token_id": 30237,
|
| 678 |
+
"utf8_hex": "444e"
|
| 679 |
+
},
|
| 680 |
+
{
|
| 681 |
+
"code": "DO",
|
| 682 |
+
"position": 113,
|
| 683 |
+
"token_id": 5683,
|
| 684 |
+
"utf8_hex": "444f"
|
| 685 |
+
},
|
| 686 |
+
{
|
| 687 |
+
"code": "DP",
|
| 688 |
+
"position": 114,
|
| 689 |
+
"token_id": 9992,
|
| 690 |
+
"utf8_hex": "4450"
|
| 691 |
+
},
|
| 692 |
+
{
|
| 693 |
+
"code": "DR",
|
| 694 |
+
"position": 115,
|
| 695 |
+
"token_id": 7444,
|
| 696 |
+
"utf8_hex": "4452"
|
| 697 |
+
},
|
| 698 |
+
{
|
| 699 |
+
"code": "DS",
|
| 700 |
+
"position": 116,
|
| 701 |
+
"token_id": 5751,
|
| 702 |
+
"utf8_hex": "4453"
|
| 703 |
+
},
|
| 704 |
+
{
|
| 705 |
+
"code": "DT",
|
| 706 |
+
"position": 117,
|
| 707 |
+
"token_id": 10284,
|
| 708 |
+
"utf8_hex": "4454"
|
| 709 |
+
},
|
| 710 |
+
{
|
| 711 |
+
"code": "DU",
|
| 712 |
+
"position": 118,
|
| 713 |
+
"token_id": 20887,
|
| 714 |
+
"utf8_hex": "4455"
|
| 715 |
+
},
|
| 716 |
+
{
|
| 717 |
+
"code": "DV",
|
| 718 |
+
"position": 119,
|
| 719 |
+
"token_id": 59884,
|
| 720 |
+
"utf8_hex": "4456"
|
| 721 |
+
},
|
| 722 |
+
{
|
| 723 |
+
"code": "DW",
|
| 724 |
+
"position": 120,
|
| 725 |
+
"token_id": 52396,
|
| 726 |
+
"utf8_hex": "4457"
|
| 727 |
+
},
|
| 728 |
+
{
|
| 729 |
+
"code": "DX",
|
| 730 |
+
"position": 121,
|
| 731 |
+
"token_id": 16103,
|
| 732 |
+
"utf8_hex": "4458"
|
| 733 |
+
},
|
| 734 |
+
{
|
| 735 |
+
"code": "DY",
|
| 736 |
+
"position": 122,
|
| 737 |
+
"token_id": 67547,
|
| 738 |
+
"utf8_hex": "4459"
|
| 739 |
+
},
|
| 740 |
+
{
|
| 741 |
+
"code": "EA",
|
| 742 |
+
"position": 123,
|
| 743 |
+
"token_id": 18535,
|
| 744 |
+
"utf8_hex": "4541"
|
| 745 |
+
},
|
| 746 |
+
{
|
| 747 |
+
"code": "EB",
|
| 748 |
+
"position": 124,
|
| 749 |
+
"token_id": 8006,
|
| 750 |
+
"utf8_hex": "4542"
|
| 751 |
+
},
|
| 752 |
+
{
|
| 753 |
+
"code": "EC",
|
| 754 |
+
"position": 125,
|
| 755 |
+
"token_id": 7263,
|
| 756 |
+
"utf8_hex": "4543"
|
| 757 |
+
},
|
| 758 |
+
{
|
| 759 |
+
"code": "ED",
|
| 760 |
+
"position": 126,
|
| 761 |
+
"token_id": 1425,
|
| 762 |
+
"utf8_hex": "4544"
|
| 763 |
+
},
|
| 764 |
+
{
|
| 765 |
+
"code": "EE",
|
| 766 |
+
"position": 127,
|
| 767 |
+
"token_id": 6878,
|
| 768 |
+
"utf8_hex": "4545"
|
| 769 |
+
},
|
| 770 |
+
{
|
| 771 |
+
"code": "EF",
|
| 772 |
+
"position": 128,
|
| 773 |
+
"token_id": 14453,
|
| 774 |
+
"utf8_hex": "4546"
|
| 775 |
+
},
|
| 776 |
+
{
|
| 777 |
+
"code": "EG",
|
| 778 |
+
"position": 129,
|
| 779 |
+
"token_id": 9097,
|
| 780 |
+
"utf8_hex": "4547"
|
| 781 |
+
},
|
| 782 |
+
{
|
| 783 |
+
"code": "EH",
|
| 784 |
+
"position": 130,
|
| 785 |
+
"token_id": 44072,
|
| 786 |
+
"utf8_hex": "4548"
|
| 787 |
+
},
|
| 788 |
+
{
|
| 789 |
+
"code": "EI",
|
| 790 |
+
"position": 131,
|
| 791 |
+
"token_id": 76089,
|
| 792 |
+
"utf8_hex": "4549"
|
| 793 |
+
},
|
| 794 |
+
{
|
| 795 |
+
"code": "EK",
|
| 796 |
+
"position": 132,
|
| 797 |
+
"token_id": 68720,
|
| 798 |
+
"utf8_hex": "454b"
|
| 799 |
+
},
|
| 800 |
+
{
|
| 801 |
+
"code": "EL",
|
| 802 |
+
"position": 133,
|
| 803 |
+
"token_id": 2662,
|
| 804 |
+
"utf8_hex": "454c"
|
| 805 |
+
},
|
| 806 |
+
{
|
| 807 |
+
"code": "EM",
|
| 808 |
+
"position": 134,
|
| 809 |
+
"token_id": 2629,
|
| 810 |
+
"utf8_hex": "454d"
|
| 811 |
+
},
|
| 812 |
+
{
|
| 813 |
+
"code": "EN",
|
| 814 |
+
"position": 135,
|
| 815 |
+
"token_id": 923,
|
| 816 |
+
"utf8_hex": "454e"
|
| 817 |
+
},
|
| 818 |
+
{
|
| 819 |
+
"code": "EO",
|
| 820 |
+
"position": 136,
|
| 821 |
+
"token_id": 6548,
|
| 822 |
+
"utf8_hex": "454f"
|
| 823 |
+
},
|
| 824 |
+
{
|
| 825 |
+
"code": "EP",
|
| 826 |
+
"position": 137,
|
| 827 |
+
"token_id": 8924,
|
| 828 |
+
"utf8_hex": "4550"
|
| 829 |
+
},
|
| 830 |
+
{
|
| 831 |
+
"code": "EQ",
|
| 832 |
+
"position": 138,
|
| 833 |
+
"token_id": 52194,
|
| 834 |
+
"utf8_hex": "4551"
|
| 835 |
+
},
|
| 836 |
+
{
|
| 837 |
+
"code": "ER",
|
| 838 |
+
"position": 139,
|
| 839 |
+
"token_id": 622,
|
| 840 |
+
"utf8_hex": "4552"
|
| 841 |
+
},
|
| 842 |
+
{
|
| 843 |
+
"code": "ES",
|
| 844 |
+
"position": 140,
|
| 845 |
+
"token_id": 1515,
|
| 846 |
+
"utf8_hex": "4553"
|
| 847 |
+
},
|
| 848 |
+
{
|
| 849 |
+
"code": "ET",
|
| 850 |
+
"position": 141,
|
| 851 |
+
"token_id": 1300,
|
| 852 |
+
"utf8_hex": "4554"
|
| 853 |
+
},
|
| 854 |
+
{
|
| 855 |
+
"code": "EU",
|
| 856 |
+
"position": 142,
|
| 857 |
+
"token_id": 37523,
|
| 858 |
+
"utf8_hex": "4555"
|
| 859 |
+
},
|
| 860 |
+
{
|
| 861 |
+
"code": "EV",
|
| 862 |
+
"position": 143,
|
| 863 |
+
"token_id": 44473,
|
| 864 |
+
"utf8_hex": "4556"
|
| 865 |
+
},
|
| 866 |
+
{
|
| 867 |
+
"code": "EW",
|
| 868 |
+
"position": 144,
|
| 869 |
+
"token_id": 36530,
|
| 870 |
+
"utf8_hex": "4557"
|
| 871 |
+
},
|
| 872 |
+
{
|
| 873 |
+
"code": "EX",
|
| 874 |
+
"position": 145,
|
| 875 |
+
"token_id": 3152,
|
| 876 |
+
"utf8_hex": "4558"
|
| 877 |
+
},
|
| 878 |
+
{
|
| 879 |
+
"code": "EZ",
|
| 880 |
+
"position": 146,
|
| 881 |
+
"token_id": 93924,
|
| 882 |
+
"utf8_hex": "455a"
|
| 883 |
+
},
|
| 884 |
+
{
|
| 885 |
+
"code": "FA",
|
| 886 |
+
"position": 147,
|
| 887 |
+
"token_id": 3505,
|
| 888 |
+
"utf8_hex": "4641"
|
| 889 |
+
},
|
| 890 |
+
{
|
| 891 |
+
"code": "FB",
|
| 892 |
+
"position": 148,
|
| 893 |
+
"token_id": 15731,
|
| 894 |
+
"utf8_hex": "4642"
|
| 895 |
+
},
|
| 896 |
+
{
|
| 897 |
+
"code": "FC",
|
| 898 |
+
"position": 149,
|
| 899 |
+
"token_id": 6542,
|
| 900 |
+
"utf8_hex": "4643"
|
| 901 |
+
},
|
| 902 |
+
{
|
| 903 |
+
"code": "FD",
|
| 904 |
+
"position": 150,
|
| 905 |
+
"token_id": 14176,
|
| 906 |
+
"utf8_hex": "4644"
|
| 907 |
+
},
|
| 908 |
+
{
|
| 909 |
+
"code": "FE",
|
| 910 |
+
"position": 151,
|
| 911 |
+
"token_id": 11091,
|
| 912 |
+
"utf8_hex": "4645"
|
| 913 |
+
},
|
| 914 |
+
{
|
| 915 |
+
"code": "FF",
|
| 916 |
+
"position": 152,
|
| 917 |
+
"token_id": 1686,
|
| 918 |
+
"utf8_hex": "4646"
|
| 919 |
+
},
|
| 920 |
+
{
|
| 921 |
+
"code": "FG",
|
| 922 |
+
"position": 153,
|
| 923 |
+
"token_id": 11660,
|
| 924 |
+
"utf8_hex": "4647"
|
| 925 |
+
},
|
| 926 |
+
{
|
| 927 |
+
"code": "FH",
|
| 928 |
+
"position": 154,
|
| 929 |
+
"token_id": 80440,
|
| 930 |
+
"utf8_hex": "4648"
|
| 931 |
+
},
|
| 932 |
+
{
|
| 933 |
+
"code": "FI",
|
| 934 |
+
"position": 155,
|
| 935 |
+
"token_id": 18836,
|
| 936 |
+
"utf8_hex": "4649"
|
| 937 |
+
},
|
| 938 |
+
{
|
| 939 |
+
"code": "FK",
|
| 940 |
+
"position": 156,
|
| 941 |
+
"token_id": 26132,
|
| 942 |
+
"utf8_hex": "464b"
|
| 943 |
+
},
|
| 944 |
+
{
|
| 945 |
+
"code": "FL",
|
| 946 |
+
"position": 157,
|
| 947 |
+
"token_id": 5934,
|
| 948 |
+
"utf8_hex": "464c"
|
| 949 |
+
},
|
| 950 |
+
{
|
| 951 |
+
"code": "FM",
|
| 952 |
+
"position": 158,
|
| 953 |
+
"token_id": 24794,
|
| 954 |
+
"utf8_hex": "464d"
|
| 955 |
+
},
|
| 956 |
+
{
|
| 957 |
+
"code": "FN",
|
| 958 |
+
"position": 159,
|
| 959 |
+
"token_id": 40229,
|
| 960 |
+
"utf8_hex": "464e"
|
| 961 |
+
},
|
| 962 |
+
{
|
| 963 |
+
"code": "FO",
|
| 964 |
+
"position": 160,
|
| 965 |
+
"token_id": 3660,
|
| 966 |
+
"utf8_hex": "464f"
|
| 967 |
+
},
|
| 968 |
+
{
|
| 969 |
+
"code": "FP",
|
| 970 |
+
"position": 161,
|
| 971 |
+
"token_id": 11361,
|
| 972 |
+
"utf8_hex": "4650"
|
| 973 |
+
},
|
| 974 |
+
{
|
| 975 |
+
"code": "FR",
|
| 976 |
+
"position": 162,
|
| 977 |
+
"token_id": 10191,
|
| 978 |
+
"utf8_hex": "4652"
|
| 979 |
+
},
|
| 980 |
+
{
|
| 981 |
+
"code": "FS",
|
| 982 |
+
"position": 163,
|
| 983 |
+
"token_id": 8225,
|
| 984 |
+
"utf8_hex": "4653"
|
| 985 |
+
},
|
| 986 |
+
{
|
| 987 |
+
"code": "FT",
|
| 988 |
+
"position": 164,
|
| 989 |
+
"token_id": 3860,
|
| 990 |
+
"utf8_hex": "4654"
|
| 991 |
+
},
|
| 992 |
+
{
|
| 993 |
+
"code": "FU",
|
| 994 |
+
"position": 165,
|
| 995 |
+
"token_id": 78413,
|
| 996 |
+
"utf8_hex": "4655"
|
| 997 |
+
},
|
| 998 |
+
{
|
| 999 |
+
"code": "FW",
|
| 1000 |
+
"position": 166,
|
| 1001 |
+
"token_id": 17680,
|
| 1002 |
+
"utf8_hex": "4657"
|
| 1003 |
+
},
|
| 1004 |
+
{
|
| 1005 |
+
"code": "FX",
|
| 1006 |
+
"position": 167,
|
| 1007 |
+
"token_id": 15900,
|
| 1008 |
+
"utf8_hex": "4658"
|
| 1009 |
+
},
|
| 1010 |
+
{
|
| 1011 |
+
"code": "FY",
|
| 1012 |
+
"position": 168,
|
| 1013 |
+
"token_id": 78138,
|
| 1014 |
+
"utf8_hex": "4659"
|
| 1015 |
+
},
|
| 1016 |
+
{
|
| 1017 |
+
"code": "GA",
|
| 1018 |
+
"position": 169,
|
| 1019 |
+
"token_id": 15653,
|
| 1020 |
+
"utf8_hex": "4741"
|
| 1021 |
+
},
|
| 1022 |
+
{
|
| 1023 |
+
"code": "GB",
|
| 1024 |
+
"position": 170,
|
| 1025 |
+
"token_id": 5213,
|
| 1026 |
+
"utf8_hex": "4742"
|
| 1027 |
+
},
|
| 1028 |
+
{
|
| 1029 |
+
"code": "GC",
|
| 1030 |
+
"position": 171,
|
| 1031 |
+
"token_id": 22150,
|
| 1032 |
+
"utf8_hex": "4743"
|
| 1033 |
+
},
|
| 1034 |
+
{
|
| 1035 |
+
"code": "GD",
|
| 1036 |
+
"position": 172,
|
| 1037 |
+
"token_id": 39500,
|
| 1038 |
+
"utf8_hex": "4744"
|
| 1039 |
+
},
|
| 1040 |
+
{
|
| 1041 |
+
"code": "GE",
|
| 1042 |
+
"position": 173,
|
| 1043 |
+
"token_id": 10460,
|
| 1044 |
+
"utf8_hex": "4745"
|
| 1045 |
+
},
|
| 1046 |
+
{
|
| 1047 |
+
"code": "GF",
|
| 1048 |
+
"position": 174,
|
| 1049 |
+
"token_id": 35131,
|
| 1050 |
+
"utf8_hex": "4746"
|
| 1051 |
+
},
|
| 1052 |
+
{
|
| 1053 |
+
"code": "GG",
|
| 1054 |
+
"position": 175,
|
| 1055 |
+
"token_id": 21563,
|
| 1056 |
+
"utf8_hex": "4747"
|
| 1057 |
+
},
|
| 1058 |
+
{
|
| 1059 |
+
"code": "GH",
|
| 1060 |
+
"position": 176,
|
| 1061 |
+
"token_id": 43194,
|
| 1062 |
+
"utf8_hex": "4748"
|
| 1063 |
+
},
|
| 1064 |
+
{
|
| 1065 |
+
"code": "GI",
|
| 1066 |
+
"position": 177,
|
| 1067 |
+
"token_id": 27127,
|
| 1068 |
+
"utf8_hex": "4749"
|
| 1069 |
+
},
|
| 1070 |
+
{
|
| 1071 |
+
"code": "GL",
|
| 1072 |
+
"position": 178,
|
| 1073 |
+
"token_id": 3697,
|
| 1074 |
+
"utf8_hex": "474c"
|
| 1075 |
+
},
|
| 1076 |
+
{
|
| 1077 |
+
"code": "GM",
|
| 1078 |
+
"position": 179,
|
| 1079 |
+
"token_id": 20011,
|
| 1080 |
+
"utf8_hex": "474d"
|
| 1081 |
+
},
|
| 1082 |
+
{
|
| 1083 |
+
"code": "GN",
|
| 1084 |
+
"position": 180,
|
| 1085 |
+
"token_id": 24368,
|
| 1086 |
+
"utf8_hex": "474e"
|
| 1087 |
+
},
|
| 1088 |
+
{
|
| 1089 |
+
"code": "GO",
|
| 1090 |
+
"position": 181,
|
| 1091 |
+
"token_id": 15058,
|
| 1092 |
+
"utf8_hex": "474f"
|
| 1093 |
+
},
|
| 1094 |
+
{
|
| 1095 |
+
"code": "GP",
|
| 1096 |
+
"position": 182,
|
| 1097 |
+
"token_id": 23658,
|
| 1098 |
+
"utf8_hex": "4750"
|
| 1099 |
+
},
|
| 1100 |
+
{
|
| 1101 |
+
"code": "GR",
|
| 1102 |
+
"position": 183,
|
| 1103 |
+
"token_id": 8362,
|
| 1104 |
+
"utf8_hex": "4752"
|
| 1105 |
+
},
|
| 1106 |
+
{
|
| 1107 |
+
"code": "GS",
|
| 1108 |
+
"position": 184,
|
| 1109 |
+
"token_id": 16035,
|
| 1110 |
+
"utf8_hex": "4753"
|
| 1111 |
+
},
|
| 1112 |
+
{
|
| 1113 |
+
"code": "GT",
|
| 1114 |
+
"position": 185,
|
| 1115 |
+
"token_id": 24583,
|
| 1116 |
+
"utf8_hex": "4754"
|
| 1117 |
+
},
|
| 1118 |
+
{
|
| 1119 |
+
"code": "GU",
|
| 1120 |
+
"position": 186,
|
| 1121 |
+
"token_id": 52857,
|
| 1122 |
+
"utf8_hex": "4755"
|
| 1123 |
+
},
|
| 1124 |
+
{
|
| 1125 |
+
"code": "GV",
|
| 1126 |
+
"position": 187,
|
| 1127 |
+
"token_id": 38403,
|
| 1128 |
+
"utf8_hex": "4756"
|
| 1129 |
+
},
|
| 1130 |
+
{
|
| 1131 |
+
"code": "GW",
|
| 1132 |
+
"position": 188,
|
| 1133 |
+
"token_id": 60456,
|
| 1134 |
+
"utf8_hex": "4757"
|
| 1135 |
+
},
|
| 1136 |
+
{
|
| 1137 |
+
"code": "GX",
|
| 1138 |
+
"position": 189,
|
| 1139 |
+
"token_id": 81519,
|
| 1140 |
+
"utf8_hex": "4758"
|
| 1141 |
+
},
|
| 1142 |
+
{
|
| 1143 |
+
"code": "GY",
|
| 1144 |
+
"position": 190,
|
| 1145 |
+
"token_id": 40097,
|
| 1146 |
+
"utf8_hex": "4759"
|
| 1147 |
+
},
|
| 1148 |
+
{
|
| 1149 |
+
"code": "HA",
|
| 1150 |
+
"position": 191,
|
| 1151 |
+
"token_id": 16522,
|
| 1152 |
+
"utf8_hex": "4841"
|
| 1153 |
+
},
|
| 1154 |
+
{
|
| 1155 |
+
"code": "HB",
|
| 1156 |
+
"position": 192,
|
| 1157 |
+
"token_id": 29722,
|
| 1158 |
+
"utf8_hex": "4842"
|
| 1159 |
+
},
|
| 1160 |
+
{
|
| 1161 |
+
"code": "HC",
|
| 1162 |
+
"position": 193,
|
| 1163 |
+
"token_id": 21756,
|
| 1164 |
+
"utf8_hex": "4843"
|
| 1165 |
+
},
|
| 1166 |
+
{
|
| 1167 |
+
"code": "HD",
|
| 1168 |
+
"position": 194,
|
| 1169 |
+
"token_id": 18567,
|
| 1170 |
+
"utf8_hex": "4844"
|
| 1171 |
+
},
|
| 1172 |
+
{
|
| 1173 |
+
"code": "HE",
|
| 1174 |
+
"position": 195,
|
| 1175 |
+
"token_id": 1736,
|
| 1176 |
+
"utf8_hex": "4845"
|
| 1177 |
+
},
|
| 1178 |
+
{
|
| 1179 |
+
"code": "HF",
|
| 1180 |
+
"position": 196,
|
| 1181 |
+
"token_id": 48043,
|
| 1182 |
+
"utf8_hex": "4846"
|
| 1183 |
+
},
|
| 1184 |
+
{
|
| 1185 |
+
"code": "HG",
|
| 1186 |
+
"position": 197,
|
| 1187 |
+
"token_id": 87013,
|
| 1188 |
+
"utf8_hex": "4847"
|
| 1189 |
+
},
|
| 1190 |
+
{
|
| 1191 |
+
"code": "HH",
|
| 1192 |
+
"position": 198,
|
| 1193 |
+
"token_id": 22456,
|
| 1194 |
+
"utf8_hex": "4848"
|
| 1195 |
+
},
|
| 1196 |
+
{
|
| 1197 |
+
"code": "HI",
|
| 1198 |
+
"position": 199,
|
| 1199 |
+
"token_id": 23165,
|
| 1200 |
+
"utf8_hex": "4849"
|
| 1201 |
+
},
|
| 1202 |
+
{
|
| 1203 |
+
"code": "HK",
|
| 1204 |
+
"position": 200,
|
| 1205 |
+
"token_id": 55523,
|
| 1206 |
+
"utf8_hex": "484b"
|
| 1207 |
+
},
|
| 1208 |
+
{
|
| 1209 |
+
"code": "HL",
|
| 1210 |
+
"position": 201,
|
| 1211 |
+
"token_id": 13097,
|
| 1212 |
+
"utf8_hex": "484c"
|
| 1213 |
+
},
|
| 1214 |
+
{
|
| 1215 |
+
"code": "HM",
|
| 1216 |
+
"position": 202,
|
| 1217 |
+
"token_id": 50397,
|
| 1218 |
+
"utf8_hex": "484d"
|
| 1219 |
+
},
|
| 1220 |
+
{
|
| 1221 |
+
"code": "HN",
|
| 1222 |
+
"position": 203,
|
| 1223 |
+
"token_id": 41741,
|
| 1224 |
+
"utf8_hex": "484e"
|
| 1225 |
+
},
|
| 1226 |
+
{
|
| 1227 |
+
"code": "HO",
|
| 1228 |
+
"position": 204,
|
| 1229 |
+
"token_id": 23073,
|
| 1230 |
+
"utf8_hex": "484f"
|
| 1231 |
+
},
|
| 1232 |
+
{
|
| 1233 |
+
"code": "HP",
|
| 1234 |
+
"position": 205,
|
| 1235 |
+
"token_id": 6401,
|
| 1236 |
+
"utf8_hex": "4850"
|
| 1237 |
+
},
|
| 1238 |
+
{
|
| 1239 |
+
"code": "HQ",
|
| 1240 |
+
"position": 206,
|
| 1241 |
+
"token_id": 86538,
|
| 1242 |
+
"utf8_hex": "4851"
|
| 1243 |
+
},
|
| 1244 |
+
{
|
| 1245 |
+
"code": "HR",
|
| 1246 |
+
"position": 207,
|
| 1247 |
+
"token_id": 16585,
|
| 1248 |
+
"utf8_hex": "4852"
|
| 1249 |
+
},
|
| 1250 |
+
{
|
| 1251 |
+
"code": "HS",
|
| 1252 |
+
"position": 208,
|
| 1253 |
+
"token_id": 11622,
|
| 1254 |
+
"utf8_hex": "4853"
|
| 1255 |
+
},
|
| 1256 |
+
{
|
| 1257 |
+
"code": "HT",
|
| 1258 |
+
"position": 209,
|
| 1259 |
+
"token_id": 2464,
|
| 1260 |
+
"utf8_hex": "4854"
|
| 1261 |
+
},
|
| 1262 |
+
{
|
| 1263 |
+
"code": "HU",
|
| 1264 |
+
"position": 210,
|
| 1265 |
+
"token_id": 84982,
|
| 1266 |
+
"utf8_hex": "4855"
|
| 1267 |
+
},
|
| 1268 |
+
{
|
| 1269 |
+
"code": "HV",
|
| 1270 |
+
"position": 211,
|
| 1271 |
+
"token_id": 75516,
|
| 1272 |
+
"utf8_hex": "4856"
|
| 1273 |
+
},
|
| 1274 |
+
{
|
| 1275 |
+
"code": "HW",
|
| 1276 |
+
"position": 212,
|
| 1277 |
+
"token_id": 36984,
|
| 1278 |
+
"utf8_hex": "4857"
|
| 1279 |
+
},
|
| 1280 |
+
{
|
| 1281 |
+
"code": "HX",
|
| 1282 |
+
"position": 213,
|
| 1283 |
+
"token_id": 58795,
|
| 1284 |
+
"utf8_hex": "4858"
|
| 1285 |
+
},
|
| 1286 |
+
{
|
| 1287 |
+
"code": "HY",
|
| 1288 |
+
"position": 214,
|
| 1289 |
+
"token_id": 47217,
|
| 1290 |
+
"utf8_hex": "4859"
|
| 1291 |
+
},
|
| 1292 |
+
{
|
| 1293 |
+
"code": "HZ",
|
| 1294 |
+
"position": 215,
|
| 1295 |
+
"token_id": 59675,
|
| 1296 |
+
"utf8_hex": "485a"
|
| 1297 |
+
},
|
| 1298 |
+
{
|
| 1299 |
+
"code": "IA",
|
| 1300 |
+
"position": 216,
|
| 1301 |
+
"token_id": 5681,
|
| 1302 |
+
"utf8_hex": "4941"
|
| 1303 |
+
},
|
| 1304 |
+
{
|
| 1305 |
+
"code": "IB",
|
| 1306 |
+
"position": 217,
|
| 1307 |
+
"token_id": 3151,
|
| 1308 |
+
"utf8_hex": "4942"
|
| 1309 |
+
},
|
| 1310 |
+
{
|
| 1311 |
+
"code": "IC",
|
| 1312 |
+
"position": 218,
|
| 1313 |
+
"token_id": 1271,
|
| 1314 |
+
"utf8_hex": "4943"
|
| 1315 |
+
},
|
| 1316 |
+
{
|
| 1317 |
+
"code": "ID",
|
| 1318 |
+
"position": 219,
|
| 1319 |
+
"token_id": 887,
|
| 1320 |
+
"utf8_hex": "4944"
|
| 1321 |
+
},
|
| 1322 |
+
{
|
| 1323 |
+
"code": "IE",
|
| 1324 |
+
"position": 220,
|
| 1325 |
+
"token_id": 5203,
|
| 1326 |
+
"utf8_hex": "4945"
|
| 1327 |
+
},
|
| 1328 |
+
{
|
| 1329 |
+
"code": "IF",
|
| 1330 |
+
"position": 221,
|
| 1331 |
+
"token_id": 2685,
|
| 1332 |
+
"utf8_hex": "4946"
|
| 1333 |
+
},
|
| 1334 |
+
{
|
| 1335 |
+
"code": "IG",
|
| 1336 |
+
"position": 222,
|
| 1337 |
+
"token_id": 1849,
|
| 1338 |
+
"utf8_hex": "4947"
|
| 1339 |
+
},
|
| 1340 |
+
{
|
| 1341 |
+
"code": "IH",
|
| 1342 |
+
"position": 223,
|
| 1343 |
+
"token_id": 72470,
|
| 1344 |
+
"utf8_hex": "4948"
|
| 1345 |
+
},
|
| 1346 |
+
{
|
| 1347 |
+
"code": "II",
|
| 1348 |
+
"position": 224,
|
| 1349 |
+
"token_id": 5370,
|
| 1350 |
+
"utf8_hex": "4949"
|
| 1351 |
+
},
|
| 1352 |
+
{
|
| 1353 |
+
"code": "IJ",
|
| 1354 |
+
"position": 225,
|
| 1355 |
+
"token_id": 74063,
|
| 1356 |
+
"utf8_hex": "494a"
|
| 1357 |
+
},
|
| 1358 |
+
{
|
| 1359 |
+
"code": "IK",
|
| 1360 |
+
"position": 226,
|
| 1361 |
+
"token_id": 27629,
|
| 1362 |
+
"utf8_hex": "494b"
|
| 1363 |
+
},
|
| 1364 |
+
{
|
| 1365 |
+
"code": "IL",
|
| 1366 |
+
"position": 227,
|
| 1367 |
+
"token_id": 1655,
|
| 1368 |
+
"utf8_hex": "494c"
|
| 1369 |
+
},
|
| 1370 |
+
{
|
| 1371 |
+
"code": "IM",
|
| 1372 |
+
"position": 228,
|
| 1373 |
+
"token_id": 1728,
|
| 1374 |
+
"utf8_hex": "494d"
|
| 1375 |
+
},
|
| 1376 |
+
{
|
| 1377 |
+
"code": "IN",
|
| 1378 |
+
"position": 229,
|
| 1379 |
+
"token_id": 669,
|
| 1380 |
+
"utf8_hex": "494e"
|
| 1381 |
+
},
|
| 1382 |
+
{
|
| 1383 |
+
"code": "IO",
|
| 1384 |
+
"position": 230,
|
| 1385 |
+
"token_id": 3682,
|
| 1386 |
+
"utf8_hex": "494f"
|
| 1387 |
+
},
|
| 1388 |
+
{
|
| 1389 |
+
"code": "IP",
|
| 1390 |
+
"position": 231,
|
| 1391 |
+
"token_id": 3191,
|
| 1392 |
+
"utf8_hex": "4950"
|
| 1393 |
+
},
|
| 1394 |
+
{
|
| 1395 |
+
"code": "IQ",
|
| 1396 |
+
"position": 232,
|
| 1397 |
+
"token_id": 59865,
|
| 1398 |
+
"utf8_hex": "4951"
|
| 1399 |
+
},
|
| 1400 |
+
{
|
| 1401 |
+
"code": "IR",
|
| 1402 |
+
"position": 233,
|
| 1403 |
+
"token_id": 2712,
|
| 1404 |
+
"utf8_hex": "4952"
|
| 1405 |
+
},
|
| 1406 |
+
{
|
| 1407 |
+
"code": "IS",
|
| 1408 |
+
"position": 234,
|
| 1409 |
+
"token_id": 1580,
|
| 1410 |
+
"utf8_hex": "4953"
|
| 1411 |
+
},
|
| 1412 |
+
{
|
| 1413 |
+
"code": "IT",
|
| 1414 |
+
"position": 235,
|
| 1415 |
+
"token_id": 922,
|
| 1416 |
+
"utf8_hex": "4954"
|
| 1417 |
+
},
|
| 1418 |
+
{
|
| 1419 |
+
"code": "IU",
|
| 1420 |
+
"position": 236,
|
| 1421 |
+
"token_id": 77243,
|
| 1422 |
+
"utf8_hex": "4955"
|
| 1423 |
+
},
|
| 1424 |
+
{
|
| 1425 |
+
"code": "IV",
|
| 1426 |
+
"position": 237,
|
| 1427 |
+
"token_id": 2990,
|
| 1428 |
+
"utf8_hex": "4956"
|
| 1429 |
+
},
|
| 1430 |
+
{
|
| 1431 |
+
"code": "IW",
|
| 1432 |
+
"position": 238,
|
| 1433 |
+
"token_id": 78493,
|
| 1434 |
+
"utf8_hex": "4957"
|
| 1435 |
+
},
|
| 1436 |
+
{
|
| 1437 |
+
"code": "IX",
|
| 1438 |
+
"position": 239,
|
| 1439 |
+
"token_id": 5228,
|
| 1440 |
+
"utf8_hex": "4958"
|
| 1441 |
+
},
|
| 1442 |
+
{
|
| 1443 |
+
"code": "IZ",
|
| 1444 |
+
"position": 240,
|
| 1445 |
+
"token_id": 2750,
|
| 1446 |
+
"utf8_hex": "495a"
|
| 1447 |
+
},
|
| 1448 |
+
{
|
| 1449 |
+
"code": "JA",
|
| 1450 |
+
"position": 241,
|
| 1451 |
+
"token_id": 42711,
|
| 1452 |
+
"utf8_hex": "4a41"
|
| 1453 |
+
},
|
| 1454 |
+
{
|
| 1455 |
+
"code": "JB",
|
| 1456 |
+
"position": 242,
|
| 1457 |
+
"token_id": 44568,
|
| 1458 |
+
"utf8_hex": "4a42"
|
| 1459 |
+
},
|
| 1460 |
+
{
|
| 1461 |
+
"code": "JC",
|
| 1462 |
+
"position": 243,
|
| 1463 |
+
"token_id": 56402,
|
| 1464 |
+
"utf8_hex": "4a43"
|
| 1465 |
+
},
|
| 1466 |
+
{
|
| 1467 |
+
"code": "JD",
|
| 1468 |
+
"position": 244,
|
| 1469 |
+
"token_id": 48236,
|
| 1470 |
+
"utf8_hex": "4a44"
|
| 1471 |
+
},
|
| 1472 |
+
{
|
| 1473 |
+
"code": "JE",
|
| 1474 |
+
"position": 245,
|
| 1475 |
+
"token_id": 38993,
|
| 1476 |
+
"utf8_hex": "4a45"
|
| 1477 |
+
},
|
| 1478 |
+
{
|
| 1479 |
+
"code": "JI",
|
| 1480 |
+
"position": 246,
|
| 1481 |
+
"token_id": 43559,
|
| 1482 |
+
"utf8_hex": "4a49"
|
| 1483 |
+
},
|
| 1484 |
+
{
|
| 1485 |
+
"code": "JJ",
|
| 1486 |
+
"position": 247,
|
| 1487 |
+
"token_id": 61250,
|
| 1488 |
+
"utf8_hex": "4a4a"
|
| 1489 |
+
},
|
| 1490 |
+
{
|
| 1491 |
+
"code": "JK",
|
| 1492 |
+
"position": 248,
|
| 1493 |
+
"token_id": 32942,
|
| 1494 |
+
"utf8_hex": "4a4b"
|
| 1495 |
+
},
|
| 1496 |
+
{
|
| 1497 |
+
"code": "JM",
|
| 1498 |
+
"position": 249,
|
| 1499 |
+
"token_id": 86302,
|
| 1500 |
+
"utf8_hex": "4a4d"
|
| 1501 |
+
},
|
| 1502 |
+
{
|
| 1503 |
+
"code": "JO",
|
| 1504 |
+
"position": 250,
|
| 1505 |
+
"token_id": 25310,
|
| 1506 |
+
"utf8_hex": "4a4f"
|
| 1507 |
+
},
|
| 1508 |
+
{
|
| 1509 |
+
"code": "JP",
|
| 1510 |
+
"position": 251,
|
| 1511 |
+
"token_id": 26313,
|
| 1512 |
+
"utf8_hex": "4a50"
|
| 1513 |
+
},
|
| 1514 |
+
{
|
| 1515 |
+
"code": "JR",
|
| 1516 |
+
"position": 252,
|
| 1517 |
+
"token_id": 81770,
|
| 1518 |
+
"utf8_hex": "4a52"
|
| 1519 |
+
},
|
| 1520 |
+
{
|
| 1521 |
+
"code": "JS",
|
| 1522 |
+
"position": 253,
|
| 1523 |
+
"token_id": 12185,
|
| 1524 |
+
"utf8_hex": "4a53"
|
| 1525 |
+
},
|
| 1526 |
+
{
|
| 1527 |
+
"code": "JT",
|
| 1528 |
+
"position": 254,
|
| 1529 |
+
"token_id": 78382,
|
| 1530 |
+
"utf8_hex": "4a54"
|
| 1531 |
+
}
|
| 1532 |
+
]
|
assets/examples/bounding-box.png
ADDED
|
Git LFS Details
|
assets/examples/intent-classification.png
ADDED
|
assets/examples/visual-coordinates.png
ADDED
|
Git LFS Details
|
assets/runtime_manifest.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"adapter": {
|
| 3 |
+
"adapter_config_sha256": "a17d24feae695bb21f1635d865f41ecc0e65f3b9e05f8a5e8e8ad5deb97535be",
|
| 4 |
+
"adapter_model_sha256": "2b2521cff2a98afdd42e3051c8c6de60ad928b1268244f2c13b4193059c6e1f4",
|
| 5 |
+
"adapter_tensor_digest": "f0e421ca7439a84b50f840d34dea59454c4cc389215101a79ae35c38d0309a00",
|
| 6 |
+
"default_path": "adapter",
|
| 7 |
+
"distribution": "bundled_sanitized_adapter"
|
| 8 |
+
},
|
| 9 |
+
"admission": {
|
| 10 |
+
"accepted_modes": [
|
| 11 |
+
"atomic",
|
| 12 |
+
"numeric101_text",
|
| 13 |
+
"numeric101_image",
|
| 14 |
+
"finite_joint"
|
| 15 |
+
],
|
| 16 |
+
"default_max_input_tokens": 262144,
|
| 17 |
+
"note": "Operational default, not a model context-length claim. Requests over the configured limit fail without truncation."
|
| 18 |
+
},
|
| 19 |
+
"assets": {
|
| 20 |
+
"base_files": "assets/base_files.sha256.json",
|
| 21 |
+
"base_files_sha256": "c163d38c5dcd10d307ec81f5b4709b8ec154ef3640d2197bdd1bba3ce27e5993",
|
| 22 |
+
"codebook255": "assets/codebook255.json",
|
| 23 |
+
"codebook255_sha256": "4e2e52b4baf578c66bc1d9cf713cb9208ebbd44ce4bd09ed55d79f48fc0f67b7"
|
| 24 |
+
},
|
| 25 |
+
"base": {
|
| 26 |
+
"attention": "sdpa",
|
| 27 |
+
"dtype": "torch.bfloat16",
|
| 28 |
+
"model_id": "harshatheg/GPC-1",
|
| 29 |
+
"revision": "base-278124e95f96d21b",
|
| 30 |
+
"tokenizer_json_sha256": "5f9e4d4901a92b997e463c1f46055088b6cca5ca61a6522d1b9f64c4bb81cb42",
|
| 31 |
+
"upstream_model_id": "Qwen/Qwen3.5-35B-A3B",
|
| 32 |
+
"upstream_revision": "59d61f3ce65a6d9863b86d2e96597125219dc754"
|
| 33 |
+
},
|
| 34 |
+
"protocol": "gpc1-inference-release-runtime-v1",
|
| 35 |
+
"served_model_id": "gpc-1",
|
| 36 |
+
"weight_format": "adapter"
|
| 37 |
+
}
|
config.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"Qwen3_5MoeForConditionalGeneration"
|
| 4 |
+
],
|
| 5 |
+
"gpc1_release": {
|
| 6 |
+
"adapter_path": "adapter",
|
| 7 |
+
"format_version": 1,
|
| 8 |
+
"model_path": "model",
|
| 9 |
+
"runtime_manifest": "assets/runtime_manifest.json",
|
| 10 |
+
"runtime_sha256": "d6b0407aef04676d26d54c8dabd20de72e3ecb977172ebcd843837e059f60d91"
|
| 11 |
+
},
|
| 12 |
+
"image_token_id": 248056,
|
| 13 |
+
"model_type": "qwen3_5_moe",
|
| 14 |
+
"text_config": {
|
| 15 |
+
"attention_bias": false,
|
| 16 |
+
"attention_dropout": 0.0,
|
| 17 |
+
"attn_output_gate": true,
|
| 18 |
+
"dtype": "bfloat16",
|
| 19 |
+
"eos_token_id": 248044,
|
| 20 |
+
"full_attention_interval": 4,
|
| 21 |
+
"head_dim": 256,
|
| 22 |
+
"hidden_act": "silu",
|
| 23 |
+
"hidden_size": 2048,
|
| 24 |
+
"initializer_range": 0.02,
|
| 25 |
+
"layer_types": [
|
| 26 |
+
"linear_attention",
|
| 27 |
+
"linear_attention",
|
| 28 |
+
"linear_attention",
|
| 29 |
+
"full_attention",
|
| 30 |
+
"linear_attention",
|
| 31 |
+
"linear_attention",
|
| 32 |
+
"linear_attention",
|
| 33 |
+
"full_attention",
|
| 34 |
+
"linear_attention",
|
| 35 |
+
"linear_attention",
|
| 36 |
+
"linear_attention",
|
| 37 |
+
"full_attention",
|
| 38 |
+
"linear_attention",
|
| 39 |
+
"linear_attention",
|
| 40 |
+
"linear_attention",
|
| 41 |
+
"full_attention",
|
| 42 |
+
"linear_attention",
|
| 43 |
+
"linear_attention",
|
| 44 |
+
"linear_attention",
|
| 45 |
+
"full_attention",
|
| 46 |
+
"linear_attention",
|
| 47 |
+
"linear_attention",
|
| 48 |
+
"linear_attention",
|
| 49 |
+
"full_attention",
|
| 50 |
+
"linear_attention",
|
| 51 |
+
"linear_attention",
|
| 52 |
+
"linear_attention",
|
| 53 |
+
"full_attention",
|
| 54 |
+
"linear_attention",
|
| 55 |
+
"linear_attention",
|
| 56 |
+
"linear_attention",
|
| 57 |
+
"full_attention",
|
| 58 |
+
"linear_attention",
|
| 59 |
+
"linear_attention",
|
| 60 |
+
"linear_attention",
|
| 61 |
+
"full_attention",
|
| 62 |
+
"linear_attention",
|
| 63 |
+
"linear_attention",
|
| 64 |
+
"linear_attention",
|
| 65 |
+
"full_attention"
|
| 66 |
+
],
|
| 67 |
+
"linear_conv_kernel_dim": 4,
|
| 68 |
+
"linear_key_head_dim": 128,
|
| 69 |
+
"linear_num_key_heads": 16,
|
| 70 |
+
"linear_num_value_heads": 32,
|
| 71 |
+
"linear_value_head_dim": 128,
|
| 72 |
+
"mamba_ssm_dtype": "float32",
|
| 73 |
+
"max_position_embeddings": 262144,
|
| 74 |
+
"mlp_only_layers": [],
|
| 75 |
+
"model_type": "qwen3_5_moe_text",
|
| 76 |
+
"moe_intermediate_size": 512,
|
| 77 |
+
"mtp_num_hidden_layers": 1,
|
| 78 |
+
"mtp_use_dedicated_embeddings": false,
|
| 79 |
+
"num_attention_heads": 16,
|
| 80 |
+
"num_experts": 256,
|
| 81 |
+
"num_experts_per_tok": 8,
|
| 82 |
+
"num_hidden_layers": 40,
|
| 83 |
+
"num_key_value_heads": 2,
|
| 84 |
+
"rms_norm_eps": 1e-06,
|
| 85 |
+
"rope_parameters": {
|
| 86 |
+
"mrope_interleaved": true,
|
| 87 |
+
"mrope_section": [
|
| 88 |
+
11,
|
| 89 |
+
11,
|
| 90 |
+
10
|
| 91 |
+
],
|
| 92 |
+
"partial_rotary_factor": 0.25,
|
| 93 |
+
"rope_theta": 10000000,
|
| 94 |
+
"rope_type": "default"
|
| 95 |
+
},
|
| 96 |
+
"router_aux_loss_coef": 0.001,
|
| 97 |
+
"shared_expert_intermediate_size": 512,
|
| 98 |
+
"use_cache": true,
|
| 99 |
+
"vocab_size": 248320
|
| 100 |
+
},
|
| 101 |
+
"tie_word_embeddings": false,
|
| 102 |
+
"transformers_version": "4.57.0.dev0",
|
| 103 |
+
"video_token_id": 248057,
|
| 104 |
+
"vision_config": {
|
| 105 |
+
"deepstack_visual_indexes": [],
|
| 106 |
+
"depth": 27,
|
| 107 |
+
"hidden_act": "gelu_pytorch_tanh",
|
| 108 |
+
"hidden_size": 1152,
|
| 109 |
+
"in_channels": 3,
|
| 110 |
+
"initializer_range": 0.02,
|
| 111 |
+
"intermediate_size": 4304,
|
| 112 |
+
"model_type": "qwen3_5_moe",
|
| 113 |
+
"num_heads": 16,
|
| 114 |
+
"num_position_embeddings": 2304,
|
| 115 |
+
"out_hidden_size": 2048,
|
| 116 |
+
"patch_size": 16,
|
| 117 |
+
"spatial_merge_size": 2,
|
| 118 |
+
"temporal_patch_size": 2
|
| 119 |
+
},
|
| 120 |
+
"vision_end_token_id": 248054,
|
| 121 |
+
"vision_start_token_id": 248053
|
| 122 |
+
}
|
demo/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: GPC-1
|
| 3 |
+
emoji: ◈
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 6.9.0
|
| 8 |
+
python_version: '3.12'
|
| 9 |
+
app_file: app.py
|
| 10 |
+
models:
|
| 11 |
+
- harshatheg/GPC-1
|
| 12 |
+
short_description: Context in. Structured predictions out.
|
| 13 |
+
license: apache-2.0
|
| 14 |
+
header: mini
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
# GPC-1 demo
|
| 18 |
+
|
| 19 |
+
Upload context, define your output, and select **Run**. Presets cover classification, numerical analysis, boxes, pose, pixel art, and structured decisions.
|
| 20 |
+
|
| 21 |
+
## Hosted demo: ZeroGPU
|
| 22 |
+
|
| 23 |
+
The [hosted demo](https://huggingface.co/spaces/harshatheg/GPC-1-Demo) loads GPC-1 directly on Hugging Face ZeroGPU. No separate inference server is needed. To host that version yourself, duplicate the Space, select ZeroGPU hardware, and add an `HF_TOKEN` secret with read access if the model requires it.
|
| 24 |
+
|
| 25 |
+
## Server-backed demo
|
| 26 |
+
|
| 27 |
+
This directory contains a lightweight UI for an existing GPC-1 NVIDIA server. Start the server using the [setup guide](../README.md#get-started), then run these commands from the release directory:
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
cd demo
|
| 31 |
+
python -m pip install -r requirements.txt
|
| 32 |
+
export GPC1_API_URL=http://127.0.0.1:8000
|
| 33 |
+
export GPC1_API_KEY=YOUR_SERVER_KEY
|
| 34 |
+
python app.py
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
### Host the server-backed UI on Spaces
|
| 38 |
+
|
| 39 |
+
Copy this directory to a Gradio Space. Configure `GPC1_API_URL` as the HTTPS origin of your NVIDIA server and `GPC1_API_KEY` as a **Space secret**. The lightweight UI calls the server; it does not load model weights on the Space CPU.
|
| 40 |
+
|
| 41 |
+
The key stays server-side. Requests are not retried automatically. Uploaded context is sent only to the configured model endpoint; temporary uploads are periodically removed. Images are accepted for numeric outputs. Numeric fields use finite-resolution ranges; JSON records select from the records supplied.
|
| 42 |
+
|
| 43 |
+
## Input limits
|
| 44 |
+
|
| 45 |
+
The UI accepts up to 256 KiB of context text and a 4 MiB request body. The hosted ZeroGPU version also limits compiled inputs to 8,192 tokens. Use the server API directly for larger inputs; see [context configuration](../API.md#context-window) for its 262,144-token ceiling and hardware requirements.
|
| 46 |
+
|
| 47 |
+
[Model and API](https://huggingface.co/harshatheg/GPC-1)
|
| 48 |
+
|
| 49 |
+
The person photo used by the bounding-box and pose presets is [“Tim running” by Liber4l](https://commons.wikimedia.org/wiki/File:Tim_running.jpg), public domain.
|
demo/app.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import gradio as gr
|
| 6 |
+
from PIL import Image, ImageDraw
|
| 7 |
+
from client import MODES, build_request, infer
|
| 8 |
+
from presets import preset
|
| 9 |
+
|
| 10 |
+
CSS = """
|
| 11 |
+
body, .gradio-container { background: #fff !important; color-scheme: light; }
|
| 12 |
+
.gradio-container { max-width: 1160px !important; padding: 36px 24px !important; }
|
| 13 |
+
#heading h1 { font-size: 32px; font-weight: 650; letter-spacing: -1.1px; margin-bottom: 4px; }
|
| 14 |
+
#heading p { color: #677078; font-size: 15px; }
|
| 15 |
+
#main { gap: 32px; margin-top: 24px; }
|
| 16 |
+
#run { min-height: 46px; border-radius: 10px; }
|
| 17 |
+
button:active { transform: scale(.96); }
|
| 18 |
+
button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px solid #147f85; outline-offset: 2px; }
|
| 19 |
+
#timing { color: #677078; font-size: 12px; min-height: 18px; }
|
| 20 |
+
.image-container { outline: 1px solid rgb(0 0 0 / .1); }
|
| 21 |
+
.gradio-container .cm-editor, .gradio-container .cm-content,
|
| 22 |
+
.gradio-container .cm-gutters { background: #f8fafb !important; color: #17252b !important; }
|
| 23 |
+
.gradio-container .cm-content span { color: #17252b !important; }
|
| 24 |
+
.gradio-container .cm-gutters { color: #52616b !important; }
|
| 25 |
+
.gradio-container .cm-cursor { border-left-color: #17252b !important; }
|
| 26 |
+
.gradio-container .cm-selectionBackground { background: #cce8ea !important; }
|
| 27 |
+
.gradio-container .cm-activeLine, .gradio-container .cm-activeLineGutter { background: #edf3f4 !important; }
|
| 28 |
+
.gradio-container .icon-button { color: #52616b !important; }
|
| 29 |
+
.gradio-container button[aria-label="Paste from clipboard"] { color: #52616b !important; }
|
| 30 |
+
.gradio-container button { transition-property: background-color, color, border-color; transition-duration: 120ms; }
|
| 31 |
+
@media(max-width: 650px) { #heading, #main { margin-inline: -24px; width: calc(100% + 48px); } #main { gap: 24px; } }
|
| 32 |
+
@media(prefers-reduced-motion: reduce) { button:active { transform: none; } }
|
| 33 |
+
"""
|
| 34 |
+
LIGHT_JS = """() => { document.documentElement.classList.remove('dark'); document.body.classList.remove('dark'); }"""
|
| 35 |
+
|
| 36 |
+
def visual_result(image, result, details):
|
| 37 |
+
fields=details.get('fields',{})
|
| 38 |
+
means={k:v['expected_value'] for k,v in fields.items()}
|
| 39 |
+
if image is not None:
|
| 40 |
+
output=image.convert('RGB').copy(); draw=ImageDraw.Draw(output); w,h=output.size
|
| 41 |
+
annotated=False
|
| 42 |
+
if {'x_min','y_min','x_max','y_max'} <= set(means):
|
| 43 |
+
box=[means[k] for k in ('x_min','y_min','x_max','y_max')]
|
| 44 |
+
if all(0<=v<=1 for v in box) and box[0]<box[2] and box[1]<box[3]:
|
| 45 |
+
draw.rectangle((box[0]*w,box[1]*h,box[2]*w,box[3]*h),outline='#008f95',width=max(2,w//180))
|
| 46 |
+
annotated=True
|
| 47 |
+
for key,x in means.items():
|
| 48 |
+
if key.endswith('_x') and key[:-2]+'_y' in means:
|
| 49 |
+
y=means[key[:-2]+'_y']; r=max(3,w//100)
|
| 50 |
+
if 0<=x<=1 and 0<=y<=1:
|
| 51 |
+
draw.ellipse((x*w-r,y*h-r,x*w+r,y*h+r), fill='#f56538',outline='white',width=2)
|
| 52 |
+
annotated=True
|
| 53 |
+
return output if annotated else None
|
| 54 |
+
keys=[f'p{r}_{c}' for r in range(5) for c in range(5)]
|
| 55 |
+
if set(result)==set(keys):
|
| 56 |
+
palette=['#ffffff','#238454','#ef625c','#f3bd43']
|
| 57 |
+
out=Image.new('RGB',(5,5),'white')
|
| 58 |
+
for r in range(5):
|
| 59 |
+
for c in range(5):
|
| 60 |
+
value=result[f'p{r}_{c}']
|
| 61 |
+
out.putpixel((c,r),tuple(bytes.fromhex(palette[max(0,min(3,round(value)))][1:])))
|
| 62 |
+
return out.resize((400,400),Image.Resampling.NEAREST)
|
| 63 |
+
return None
|
| 64 |
+
|
| 65 |
+
def run(context, image, mode, definition, context_file, infer_function=None):
|
| 66 |
+
try:
|
| 67 |
+
payload=build_request(context,image,mode,definition,context_file)
|
| 68 |
+
result,details,elapsed=(infer_function or infer)(payload)
|
| 69 |
+
picture=visual_result(image,result,details)
|
| 70 |
+
summaries={k:{'most_likely':v['map_value'],'mean':v['expected_value']} for k,v in details.get('fields',{}).items()}
|
| 71 |
+
timing_label='Model execution' if infer_function else 'API round trip'
|
| 72 |
+
return result,gr.update(value=picture,visible=True if picture is not None else 'hidden'),f'{elapsed:.2f} s · {timing_label}',summaries or details,details
|
| 73 |
+
except (ValueError, RuntimeError) as error:
|
| 74 |
+
raise gr.Error(str(error)) from None
|
| 75 |
+
|
| 76 |
+
def select_preset(name):
|
| 77 |
+
mode,context,definition=preset(name)
|
| 78 |
+
picture=Path(__file__).parent/'assets/person.jpg'
|
| 79 |
+
image=str(picture) if name in {'Bounding box','Human pose'} and picture.exists() else None
|
| 80 |
+
return mode,context,definition,image,None,None,gr.update(value=None,visible='hidden'),'',None,None,gr.update(open=image is not None)
|
| 81 |
+
|
| 82 |
+
def mode_default(mode):
|
| 83 |
+
names={'Categories':'Support routing','Numeric ranges':'Numerical analysis','JSON records':'Workflow decision'}
|
| 84 |
+
return preset(names[mode])[2]
|
| 85 |
+
|
| 86 |
+
def clear_output():
|
| 87 |
+
return None, gr.update(value=None,visible='hidden'), '', None, None
|
| 88 |
+
|
| 89 |
+
def create_demo(infer_function=None):
|
| 90 |
+
mode,context,definition=preset('Support routing')
|
| 91 |
+
with gr.Blocks(title='GPC-1',analytics_enabled=False,delete_cache=(600,600)) as demo:
|
| 92 |
+
gr.Markdown('# GPC-1\nContext in. Structured predictions out.',elem_id='heading')
|
| 93 |
+
with gr.Row(elem_id='main'):
|
| 94 |
+
with gr.Column(scale=1,min_width=300):
|
| 95 |
+
examples=gr.Dropdown(['Support routing','Numerical analysis','Bounding box','Human pose','Pixel art','Workflow decision'],value='Support routing',label='Try an example',filterable=False)
|
| 96 |
+
text=gr.Textbox(value=context,label='Context',placeholder='Describe the task and add your context…',lines=5,max_lines=12)
|
| 97 |
+
with gr.Accordion('Add an image or text file',open=False) as attachments:
|
| 98 |
+
image=gr.Image(type='pil',sources=['upload','clipboard'],label='Image · numeric ranges',height=220,format='png')
|
| 99 |
+
file=gr.File(file_types=['.txt','.md','.json'],file_count='single',label='Context file',type='filepath')
|
| 100 |
+
mode_input=gr.Radio(MODES,value=mode,label='Output type')
|
| 101 |
+
fields=gr.Code(value=definition,language='json',label='Define your JSON output',lines=9,max_lines=16,interactive=True)
|
| 102 |
+
button=gr.Button('Run',variant='primary',elem_id='run')
|
| 103 |
+
with gr.Column(scale=1,min_width=300):
|
| 104 |
+
result=gr.JSON(label='Output',value=None)
|
| 105 |
+
timing=gr.Markdown('',elem_id='timing')
|
| 106 |
+
picture=gr.Image(label='Visualization',visible='hidden',interactive=False,type='pil',format='png')
|
| 107 |
+
with gr.Accordion('Scores',open=False):
|
| 108 |
+
summaries=gr.JSON(label='Summary')
|
| 109 |
+
gr.Markdown('Numeric overlays use probability-weighted means. Pixel art rounds the most likely value to the nearest palette entry. [Score interpretation](https://huggingface.co/harshatheg/GPC-1/blob/main/API.md#interpreting-scores).')
|
| 110 |
+
details=gr.JSON(label='Full distributions')
|
| 111 |
+
mode_input.input(mode_default,inputs=mode_input,outputs=fields,queue=False)
|
| 112 |
+
examples.input(select_preset,inputs=examples,outputs=[mode_input,text,fields,image,file,result,picture,timing,summaries,details,attachments],queue=False)
|
| 113 |
+
for component in (text,image,mode_input,fields):
|
| 114 |
+
component.input(clear_output,outputs=[result,picture,timing,summaries,details],queue=False)
|
| 115 |
+
file.change(clear_output,outputs=[result,picture,timing,summaries,details],queue=False)
|
| 116 |
+
from functools import partial
|
| 117 |
+
runner=partial(run,infer_function=infer_function) if infer_function else run
|
| 118 |
+
button.click(runner,inputs=[text,image,mode_input,fields,file],outputs=[result,picture,timing,summaries,details],concurrency_limit=1,api_name='predict')
|
| 119 |
+
return demo.queue(max_size=8,default_concurrency_limit=1)
|
| 120 |
+
|
| 121 |
+
def light_theme():
|
| 122 |
+
theme=gr.themes.Base(primary_hue='teal',neutral_hue='gray',font=['Inter','system-ui','sans-serif']).set(
|
| 123 |
+
background_fill_primary='#ffffff', background_fill_secondary='#f8fafb',
|
| 124 |
+
body_background_fill='#ffffff', body_text_color='#17252b', body_text_color_subdued='#52616b',
|
| 125 |
+
block_background_fill='#ffffff', block_label_background_fill='#ffffff',
|
| 126 |
+
block_label_text_color='#52616b', block_title_text_color='#52616b',
|
| 127 |
+
input_background_fill='#f8fafb', input_placeholder_color='#52616b',
|
| 128 |
+
input_border_color='#a7b2b9', input_border_width='1px', input_border_color_focus='#147f85',
|
| 129 |
+
button_primary_background_fill='#147f85', button_primary_background_fill_hover='#106970',
|
| 130 |
+
button_primary_border_color='#147f85', button_primary_border_color_hover='#106970',
|
| 131 |
+
button_primary_text_color='#ffffff', button_primary_text_color_hover='#ffffff',
|
| 132 |
+
button_secondary_background_fill='#f0f4f5', button_secondary_background_fill_hover='#e3eaed',
|
| 133 |
+
button_secondary_text_color='#17252b', button_secondary_text_color_hover='#17252b',
|
| 134 |
+
checkbox_label_background_fill='#f0f4f5', checkbox_label_background_fill_selected='#deeff0',
|
| 135 |
+
checkbox_label_text_color='#17252b', checkbox_label_text_color_selected='#114e53',
|
| 136 |
+
checkbox_background_color_selected='#147f85', checkbox_border_color_selected='#147f85',
|
| 137 |
+
checkbox_border_color='#87969f', checkbox_border_color_hover='#52616b',
|
| 138 |
+
color_accent='#147f85', code_background_fill='#f8fafb')
|
| 139 |
+
# Gradio may inherit dark mode from the host page. Keep every token paired.
|
| 140 |
+
tokens=theme.to_dict()['theme']
|
| 141 |
+
return theme.set(background_fill_primary=tokens['background_fill_primary'],
|
| 142 |
+
background_fill_secondary=tokens['background_fill_secondary'],
|
| 143 |
+
**{key:tokens[key[:-5]] for key in tokens if key.endswith('_dark') and key[:-5] in tokens})
|
| 144 |
+
|
| 145 |
+
def launch_demo(infer_function=None,blocked_paths=None):
|
| 146 |
+
create_demo(infer_function).launch(server_name=os.getenv('GRADIO_SERVER_NAME','127.0.0.1'),server_port=int(os.getenv('PORT','7860')),
|
| 147 |
+
theme=light_theme(),css=CSS,js=LIGHT_JS,show_error=False,max_file_size='3mb',footer_links=[],blocked_paths=blocked_paths)
|
| 148 |
+
|
| 149 |
+
if __name__=='__main__':
|
| 150 |
+
launch_demo()
|
demo/assets/person.jpg
ADDED
|
Git LFS Details
|
demo/client.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Build typed requests and call a configured GPC-1 server."""
|
| 2 |
+
import base64
|
| 3 |
+
from io import BytesIO
|
| 4 |
+
import json
|
| 5 |
+
import math
|
| 6 |
+
import os
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
import time
|
| 9 |
+
from urllib.parse import urlsplit
|
| 10 |
+
|
| 11 |
+
import httpx
|
| 12 |
+
|
| 13 |
+
MODES = ('Categories', 'Numeric ranges', 'JSON records')
|
| 14 |
+
MAX_TEXT_BYTES = 256 * 1024
|
| 15 |
+
MAX_IMAGE_BYTES = 3 * 1024 * 1024
|
| 16 |
+
|
| 17 |
+
def parse_json(value):
|
| 18 |
+
if not isinstance(value, str): raise ValueError('Define the output as JSON.')
|
| 19 |
+
def reject(value): raise ValueError('Use finite JSON numbers.')
|
| 20 |
+
def pairs(items):
|
| 21 |
+
result = {}
|
| 22 |
+
for key, item in items:
|
| 23 |
+
if key in result: raise ValueError('JSON keys must be unique.')
|
| 24 |
+
result[key] = item
|
| 25 |
+
return result
|
| 26 |
+
if len(value.encode()) > 64 * 1024: raise ValueError('Output definition is too large.')
|
| 27 |
+
try: return json.loads(value, parse_constant=reject, object_pairs_hook=pairs)
|
| 28 |
+
except json.JSONDecodeError as error: raise ValueError(f'Check JSON at line {error.lineno}.') from None
|
| 29 |
+
except RecursionError: raise ValueError('Output definition is too deeply nested.') from None
|
| 30 |
+
|
| 31 |
+
def key_name(key):
|
| 32 |
+
if not isinstance(key, str) or not key.strip() or key != key.strip() or len(key) > 64:
|
| 33 |
+
raise ValueError('Use a nonempty JSON key of up to 64 characters.')
|
| 34 |
+
return key
|
| 35 |
+
|
| 36 |
+
def value_type(value):
|
| 37 |
+
if value is None: return 'null'
|
| 38 |
+
if isinstance(value, bool): return 'boolean'
|
| 39 |
+
if isinstance(value, str): return 'string'
|
| 40 |
+
if isinstance(value, (int, float)) and math.isfinite(value): return 'number'
|
| 41 |
+
raise ValueError('Record values must be strings, finite numbers, booleans, or null.')
|
| 42 |
+
|
| 43 |
+
def enum_schema(values):
|
| 44 |
+
types = {value_type(x) for x in values}
|
| 45 |
+
if len(types) != 1: raise ValueError('Use the same value type for each key.')
|
| 46 |
+
unique = list({json.dumps(x, sort_keys=True): x for x in values}.values())
|
| 47 |
+
return {'type': types.pop(), 'enum': unique}
|
| 48 |
+
|
| 49 |
+
def build_request(context, image, mode, definition, context_file=None):
|
| 50 |
+
if mode not in MODES: raise ValueError('Choose an output type.')
|
| 51 |
+
if context is None: context = ''
|
| 52 |
+
if not isinstance(context, str): raise ValueError('Enter text for the context.')
|
| 53 |
+
if context_file:
|
| 54 |
+
try: path = Path(context_file)
|
| 55 |
+
except TypeError: raise ValueError('Upload a text file.') from None
|
| 56 |
+
if path.suffix.lower() not in {'.txt', '.md', '.json'}:
|
| 57 |
+
raise ValueError('Upload a UTF-8 text, Markdown, or JSON file.')
|
| 58 |
+
try:
|
| 59 |
+
if path.stat().st_size > MAX_TEXT_BYTES: raise ValueError('Context file exceeds 256 KiB.')
|
| 60 |
+
uploaded = path.read_text(encoding='utf-8')
|
| 61 |
+
except UnicodeError: raise ValueError('Context file must contain UTF-8 text.') from None
|
| 62 |
+
except OSError: raise ValueError('Cannot read the context file. Upload it again.') from None
|
| 63 |
+
context = context + '\n\n' + uploaded
|
| 64 |
+
if not context.strip(): raise ValueError('Add context or upload a text file.')
|
| 65 |
+
if len(context.encode()) > MAX_TEXT_BYTES: raise ValueError('Context exceeds 256 KiB. Nothing was truncated.')
|
| 66 |
+
definition = parse_json(definition)
|
| 67 |
+
properties = {}
|
| 68 |
+
if mode == 'Categories':
|
| 69 |
+
if not isinstance(definition, dict) or len(definition) != 1:
|
| 70 |
+
raise ValueError('Define one key with a list of choices, such as {"intent": ["sales", "support"]}.')
|
| 71 |
+
key, choices = next(iter(definition.items())); key_name(key)
|
| 72 |
+
if not isinstance(choices, list) or not 2 <= len(choices) <= 255:
|
| 73 |
+
raise ValueError('Supply 2 to 255 choices.')
|
| 74 |
+
properties[key] = enum_schema(choices)
|
| 75 |
+
if len(properties[key]['enum']) != len(choices): raise ValueError('Choices must be unique.')
|
| 76 |
+
options = dict(mode='atomic', field_key=key, choices=choices)
|
| 77 |
+
elif mode == 'Numeric ranges':
|
| 78 |
+
if not isinstance(definition, list) or not 1 <= len(definition) <= 128:
|
| 79 |
+
raise ValueError('Supply 1 to 128 field definitions.')
|
| 80 |
+
fields = []
|
| 81 |
+
for field in definition:
|
| 82 |
+
if not isinstance(field, dict) or set(field) != {'key','description','minimum','maximum','unit'}:
|
| 83 |
+
raise ValueError('Each field needs key, description, minimum, maximum, and unit.')
|
| 84 |
+
key = key_name(field['key'])
|
| 85 |
+
if key in properties: raise ValueError('Field keys must be unique.')
|
| 86 |
+
for name in ('description', 'unit'):
|
| 87 |
+
if not isinstance(field[name], str) or not field[name].strip() or field[name] != field[name].strip():
|
| 88 |
+
raise ValueError('Add a description and unit without surrounding whitespace.')
|
| 89 |
+
low, high = field['minimum'], field['maximum']
|
| 90 |
+
if any(isinstance(x, bool) or not isinstance(x,(int,float)) or not math.isfinite(x) for x in (low,high)) or low >= high:
|
| 91 |
+
raise ValueError('Minimum and maximum must be finite numbers, with minimum below maximum.')
|
| 92 |
+
fields.append(field)
|
| 93 |
+
properties[key] = dict(type='number', minimum=low, maximum=high)
|
| 94 |
+
options = dict(mode='numeric101', numeric_fields=fields)
|
| 95 |
+
else:
|
| 96 |
+
if not isinstance(definition, list) or not 2 <= len(definition) <= 32 or any(not isinstance(x,dict) or not x for x in definition):
|
| 97 |
+
raise ValueError('Supply 2 to 32 complete JSON records.')
|
| 98 |
+
if len({json.dumps(x,sort_keys=True) for x in definition}) != len(definition): raise ValueError('Records must be unique.')
|
| 99 |
+
keys = list(definition[0])
|
| 100 |
+
if any(set(x) != set(keys) for x in definition): raise ValueError('Every record must have the same keys.')
|
| 101 |
+
for key in keys:
|
| 102 |
+
key_name(key); properties[key] = enum_schema([x[key] for x in definition])
|
| 103 |
+
options = dict(mode='finite_joint', allowed_records=definition)
|
| 104 |
+
content = context
|
| 105 |
+
if image is not None:
|
| 106 |
+
if mode != 'Numeric ranges': raise ValueError('Images support Numeric ranges. Choose that type or remove the image.')
|
| 107 |
+
from PIL import Image
|
| 108 |
+
if not isinstance(image, Image.Image): raise ValueError('Upload a valid image.')
|
| 109 |
+
if image.width*image.height > 16777216: raise ValueError('Image exceeds 16 megapixels. Resize before uploading.')
|
| 110 |
+
try:
|
| 111 |
+
buffer = BytesIO(); image.convert('RGB').save(buffer, format='PNG')
|
| 112 |
+
except (OSError, ValueError): raise ValueError('Cannot read the image. Upload it again.') from None
|
| 113 |
+
if buffer.tell() > MAX_IMAGE_BYTES: raise ValueError('Encoded image exceeds 3 MiB. Resize before uploading.')
|
| 114 |
+
uri = 'data:image/png;base64,' + base64.b64encode(buffer.getvalue()).decode()
|
| 115 |
+
content = [dict(type='text', text=context), dict(type='image_url', image_url=dict(url=uri))]
|
| 116 |
+
payload = dict(model='gpc-1', messages=[dict(role='user', content=content)],
|
| 117 |
+
response_format=dict(type='json_schema', json_schema=dict(name='result',strict=True,
|
| 118 |
+
schema=dict(type='object',properties=properties,required=list(properties),additionalProperties=False))),
|
| 119 |
+
gpc1=options, stream=False)
|
| 120 |
+
if len(json.dumps(payload).encode()) > 4*1024*1024: raise ValueError('Request exceeds 4 MiB. Nothing was truncated.')
|
| 121 |
+
return payload
|
| 122 |
+
|
| 123 |
+
def infer(payload):
|
| 124 |
+
endpoint = os.getenv('GPC1_API_URL','').rstrip('/')
|
| 125 |
+
token = os.getenv('GPC1_API_KEY','')
|
| 126 |
+
parsed = urlsplit(endpoint)
|
| 127 |
+
if not token or not parsed.hostname or parsed.username or parsed.password or parsed.query or parsed.fragment:
|
| 128 |
+
raise RuntimeError('The model endpoint is not configured.')
|
| 129 |
+
if parsed.scheme != 'https' and not (parsed.scheme=='http' and parsed.hostname in {'localhost','127.0.0.1','::1'}):
|
| 130 |
+
raise RuntimeError('The model endpoint requires HTTPS.')
|
| 131 |
+
started = time.perf_counter()
|
| 132 |
+
try:
|
| 133 |
+
with httpx.Client(timeout=httpx.Timeout(180, connect=10), follow_redirects=False, trust_env=False) as client:
|
| 134 |
+
response = client.post(endpoint+'/v1/chat/completions', json=payload,
|
| 135 |
+
headers={'Authorization': 'Bearer '+token})
|
| 136 |
+
except httpx.TimeoutException: raise RuntimeError('The request timed out. It was not retried.') from None
|
| 137 |
+
except httpx.HTTPError: raise RuntimeError('Cannot reach the model. Try again later.') from None
|
| 138 |
+
if response.status_code == 429: raise RuntimeError('The model is busy. Try again shortly.')
|
| 139 |
+
if response.status_code == 413: raise ValueError('The request is too large. Reduce context or image size.')
|
| 140 |
+
if response.status_code != 200: raise RuntimeError('The model could not process this request. Check the field definitions and context length.')
|
| 141 |
+
try:
|
| 142 |
+
data=response.json(); result=json.loads(data['choices'][0]['message']['content'])
|
| 143 |
+
details=data['gpc1']
|
| 144 |
+
if not isinstance(result, dict) or not isinstance(details, dict): raise ValueError
|
| 145 |
+
expected=set(payload['response_format']['json_schema']['schema']['properties'])
|
| 146 |
+
if set(result) != expected: raise ValueError
|
| 147 |
+
if payload['gpc1']['mode']=='atomic':
|
| 148 |
+
value=json.dumps(result[payload['gpc1']['field_key']],sort_keys=True,allow_nan=False)
|
| 149 |
+
if value not in {json.dumps(x,sort_keys=True,allow_nan=False) for x in payload['gpc1']['choices']}: raise ValueError
|
| 150 |
+
elif payload['gpc1']['mode']=='finite_joint':
|
| 151 |
+
value=json.dumps(result,sort_keys=True,allow_nan=False)
|
| 152 |
+
if value not in {json.dumps(x,sort_keys=True,allow_nan=False) for x in payload['gpc1']['allowed_records']}: raise ValueError
|
| 153 |
+
else:
|
| 154 |
+
fields=details.get('fields')
|
| 155 |
+
if not isinstance(fields, dict) or set(fields) != expected: raise ValueError
|
| 156 |
+
for key, value in result.items():
|
| 157 |
+
field=fields[key]
|
| 158 |
+
if (isinstance(value,bool) or not isinstance(value,(int,float)) or not math.isfinite(value)
|
| 159 |
+
or not isinstance(field,dict) or any(isinstance(field.get(k),bool) or not isinstance(field.get(k),(int,float)) or not math.isfinite(field[k]) for k in ('map_value','expected_value'))):
|
| 160 |
+
raise ValueError
|
| 161 |
+
bounds=payload['response_format']['json_schema']['schema']['properties'][key]
|
| 162 |
+
if value != field['map_value'] or not bounds['minimum'] <= value <= bounds['maximum'] or not bounds['minimum'] <= field['expected_value'] <= bounds['maximum']:
|
| 163 |
+
raise ValueError
|
| 164 |
+
except (ValueError, KeyError, IndexError, TypeError): raise RuntimeError('The model returned an invalid response.') from None
|
| 165 |
+
elapsed=time.perf_counter()-started
|
| 166 |
+
return result, details, elapsed
|
demo/presets.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
|
| 3 |
+
def field(key, description, minimum=0, maximum=1, unit='image fraction'):
|
| 4 |
+
return dict(key=key, description=description, minimum=minimum, maximum=maximum, unit=unit)
|
| 5 |
+
|
| 6 |
+
PRESETS = {
|
| 7 |
+
'Support routing': ('Categories', 'I was charged twice for my subscription this month. Please refund the duplicate payment.',
|
| 8 |
+
{'intent': ['billing', 'technical_support', 'sales', 'cancellation']}),
|
| 9 |
+
'Numerical analysis': ('Numeric ranges', 'A tank holds 200 liters. It is 35% full. Return its current volume in liters.',
|
| 10 |
+
[field('volume', 'Current volume of liquid in the tank', 0, 200, 'liters')]),
|
| 11 |
+
'Bounding box': ('Numeric ranges', 'Locate the runner in the red shirt. Return their full visible body bounding box. Origin is top-left; x increases right and y increases down. All coordinates are normalized from 0 to 1.',
|
| 12 |
+
[field(k, d) for k,d in zip(['x_min','y_min','x_max','y_max'],['Left edge of runner in red','Top edge of runner in red','Right edge of runner in red','Bottom edge of runner in red'])]),
|
| 13 |
+
'Human pose': ('Numeric ranges', 'Locate the runner in the red shirt\'s nose and left wrist (the runner\'s left, not the viewer\'s). Return visible landmark coordinates normalized to 0–1. Origin is top-left, x increases right and y down.',
|
| 14 |
+
[field(k, d) for k,d in zip(['nose_x','nose_y','left_wrist_x','left_wrist_y'],['Runner nose horizontal coordinate','Runner nose vertical coordinate','Runner left wrist horizontal coordinate','Runner left wrist vertical coordinate'])]),
|
| 15 |
+
'Pixel art': ('Numeric ranges', 'Draw a recognizable flower on a 5 by 5 pixel grid. Each key is row_column, zero-indexed from top-left. Pixel codes: 0=white background, 1=green stem or leaf, 2=red petals, 3=yellow center. Use a cohesive shape with one stem and a centered blossom.',
|
| 16 |
+
[field(f'p{r}_{c}',f'Pixel at row {r}, column {c}',0,3,'palette index') for r in range(5) for c in range(5)]),
|
| 17 |
+
'Workflow decision': ('JSON records', 'The customer reports a duplicate subscription charge and requests a refund. Select the appropriate team and action.',
|
| 18 |
+
[{'team':'billing','action':'review_refund'}, {'team':'support','action':'troubleshoot'}, {'team':'sales','action':'contact_customer'}]),
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
def preset(name):
|
| 22 |
+
mode,context,definition=PRESETS[name]
|
| 23 |
+
return mode, context, json.dumps(definition,indent=2,ensure_ascii=False)
|
demo/requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==6.9.0
|
| 2 |
+
httpx==0.28.1
|
| 3 |
+
Pillow==12.3.0
|
docs/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architecture
|
| 2 |
+
|
| 3 |
+
GPC-1 uses the Qwen3.5-35B-A3B multimodal mixture-of-experts architecture with schema-bound inference. You define the output space; the server scores it and returns typed values.
|
| 4 |
+
|
| 5 |
+
## Model package
|
| 6 |
+
|
| 7 |
+
`model/` contains GPC-1's modified backbone weights; `adapter/` contains the matching adapter applied at load time. Use both components from the same release. Qwen identifies the upstream architecture; the packaged weights belong to GPC-1.
|
| 8 |
+
|
| 9 |
+
The server loads the backbone in BF16 and the adapter in FP32. It uses the existing language-model output projection to score the request's allowed outputs.
|
| 10 |
+
|
| 11 |
+
| Mode | Output |
|
| 12 |
+
| --- | --- |
|
| 13 |
+
| Categorical | A selected label and probabilities over your choices |
|
| 14 |
+
| Numeric | A grid estimate, probability-weighted mean, and distribution per field |
|
| 15 |
+
| Image numeric | Numeric fields conditioned on one image |
|
| 16 |
+
| Finite joint | A selected complete record and probabilities over allowed records |
|
| 17 |
+
|
| 18 |
+
## Numeric ranges
|
| 19 |
+
|
| 20 |
+
For bounds `[minimum, maximum]`, the 101-point grid is:
|
| 21 |
+
|
| 22 |
+
`value(i) = minimum + (maximum - minimum) × i / 100`
|
| 23 |
+
|
| 24 |
+
Numeric fields share one model pass. Each returns a marginal distribution; shared context does not make them a full joint distribution. The probability-weighted mean can fall between grid positions. Angles and other circular quantities need application-specific interpretation.
|
| 25 |
+
|
| 26 |
+
## Dependent records
|
| 27 |
+
|
| 28 |
+
Finite-joint mode scores complete records rather than choosing their fields independently. Probabilities are conditional on the supplied records. Compute and memory grow with the number and length of candidates.
|
| 29 |
+
|
| 30 |
+
## Serving
|
| 31 |
+
|
| 32 |
+
The runtime verifies packaged asset identities, uses request-local execution, and rejects over-limit inputs without truncation. Its input ceiling is 256K tokens (262,144), including compiled request overhead. See [context configuration](../API.md#context-window) for deployment requirements. Preserve the supplied precision, prompt formatting, and token mapping when reproducing outputs. Scale concurrent traffic with separate replicas.
|
| 33 |
+
|
| 34 |
+
See the [API guide](../API.md) for request formats and limits.
|
docs/DATA_AND_LICENSES.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Third-party notices
|
| 2 |
+
|
| 3 |
+
The following notices preserve applicable attribution. They do not grant additional rights to third-party material.
|
| 4 |
+
|
| 5 |
+
## Code and base model
|
| 6 |
+
|
| 7 |
+
- **GPC-1 inference code:** Apache-2.0.
|
| 8 |
+
- **Qwen3.5-35B-A3B:** the pinned upstream revision `59d61f3ce65a6d9863b86d2e96597125219dc754` is published under Apache-2.0. Preserve its upstream license, copyright, patent, trademark, and attribution notices.
|
| 9 |
+
|
| 10 |
+
The GPC-1 code license does not replace the terms of the base model, adapter artifacts, source material, or dependencies.
|
| 11 |
+
|
| 12 |
+
## Material source notices
|
| 13 |
+
|
| 14 |
+
- **CLINC OOS:** CC-BY-3.0; preserve the Larson et al. source attribution and license notice.
|
| 15 |
+
- **UCI SMS Spam Collection:** CC-BY-4.0; Almeida, T. and Hidalgo, J. (2011), *SMS Spam Collection*, UCI Machine Learning Repository, DOI `10.24432/C5CC84`.
|
| 16 |
+
- **HelpSteer2:** CC-BY-4.0 at revision `990b2711a36180dd19d9c94b8627844866f8982a`.
|
| 17 |
+
- **Example photograph:** Liber4l, [Tim running](https://commons.wikimedia.org/wiki/File:Tim_running.jpg), public domain. The bounding-box illustration adds the model's predicted coordinates and presentation layout. No endorsement is implied.
|
| 18 |
+
- **Pose illustration photograph:** [Misty / sub_lime79](https://www.flickr.com/photos/16932921@N08/4668205945/), [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/), with a point overlay. No endorsement is implied.
|
| 19 |
+
- **Intent illustration text:** [Larson et al., CLINC OOS](https://github.com/clinc/oos-eval), [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
|
| 20 |
+
- **CMU motion capture:** retain CMU attribution and NSF EIA-0196217 funding acknowledgment. CMU's published permission does not permit direct resale of the data or converted data. No capture data are distributed here.
|
| 21 |
+
- **GLiClass logic, Typed Decisions, OpenBookQA adaptation, and TruthfulQA MC1 adaptation:** preserve their recorded Apache-2.0 notices; retain the OpenBookQA transport repository's MIT notice.
|
| 22 |
+
- **Google Civil Comments and public Open-Jev:** CC0-1.0. Open-Jev revision: `c67699e13d0ae25e35b77165a4b6b079bedc8aba`.
|
| 23 |
+
- **CFPB Consumer Complaint Database:** official public-use source. This project does not recast that public-use statement as an SPDX license.
|
| 24 |
+
- **U.S. Census Bureau:** 2024 American Community Survey 5-year Summary File, tract estimates. U.S. federal government public-domain material; source credit retained.
|
| 25 |
+
- **Material UI:** MIT, copyright (c) 2014 Call-Em-All. The notice below applies to the upstream component declarations.
|
| 26 |
+
- **PushT:** `lerobot/pusht`, revision `7628202a2180972f291ba1bc6723834921e72c19`, published under MIT. No source images or action records are distributed here.
|
| 27 |
+
|
| 28 |
+
Source-specific rights and restrictions remain applicable independently of the GPC-1 code license.
|
| 29 |
+
|
| 30 |
+
## Material UI license
|
| 31 |
+
|
| 32 |
+
Copyright (c) 2014 Call-Em-All
|
| 33 |
+
|
| 34 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 35 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 36 |
+
in the Software without restriction, including without limitation the rights
|
| 37 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 38 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 39 |
+
furnished to do so, subject to the following conditions:
|
| 40 |
+
|
| 41 |
+
The above copyright notice and this permission notice shall be included in
|
| 42 |
+
all copies or substantial portions of the Software.
|
| 43 |
+
|
| 44 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 45 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 46 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 47 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 48 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 49 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
| 50 |
+
THE SOFTWARE.
|
docs/DOWNLOADS.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Download GPC-1
|
| 2 |
+
|
| 3 |
+
Download the complete GPC-1 release: the GPC-1 backbone in `model/`, its matching adapter in `adapter/`, and the serving package. Keep both weight components and release metadata together; the server loads them automatically.
|
| 4 |
+
|
| 5 |
+
## Command line
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
python -m pip install "huggingface-hub>=1.7,<2"
|
| 9 |
+
hf download harshatheg/GPC-1 --local-dir gpc-1
|
| 10 |
+
python gpc-1/download.py verify gpc-1
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
For a reproducible deployment, pin a commit SHA and use the same revision in every environment:
|
| 14 |
+
|
| 15 |
+
```bash
|
| 16 |
+
hf download harshatheg/GPC-1 --revision YOUR_COMMIT_SHA --local-dir gpc-1
|
| 17 |
+
python gpc-1/download.py verify gpc-1
|
| 18 |
+
```
|
| 19 |
+
|
| 20 |
+
Private or gated access uses `HF_TOKEN` in the environment. Keep the token in your secret manager; do not put it in command arguments or logs.
|
| 21 |
+
|
| 22 |
+
## Python
|
| 23 |
+
|
| 24 |
+
If you already have `download.py` from the release package, run Python from its directory:
|
| 25 |
+
|
| 26 |
+
```python
|
| 27 |
+
from pathlib import Path
|
| 28 |
+
from download import fetch
|
| 29 |
+
|
| 30 |
+
package = fetch(
|
| 31 |
+
"harshatheg/GPC-1",
|
| 32 |
+
revision="YOUR_COMMIT_SHA",
|
| 33 |
+
local_dir=Path("gpc-1"),
|
| 34 |
+
cache_dir=None,
|
| 35 |
+
offline=False,
|
| 36 |
+
)
|
| 37 |
+
print(package["model"])
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
`fetch` uses `huggingface_hub.snapshot_download` for the complete repository. Verification checks that the root and packaged base configs match, all inventoried base files and indexed weight shards are present, and the runtime manifest, base metadata, and adapter hashes match. It does not hash every base weight shard; the server checks those hashes when it loads the model. No tracking endpoint is called.
|
| 41 |
+
|
| 42 |
+
## Cache and offline use
|
| 43 |
+
|
| 44 |
+
After an online download, verify a copied package without contacting the Hub:
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
python gpc-1/download.py verify gpc-1
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
For offline deployment, download the complete package with `--local-dir` while online, then copy that directory to the offline machine. Run the verification command above on the copied directory. Verification makes no Hub request. Use the materialized package directory, not a cache snapshot containing symlinks.
|
| 51 |
+
|
| 52 |
+
## CI
|
| 53 |
+
|
| 54 |
+
Pin the revision in your pipeline and keep the token in a CI secret. A complete package can then be mounted read-only by the serving process:
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
hf download harshatheg/GPC-1 --revision "$GPC1_REVISION" --local-dir gpc-1
|
| 58 |
+
python gpc-1/download.py verify gpc-1
|
| 59 |
+
export GPC1_MODEL_PATH="$PWD/gpc-1/model"
|
| 60 |
+
```
|
docs/HUB_METRICS.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hub metrics for maintainers
|
| 2 |
+
|
| 3 |
+
Read the repository's download counts without fetching model weights:
|
| 4 |
+
|
| 5 |
+
```bash
|
| 6 |
+
python gpc-1/download.py counts
|
| 7 |
+
```
|
| 8 |
+
|
| 9 |
+
The command reads Hub metadata and reports `downloads` and, when available, `downloads_all_time`. It sends no tracking event.
|
| 10 |
+
|
| 11 |
+
[Hugging Face counts requests for query files](https://huggingface.co/docs/hub/models-download-stats); `config.json` is the default query file. Complete downloads through the CLI, Python, or CI normally request it. Counts are not unique users or verified installations. Offline copies and weights-only transfers may not be counted.
|
download.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Fetch and verify the complete GPC-1 release with the standard Hub client."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import argparse
|
| 6 |
+
import hashlib
|
| 7 |
+
import json
|
| 8 |
+
import os
|
| 9 |
+
import re
|
| 10 |
+
import sys
|
| 11 |
+
from pathlib import Path, PurePosixPath
|
| 12 |
+
|
| 13 |
+
DEFAULT_REPO = "harshatheg/GPC-1"
|
| 14 |
+
SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def _release_path(root: Path, value: object, *, directory: bool) -> Path:
|
| 18 |
+
if not isinstance(value, str) or not value:
|
| 19 |
+
raise ValueError("Release metadata contains an empty path")
|
| 20 |
+
if "\\" in value or any(part in ("", ".", "..") for part in value.split("/")):
|
| 21 |
+
raise ValueError(f"Invalid release path: {value}")
|
| 22 |
+
relative = PurePosixPath(value)
|
| 23 |
+
if relative.is_absolute():
|
| 24 |
+
raise ValueError(f"Invalid release path: {value}")
|
| 25 |
+
path = root.joinpath(*relative.parts)
|
| 26 |
+
if not path.resolve().is_relative_to(root.resolve()):
|
| 27 |
+
raise ValueError(f"Release path escapes package: {value}")
|
| 28 |
+
if directory and not path.is_dir():
|
| 29 |
+
raise ValueError(f"Missing release directory: {value}")
|
| 30 |
+
if not directory and not path.is_file():
|
| 31 |
+
raise ValueError(f"Missing release file: {value}")
|
| 32 |
+
return path
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _sha256_file(path: Path) -> str:
|
| 36 |
+
digest = hashlib.sha256()
|
| 37 |
+
with path.open("rb") as handle:
|
| 38 |
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
| 39 |
+
digest.update(chunk)
|
| 40 |
+
return digest.hexdigest()
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def verify_package(root: Path) -> dict[str, str]:
|
| 44 |
+
"""Validate metadata, adapter hashes, and the complete listed base-file set."""
|
| 45 |
+
root = root.resolve()
|
| 46 |
+
config_file = root / "config.json"
|
| 47 |
+
if not config_file.is_file() or config_file.is_symlink():
|
| 48 |
+
raise ValueError("Missing root config.json; fetch the full model repository")
|
| 49 |
+
config = json.loads(config_file.read_text(encoding="utf-8"))
|
| 50 |
+
if not isinstance(config, dict) or not config.get("model_type"):
|
| 51 |
+
raise ValueError("Root config.json is not a model config")
|
| 52 |
+
release = config.get("gpc1_release")
|
| 53 |
+
if not isinstance(release, dict) or release.get("format_version") != 1:
|
| 54 |
+
raise ValueError("Root config.json lacks supported gpc1_release metadata")
|
| 55 |
+
model = _release_path(root, release.get("model_path"), directory=True)
|
| 56 |
+
adapter = _release_path(root, release.get("adapter_path"), directory=True)
|
| 57 |
+
manifest_file = _release_path(root, release.get("runtime_manifest"), directory=False)
|
| 58 |
+
if model == adapter or model == root or adapter == root:
|
| 59 |
+
raise ValueError("Release model and adapter paths must be distinct package directories")
|
| 60 |
+
expected = release.get("runtime_sha256")
|
| 61 |
+
if not isinstance(expected, str) or not SHA256.fullmatch(expected):
|
| 62 |
+
raise ValueError("Invalid runtime_sha256 in release metadata")
|
| 63 |
+
actual = _sha256_file(manifest_file)
|
| 64 |
+
if actual != expected:
|
| 65 |
+
raise ValueError("Runtime manifest checksum mismatch")
|
| 66 |
+
model_config_file = model / "config.json"
|
| 67 |
+
if not model_config_file.is_file():
|
| 68 |
+
raise ValueError("Missing model/config.json")
|
| 69 |
+
model_config = json.loads(model_config_file.read_text(encoding="utf-8"))
|
| 70 |
+
root_base_config = dict(config)
|
| 71 |
+
root_base_config.pop("gpc1_release")
|
| 72 |
+
if root_base_config != model_config:
|
| 73 |
+
raise ValueError("Root model config does not match packaged base config")
|
| 74 |
+
manifest = json.loads(manifest_file.read_text(encoding="utf-8"))
|
| 75 |
+
if not isinstance(manifest, dict) or manifest.get("served_model_id") != "gpc-1":
|
| 76 |
+
raise ValueError("Runtime manifest is not for GPC-1")
|
| 77 |
+
assets = manifest.get("assets", {})
|
| 78 |
+
inventory_file = _release_path(root, assets.get("base_files"), directory=False)
|
| 79 |
+
if _sha256_file(inventory_file) != assets.get("base_files_sha256"):
|
| 80 |
+
raise ValueError("Base-file inventory checksum mismatch")
|
| 81 |
+
inventory = json.loads(inventory_file.read_text(encoding="utf-8"))
|
| 82 |
+
if (inventory.get("model_id") != manifest.get("base", {}).get("model_id")
|
| 83 |
+
or inventory.get("revision") != manifest.get("base", {}).get("revision")):
|
| 84 |
+
raise ValueError("Base-file inventory identity mismatch")
|
| 85 |
+
files = inventory.get("files")
|
| 86 |
+
if not isinstance(files, dict) or not files:
|
| 87 |
+
raise ValueError("Base-file inventory is empty")
|
| 88 |
+
for name, digest in files.items():
|
| 89 |
+
if not isinstance(digest, str) or not SHA256.fullmatch(digest):
|
| 90 |
+
raise ValueError("Invalid base-file inventory digest")
|
| 91 |
+
path = _release_path(model, name, directory=False)
|
| 92 |
+
if path.stat().st_size == 0:
|
| 93 |
+
raise ValueError(f"Empty base file: {name}")
|
| 94 |
+
if name in ("config.json", "model.safetensors.index.json") and _sha256_file(path) != digest:
|
| 95 |
+
raise ValueError(f"Base file checksum mismatch: {name}")
|
| 96 |
+
index_file = model / "model.safetensors.index.json"
|
| 97 |
+
if "model.safetensors.index.json" not in files or not index_file.is_file():
|
| 98 |
+
raise ValueError("Missing model shard index")
|
| 99 |
+
index = json.loads(index_file.read_text(encoding="utf-8"))
|
| 100 |
+
shards = set(index.get("weight_map", {}).values())
|
| 101 |
+
if not shards or not shards.issubset(files.keys()) or any(not isinstance(s, str) or not s.endswith(".safetensors") for s in shards):
|
| 102 |
+
raise ValueError("Model shard index does not match base-file inventory")
|
| 103 |
+
adapter_manifest = manifest.get("adapter", {})
|
| 104 |
+
for name, key in (("adapter_config.json", "adapter_config_sha256"),
|
| 105 |
+
("adapter_model.safetensors", "adapter_model_sha256")):
|
| 106 |
+
path = _release_path(adapter, name, directory=False)
|
| 107 |
+
if _sha256_file(path) != adapter_manifest.get(key):
|
| 108 |
+
raise ValueError(f"Adapter file checksum mismatch: {name}")
|
| 109 |
+
return {"root": str(root), "model": str(model), "adapter": str(adapter), "runtime_sha256": actual}
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def fetch(repo: str, revision: str | None, local_dir: Path | None, cache_dir: Path | None, offline: bool) -> dict[str, str]:
|
| 113 |
+
try:
|
| 114 |
+
from huggingface_hub import snapshot_download
|
| 115 |
+
except ImportError as exc:
|
| 116 |
+
raise RuntimeError("Install huggingface-hub to fetch the package") from exc
|
| 117 |
+
|
| 118 |
+
try:
|
| 119 |
+
path = snapshot_download(
|
| 120 |
+
repo_id=repo,
|
| 121 |
+
repo_type="model",
|
| 122 |
+
revision=revision,
|
| 123 |
+
local_dir=str(local_dir) if local_dir else None,
|
| 124 |
+
cache_dir=str(cache_dir) if cache_dir else None,
|
| 125 |
+
local_files_only=offline,
|
| 126 |
+
token=os.environ.get("HF_TOKEN") or None,
|
| 127 |
+
)
|
| 128 |
+
except Exception:
|
| 129 |
+
raise RuntimeError("Hub fetch failed; check access, revision, cache, and connectivity") from None
|
| 130 |
+
return verify_package(Path(path))
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def counts(repo: str) -> dict[str, object]:
|
| 134 |
+
"""Read a Hub model-info snapshot; never fetch query files for analytics."""
|
| 135 |
+
try:
|
| 136 |
+
from huggingface_hub import HfApi
|
| 137 |
+
except ImportError as exc:
|
| 138 |
+
raise RuntimeError("Install huggingface-hub to read counts") from exc
|
| 139 |
+
|
| 140 |
+
try:
|
| 141 |
+
info = HfApi(token=os.environ.get("HF_TOKEN") or None).model_info(
|
| 142 |
+
repo_id=repo, expand=["downloads", "downloadsAllTime"]
|
| 143 |
+
)
|
| 144 |
+
except Exception:
|
| 145 |
+
raise RuntimeError("Hub model-info request failed; check access and connectivity") from None
|
| 146 |
+
return {
|
| 147 |
+
"repo": repo,
|
| 148 |
+
"downloads_30d": getattr(info, "downloads", None),
|
| 149 |
+
"downloads_all_time": getattr(info, "downloads_all_time", None),
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def main(argv: list[str] | None = None) -> int:
|
| 154 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 155 |
+
sub = parser.add_subparsers(dest="command", required=True)
|
| 156 |
+
fetch_parser = sub.add_parser("fetch", help="Download the full model package and verify its release metadata")
|
| 157 |
+
fetch_parser.add_argument("--repo", default=DEFAULT_REPO)
|
| 158 |
+
fetch_parser.add_argument("--revision", help="Pin a tag or, preferably, a commit SHA")
|
| 159 |
+
fetch_parser.add_argument("--local-dir", type=Path)
|
| 160 |
+
fetch_parser.add_argument("--cache-dir", type=Path)
|
| 161 |
+
fetch_parser.add_argument("--offline", action="store_true", help="Use cached files only; no Hub request")
|
| 162 |
+
verify_parser = sub.add_parser("verify", help="Verify a package already on disk without network access")
|
| 163 |
+
verify_parser.add_argument("path", type=Path)
|
| 164 |
+
count_parser = sub.add_parser("counts", help="Read current Hub download counts")
|
| 165 |
+
count_parser.add_argument("--repo", default=DEFAULT_REPO)
|
| 166 |
+
args = parser.parse_args(argv)
|
| 167 |
+
try:
|
| 168 |
+
if args.command == "fetch":
|
| 169 |
+
result = fetch(args.repo, args.revision, args.local_dir, args.cache_dir, args.offline)
|
| 170 |
+
elif args.command == "verify":
|
| 171 |
+
result = verify_package(args.path)
|
| 172 |
+
else:
|
| 173 |
+
result = counts(args.repo)
|
| 174 |
+
except (OSError, ValueError, RuntimeError) as exc:
|
| 175 |
+
parser.exit(1, f"{args.command}: {exc}\n")
|
| 176 |
+
print(json.dumps(result, sort_keys=True))
|
| 177 |
+
return 0
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
if __name__ == "__main__":
|
| 181 |
+
sys.exit(main())
|
examples/atomic_request.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"model":"gpc-1","messages":[{"role":"user","content":"Is the invoice approved? The record says approved."}],"response_format":{"type":"json_schema","json_schema":{"name":"decision","strict":true,"schema":{"type":"object","properties":{"label":{"type":"string","enum":["approved","rejected"]}},"required":["label"],"additionalProperties":false}}},"gpc1":{"mode":"atomic","field_key":"label","choices":["approved","rejected"]},"stream":false}
|
examples/numeric_request.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model": "gpc-1",
|
| 3 |
+
"messages": [{"role": "user", "content": "A 200 cm rail is filled to 75%. Report filled length."}],
|
| 4 |
+
"response_format": {"type": "json_schema", "json_schema": {"name": "rail", "strict": true, "schema": {"type": "object", "properties": {"filled_length": {"type": "number", "minimum": 0, "maximum": 200}}, "required": ["filled_length"], "additionalProperties": false}}},
|
| 5 |
+
"gpc1": {"mode": "numeric101", "numeric_fields": [{"key": "filled_length", "description": "filled rail length", "minimum": 0, "maximum": 200, "unit": "cm"}]},
|
| 6 |
+
"stream": false
|
| 7 |
+
}
|
gpc1_server/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""GPC-1 inference-only OpenAI-style serving package."""
|
| 2 |
+
|
| 3 |
+
__version__ = "0.1.0"
|
gpc1_server/api.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import asyncio
|
| 4 |
+
import base64
|
| 5 |
+
import binascii
|
| 6 |
+
from contextlib import asynccontextmanager
|
| 7 |
+
from io import BytesIO
|
| 8 |
+
import json
|
| 9 |
+
import math
|
| 10 |
+
import os
|
| 11 |
+
import secrets
|
| 12 |
+
import time
|
| 13 |
+
import uuid
|
| 14 |
+
from typing import Any
|
| 15 |
+
|
| 16 |
+
from fastapi import FastAPI, Header, Request
|
| 17 |
+
from fastapi.responses import JSONResponse
|
| 18 |
+
from PIL import Image
|
| 19 |
+
from pydantic import ValidationError
|
| 20 |
+
from jsonschema import Draft202012Validator
|
| 21 |
+
|
| 22 |
+
from .compiler import NumericField
|
| 23 |
+
from .errors import APIError
|
| 24 |
+
from .runtime import Backend, TransformersBackend
|
| 25 |
+
from .schemas import ChatRequest
|
| 26 |
+
|
| 27 |
+
MAX_BODY_BYTES = int(os.getenv("GPC1_MAX_BODY_BYTES", str(16 * 1024 * 1024)))
|
| 28 |
+
MAX_IMAGE_BYTES = int(os.getenv("GPC1_MAX_IMAGE_BYTES", str(3 * 1024 * 1024)))
|
| 29 |
+
MAX_IMAGE_PIXELS = int(os.getenv("GPC1_MAX_IMAGE_PIXELS", "16777216"))
|
| 30 |
+
Image.MAX_IMAGE_PIXELS = MAX_IMAGE_PIXELS
|
| 31 |
+
MAX_QUEUE_WAIT_SECONDS = float(os.getenv("GPC1_MAX_QUEUE_WAIT_SECONDS", "2"))
|
| 32 |
+
SUPPORTED_SCHEMA_KEYS = {"type", "properties", "required", "additionalProperties", "description",
|
| 33 |
+
"enum", "items", "minimum", "maximum"}
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def error_response(error: APIError, request_id: str | None = None) -> JSONResponse:
|
| 37 |
+
headers = {"x-request-id": request_id} if request_id else {}
|
| 38 |
+
if error.status_code == 429:
|
| 39 |
+
headers["Retry-After"] = "1"
|
| 40 |
+
return JSONResponse(error.body(), status_code=error.status_code, headers=headers)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def validate_schema(schema: Any, path: str = "response_format.json_schema.schema", depth: int = 0) -> None:
|
| 44 |
+
if depth > 16: raise APIError("JSON Schema nesting exceeds 16",param=path)
|
| 45 |
+
if not isinstance(schema, dict):
|
| 46 |
+
raise APIError("JSON Schema must be an object", param=path)
|
| 47 |
+
unknown = set(schema) - SUPPORTED_SCHEMA_KEYS
|
| 48 |
+
if unknown:
|
| 49 |
+
raise APIError(f"unsupported JSON Schema keywords: {sorted(unknown)}", param=path)
|
| 50 |
+
def finite(value: Any) -> bool:
|
| 51 |
+
if isinstance(value,float): return math.isfinite(value)
|
| 52 |
+
if isinstance(value,dict): return all(finite(k) and finite(v) for k,v in value.items())
|
| 53 |
+
if isinstance(value,list): return all(finite(v) for v in value)
|
| 54 |
+
return True
|
| 55 |
+
if not finite(schema):
|
| 56 |
+
raise APIError("JSON Schema contains a non-finite number", param=path)
|
| 57 |
+
kind = schema.get("type")
|
| 58 |
+
if not isinstance(kind, str) or kind not in {"object", "array", "string", "number", "integer", "boolean", "null"}:
|
| 59 |
+
raise APIError("schema type must be one supported explicit type", param=f"{path}.type")
|
| 60 |
+
if kind == "object":
|
| 61 |
+
properties = schema.get("properties")
|
| 62 |
+
required = schema.get("required")
|
| 63 |
+
if not isinstance(properties, dict) or not isinstance(required, list):
|
| 64 |
+
raise APIError("objects require properties and required", param=path)
|
| 65 |
+
if any(not isinstance(v, str) for v in required) or len(required) != len(set(required)):
|
| 66 |
+
raise APIError("required must contain unique property-name strings", param=f"{path}.required")
|
| 67 |
+
if set(required) != set(properties) or schema.get("additionalProperties") is not False:
|
| 68 |
+
raise APIError("strict objects require every property and additionalProperties=false", param=path)
|
| 69 |
+
for name, child in properties.items(): validate_schema(child, f"{path}.properties.{name}",depth+1)
|
| 70 |
+
if kind == "array":
|
| 71 |
+
validate_schema(schema.get("items"), f"{path}.items",depth+1)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def validate_instance(value: Any, schema: dict[str, Any], path: str = "output") -> None:
|
| 75 |
+
def finite(v: Any) -> bool:
|
| 76 |
+
if isinstance(v, float): return math.isfinite(v)
|
| 77 |
+
if isinstance(v, dict): return all(finite(k) and finite(x) for k, x in v.items())
|
| 78 |
+
if isinstance(v, list): return all(finite(x) for x in v)
|
| 79 |
+
return True
|
| 80 |
+
if not finite(value): raise APIError(f"{path} contains a non-finite number", param="gpc1")
|
| 81 |
+
errors=sorted(Draft202012Validator(schema).iter_errors(value),key=lambda e:list(e.path))
|
| 82 |
+
if errors: raise APIError(f"{path} violates schema: {errors[0].message}",param="gpc1")
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _decode_image(url: str) -> Image.Image:
|
| 86 |
+
if not url.startswith("data:image/"):
|
| 87 |
+
raise APIError("only inline data:image URIs are accepted; remote URLs are disabled",
|
| 88 |
+
param="messages.content.image_url.url", code="remote_image_disabled")
|
| 89 |
+
try:
|
| 90 |
+
header, encoded = url.split(",", 1)
|
| 91 |
+
media = header[5:].split(";", 1)[0].lower()
|
| 92 |
+
if header != f"data:{media};base64" or media not in {"image/png", "image/jpeg", "image/webp"}:
|
| 93 |
+
raise ValueError
|
| 94 |
+
raw = base64.b64decode(encoded, validate=True)
|
| 95 |
+
except (ValueError, binascii.Error) as error:
|
| 96 |
+
raise APIError("invalid base64 image data URI", param="messages.content.image_url.url") from error
|
| 97 |
+
if len(raw) > MAX_IMAGE_BYTES:
|
| 98 |
+
raise APIError("decoded image exceeds byte limit", status_code=413, code="image_too_large")
|
| 99 |
+
try:
|
| 100 |
+
image = Image.open(BytesIO(raw))
|
| 101 |
+
if image.width * image.height > MAX_IMAGE_PIXELS:
|
| 102 |
+
raise APIError("decoded image exceeds pixel limit", status_code=413, code="image_too_large")
|
| 103 |
+
image.verify()
|
| 104 |
+
image = Image.open(BytesIO(raw)).convert("RGB")
|
| 105 |
+
except APIError:
|
| 106 |
+
raise
|
| 107 |
+
except Exception as error:
|
| 108 |
+
raise APIError("image payload failed safe decode", param="messages.content.image_url.url") from error
|
| 109 |
+
return image
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def flatten_messages(messages: list[Any]) -> tuple[str, Image.Image | None]:
|
| 113 |
+
texts=[]; image=None
|
| 114 |
+
for message in messages:
|
| 115 |
+
if isinstance(message.content, str):
|
| 116 |
+
texts.append(f"{message.role.upper()}: {message.content}")
|
| 117 |
+
continue
|
| 118 |
+
for part in message.content:
|
| 119 |
+
if set(part) == {"type", "text"} and part["type"] == "text" and isinstance(part["text"], str):
|
| 120 |
+
texts.append(f"{message.role.upper()}: {part['text']}")
|
| 121 |
+
elif set(part) == {"type", "image_url"} and part["type"] == "image_url" \
|
| 122 |
+
and isinstance(part["image_url"], dict) and set(part["image_url"]) <= {"url", "detail"}:
|
| 123 |
+
if image is not None:
|
| 124 |
+
raise APIError("at most one image is supported", param="messages")
|
| 125 |
+
image = _decode_image(str(part["image_url"].get("url", "")))
|
| 126 |
+
else:
|
| 127 |
+
raise APIError("unsupported message content part", param="messages.content")
|
| 128 |
+
context="\n".join(texts)
|
| 129 |
+
if not context.strip(): raise APIError("messages must include text context", param="messages")
|
| 130 |
+
return context, image
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def create_app(backend: Backend | None = None) -> FastAPI:
|
| 134 |
+
selected = backend
|
| 135 |
+
if int(os.getenv("GPC1_MAX_CONCURRENCY", "1")) != 1:
|
| 136 |
+
raise RuntimeError("canonical request-local runtime requires GPC1_MAX_CONCURRENCY=1")
|
| 137 |
+
max_pending = int(os.getenv("GPC1_MAX_PENDING_REQUESTS", "4"))
|
| 138 |
+
if max_pending < 0:
|
| 139 |
+
raise RuntimeError("GPC1_MAX_PENDING_REQUESTS must be nonnegative")
|
| 140 |
+
if not math.isfinite(MAX_QUEUE_WAIT_SECONDS) or MAX_QUEUE_WAIT_SECONDS <= 0:
|
| 141 |
+
raise RuntimeError("GPC1_MAX_QUEUE_WAIT_SECONDS must be finite and positive")
|
| 142 |
+
semaphore = asyncio.Semaphore(1)
|
| 143 |
+
admitted = 0
|
| 144 |
+
|
| 145 |
+
@asynccontextmanager
|
| 146 |
+
async def lifespan(app: FastAPI):
|
| 147 |
+
nonlocal selected
|
| 148 |
+
if selected is None:
|
| 149 |
+
if not os.getenv("GPC1_API_KEY"): raise RuntimeError("GPC1_API_KEY is required")
|
| 150 |
+
selected = TransformersBackend()
|
| 151 |
+
yield
|
| 152 |
+
|
| 153 |
+
app = FastAPI(title="GPC-1 OpenAI-style API", version="0.1.0", lifespan=lifespan)
|
| 154 |
+
|
| 155 |
+
async def run_native(call, *args):
|
| 156 |
+
task=asyncio.create_task(asyncio.to_thread(call,*args))
|
| 157 |
+
try: return await asyncio.shield(task)
|
| 158 |
+
except asyncio.CancelledError:
|
| 159 |
+
# Retain the model lease until the worker thread finishes.
|
| 160 |
+
while not task.done():
|
| 161 |
+
try:
|
| 162 |
+
await asyncio.shield(task)
|
| 163 |
+
except asyncio.CancelledError:
|
| 164 |
+
continue
|
| 165 |
+
except Exception:
|
| 166 |
+
break
|
| 167 |
+
if not task.cancelled():
|
| 168 |
+
task.exception()
|
| 169 |
+
raise
|
| 170 |
+
|
| 171 |
+
@app.exception_handler(APIError)
|
| 172 |
+
async def api_error_handler(_: Request, error: APIError): return error_response(error)
|
| 173 |
+
|
| 174 |
+
@app.middleware("http")
|
| 175 |
+
async def admission(request: Request, call_next):
|
| 176 |
+
request_id = "req_" + uuid.uuid4().hex
|
| 177 |
+
if request.method == "POST" and "content-length" not in request.headers:
|
| 178 |
+
return error_response(APIError("Content-Length is required", 411, code="length_required"), request_id)
|
| 179 |
+
try: content_length = int(request.headers.get("content-length", "0") or 0)
|
| 180 |
+
except ValueError: return error_response(APIError("invalid Content-Length", 400), request_id)
|
| 181 |
+
if content_length < 0 or content_length > MAX_BODY_BYTES:
|
| 182 |
+
return error_response(APIError("request body exceeds byte limit", 413, code="request_too_large"), request_id)
|
| 183 |
+
configured = os.getenv("GPC1_API_KEY")
|
| 184 |
+
supplied = request.headers.get("authorization", "")
|
| 185 |
+
if request.url.path not in {"/healthz", "/readyz"} and configured and not secrets.compare_digest(supplied, f"Bearer {configured}"):
|
| 186 |
+
return error_response(APIError("invalid API key", 401, "authentication_error", code="invalid_api_key"), request_id)
|
| 187 |
+
response = await call_next(request); response.headers["x-request-id"] = request_id; return response
|
| 188 |
+
|
| 189 |
+
@app.get("/healthz", include_in_schema=False)
|
| 190 |
+
async def health():
|
| 191 |
+
return {"status": "ok"}
|
| 192 |
+
|
| 193 |
+
@app.get("/readyz", include_in_schema=False)
|
| 194 |
+
async def ready():
|
| 195 |
+
if selected is None:
|
| 196 |
+
return JSONResponse({"status": "not_ready"}, status_code=503)
|
| 197 |
+
return {"status": "ready"}
|
| 198 |
+
|
| 199 |
+
@app.get("/v1/models")
|
| 200 |
+
async def models():
|
| 201 |
+
assert selected is not None
|
| 202 |
+
return {"object": "list", "data": [{"id": selected.model_id, "object": "model",
|
| 203 |
+
"created": 0, "owned_by": "gpc-1"}]}
|
| 204 |
+
|
| 205 |
+
@app.post("/v1/chat/completions")
|
| 206 |
+
async def completions(request: Request):
|
| 207 |
+
nonlocal admitted
|
| 208 |
+
if admitted >= 1 + max_pending:
|
| 209 |
+
raise APIError("server is at capacity; retry with backoff", 429, "rate_limit_error", code="server_busy")
|
| 210 |
+
admitted += 1
|
| 211 |
+
try:
|
| 212 |
+
try:
|
| 213 |
+
await asyncio.wait_for(semaphore.acquire(), timeout=MAX_QUEUE_WAIT_SECONDS)
|
| 214 |
+
except TimeoutError as error:
|
| 215 |
+
raise APIError("server is at capacity; retry with backoff", 429, "rate_limit_error", code="server_busy") from error
|
| 216 |
+
try:
|
| 217 |
+
return await complete_admitted(request)
|
| 218 |
+
finally:
|
| 219 |
+
semaphore.release()
|
| 220 |
+
finally:
|
| 221 |
+
admitted -= 1
|
| 222 |
+
|
| 223 |
+
async def complete_admitted(request: Request):
|
| 224 |
+
assert selected is not None
|
| 225 |
+
try:
|
| 226 |
+
chunks=[]; actual=0
|
| 227 |
+
async for chunk in request.stream():
|
| 228 |
+
actual += len(chunk)
|
| 229 |
+
if actual > MAX_BODY_BYTES: raise APIError("request body exceeds byte limit",413,code="request_too_large")
|
| 230 |
+
chunks.append(chunk)
|
| 231 |
+
raw=await asyncio.to_thread(json.loads, b"".join(chunks),
|
| 232 |
+
parse_constant=lambda value: (_ for _ in ()).throw(
|
| 233 |
+
ValueError("JSON contains a non-finite number; use finite numeric values")))
|
| 234 |
+
parsed = await asyncio.to_thread(ChatRequest.model_validate, raw)
|
| 235 |
+
except (json.JSONDecodeError, UnicodeDecodeError, ValueError, ValidationError) as error:
|
| 236 |
+
raise APIError(str(error), param="body") from error
|
| 237 |
+
if parsed.model != selected.model_id:
|
| 238 |
+
raise APIError(f"model {parsed.model!r} not found", 404, "invalid_request_error", "model", "model_not_found")
|
| 239 |
+
wrapper = parsed.response_format.json_schema
|
| 240 |
+
if wrapper.get("strict") is not True or not isinstance(wrapper.get("name"), str) or set(wrapper) != {"name", "strict", "schema"}:
|
| 241 |
+
raise APIError("json_schema requires exactly name, strict=true, and schema", param="response_format.json_schema")
|
| 242 |
+
await asyncio.to_thread(validate_schema, wrapper["schema"])
|
| 243 |
+
try: await asyncio.to_thread(Draft202012Validator.check_schema, wrapper["schema"])
|
| 244 |
+
except Exception as error: raise APIError(f"invalid JSON Schema: {error}",param="response_format.json_schema.schema") from error
|
| 245 |
+
context, image = await asyncio.to_thread(flatten_messages, parsed.messages)
|
| 246 |
+
if parsed.temperature is not None or parsed.max_completion_tokens is not None:
|
| 247 |
+
raise APIError("temperature and max_completion_tokens are unsupported; omit them",param="temperature")
|
| 248 |
+
try:
|
| 249 |
+
if parsed.gpc1.mode == "atomic":
|
| 250 |
+
if image is not None: raise APIError("atomic image mode is not supported",param="messages")
|
| 251 |
+
choices=parsed.gpc1.choices or []
|
| 252 |
+
try:
|
| 253 |
+
for choice in choices: json.dumps(choice,allow_nan=False)
|
| 254 |
+
except (TypeError,ValueError) as error: raise APIError("atomic choices must be finite JSON",param="gpc1.choices") from error
|
| 255 |
+
field_key=str(parsed.gpc1.field_key)
|
| 256 |
+
schema=wrapper["schema"]
|
| 257 |
+
properties=schema.get("properties",{})
|
| 258 |
+
if set(properties)!={field_key} or properties[field_key].get("enum") != choices:
|
| 259 |
+
raise APIError("atomic field and choices must exactly match the schema enum",param="gpc1")
|
| 260 |
+
result=await run_native(selected.atomic,context,field_key,choices)
|
| 261 |
+
value={field_key:result["value"]}
|
| 262 |
+
elif parsed.gpc1.mode == "numeric101":
|
| 263 |
+
raw_fields = parsed.gpc1.numeric_fields or []
|
| 264 |
+
if not 1 <= len(raw_fields) <= 512:
|
| 265 |
+
raise APIError("numeric101 requires 1..512 fields", param="gpc1.numeric_fields")
|
| 266 |
+
expected_field_keys = {"key", "description", "minimum", "maximum", "unit"}
|
| 267 |
+
optional_field_keys = {"aliases", "reference"}
|
| 268 |
+
if any(not expected_field_keys <= set(v) or set(v) - expected_field_keys - optional_field_keys for v in raw_fields):
|
| 269 |
+
raise APIError("numeric field keys are invalid", param="gpc1.numeric_fields")
|
| 270 |
+
fields = [NumericField(
|
| 271 |
+
key=str(v["key"]), description=str(v["description"]),
|
| 272 |
+
minimum=float(v["minimum"]), maximum=float(v["maximum"]), unit=str(v["unit"]),
|
| 273 |
+
aliases=tuple(map(str, v.get("aliases", []))), reference=str(v.get("reference", "declared_by_caller")))
|
| 274 |
+
for v in raw_fields]
|
| 275 |
+
if len({field.key for field in fields}) != len(fields):
|
| 276 |
+
raise APIError("numeric field keys must be unique", param="gpc1.numeric_fields")
|
| 277 |
+
names=[name for field in fields for name in (field.key,*field.aliases)]
|
| 278 |
+
if len(names) != len(set(names)):
|
| 279 |
+
raise APIError("numeric canonical keys and aliases must be globally unique",param="gpc1.numeric_fields")
|
| 280 |
+
properties = wrapper["schema"].get("properties", {})
|
| 281 |
+
if set(properties) != {field.key for field in fields} or any(
|
| 282 |
+
properties[f.key].get("type") != "number"
|
| 283 |
+
or properties[f.key].get("minimum") != f.minimum
|
| 284 |
+
or properties[f.key].get("maximum") != f.maximum for f in fields):
|
| 285 |
+
raise APIError("numeric fields and bounds must exactly match number properties", param="gpc1.numeric_fields")
|
| 286 |
+
result = await run_native(selected.numeric, context, fields, image)
|
| 287 |
+
value = {key: field["value"] for key, field in result["fields"].items()}
|
| 288 |
+
else:
|
| 289 |
+
if image is not None: raise APIError("finite_joint image mode is not supported", param="messages")
|
| 290 |
+
records = parsed.gpc1.allowed_records or []
|
| 291 |
+
if not 2 <= len(records) <= 255: raise APIError("finite_joint requires 2..255 allowed records", param="gpc1.allowed_records")
|
| 292 |
+
try:
|
| 293 |
+
for record in records: json.dumps(record,allow_nan=False)
|
| 294 |
+
except (TypeError,ValueError) as error: raise APIError("allowed records must be finite JSON",param="gpc1.allowed_records") from error
|
| 295 |
+
for index, record in enumerate(records):
|
| 296 |
+
await asyncio.to_thread(validate_instance, record, wrapper["schema"], f"allowed_records[{index}]")
|
| 297 |
+
encoded = [json.dumps(v, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False) for v in records]
|
| 298 |
+
if len(encoded) != len(set(encoded)): raise APIError("allowed records must be unique", param="gpc1.allowed_records")
|
| 299 |
+
result = await run_native(selected.finite_joint, context, wrapper["schema"], records)
|
| 300 |
+
value = result["value"]
|
| 301 |
+
await asyncio.to_thread(validate_instance, value, wrapper["schema"])
|
| 302 |
+
except APIError: raise
|
| 303 |
+
except (KeyError, TypeError, ValueError) as error:
|
| 304 |
+
raise APIError(str(error), param="gpc1") from error
|
| 305 |
+
content = json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
| 306 |
+
now = int(time.time()); completion_id = "chatcmpl_" + uuid.uuid4().hex
|
| 307 |
+
return {"id": completion_id, "object": "chat.completion", "created": now,
|
| 308 |
+
"model": selected.model_id, "choices": [{"index": 0,
|
| 309 |
+
"message": {"role": "assistant", "content": content}, "finish_reason": "stop", "logprobs": None}],
|
| 310 |
+
"usage": None,
|
| 311 |
+
"gpc1": result}
|
| 312 |
+
return app
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
app = create_app()
|
gpc1_server/assets.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import hashlib
|
| 4 |
+
import json
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
PACKAGE_ROOT = Path(__file__).resolve().parents[1]
|
| 9 |
+
ASSET_ROOT = PACKAGE_ROOT / "assets"
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def sha256_file(path: Path) -> str:
|
| 13 |
+
digest = hashlib.sha256()
|
| 14 |
+
with path.open("rb") as handle:
|
| 15 |
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
| 16 |
+
digest.update(chunk)
|
| 17 |
+
return digest.hexdigest()
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def load_runtime_manifest() -> dict[str, Any]:
|
| 21 |
+
manifest = json.loads((ASSET_ROOT / "runtime_manifest.json").read_text())
|
| 22 |
+
codebook = ASSET_ROOT / "codebook255.json"
|
| 23 |
+
if sha256_file(codebook) != manifest["assets"]["codebook255_sha256"]:
|
| 24 |
+
raise RuntimeError("frozen codebook hash mismatch")
|
| 25 |
+
base_files = ASSET_ROOT / "base_files.sha256.json"
|
| 26 |
+
if sha256_file(base_files) != manifest["assets"]["base_files_sha256"]:
|
| 27 |
+
raise RuntimeError("frozen base-file manifest hash mismatch")
|
| 28 |
+
return manifest
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def load_codebook() -> tuple[dict[str, Any], ...]:
|
| 32 |
+
manifest = load_runtime_manifest()
|
| 33 |
+
rows = json.loads((ASSET_ROOT / "codebook255.json").read_text())
|
| 34 |
+
if len(rows) != 255 or [r["position"] for r in rows] != list(range(255)):
|
| 35 |
+
raise RuntimeError("frozen codebook must contain positions 0..254")
|
| 36 |
+
if len({r["code"] for r in rows}) != 255 or len({r["token_id"] for r in rows}) != 255:
|
| 37 |
+
raise RuntimeError("frozen codebook codes/token IDs are not unique")
|
| 38 |
+
return tuple(rows)
|
gpc1_server/bootstrap.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Verify or copy the exact model bundled with this release."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
import shutil
|
| 7 |
+
from .assets import ASSET_ROOT,sha256_file
|
| 8 |
+
|
| 9 |
+
def main():
|
| 10 |
+
parser=argparse.ArgumentParser();parser.add_argument('--output',type=Path,required=True)
|
| 11 |
+
args=parser.parse_args();source=ASSET_ROOT.parent/'model'
|
| 12 |
+
inventory=json.loads((ASSET_ROOT/'base_files.sha256.json').read_text())
|
| 13 |
+
if args.output.resolve()!=source.resolve() and args.output.exists() and any(args.output.iterdir()):
|
| 14 |
+
raise RuntimeError('--output must be absent or empty')
|
| 15 |
+
for name,digest in inventory['files'].items():
|
| 16 |
+
if not (source/name).is_file() or sha256_file(source/name)!=digest:
|
| 17 |
+
raise RuntimeError(f'Bundled model identity mismatch: {name}')
|
| 18 |
+
if args.output.resolve()!=source.resolve():
|
| 19 |
+
args.output.mkdir(parents=True,exist_ok=True)
|
| 20 |
+
for name in inventory['files']:shutil.copyfile(source/name,args.output/name)
|
| 21 |
+
print(args.output.resolve())
|
| 22 |
+
|
| 23 |
+
if __name__=='__main__':main()
|
gpc1_server/compiler.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Compile numeric requests and decode range predictions."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
import json
|
| 6 |
+
import math
|
| 7 |
+
from typing import Any, Sequence
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@dataclass(frozen=True, slots=True)
|
| 11 |
+
class NumericField:
|
| 12 |
+
key: str
|
| 13 |
+
description: str
|
| 14 |
+
minimum: float
|
| 15 |
+
maximum: float
|
| 16 |
+
unit: str
|
| 17 |
+
aliases: tuple[str, ...] = ()
|
| 18 |
+
reference: str = "declared_by_caller"
|
| 19 |
+
|
| 20 |
+
def __post_init__(self) -> None:
|
| 21 |
+
values=(self.key,self.description,self.unit,self.reference,*self.aliases)
|
| 22 |
+
if any(not isinstance(v,str) or not v.strip() or v != v.strip() for v in values):
|
| 23 |
+
raise ValueError("numeric names and descriptions must be nonempty and edge-whitespace-free")
|
| 24 |
+
if self.key in self.aliases or len(self.aliases) != len(set(self.aliases)):
|
| 25 |
+
raise ValueError("numeric aliases must be unique and exclude the canonical key")
|
| 26 |
+
if not math.isfinite(self.minimum) or not math.isfinite(self.maximum) or self.minimum >= self.maximum:
|
| 27 |
+
raise ValueError("numeric bounds must be finite and strictly increasing")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@dataclass(frozen=True, slots=True)
|
| 31 |
+
class CompiledNumeric:
|
| 32 |
+
prompt: str
|
| 33 |
+
input_ids: tuple[int, ...]
|
| 34 |
+
positions: tuple[int, ...]
|
| 35 |
+
candidate_token_ids: tuple[int, ...]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@dataclass(frozen=True, slots=True)
|
| 39 |
+
class CompiledVisionNumeric:
|
| 40 |
+
numeric: CompiledNumeric
|
| 41 |
+
processor_inputs: dict[str, Any]
|
| 42 |
+
positions: tuple[int, ...]
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _query(field: NumericField) -> str:
|
| 46 |
+
# Preserve formatting for tokenizer compatibility.
|
| 47 |
+
return ("QUERY canonical_key=" + json.dumps(field.key, ensure_ascii=False)
|
| 48 |
+
+ " unit=" + json.dumps(field.unit, ensure_ascii=False)
|
| 49 |
+
+ f" bounds=[{field.minimum!r},{field.maximum!r}]\nAnswer code:\n")
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def render_prefix(context: str, fields: Sequence[NumericField], codes: Sequence[str]) -> str:
|
| 53 |
+
shared_map = [{"transport_code": codes[i], "percentage_index": i,
|
| 54 |
+
"normalized_point": i / 100.0} for i in range(101)]
|
| 55 |
+
definitions = [{"canonical_key": f.key, "aliases": list(f.aliases),
|
| 56 |
+
"meaning": f.description, "range": [f.minimum, f.maximum],
|
| 57 |
+
"unit": f.unit, "reference": f.reference,
|
| 58 |
+
"affine_decode": "value=lower+normalized_point*(upper-lower)"}
|
| 59 |
+
for f in fields]
|
| 60 |
+
return ("NATIVE_NUMERIC_SHARED_CODEBOOK_QUERY_SLOTS_V1\n"
|
| 61 |
+
"Return exactly one listed opaque transport code for every point field. "
|
| 62 |
+
"The shared map binds each transport code to one normalized point. "
|
| 63 |
+
"Each field definition supplies its own affine bounds and unit. Transport strings "
|
| 64 |
+
"are labels, not pretrained numeric semantics.\n"
|
| 65 |
+
"All maps and field definitions precede all answer-query slots. "
|
| 66 |
+
"No answer is present in this input.\nSHARED_TRANSPORT_MAP="
|
| 67 |
+
+ json.dumps(shared_map, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
| 68 |
+
+ "\nFIELD_DEFINITIONS="
|
| 69 |
+
+ json.dumps(definitions, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
| 70 |
+
+ "\nCONTEXT_BEGIN\n" + context + "\nCONTEXT_END\nBEGIN_QUERY_SLOTS\n")
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def compile_numeric(tokenizer: Any, context: str, fields: Sequence[NumericField],
|
| 74 |
+
codebook: Sequence[dict[str, Any]], max_input_tokens: int) -> CompiledNumeric:
|
| 75 |
+
if not context.strip() or not fields:
|
| 76 |
+
raise ValueError("context and fields must be nonempty")
|
| 77 |
+
names=[name for field in fields for name in (field.key,*field.aliases)]
|
| 78 |
+
if len(names) != len(set(names)):
|
| 79 |
+
raise ValueError("numeric canonical keys and aliases must be globally unique")
|
| 80 |
+
codes = [str(row["code"]) for row in codebook[:101]]
|
| 81 |
+
prefix = render_prefix(context, fields, codes)
|
| 82 |
+
cumulative = prefix
|
| 83 |
+
previous = tuple(tokenizer.encode(cumulative, add_special_tokens=False))
|
| 84 |
+
positions: list[int] = []
|
| 85 |
+
for field in fields:
|
| 86 |
+
cumulative += _query(field)
|
| 87 |
+
current = tuple(tokenizer.encode(cumulative, add_special_tokens=False))
|
| 88 |
+
if len(current) <= len(previous) or current[:len(previous)] != previous:
|
| 89 |
+
raise ValueError("tokenizer resegmented an earlier query boundary")
|
| 90 |
+
# Native LM logits at the last prompt token predict the opaque code.
|
| 91 |
+
positions.append(len(current) - 1)
|
| 92 |
+
previous = current
|
| 93 |
+
if len(previous) > max_input_tokens:
|
| 94 |
+
raise ValueError(f"request needs {len(previous)} tokens; limit is {max_input_tokens}; no truncation performed")
|
| 95 |
+
return CompiledNumeric(cumulative, previous, tuple(positions),
|
| 96 |
+
tuple(int(row["token_id"]) for row in codebook[:101]))
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def decode_numeric(probabilities: Sequence[float], field: NumericField) -> dict[str, Any]:
|
| 100 |
+
if len(probabilities) != 101:
|
| 101 |
+
raise ValueError("numeric probability vector must have 101 entries")
|
| 102 |
+
total = math.fsum(float(p) for p in probabilities)
|
| 103 |
+
if not math.isfinite(total) or total <= 0 or any(float(p) < 0 for p in probabilities):
|
| 104 |
+
raise ValueError("invalid numeric probability vector")
|
| 105 |
+
probs = [float(p) / total for p in probabilities]
|
| 106 |
+
map_index = max(range(101), key=probs.__getitem__)
|
| 107 |
+
expected_index = math.fsum(i * p for i, p in enumerate(probs))
|
| 108 |
+
span = field.maximum - field.minimum
|
| 109 |
+
return {
|
| 110 |
+
"value": field.minimum + span * map_index / 100.0,
|
| 111 |
+
"map_value": field.minimum + span * map_index / 100.0,
|
| 112 |
+
"expected_value": field.minimum + span * expected_index / 100.0,
|
| 113 |
+
"map_index": map_index,
|
| 114 |
+
"normalized_grid": [i / 100.0 for i in range(101)],
|
| 115 |
+
"probabilities": probs,
|
| 116 |
+
"minimum": field.minimum, "maximum": field.maximum, "unit": field.unit,
|
| 117 |
+
"calibrated": False,
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def _unique_subsequence_offset(values: Sequence[int], needle: Sequence[int]) -> int:
|
| 122 |
+
"""Find one token span in linear time, rejecting absent or repeated spans."""
|
| 123 |
+
if not needle:
|
| 124 |
+
raise ValueError("target-free numeric prompt must occur exactly once")
|
| 125 |
+
failure = [0] * len(needle)
|
| 126 |
+
matched = 0
|
| 127 |
+
for index in range(1, len(needle)):
|
| 128 |
+
while matched and needle[index] != needle[matched]:
|
| 129 |
+
matched = failure[matched - 1]
|
| 130 |
+
if needle[index] == needle[matched]:
|
| 131 |
+
matched += 1
|
| 132 |
+
failure[index] = matched
|
| 133 |
+
matched = 0
|
| 134 |
+
offset = -1
|
| 135 |
+
for index, value in enumerate(values):
|
| 136 |
+
while matched and value != needle[matched]:
|
| 137 |
+
matched = failure[matched - 1]
|
| 138 |
+
if value == needle[matched]:
|
| 139 |
+
matched += 1
|
| 140 |
+
if matched == len(needle):
|
| 141 |
+
if offset >= 0:
|
| 142 |
+
raise ValueError("target-free numeric prompt must occur exactly once")
|
| 143 |
+
offset = index + 1 - len(needle)
|
| 144 |
+
matched = failure[matched - 1]
|
| 145 |
+
if offset < 0:
|
| 146 |
+
raise ValueError("target-free numeric prompt must occur exactly once")
|
| 147 |
+
return offset
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def compile_vision_numeric(processor: Any, image: Any, context: str,
|
| 151 |
+
fields: Sequence[NumericField], codebook: Sequence[dict[str, Any]],
|
| 152 |
+
max_input_tokens: int, model_config: Any) -> CompiledVisionNumeric:
|
| 153 |
+
"""Compile one image and numeric fields using the native processor."""
|
| 154 |
+
import torch
|
| 155 |
+
width, height = getattr(image, "size", (0, 0))
|
| 156 |
+
if width <= 0 or height <= 0:
|
| 157 |
+
raise ValueError("source raster dimensions must be positive")
|
| 158 |
+
numeric = compile_numeric(processor.tokenizer, context, fields, codebook, max_input_tokens)
|
| 159 |
+
messages = [[
|
| 160 |
+
{"role": "user", "content": [
|
| 161 |
+
{"type": "image", "image": image},
|
| 162 |
+
{"type": "text", "text": (
|
| 163 |
+
"Inspect only this supplied full uncropped image. The following assistant "
|
| 164 |
+
"schema contains target-free coordinate query slots for this image.")}]},
|
| 165 |
+
{"role": "assistant", "content": [{"type": "text",
|
| 166 |
+
"text": numeric.prompt + "END_TARGET_FREE_QUERY_BLOCK\n"}]},
|
| 167 |
+
]]
|
| 168 |
+
batch = processor.apply_chat_template(messages, tokenize=True, truncation=False,
|
| 169 |
+
add_generation_prompt=False, continue_final_message=False, enable_thinking=False,
|
| 170 |
+
return_dict=True, return_tensors="pt", return_mm_token_type_ids=True)
|
| 171 |
+
tensors = {k: v for k, v in batch.items() if isinstance(v, torch.Tensor)}
|
| 172 |
+
required = {"input_ids", "attention_mask", "pixel_values", "image_grid_thw", "mm_token_type_ids"}
|
| 173 |
+
if set(tensors) != required:
|
| 174 |
+
raise ValueError(f"processor tensor keys must be exactly {sorted(required)}, got {sorted(tensors)}")
|
| 175 |
+
ids, mask, mm = tensors["input_ids"], tensors["attention_mask"], tensors["mm_token_type_ids"]
|
| 176 |
+
if ids.ndim != 2 or ids.shape[0] != 1 or mask.shape != ids.shape or mm.shape != ids.shape:
|
| 177 |
+
raise ValueError("vision request requires aligned singleton text tensors")
|
| 178 |
+
if not bool(mask.eq(1).all()) or ids.shape[1] + 1 > max_input_tokens:
|
| 179 |
+
raise ValueError("vision request exceeds token admission or contains padding; no truncation performed")
|
| 180 |
+
grid = tensors["image_grid_thw"]
|
| 181 |
+
if grid.shape != (1, 3) or not bool((grid > 0).all()):
|
| 182 |
+
raise ValueError("image_grid_thw must be one positive [t,h,w] row")
|
| 183 |
+
patch_rows = int(grid.prod().item())
|
| 184 |
+
if tensors["pixel_values"].ndim != 2 or tensors["pixel_values"].shape[0] != patch_rows:
|
| 185 |
+
raise ValueError("pixel_values and processor grid disagree")
|
| 186 |
+
config = model_config.to_dict() if hasattr(model_config, "to_dict") else dict(model_config)
|
| 187 |
+
if config.get("model_type") != "qwen3_5_moe" or not isinstance(config.get("vision_config"), dict):
|
| 188 |
+
raise ValueError("vision compiler requires the pinned multimodal Qwen3.5 MoE config")
|
| 189 |
+
merge = int(processor.image_processor.merge_size)
|
| 190 |
+
if patch_rows % (merge * merge): raise ValueError("image grid is not divisible by merge area")
|
| 191 |
+
image_token = int(config["image_token_id"])
|
| 192 |
+
active = [int(v) for v in ids[0].tolist()]
|
| 193 |
+
expected_image_tokens = patch_rows // (merge * merge)
|
| 194 |
+
if active.count(image_token) != expected_image_tokens:
|
| 195 |
+
raise ValueError("image token count and processor grid disagree")
|
| 196 |
+
vision_start = int(config["vision_start_token_id"]); vision_end = int(config["vision_end_token_id"])
|
| 197 |
+
if active.count(vision_start) != 1 or active.count(vision_end) != 1:
|
| 198 |
+
raise ValueError("vision boundary token count is not exactly one pair")
|
| 199 |
+
mm_values = [int(v) for v in mm[0].tolist()]
|
| 200 |
+
if any((kind == 1) != (token == image_token) for token, kind in zip(active, mm_values, strict=True)):
|
| 201 |
+
raise ValueError("image tokens and mm_token_type_ids disagree")
|
| 202 |
+
needle = numeric.input_ids
|
| 203 |
+
offset = _unique_subsequence_offset(active, needle)
|
| 204 |
+
positions = tuple(offset + p for p in numeric.positions)
|
| 205 |
+
return CompiledVisionNumeric(numeric, tensors, positions)
|
gpc1_server/errors.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
@dataclass(slots=True)
|
| 7 |
+
class APIError(Exception):
|
| 8 |
+
message: str
|
| 9 |
+
status_code: int = 400
|
| 10 |
+
error_type: str = "invalid_request_error"
|
| 11 |
+
param: str | None = None
|
| 12 |
+
code: str | None = None
|
| 13 |
+
|
| 14 |
+
def body(self) -> dict:
|
| 15 |
+
return {"error": {"message": self.message, "type": self.error_type,
|
| 16 |
+
"param": self.param, "code": self.code}}
|
gpc1_server/native_onepass.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Score complete candidate records in one model call."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from typing import Any, Callable, Sequence
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@dataclass(frozen=True, slots=True)
|
| 11 |
+
class Row:
|
| 12 |
+
index: int
|
| 13 |
+
candidate_id: str
|
| 14 |
+
targets: tuple[int, ...]
|
| 15 |
+
positions: tuple[int, ...]
|
| 16 |
+
prefix_tokens: int
|
| 17 |
+
input_tokens: int
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@dataclass(frozen=True, slots=True)
|
| 21 |
+
class CompiledJoint:
|
| 22 |
+
input_ids: torch.Tensor
|
| 23 |
+
attention_mask: torch.Tensor
|
| 24 |
+
position_ids: torch.Tensor
|
| 25 |
+
logits_to_keep: torch.Tensor
|
| 26 |
+
rows: tuple[Row, ...]
|
| 27 |
+
candidate_order: tuple[str, ...]
|
| 28 |
+
padded_tokens: int
|
| 29 |
+
logit_vectors: int
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _contains(values: tuple[int, ...], needle: tuple[int, ...]) -> bool:
|
| 33 |
+
return len(needle) <= len(values) and any(
|
| 34 |
+
values[i:i + len(needle)] == needle for i in range(len(values) - len(needle) + 1)
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def compile_joint(prefix: Sequence[int], candidate_ids: Sequence[str],
|
| 39 |
+
suffixes: Sequence[Sequence[int]], terminal: Sequence[int], *,
|
| 40 |
+
pad_token_id: int, max_rows: int, max_padded_tokens: int,
|
| 41 |
+
max_logit_vectors: int, max_input_tokens: int) -> CompiledJoint:
|
| 42 |
+
prefix = tuple(prefix); terminal = tuple(terminal)
|
| 43 |
+
ids = tuple(candidate_ids); suffixes = tuple(tuple(v) for v in suffixes)
|
| 44 |
+
if not prefix or not terminal or any(type(v) is not int or v < 0 for v in prefix + terminal):
|
| 45 |
+
raise ValueError("prefix and terminal must contain nonnegative token IDs")
|
| 46 |
+
if not ids or len(ids) != len(set(ids)) or any(not isinstance(v, str) or not v for v in ids):
|
| 47 |
+
raise ValueError("candidate IDs must be unique nonempty strings")
|
| 48 |
+
if len(ids) != len(suffixes) or len(suffixes) != len(set(suffixes)):
|
| 49 |
+
raise ValueError("candidate suffixes must be unique and align with candidate IDs")
|
| 50 |
+
if any(prefix[-n:] == terminal[:n] for n in range(1, min(len(prefix), len(terminal)) + 1)):
|
| 51 |
+
raise ValueError("prefix overlaps the declared terminal boundary")
|
| 52 |
+
for suffix in suffixes:
|
| 53 |
+
if (len(suffix) < len(terminal) or suffix[-len(terminal):] != terminal
|
| 54 |
+
or _contains(suffix[:-len(terminal)], terminal)):
|
| 55 |
+
raise ValueError("every candidate must contain the common terminal exactly at its end")
|
| 56 |
+
for i, left in enumerate(suffixes):
|
| 57 |
+
for j, right in enumerate(suffixes):
|
| 58 |
+
if i != j and len(left) < len(right) and right[:len(left)] == left:
|
| 59 |
+
raise ValueError("candidate tokenizations prefix-overlap")
|
| 60 |
+
if any(any(type(v) is not int or v < 0 for v in suffix) for suffix in suffixes):
|
| 61 |
+
raise ValueError("candidate suffixes must contain nonnegative token IDs")
|
| 62 |
+
if len(suffixes) > max_rows:
|
| 63 |
+
raise ValueError(f"compiled rows {len(suffixes)} exceed max_rows {max_rows}")
|
| 64 |
+
sequences = tuple(prefix + suffix for suffix in suffixes)
|
| 65 |
+
if max(map(len,sequences)) > max_input_tokens:
|
| 66 |
+
raise ValueError(f"joint request exceeds max_input_tokens {max_input_tokens}; no truncation performed")
|
| 67 |
+
width = max(map(len, sequences)); padded = len(sequences) * width
|
| 68 |
+
if padded > max_padded_tokens:
|
| 69 |
+
raise ValueError(f"compiled padded tokens {padded} exceed max_padded_tokens {max_padded_tokens}")
|
| 70 |
+
positions = sorted({p for seq in sequences for p in range(len(prefix) - 1, len(seq) - 1)})
|
| 71 |
+
vectors = len(sequences) * len(positions)
|
| 72 |
+
if vectors > max_logit_vectors:
|
| 73 |
+
raise ValueError(f"compiled logit vectors {vectors} exceed max_logit_vectors {max_logit_vectors}")
|
| 74 |
+
input_ids = torch.full((len(sequences), width), pad_token_id, dtype=torch.long)
|
| 75 |
+
mask = torch.zeros_like(input_ids)
|
| 76 |
+
rows = []
|
| 77 |
+
for i, (candidate_id, suffix, sequence) in enumerate(zip(ids, suffixes, sequences, strict=True)):
|
| 78 |
+
input_ids[i, :len(sequence)] = torch.tensor(sequence)
|
| 79 |
+
mask[i, :len(sequence)] = 1
|
| 80 |
+
rows.append(Row(i, candidate_id, suffix,
|
| 81 |
+
tuple(range(len(prefix) - 1, len(sequence) - 1)), len(prefix), len(sequence)))
|
| 82 |
+
position_ids = mask.cumsum(-1) - 1; position_ids.masked_fill_(mask == 0, 0)
|
| 83 |
+
return CompiledJoint(input_ids, mask, position_ids, torch.tensor(positions), tuple(rows), ids,
|
| 84 |
+
padded, vectors)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def score_joint(model_forward: Callable[..., Any], compiled: CompiledJoint, *, device: Any) -> dict[str, Any]:
|
| 88 |
+
expected_positions = compiled.attention_mask.cumsum(-1) - 1
|
| 89 |
+
expected_positions.masked_fill_(compiled.attention_mask == 0, 0)
|
| 90 |
+
if not torch.equal(compiled.position_ids, expected_positions):
|
| 91 |
+
raise ValueError("compiled position IDs mutated before forward")
|
| 92 |
+
keep = [int(value) for value in compiled.logits_to_keep.tolist()]
|
| 93 |
+
with torch.inference_mode():
|
| 94 |
+
output = model_forward(input_ids=compiled.input_ids.to(device),
|
| 95 |
+
attention_mask=compiled.attention_mask.to(device),
|
| 96 |
+
position_ids=compiled.position_ids.to(device), logits_to_keep=keep,
|
| 97 |
+
use_cache=False, return_dict=True)
|
| 98 |
+
logits = output.logits
|
| 99 |
+
if logits.ndim != 3 or tuple(logits.shape[:2]) != (len(compiled.rows), len(keep)):
|
| 100 |
+
raise ValueError("native sparse logits do not align to compiled joint positions")
|
| 101 |
+
logits = logits.float()
|
| 102 |
+
if not bool(torch.isfinite(logits).all()):
|
| 103 |
+
raise ValueError("model logits contain non-finite values")
|
| 104 |
+
columns = {int(p): i for i, p in enumerate(compiled.logits_to_keep.tolist())}
|
| 105 |
+
scores = []
|
| 106 |
+
for row in compiled.rows:
|
| 107 |
+
terms = []
|
| 108 |
+
for position, target in zip(row.positions, row.targets, strict=True):
|
| 109 |
+
distribution = logits[row.index, columns[position]]
|
| 110 |
+
if target >= distribution.numel(): raise ValueError("target token is outside vocabulary")
|
| 111 |
+
terms.append(distribution[target] - torch.logsumexp(distribution, -1))
|
| 112 |
+
scores.append(torch.stack(terms).sum())
|
| 113 |
+
scores_t = torch.stack(scores); probs = torch.exp(scores_t - torch.logsumexp(scores_t, 0))
|
| 114 |
+
selected = int(torch.argmax(scores_t))
|
| 115 |
+
return {"selected_index": selected, "probabilities": probs.cpu().tolist(),
|
| 116 |
+
"log_scores": scores_t.cpu().tolist(),
|
| 117 |
+
"row_map": [{"row_index": r.index, "candidate_id": r.candidate_id,
|
| 118 |
+
"prediction_positions": list(r.positions)} for r in compiled.rows],
|
| 119 |
+
"accounting": {"backbone_calls": 1, "physical_rows": len(compiled.rows),
|
| 120 |
+
"padded_tokens": compiled.padded_tokens,
|
| 121 |
+
"logit_vectors": compiled.logit_vectors, "truncated": False},
|
| 122 |
+
"calibrated": False}
|
gpc1_server/runtime.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import hashlib
|
| 5 |
+
import os
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from typing import Any, Protocol, Sequence
|
| 8 |
+
|
| 9 |
+
from .assets import load_codebook, load_runtime_manifest, sha256_file
|
| 10 |
+
from .compiler import NumericField, compile_numeric, compile_vision_numeric, decode_numeric
|
| 11 |
+
from .native_onepass import compile_joint, score_joint
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class Backend(Protocol):
|
| 15 |
+
model_id: str
|
| 16 |
+
def numeric(self, context: str, fields: Sequence[NumericField], image: Any | None = None) -> dict[str, Any]: ...
|
| 17 |
+
def atomic(self, context: str, field_key: str, choices: Sequence[Any]) -> dict[str, Any]: ...
|
| 18 |
+
def finite_joint(self, context: str, schema: dict[str, Any], candidates: Sequence[dict[str, Any]]) -> dict[str, Any]: ...
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class TransformersBackend:
|
| 22 |
+
"""Pinned Transformers/PEFT backend; no custom CUDA kernels or generation."""
|
| 23 |
+
|
| 24 |
+
@staticmethod
|
| 25 |
+
def _context_limits(manifest: dict[str, Any], model_config: dict[str, Any],
|
| 26 |
+
environ: dict[str, str]) -> tuple[int, int]:
|
| 27 |
+
capacity = int(model_config["text_config"]["max_position_embeddings"])
|
| 28 |
+
default = int(manifest["admission"]["default_max_input_tokens"])
|
| 29 |
+
limit = int(environ.get("GPC1_MAX_INPUT_TOKENS", str(default)))
|
| 30 |
+
if not 0 < limit <= capacity:
|
| 31 |
+
raise ValueError(f"GPC1_MAX_INPUT_TOKENS must be between 1 and {capacity}")
|
| 32 |
+
padded = int(environ.get("GPC1_MAX_PADDED_TOKENS", str(max(131072, 2 * limit))))
|
| 33 |
+
if padded <= 0:
|
| 34 |
+
raise ValueError("GPC1_MAX_PADDED_TOKENS must be positive")
|
| 35 |
+
return limit, padded
|
| 36 |
+
|
| 37 |
+
@staticmethod
|
| 38 |
+
def _verify_model_file_closure(model_path: Path, expected_files: set[str]) -> None:
|
| 39 |
+
top_files={p.name for p in model_path.iterdir() if p.is_file()}
|
| 40 |
+
suspicious={name for name in top_files if (
|
| 41 |
+
name.endswith((".safetensors",".json",".jinja",".txt"))
|
| 42 |
+
or "config" in name or "tokenizer" in name or "processor" in name or "index" in name)}
|
| 43 |
+
if suspicious != expected_files:
|
| 44 |
+
raise RuntimeError(f"loader-visible model file closure mismatch: {sorted(suspicious ^ expected_files)}")
|
| 45 |
+
|
| 46 |
+
@staticmethod
|
| 47 |
+
def _verify_adapter_file_closure(adapter_path: Path) -> None:
|
| 48 |
+
adapter_files={p.name for p in adapter_path.iterdir() if p.is_file()}
|
| 49 |
+
if adapter_files != {"adapter_config.json","adapter_model.safetensors"}:
|
| 50 |
+
raise RuntimeError("adapter directory must contain exactly the two bound release files")
|
| 51 |
+
|
| 52 |
+
@staticmethod
|
| 53 |
+
def _adapter_path(manifest: dict[str, Any], package_root: Path,
|
| 54 |
+
environ: dict[str, str]) -> Path:
|
| 55 |
+
if manifest.get("weight_format") != "adapter" or not isinstance(manifest.get("adapter"), dict) \
|
| 56 |
+
or manifest.get("merged_full_weights") is not None:
|
| 57 |
+
raise RuntimeError("release runtime requires adapter weight metadata only")
|
| 58 |
+
return Path(environ.get("GPC1_ADAPTER_PATH", package_root / "adapter"))
|
| 59 |
+
|
| 60 |
+
def __init__(self) -> None:
|
| 61 |
+
import torch
|
| 62 |
+
from transformers import AutoProcessor
|
| 63 |
+
from transformers import Qwen3_5MoeForConditionalGeneration
|
| 64 |
+
|
| 65 |
+
self.torch = torch
|
| 66 |
+
torch.backends.cuda.matmul.allow_tf32 = False
|
| 67 |
+
torch.backends.cudnn.allow_tf32 = False
|
| 68 |
+
torch.set_float32_matmul_precision("highest")
|
| 69 |
+
self.manifest = load_runtime_manifest()
|
| 70 |
+
package_root = Path(__file__).resolve().parents[1]
|
| 71 |
+
model_path = Path(os.environ["GPC1_MODEL_PATH"])
|
| 72 |
+
self.max_input_tokens, self.max_padded_tokens = self._context_limits(
|
| 73 |
+
self.manifest, json.loads((model_path / "config.json").read_text()), os.environ)
|
| 74 |
+
adapter_path = self._adapter_path(self.manifest, package_root, os.environ)
|
| 75 |
+
self._verify_artifacts(model_path, adapter_path)
|
| 76 |
+
self.processor = AutoProcessor.from_pretrained(model_path, local_files_only=True,
|
| 77 |
+
trust_remote_code=False)
|
| 78 |
+
base = Qwen3_5MoeForConditionalGeneration.from_pretrained(
|
| 79 |
+
model_path, local_files_only=True, trust_remote_code=False,
|
| 80 |
+
dtype=torch.bfloat16, attn_implementation="sdpa",
|
| 81 |
+
low_cpu_mem_usage=True, device_map={"": 0})
|
| 82 |
+
from peft import PeftModel
|
| 83 |
+
self.model = PeftModel.from_pretrained(base, adapter_path, is_trainable=False,
|
| 84 |
+
autocast_adapter_dtype=True)
|
| 85 |
+
self.model.eval()
|
| 86 |
+
for parameter in self.model.parameters():
|
| 87 |
+
parameter.requires_grad_(False)
|
| 88 |
+
if base.__class__.__name__ != "Qwen3_5MoeForConditionalGeneration" \
|
| 89 |
+
or getattr(base.config, "model_type", None) != "qwen3_5_moe":
|
| 90 |
+
raise RuntimeError("loaded base is not the pinned Qwen3.5 MoE architecture")
|
| 91 |
+
adapter_parameters = {name: p for name, p in self.model.named_parameters() if "lora_" in name}
|
| 92 |
+
base_dtypes = {p.dtype for name, p in self.model.named_parameters()
|
| 93 |
+
if p.is_floating_point() and "lora_" not in name}
|
| 94 |
+
if base_dtypes != {torch.bfloat16} or not adapter_parameters \
|
| 95 |
+
or {p.dtype for p in adapter_parameters.values()} != {torch.float32}:
|
| 96 |
+
raise RuntimeError("loaded base/adapter dtype inventory differs from BF16/FP32 contract")
|
| 97 |
+
from peft.utils import get_peft_model_state_dict
|
| 98 |
+
digest = hashlib.sha256()
|
| 99 |
+
state = get_peft_model_state_dict(self.model)
|
| 100 |
+
for name in sorted(state):
|
| 101 |
+
tensor = state[name].detach().cpu().contiguous()
|
| 102 |
+
metadata = json.dumps({"dtype":str(tensor.dtype),"name":name,"shape":list(tensor.shape)},
|
| 103 |
+
sort_keys=True,separators=(",", ":")).encode()
|
| 104 |
+
raw = tensor.view(torch.uint8).numpy().tobytes()
|
| 105 |
+
digest.update(len(metadata).to_bytes(8,"big")); digest.update(metadata)
|
| 106 |
+
digest.update(len(raw).to_bytes(8,"big")); digest.update(raw)
|
| 107 |
+
if digest.hexdigest() != self.manifest["adapter"]["adapter_tensor_digest"]:
|
| 108 |
+
raise RuntimeError("loaded adapter tensor digest mismatch")
|
| 109 |
+
self.model_id = self.manifest["served_model_id"]
|
| 110 |
+
self.codebook = load_codebook()
|
| 111 |
+
self.max_joint_rows = int(os.environ.get("GPC1_MAX_JOINT_ROWS", "32"))
|
| 112 |
+
self.max_logit_vectors = int(os.environ.get("GPC1_MAX_LOGIT_VECTORS", "512"))
|
| 113 |
+
self.max_logits_bytes = int(os.environ.get("GPC1_MAX_LOGITS_BYTES", str(512 * 1024 * 1024)))
|
| 114 |
+
accepted_modes = set(self.manifest["admission"]["accepted_modes"])
|
| 115 |
+
self.admit_vision = "numeric101_image" in accepted_modes \
|
| 116 |
+
and os.environ.get("GPC1_ADMIT_VISION_NUMERIC", "accepted") == "accepted"
|
| 117 |
+
self.admit_joint = "finite_joint" in accepted_modes \
|
| 118 |
+
and os.environ.get("GPC1_ADMIT_FINITE_JOINT", "accepted") == "accepted"
|
| 119 |
+
cache_mb = int(os.environ.get("GPC1_IMAGE_CACHE_MB", "64"))
|
| 120 |
+
if not 0 <= cache_mb <= 64:
|
| 121 |
+
raise ValueError("GPC1_IMAGE_CACHE_MB must be an integer between 0 and 64")
|
| 122 |
+
self._vision_cache = None
|
| 123 |
+
if cache_mb:
|
| 124 |
+
from .vision_cache import VisionFeatureCache
|
| 125 |
+
self._vision_cache = VisionFeatureCache(
|
| 126 |
+
self.model.base_model.model.model.visual, torch, cache_mb * 1024 * 1024)
|
| 127 |
+
|
| 128 |
+
def _verify_artifacts(self, model_path: Path, adapter_path: Path) -> None:
|
| 129 |
+
expected = self.manifest
|
| 130 |
+
package_root = Path(__file__).resolve().parents[1]
|
| 131 |
+
inventory_path = package_root / "assets" / "base_files.sha256.json"
|
| 132 |
+
inventory = json.loads(inventory_path.read_text())
|
| 133 |
+
if inventory["model_id"] != expected["base"]["model_id"] or inventory["revision"] != expected["base"]["revision"]:
|
| 134 |
+
raise RuntimeError("model-file inventory identity mismatch")
|
| 135 |
+
self._verify_model_file_closure(model_path, set(inventory["files"]))
|
| 136 |
+
for relative, digest in inventory["files"].items():
|
| 137 |
+
path = model_path / relative
|
| 138 |
+
if not path.is_file() or sha256_file(path) != digest:
|
| 139 |
+
raise RuntimeError(f"pinned model file identity mismatch: {relative}")
|
| 140 |
+
self._verify_adapter_file_closure(adapter_path)
|
| 141 |
+
if sha256_file(adapter_path / "adapter_model.safetensors") != expected["adapter"]["adapter_model_sha256"]:
|
| 142 |
+
raise RuntimeError("bundled adapter identity mismatch")
|
| 143 |
+
if sha256_file(adapter_path / "adapter_config.json") != expected["adapter"]["adapter_config_sha256"]:
|
| 144 |
+
raise RuntimeError("bundled adapter config identity mismatch")
|
| 145 |
+
|
| 146 |
+
def _forward(self, inputs: dict[str, Any], positions: Sequence[int]) -> Any:
|
| 147 |
+
torch = self.torch
|
| 148 |
+
device = next(self.model.parameters()).device
|
| 149 |
+
moved = {k: v.to(device) if hasattr(v, "to") else v for k, v in inputs.items()}
|
| 150 |
+
with torch.inference_mode():
|
| 151 |
+
# Project logits only at requested positions.
|
| 152 |
+
output = self.model(**moved, logits_to_keep=[int(p) for p in positions],
|
| 153 |
+
use_cache=False, return_dict=True)
|
| 154 |
+
if output.logits.ndim != 3 or tuple(output.logits.shape[:2]) != (1, len(positions)):
|
| 155 |
+
raise RuntimeError("native sparse logits do not align to query positions")
|
| 156 |
+
return output.logits[0].float()
|
| 157 |
+
|
| 158 |
+
def numeric(self, context: str, fields: Sequence[NumericField], image: Any | None = None) -> dict[str, Any]:
|
| 159 |
+
vocab = int(self.model.config.text_config.vocab_size)
|
| 160 |
+
if len(fields) * vocab * 4 > self.max_logits_bytes:
|
| 161 |
+
raise ValueError("numeric sparse logits exceed byte admission")
|
| 162 |
+
if image is None:
|
| 163 |
+
compiled = compile_numeric(self.processor.tokenizer, context, fields, self.codebook,
|
| 164 |
+
self.max_input_tokens)
|
| 165 |
+
ids = self.torch.tensor([compiled.input_ids], dtype=self.torch.long)
|
| 166 |
+
mask = self.torch.ones_like(ids)
|
| 167 |
+
inputs = {"input_ids": ids, "attention_mask": mask,
|
| 168 |
+
"position_ids": mask.cumsum(dim=-1) - 1}
|
| 169 |
+
positions = compiled.positions
|
| 170 |
+
else:
|
| 171 |
+
if not self.admit_vision:
|
| 172 |
+
raise ValueError("vision numeric requires an accepted same-hardware parity gate")
|
| 173 |
+
vision = compile_vision_numeric(self.processor, image, context, fields, self.codebook,
|
| 174 |
+
self.max_input_tokens, self.model.config)
|
| 175 |
+
compiled = vision.numeric; inputs = vision.processor_inputs; positions = vision.positions
|
| 176 |
+
logits = self._forward(inputs, positions)
|
| 177 |
+
token_ids = self.torch.tensor(compiled.candidate_token_ids, device=logits.device)
|
| 178 |
+
results = {}
|
| 179 |
+
for index, field in enumerate(fields):
|
| 180 |
+
raw = logits[index].index_select(0, token_ids).float()
|
| 181 |
+
probs = self.torch.softmax(raw, dim=-1).cpu().tolist()
|
| 182 |
+
results[field.key] = decode_numeric(probs, field)
|
| 183 |
+
return {"fields": results, "accounting": {"backbone_calls": 1,
|
| 184 |
+
"physical_rows": 1, "field_slots": len(fields), "truncated": False},
|
| 185 |
+
"semantics": "per-field marginals; not a learned full joint distribution"}
|
| 186 |
+
|
| 187 |
+
def atomic(self, context: str, field_key: str, choices: Sequence[Any]) -> dict[str, Any]:
|
| 188 |
+
if not 2 <= len(choices) <= 255 or len({json.dumps(v,sort_keys=True) for v in choices}) != len(choices):
|
| 189 |
+
raise ValueError("atomic choices must contain 2..255 unique JSON values")
|
| 190 |
+
schema={"field_order":[field_key],"fields":{field_key:{"type":"enum","choices":list(choices)}}}
|
| 191 |
+
support="\n".join(f'{self.codebook[i]["code"]} = '+json.dumps(v,ensure_ascii=False,sort_keys=True,separators=(",",":")) for i,v in enumerate(choices))
|
| 192 |
+
user=("Classify the active field from the complete input.\n\nContext:\n"+context+"\n\n"
|
| 193 |
+
"Registered schema gpc1-public-atomic@1:\n"+json.dumps(schema,ensure_ascii=False,sort_keys=True,separators=(",",":"))
|
| 194 |
+
+"\n\nAllowed complete outputs in registered semantic order:\n"+support+"\n\nReturn exactly one allowed output.")
|
| 195 |
+
prompt=self.processor.tokenizer.apply_chat_template([
|
| 196 |
+
{"role":"system","content":"You are a fixed-schema lookup model. Return exactly one listed opaque code and no other text."},
|
| 197 |
+
{"role":"user","content":user}],tokenize=False,add_generation_prompt=True,enable_thinking=False)
|
| 198 |
+
ids=tuple(self.processor.tokenizer.encode(prompt,add_special_tokens=False))
|
| 199 |
+
if len(ids)>self.max_input_tokens: raise ValueError("atomic request exceeds token admission; no truncation performed")
|
| 200 |
+
for row in self.codebook[:len(choices)]:
|
| 201 |
+
combined=tuple(self.processor.tokenizer.encode(prompt+str(row["code"]),add_special_tokens=False))
|
| 202 |
+
if combined != ids + (int(row["token_id"]),):
|
| 203 |
+
raise ValueError("atomic response-boundary tokenization changed")
|
| 204 |
+
tensor=self.torch.tensor([ids]); logits=self._forward({"input_ids":tensor,"attention_mask":self.torch.ones_like(tensor)},[len(ids)-1])[0]
|
| 205 |
+
indices=self.torch.tensor([int(r["token_id"]) for r in self.codebook[:len(choices)]],device=logits.device)
|
| 206 |
+
probs=self.torch.softmax(logits.index_select(0,indices).float(),dim=-1).cpu().tolist(); selected=max(range(len(choices)),key=probs.__getitem__)
|
| 207 |
+
return {"value":choices[selected],"probabilities":probs,"selected_index":selected,"calibrated":False,
|
| 208 |
+
"accounting":{"backbone_calls":1,"physical_rows":1,"truncated":False},
|
| 209 |
+
"semantics":"conditional distribution over the complete caller-declared choice set"}
|
| 210 |
+
|
| 211 |
+
def finite_joint(self, context: str, schema: dict[str, Any], candidates: Sequence[dict[str, Any]]) -> dict[str, Any]:
|
| 212 |
+
if not self.admit_joint:
|
| 213 |
+
raise ValueError("finite_joint requires an accepted same-hardware parity gate")
|
| 214 |
+
tokenizer = self.processor.tokenizer
|
| 215 |
+
canonical = [json.dumps(v, ensure_ascii=False, sort_keys=True,
|
| 216 |
+
separators=(",", ":"), allow_nan=False) for v in candidates]
|
| 217 |
+
support = "\n".join(f"{value} = {value}" for value in canonical)
|
| 218 |
+
user = ("Select one complete allowed JSON record from the complete input.\n\nContext:\n" + context
|
| 219 |
+
+ "\n\nRegistered schema gpc1-public-finite-joint@1:\n"
|
| 220 |
+
+ json.dumps(schema, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False)
|
| 221 |
+
+ "\n\nAllowed complete outputs in registered semantic order:\n" + support
|
| 222 |
+
+ "\n\nReturn exactly one allowed output.")
|
| 223 |
+
prompt = tokenizer.apply_chat_template([
|
| 224 |
+
{"role":"system","content":"You are a fixed-schema lookup model. Return exactly one listed complete JSON record and no other text."},
|
| 225 |
+
{"role":"user","content":user}], tokenize=False, add_generation_prompt=True,
|
| 226 |
+
enable_thinking=False)
|
| 227 |
+
prefix = tuple(tokenizer.encode(prompt, add_special_tokens=False))
|
| 228 |
+
terminal_text = tokenizer.eos_token or "<|im_end|>"
|
| 229 |
+
terminal = tuple(tokenizer.encode(terminal_text, add_special_tokens=False))
|
| 230 |
+
suffixes = []
|
| 231 |
+
for value in canonical:
|
| 232 |
+
complete = tuple(tokenizer.encode(prompt + value + terminal_text, add_special_tokens=False))
|
| 233 |
+
if complete[:len(prefix)] != prefix: raise ValueError("candidate tokenization changed prompt prefix")
|
| 234 |
+
suffixes.append(complete[len(prefix):])
|
| 235 |
+
compiled = compile_joint(prefix, tuple(str(i) for i in range(len(candidates))), suffixes,
|
| 236 |
+
terminal, pad_token_id=int(tokenizer.pad_token_id), max_rows=self.max_joint_rows,
|
| 237 |
+
max_padded_tokens=self.max_padded_tokens, max_logit_vectors=self.max_logit_vectors,
|
| 238 |
+
max_input_tokens=self.max_input_tokens)
|
| 239 |
+
vocab = int(self.model.config.text_config.vocab_size)
|
| 240 |
+
if compiled.logit_vectors * vocab * 4 > self.max_logits_bytes:
|
| 241 |
+
raise ValueError("joint sparse logits exceed byte admission")
|
| 242 |
+
device = next(self.model.parameters()).device
|
| 243 |
+
result = score_joint(self.model, compiled, device=device)
|
| 244 |
+
result["value"] = candidates[result["selected_index"]]
|
| 245 |
+
result["semantics"] = "conditional distribution over the complete caller-declared joint support"
|
| 246 |
+
return result
|
gpc1_server/schemas.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Any, Literal
|
| 4 |
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class Message(BaseModel):
|
| 8 |
+
model_config = ConfigDict(extra="forbid")
|
| 9 |
+
role: Literal["system", "developer", "user", "assistant"]
|
| 10 |
+
content: str | list[dict[str, Any]]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class ResponseFormat(BaseModel):
|
| 14 |
+
model_config = ConfigDict(extra="forbid")
|
| 15 |
+
type: Literal["json_schema"]
|
| 16 |
+
json_schema: dict[str, Any]
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class GPC1Options(BaseModel):
|
| 20 |
+
model_config = ConfigDict(extra="forbid")
|
| 21 |
+
mode: Literal["atomic", "numeric101", "finite_joint"]
|
| 22 |
+
field_key: str | None = None
|
| 23 |
+
choices: list[Any] | None = None
|
| 24 |
+
numeric_fields: list[dict[str, Any]] | None = None
|
| 25 |
+
allowed_records: list[dict[str, Any]] | None = None
|
| 26 |
+
|
| 27 |
+
@model_validator(mode="after")
|
| 28 |
+
def match_mode(self):
|
| 29 |
+
active = {
|
| 30 |
+
"atomic": (self.field_key is not None and self.choices is not None,
|
| 31 |
+
self.numeric_fields is None and self.allowed_records is None),
|
| 32 |
+
"numeric101": (self.numeric_fields is not None,
|
| 33 |
+
self.field_key is None and self.choices is None and self.allowed_records is None),
|
| 34 |
+
"finite_joint": (self.allowed_records is not None,
|
| 35 |
+
self.field_key is None and self.choices is None and self.numeric_fields is None),
|
| 36 |
+
}[self.mode]
|
| 37 |
+
if not all(active):
|
| 38 |
+
raise ValueError(f"{self.mode} requires only its mode-specific members")
|
| 39 |
+
return self
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class ChatRequest(BaseModel):
|
| 43 |
+
model_config = ConfigDict(extra="forbid")
|
| 44 |
+
model: str
|
| 45 |
+
messages: list[Message] = Field(min_length=1)
|
| 46 |
+
response_format: ResponseFormat
|
| 47 |
+
gpc1: GPC1Options
|
| 48 |
+
stream: Literal[False] = False
|
| 49 |
+
temperature: float | None = None
|
| 50 |
+
max_completion_tokens: int | None = None
|
gpc1_server/vision_cache.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import hashlib
|
| 4 |
+
import threading
|
| 5 |
+
from typing import Any
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class VisionFeatureCache:
|
| 9 |
+
"""One-image, memory-bounded cache for a frozen vision encoder."""
|
| 10 |
+
|
| 11 |
+
def __init__(self, visual: Any, torch: Any, max_bytes: int = 64 * 1024 * 1024):
|
| 12 |
+
self.torch = torch
|
| 13 |
+
self.max_bytes = max_bytes
|
| 14 |
+
self._key = None
|
| 15 |
+
self._value = None
|
| 16 |
+
self._lock = threading.RLock()
|
| 17 |
+
original = visual.forward
|
| 18 |
+
|
| 19 |
+
def forward(*args, **kwargs):
|
| 20 |
+
if visual.training or torch.is_grad_enabled() or self.max_bytes <= 0:
|
| 21 |
+
self.clear()
|
| 22 |
+
return original(*args, **kwargs)
|
| 23 |
+
with self._lock:
|
| 24 |
+
key = self._fingerprint((args, kwargs))
|
| 25 |
+
if key is not None and key == self._key:
|
| 26 |
+
return self._clone(self._value)
|
| 27 |
+
output = original(*args, **kwargs)
|
| 28 |
+
self._key = self._value = None
|
| 29 |
+
size = self._size(output)
|
| 30 |
+
if key is not None and size is not None and size <= self.max_bytes:
|
| 31 |
+
self._value = self._clone(output)
|
| 32 |
+
self._key = key
|
| 33 |
+
return output
|
| 34 |
+
|
| 35 |
+
visual.forward = forward
|
| 36 |
+
|
| 37 |
+
def clear(self):
|
| 38 |
+
with self._lock:
|
| 39 |
+
self._key = self._value = None
|
| 40 |
+
|
| 41 |
+
def _fingerprint(self, value):
|
| 42 |
+
torch = self.torch
|
| 43 |
+
digest = hashlib.sha256()
|
| 44 |
+
|
| 45 |
+
def update(item):
|
| 46 |
+
if isinstance(item, torch.Tensor):
|
| 47 |
+
if item.layout != torch.strided:
|
| 48 |
+
raise TypeError
|
| 49 |
+
digest.update(repr((str(item.dtype), str(item.device), tuple(item.shape), tuple(item.stride()))).encode())
|
| 50 |
+
digest.update(item.detach().contiguous().cpu().view(torch.uint8).numpy().tobytes())
|
| 51 |
+
elif isinstance(item, (tuple, list)):
|
| 52 |
+
digest.update(type(item).__name__.encode())
|
| 53 |
+
for child in item:
|
| 54 |
+
update(child)
|
| 55 |
+
digest.update(b'\0')
|
| 56 |
+
elif isinstance(item, dict):
|
| 57 |
+
digest.update(b'dict')
|
| 58 |
+
for key in sorted(item):
|
| 59 |
+
update(key)
|
| 60 |
+
update(item[key])
|
| 61 |
+
elif item is None or type(item) in (bool, int, float, str):
|
| 62 |
+
digest.update(repr((type(item).__name__, item)).encode())
|
| 63 |
+
else:
|
| 64 |
+
raise TypeError
|
| 65 |
+
|
| 66 |
+
try:
|
| 67 |
+
update(value)
|
| 68 |
+
update((torch.backends.cuda.matmul.allow_tf32, torch.backends.cudnn.allow_tf32,
|
| 69 |
+
torch.backends.cudnn.enabled, torch.backends.cudnn.benchmark,
|
| 70 |
+
torch.backends.cudnn.deterministic, torch.get_float32_matmul_precision()))
|
| 71 |
+
except (TypeError, ValueError):
|
| 72 |
+
return None
|
| 73 |
+
return digest.digest()
|
| 74 |
+
|
| 75 |
+
def _clone(self, value):
|
| 76 |
+
if isinstance(value, self.torch.Tensor):
|
| 77 |
+
return value.detach().clone()
|
| 78 |
+
if isinstance(value, dict):
|
| 79 |
+
cloned = {k: self._clone(v) for k, v in value.items()}
|
| 80 |
+
return cloned if type(value) is dict else type(value)(**cloned)
|
| 81 |
+
if isinstance(value, tuple):
|
| 82 |
+
return tuple(self._clone(v) for v in value)
|
| 83 |
+
if isinstance(value, list):
|
| 84 |
+
return [self._clone(v) for v in value]
|
| 85 |
+
return value
|
| 86 |
+
|
| 87 |
+
def _size(self, value):
|
| 88 |
+
if isinstance(value, self.torch.Tensor):
|
| 89 |
+
return value.numel() * value.element_size()
|
| 90 |
+
if isinstance(value, dict):
|
| 91 |
+
sizes = [self._size(v) for v in value.values()]
|
| 92 |
+
elif isinstance(value, (tuple, list)):
|
| 93 |
+
sizes = [self._size(v) for v in value]
|
| 94 |
+
elif value is None or type(value) in (bool, int, float, str):
|
| 95 |
+
return 0
|
| 96 |
+
else:
|
| 97 |
+
return None
|
| 98 |
+
return None if any(v is None for v in sizes) else sum(sizes)
|
model/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright 2026 Alibaba Cloud
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
model/chat_template.jinja
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{%- set image_count = namespace(value=0) %}
|
| 2 |
+
{%- set video_count = namespace(value=0) %}
|
| 3 |
+
{%- macro render_content(content, do_vision_count, is_system_content=false) %}
|
| 4 |
+
{%- if content is string %}
|
| 5 |
+
{{- content }}
|
| 6 |
+
{%- elif content is iterable and content is not mapping %}
|
| 7 |
+
{%- for item in content %}
|
| 8 |
+
{%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
|
| 9 |
+
{%- if is_system_content %}
|
| 10 |
+
{{- raise_exception('System message cannot contain images.') }}
|
| 11 |
+
{%- endif %}
|
| 12 |
+
{%- if do_vision_count %}
|
| 13 |
+
{%- set image_count.value = image_count.value + 1 %}
|
| 14 |
+
{%- endif %}
|
| 15 |
+
{%- if add_vision_id %}
|
| 16 |
+
{{- 'Picture ' ~ image_count.value ~ ': ' }}
|
| 17 |
+
{%- endif %}
|
| 18 |
+
{{- '<|vision_start|><|image_pad|><|vision_end|>' }}
|
| 19 |
+
{%- elif 'video' in item or item.type == 'video' %}
|
| 20 |
+
{%- if is_system_content %}
|
| 21 |
+
{{- raise_exception('System message cannot contain videos.') }}
|
| 22 |
+
{%- endif %}
|
| 23 |
+
{%- if do_vision_count %}
|
| 24 |
+
{%- set video_count.value = video_count.value + 1 %}
|
| 25 |
+
{%- endif %}
|
| 26 |
+
{%- if add_vision_id %}
|
| 27 |
+
{{- 'Video ' ~ video_count.value ~ ': ' }}
|
| 28 |
+
{%- endif %}
|
| 29 |
+
{{- '<|vision_start|><|video_pad|><|vision_end|>' }}
|
| 30 |
+
{%- elif 'text' in item %}
|
| 31 |
+
{{- item.text }}
|
| 32 |
+
{%- else %}
|
| 33 |
+
{{- raise_exception('Unexpected item type in content.') }}
|
| 34 |
+
{%- endif %}
|
| 35 |
+
{%- endfor %}
|
| 36 |
+
{%- elif content is none or content is undefined %}
|
| 37 |
+
{{- '' }}
|
| 38 |
+
{%- else %}
|
| 39 |
+
{{- raise_exception('Unexpected content type.') }}
|
| 40 |
+
{%- endif %}
|
| 41 |
+
{%- endmacro %}
|
| 42 |
+
{%- if not messages %}
|
| 43 |
+
{{- raise_exception('No messages provided.') }}
|
| 44 |
+
{%- endif %}
|
| 45 |
+
{%- if tools and tools is iterable and tools is not mapping %}
|
| 46 |
+
{{- '<|im_start|>system\n' }}
|
| 47 |
+
{{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
|
| 48 |
+
{%- for tool in tools %}
|
| 49 |
+
{{- "\n" }}
|
| 50 |
+
{{- tool | tojson }}
|
| 51 |
+
{%- endfor %}
|
| 52 |
+
{{- "\n</tools>" }}
|
| 53 |
+
{{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
|
| 54 |
+
{%- if messages[0].role == 'system' %}
|
| 55 |
+
{%- set content = render_content(messages[0].content, false, true)|trim %}
|
| 56 |
+
{%- if content %}
|
| 57 |
+
{{- '\n\n' + content }}
|
| 58 |
+
{%- endif %}
|
| 59 |
+
{%- endif %}
|
| 60 |
+
{{- '<|im_end|>\n' }}
|
| 61 |
+
{%- else %}
|
| 62 |
+
{%- if messages[0].role == 'system' %}
|
| 63 |
+
{%- set content = render_content(messages[0].content, false, true)|trim %}
|
| 64 |
+
{{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
|
| 65 |
+
{%- endif %}
|
| 66 |
+
{%- endif %}
|
| 67 |
+
{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
|
| 68 |
+
{%- for message in messages[::-1] %}
|
| 69 |
+
{%- set index = (messages|length - 1) - loop.index0 %}
|
| 70 |
+
{%- if ns.multi_step_tool and message.role == "user" %}
|
| 71 |
+
{%- set content = render_content(message.content, false)|trim %}
|
| 72 |
+
{%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
|
| 73 |
+
{%- set ns.multi_step_tool = false %}
|
| 74 |
+
{%- set ns.last_query_index = index %}
|
| 75 |
+
{%- endif %}
|
| 76 |
+
{%- endif %}
|
| 77 |
+
{%- endfor %}
|
| 78 |
+
{%- if ns.multi_step_tool %}
|
| 79 |
+
{{- raise_exception('No user query found in messages.') }}
|
| 80 |
+
{%- endif %}
|
| 81 |
+
{%- for message in messages %}
|
| 82 |
+
{%- set content = render_content(message.content, true)|trim %}
|
| 83 |
+
{%- if message.role == "system" %}
|
| 84 |
+
{%- if not loop.first %}
|
| 85 |
+
{{- raise_exception('System message must be at the beginning.') }}
|
| 86 |
+
{%- endif %}
|
| 87 |
+
{%- elif message.role == "user" %}
|
| 88 |
+
{{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
|
| 89 |
+
{%- elif message.role == "assistant" %}
|
| 90 |
+
{%- set reasoning_content = '' %}
|
| 91 |
+
{%- if message.reasoning_content is string %}
|
| 92 |
+
{%- set reasoning_content = message.reasoning_content %}
|
| 93 |
+
{%- else %}
|
| 94 |
+
{%- if '</think>' in content %}
|
| 95 |
+
{%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
|
| 96 |
+
{%- set content = content.split('</think>')[-1].lstrip('\n') %}
|
| 97 |
+
{%- endif %}
|
| 98 |
+
{%- endif %}
|
| 99 |
+
{%- set reasoning_content = reasoning_content|trim %}
|
| 100 |
+
{%- if loop.index0 > ns.last_query_index %}
|
| 101 |
+
{{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
|
| 102 |
+
{%- else %}
|
| 103 |
+
{{- '<|im_start|>' + message.role + '\n' + content }}
|
| 104 |
+
{%- endif %}
|
| 105 |
+
{%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
|
| 106 |
+
{%- for tool_call in message.tool_calls %}
|
| 107 |
+
{%- if tool_call.function is defined %}
|
| 108 |
+
{%- set tool_call = tool_call.function %}
|
| 109 |
+
{%- endif %}
|
| 110 |
+
{%- if loop.first %}
|
| 111 |
+
{%- if content|trim %}
|
| 112 |
+
{{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 113 |
+
{%- else %}
|
| 114 |
+
{{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 115 |
+
{%- endif %}
|
| 116 |
+
{%- else %}
|
| 117 |
+
{{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 118 |
+
{%- endif %}
|
| 119 |
+
{%- if tool_call.arguments is defined %}
|
| 120 |
+
{%- for args_name, args_value in tool_call.arguments|items %}
|
| 121 |
+
{{- '<parameter=' + args_name + '>\n' }}
|
| 122 |
+
{%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
|
| 123 |
+
{{- args_value }}
|
| 124 |
+
{{- '\n</parameter>\n' }}
|
| 125 |
+
{%- endfor %}
|
| 126 |
+
{%- endif %}
|
| 127 |
+
{{- '</function>\n</tool_call>' }}
|
| 128 |
+
{%- endfor %}
|
| 129 |
+
{%- endif %}
|
| 130 |
+
{{- '<|im_end|>\n' }}
|
| 131 |
+
{%- elif message.role == "tool" %}
|
| 132 |
+
{%- if loop.previtem and loop.previtem.role != "tool" %}
|
| 133 |
+
{{- '<|im_start|>user' }}
|
| 134 |
+
{%- endif %}
|
| 135 |
+
{{- '\n<tool_response>\n' }}
|
| 136 |
+
{{- content }}
|
| 137 |
+
{{- '\n</tool_response>' }}
|
| 138 |
+
{%- if not loop.last and loop.nextitem.role != "tool" %}
|
| 139 |
+
{{- '<|im_end|>\n' }}
|
| 140 |
+
{%- elif loop.last %}
|
| 141 |
+
{{- '<|im_end|>\n' }}
|
| 142 |
+
{%- endif %}
|
| 143 |
+
{%- else %}
|
| 144 |
+
{{- raise_exception('Unexpected message role.') }}
|
| 145 |
+
{%- endif %}
|
| 146 |
+
{%- endfor %}
|
| 147 |
+
{%- if add_generation_prompt %}
|
| 148 |
+
{{- '<|im_start|>assistant\n' }}
|
| 149 |
+
{%- if enable_thinking is defined and enable_thinking is false %}
|
| 150 |
+
{{- '<think>\n\n</think>\n\n' }}
|
| 151 |
+
{%- else %}
|
| 152 |
+
{{- '<think>\n' }}
|
| 153 |
+
{%- endif %}
|
| 154 |
+
{%- endif %}
|
model/config.json
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"Qwen3_5MoeForConditionalGeneration"
|
| 4 |
+
],
|
| 5 |
+
"image_token_id": 248056,
|
| 6 |
+
"model_type": "qwen3_5_moe",
|
| 7 |
+
"text_config": {
|
| 8 |
+
"attention_bias": false,
|
| 9 |
+
"attention_dropout": 0.0,
|
| 10 |
+
"attn_output_gate": true,
|
| 11 |
+
"dtype": "bfloat16",
|
| 12 |
+
"eos_token_id": 248044,
|
| 13 |
+
"full_attention_interval": 4,
|
| 14 |
+
"head_dim": 256,
|
| 15 |
+
"hidden_act": "silu",
|
| 16 |
+
"hidden_size": 2048,
|
| 17 |
+
"initializer_range": 0.02,
|
| 18 |
+
"layer_types": [
|
| 19 |
+
"linear_attention",
|
| 20 |
+
"linear_attention",
|
| 21 |
+
"linear_attention",
|
| 22 |
+
"full_attention",
|
| 23 |
+
"linear_attention",
|
| 24 |
+
"linear_attention",
|
| 25 |
+
"linear_attention",
|
| 26 |
+
"full_attention",
|
| 27 |
+
"linear_attention",
|
| 28 |
+
"linear_attention",
|
| 29 |
+
"linear_attention",
|
| 30 |
+
"full_attention",
|
| 31 |
+
"linear_attention",
|
| 32 |
+
"linear_attention",
|
| 33 |
+
"linear_attention",
|
| 34 |
+
"full_attention",
|
| 35 |
+
"linear_attention",
|
| 36 |
+
"linear_attention",
|
| 37 |
+
"linear_attention",
|
| 38 |
+
"full_attention",
|
| 39 |
+
"linear_attention",
|
| 40 |
+
"linear_attention",
|
| 41 |
+
"linear_attention",
|
| 42 |
+
"full_attention",
|
| 43 |
+
"linear_attention",
|
| 44 |
+
"linear_attention",
|
| 45 |
+
"linear_attention",
|
| 46 |
+
"full_attention",
|
| 47 |
+
"linear_attention",
|
| 48 |
+
"linear_attention",
|
| 49 |
+
"linear_attention",
|
| 50 |
+
"full_attention",
|
| 51 |
+
"linear_attention",
|
| 52 |
+
"linear_attention",
|
| 53 |
+
"linear_attention",
|
| 54 |
+
"full_attention",
|
| 55 |
+
"linear_attention",
|
| 56 |
+
"linear_attention",
|
| 57 |
+
"linear_attention",
|
| 58 |
+
"full_attention"
|
| 59 |
+
],
|
| 60 |
+
"linear_conv_kernel_dim": 4,
|
| 61 |
+
"linear_key_head_dim": 128,
|
| 62 |
+
"linear_num_key_heads": 16,
|
| 63 |
+
"linear_num_value_heads": 32,
|
| 64 |
+
"linear_value_head_dim": 128,
|
| 65 |
+
"max_position_embeddings": 262144,
|
| 66 |
+
"mlp_only_layers": [],
|
| 67 |
+
"model_type": "qwen3_5_moe_text",
|
| 68 |
+
"moe_intermediate_size": 512,
|
| 69 |
+
"mtp_num_hidden_layers": 1,
|
| 70 |
+
"mtp_use_dedicated_embeddings": false,
|
| 71 |
+
"num_attention_heads": 16,
|
| 72 |
+
"num_experts": 256,
|
| 73 |
+
"num_experts_per_tok": 8,
|
| 74 |
+
"num_hidden_layers": 40,
|
| 75 |
+
"num_key_value_heads": 2,
|
| 76 |
+
"rms_norm_eps": 1e-06,
|
| 77 |
+
"router_aux_loss_coef": 0.001,
|
| 78 |
+
"shared_expert_intermediate_size": 512,
|
| 79 |
+
"use_cache": true,
|
| 80 |
+
"vocab_size": 248320,
|
| 81 |
+
"mamba_ssm_dtype": "float32",
|
| 82 |
+
"rope_parameters": {
|
| 83 |
+
"mrope_interleaved": true,
|
| 84 |
+
"mrope_section": [
|
| 85 |
+
11,
|
| 86 |
+
11,
|
| 87 |
+
10
|
| 88 |
+
],
|
| 89 |
+
"rope_type": "default",
|
| 90 |
+
"rope_theta": 10000000,
|
| 91 |
+
"partial_rotary_factor": 0.25
|
| 92 |
+
}
|
| 93 |
+
},
|
| 94 |
+
"tie_word_embeddings": false,
|
| 95 |
+
"transformers_version": "4.57.0.dev0",
|
| 96 |
+
"video_token_id": 248057,
|
| 97 |
+
"vision_config": {
|
| 98 |
+
"deepstack_visual_indexes": [],
|
| 99 |
+
"depth": 27,
|
| 100 |
+
"hidden_act": "gelu_pytorch_tanh",
|
| 101 |
+
"hidden_size": 1152,
|
| 102 |
+
"in_channels": 3,
|
| 103 |
+
"initializer_range": 0.02,
|
| 104 |
+
"intermediate_size": 4304,
|
| 105 |
+
"model_type": "qwen3_5_moe",
|
| 106 |
+
"num_heads": 16,
|
| 107 |
+
"num_position_embeddings": 2304,
|
| 108 |
+
"out_hidden_size": 2048,
|
| 109 |
+
"patch_size": 16,
|
| 110 |
+
"spatial_merge_size": 2,
|
| 111 |
+
"temporal_patch_size": 2
|
| 112 |
+
},
|
| 113 |
+
"vision_end_token_id": 248054,
|
| 114 |
+
"vision_start_token_id": 248053
|
| 115 |
+
}
|
model/generation_config.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token_id": 248044,
|
| 3 |
+
"do_sample": true,
|
| 4 |
+
"eos_token_id": [
|
| 5 |
+
248046,
|
| 6 |
+
248044
|
| 7 |
+
],
|
| 8 |
+
"pad_token_id": 248044,
|
| 9 |
+
"temperature": 1.0,
|
| 10 |
+
"top_k": 20,
|
| 11 |
+
"top_p": 0.95,
|
| 12 |
+
"transformers_version": "4.57.0.dev0"
|
| 13 |
+
}
|
model/merges.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
model/model-00001-of-00016.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:43a908b3d324d59b66c73a33e5c182809eb2df7da474b2fd2cbdabb16baeee38
|
| 3 |
+
size 4323955448
|
model/model-00002-of-00016.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4de54e39c2ecc6f3fb5e79e752f84b65c96c316d0d848c9b55d36a062df69e70
|
| 3 |
+
size 4506431768
|
model/model-00003-of-00016.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:847472ab2e3e10da0baea3993890ce393cc1b364f53674a00a92ac6ed856ec0d
|
| 3 |
+
size 4988775056
|
model/model-00004-of-00016.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:81b2b46905d62b66c6a0d2360050c266ea9416f913708e9a1447b28465f458f0
|
| 3 |
+
size 3962207584
|