• Compute
  • Customers
  • Pricing
Sign In
More Blog Posts
XDiscordLinkedInYouTube

Products

  • GPUs
  • Inference
  • Studio

Developers

  • Model library
  • Documentation
  • Glossary

Company

  • About Us
  • Blog
  • Events
  • Partnership
  • Scale
  • Career
  • Ambassador program
  • Mission & Vision

Popular models

    Stay in the loop

    By submitting, you acknowledge that we may collect and use the information you provide, which may include personal information.

    XDiscordLinkedInYouTube

    Copyright ©2026 All rights reserved.

    Privacy PolicyTerms of UseLegal Documentation
    More Blog Posts
    Other

    Which AI API Platform Lets You Test GPT-6 Astra and Kimi K3 Under One Account?

    September 25, 2026

    GMI Cloud MaaS serves openai/gpt-6-astra and moonshotai/kimi-k3 through one base URL, key, and invoice. October 2026 list input/output rates are $10/$50 and $3/$15 per million tokens respectively. Compare them with task-specific tests rather than treating the price gap as a quality verdict. (Model-as-a-Service platform)

    Start from the catalog parameters:

    openai/gpt-6-astramoonshotai/kimi-k3
    Status in the GMI Cloud model catalogACTIVE, available on MaaSACTIVE, available on MaaS
    Input / output per 1M tokens$10.00 / $50.00$3.00 / $15.00
    Second price tierfrom 272,000 input tokens: $20.00 / $75.00none listed
    Cache read per 1M tokens$1.00 standard tier, $2.00 second tier$0.30
    Cache write per 1M tokens$12.50 standard tier, $25.00 second tierno cache write price listed
    Listed discount on October 15, 2026nonenone
    Context window1,050,0001,048,576
    Quantization listedfp8fp8
    Capability detail in the catalog entrytext and image input; chat completions and Responses examples including function calling and file inputexplicit flags for tool calling, image input, and document input; chat completions examples including function calling
    ProviderOpenAIMoonshot AI

    Prices and parameters were read on October 15, 2026. Recheck the model library before budgeting; GMI's Astra engineering briefing also cites $10 input and $50 output per million tokens. (console.gmicloud.ai/model-library; wrote about scaling Astra traffic)

    Two numbers in that table decide most of what follows. The standard-tier price ratio is 3.33x on both input and output. From the 272,000 token threshold up, it widens to 6.67x on input and 5x on output. A test that does not separate those two regimes will produce an average that describes neither.

    What "under one account" actually gets you

    Testing both models under one GMI Cloud account gives you one base URL and key, one invoice, one usage view, and a reversible decision at the end. GMI Cloud is an AI-native inference cloud, and its Model-as-a-Service layer provides access to "the world's most widely used closed-source models and the most popular open-source models, all through one API." For a two-model comparison those four things are concrete:

    1. One base URL and one key. Both models answer at https://api.gmi-serving.com/v1/chat/completions. The developer page puts it as "Keep your code, change the model ID," and GET /v1/models lists everything your key can call.
    2. One billing surface. Token spend for both models lands on a single invoice, so the cost half of the comparison is an arithmetic exercise instead of a reconciliation project.
    3. One usage view. Console's Serverless Usage page filters by model name and by API key, at daily or hourly granularity, in local time or UTC, per the billing documentation.
    4. One decision afterwards. MaaS supports a "Seamless switch between models," so the losing model ID stays available for the buckets where it wins rather than requiring a contract to keep alive.

    What it does not change is the test design. One key does not make two models comparable: the price curves differ, the context ceilings differ, and each model reports its own token counts, so cost has to be read per model from each response rather than assumed from prompt length.

    How do you call both models from the same code?

    One endpoint, one key, and a model string that changes.

    curl https://api.gmi-serving.com/v1/chat/completions \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $GMI_API_KEY" \
    -d '{
    "model": "openai/gpt-6-astra",
    "messages": [{"role": "user", "content": "Summarize this changelog."}],
    "temperature": 0,
    "max_completion_tokens": 1024,
    "context_length_exceeded_behavior": "error"
    }'

    Swap "openai/gpt-6-astra" for "moonshotai/kimi-k3" and send the output cap as max_tokens, which is the field that entry's own examples use, and the call is otherwise identical. Both catalog entries document the same OpenAI-compatible chat completions interface, so the harness does not need a per-model adapter. The Astra entry additionally documents Responses API examples; during a bake-off, pick one transport and use it for both models, because a comparison that mixes /v1/responses with /v1/chat/completions is comparing two request paths as well as two models.

    Which settings have to be pinned before the first call?

    Seven settings decide whether the two columns are comparable: the sampling parameters and the output cap, truncation behavior, task buckets, a separate column for long context, one API key per bucket, usage capture on streamed calls, and the schedule. They are listed below in the order they bite.

    1. Pin every sampling parameter explicitly, and check which field caps output. The console parameter panels are not identical across the two entries: the Kimi K3 entry carries a Max Tokens default of 4,096, and relying on per-model defaults silently changes output length, latency, and cost at the same time. There is a second trap in the same place. The Astra entry's API examples cap generation with max_completion_tokens, while the Kimi K3 examples use max_tokens. Send one short call per model before the run and confirm the cap actually applied, otherwise the two models answer at different lengths and the latency and cost columns stop being comparable.

    2. Turn off silent truncation. The LLM API reference documents context_length_exceeded_behavior, which defaults to truncate and accepts error. During evaluation, set it to error. The two listed windows differ by 1,424 tokens, and each window has to hold the prompt plus whatever generation budget you request, so near the ceiling the two models stop accepting the same request at slightly different points. With the default, the model that still accepts it answers confidently from a quietly shortened prompt, and the scoring sheet records that as a quality difference.

    3. Bucket by real tasks and assign a primary repeatable metric: test pass rate for coding, schema-valid calls for agents, or labeled field accuracy for extraction; log latency alongside each. GMI's August benchmark roundup offers a taxonomy, while the Gemini 3.8 Flash versus Astra extraction article provides a single-bucket example. Public benchmark scores cannot replace your own tasks. (August 2026 benchmark roundup; Gemini 3.8 Flash vs GPT-6 Astra for document extraction)

    4. Put the long-context bucket in its own column. Astra's second tier applies from 272,000 input tokens up. Mixing those calls into a blended average hides the single largest cost difference in the test. The budget table below shows how badly.

    5. Issue a separate API key per bucket. Console's usage view filters by API key, so one key per bucket turns post-run billing reconciliation into a lookup rather than a spreadsheet exercise. This matters more than it sounds: the chat completions usage object returns prompt_tokens, completion_tokens, and total_tokens, and there is no documented field for cache hits, so any claim about caching savings has to be settled against Console usage and billing rather than against the response body.

    6. Capture usage on streamed calls from the final chunk. The API reference states that "Streaming responses include usage stats in final chunk." A harness that stops reading at the last content delta loses the token counts for every streamed call. The harness below sends stream: False for that reason, since a bake-off needs the token counts more than it needs first-token latency; if you turn streaming on to measure time to first token, read usage from the final chunk and not from the last delta.

    7. Schedule the run. LLM rate limits on GMI Cloud are measured in tokens per minute and, per the rate limit documentation, "These limits are enforced at the organization level." Enforcement at that level means a separate evaluation key is not a separate quota, so put the run in a window where organization throughput is free, or ask about a tier change first. Pace the long-context bucket in particular: a single 300,000 token prompt spends 300,000 tokens of that minute by itself, so run those calls in sequence rather than in parallel and leave headroom for the retries.

    How do you keep failed calls from corrupting the result?

    Write every outcome, including the failures, as a row in an append-only JSONL file, retry only timeouts, transport errors, 429 and 5xx, and carry the usage block forward from every attempt that reported one. An evaluation script that crashes on the first 429 produces a partial matrix and an unusable cost total instead. The harness below records failures as data, keeps the usage block from any response that was billed, and refuses to retry client errors that are really test bugs.

    import json
    import os
    import random
    import time
    from email.utils import parsedate_to_datetime

    import requests

    BASE_URL = "https://api.gmi-serving.com/v1"
    API_KEY = os.environ["GMI_API_KEY"]
    MODELS = ["openai/gpt-6-astra", "moonshotai/kimi-k3"]

    # USD per 1M tokens, list price from the GMI Cloud model library, October 15, 2026.
    PRICING = {
    "openai/gpt-6-astra": [
    {"from_input_tokens": 0, "input": 10.00, "output": 50.00},
    {"from_input_tokens": 272_000, "input": 20.00, "output": 75.00},
    ],
    "moonshotai/kimi-k3": [
    {"from_input_tokens": 0, "input": 3.00, "output": 15.00},
    ],
    }

    SAMPLING = {"temperature": 0.0, "top_p": 1.0}
    OUTPUT_CAP = 1024
    # Each catalog entry's examples cap generation with a different field name. Send the one
    # the entry documents, then confirm from the first response that the cap actually applied.
    OUTPUT_CAP_FIELD = {
    "openai/gpt-6-astra": "max_completion_tokens",
    "moonshotai/kimi-k3": "max_tokens",
    }
    MAX_PROMPT_CHARS = 3_000_000
    MAX_RETRY_WAIT = 60.0


    class PreflightError(Exception):
    """Raised before anything billable happens."""


    def list_price(model, prompt_tokens, completion_tokens):
    tier = PRICING[model][0]
    for candidate in PRICING[model]:
    if prompt_tokens >= candidate["from_input_tokens"]:
    tier = candidate
    return (prompt_tokens * tier["input"] + completion_tokens * tier["output"]) / 1_000_000


    def retry_delay(response, attempt):
    """Retry-After may be seconds or an HTTP date, and may be missing or junk."""
    header = response.headers.get("Retry-After")
    backoff = float(min(2 ** attempt, 30))
    if not header:
    return backoff
    try:
    wait = float(header)
    except (TypeError, ValueError):
    try:
    wait = parsedate_to_datetime(header).timestamp() - time.time()
    except (TypeError, ValueError, OverflowError):
    return backoff
    return min(max(0.0, wait), MAX_RETRY_WAIT)


    def run_one(model, messages, timeout=120, max_attempts=4, sleep=time.sleep):
    if not messages or not any(str(m.get("content", "")).strip() for m in messages):
    raise PreflightError("empty prompt")
    if sum(len(str(m.get("content", ""))) for m in messages) > MAX_PROMPT_CHARS:
    raise PreflightError("prompt above local guard")

    payload = {
    "model": model,
    "messages": messages,
    "stream": False,
    "context_length_exceeded_behavior": "error",
    OUTPUT_CAP_FIELD.get(model, "max_tokens"): OUTPUT_CAP,
    **SAMPLING,
    }
    headers = {"Authorization": "Bearer " + API_KEY, "Content-Type": "application/json"}
    billed = {"prompt_tokens": 0, "completion_tokens": 0, "cost": 0.0,
    "seen": False, "gap": False}

    def account(body, status_code):
    # Every attempt that reports usage consumed tokens, including failed ones.
    usage = body.get("usage") if isinstance(body, dict) else None
    if not isinstance(usage, dict):
    # A 4xx, including 429, returns no completion, so there is nothing to account
    # for. Anything else that answered without usage leaves a real gap.
    if status_code < 400 or status_code >= 500:
    billed["gap"] = True
    return
    prompt_tokens = usage.get("prompt_tokens") or 0
    completion_tokens = usage.get("completion_tokens") or 0
    billed["prompt_tokens"] += prompt_tokens
    billed["completion_tokens"] += completion_tokens
    billed["cost"] += list_price(model, prompt_tokens, completion_tokens)
    billed["seen"] = True

    def row(status, attempt, **extra):
    if not billed["seen"]:
    usage_state = "missing"
    elif billed["gap"]:
    usage_state = "partial" # at least one attempt reported nothing
    else:
    usage_state = "complete"
    result = {
    "model": model,
    "status": status,
    "attempts": attempt,
    "prompt_tokens": billed["prompt_tokens"],
    "completion_tokens": billed["completion_tokens"],
    "usage": usage_state,
    "list_cost_usd": round(billed["cost"], 6) if billed["seen"] else None,
    }
    result.update(extra)
    return result

    for attempt in range(1, max_attempts + 1):
    started = time.perf_counter()
    try:
    response = requests.post(
    BASE_URL + "/chat/completions", json=payload, headers=headers, timeout=timeout
    )
    except requests.Timeout:
    if attempt == max_attempts:
    return row("timeout", attempt)
    sleep(min(2 ** attempt, 30) + random.random())
    continue
    except requests.RequestException as exc:
    if attempt == max_attempts:
    return row("transport_error", attempt, error=str(exc)[:200])
    sleep(min(2 ** attempt, 30) + random.random())
    continue
    latency = time.perf_counter() - started

    try:
    body = response.json()
    except ValueError:
    body = None
    account(body, response.status_code)

    if response.status_code == 429 or response.status_code >= 500:
    if attempt == max_attempts:
    return row("http_%d" % response.status_code, attempt)
    sleep(min(retry_delay(response, attempt) + random.random(), MAX_RETRY_WAIT))
    continue
    if response.status_code >= 400:
    # 4xx other than 429 is a bug in the request, not flakiness. Do not retry it.
    return row("http_%d" % response.status_code, attempt, error=response.text[:200])
    if not isinstance(body, dict):
    return row("unexpected_body", attempt, error=(response.text or "")[:200])

    choices = body.get("choices") or []
    first = choices[0] if choices and isinstance(choices[0], dict) else {}
    message = first.get("message") or {}
    text = message.get("content") or ""
    tool_calls = message.get("tool_calls") or []
    if text.strip():
    status = "ok"
    elif tool_calls:
    status = "tool_calls" # a tool-call turn is a real answer, not an empty one
    else:
    status = "empty_completion"
    return row(
    status,
    attempt,
    latency_s=round(latency, 3),
    finish_reason=first.get("finish_reason"),
    output=text,
    tool_calls=tool_calls,
    )

    return row("exhausted", max_attempts)


    def run_suite(tasks, out_path="bakeoff.jsonl"):
    rows = []
    with open(out_path, "a", encoding="utf-8") as handle:
    for task in tasks:
    for model in MODELS:
    row = {"task_id": task["id"], "bucket": task["bucket"], "model": model}
    try:
    row.update(run_one(model, task["messages"]))
    except PreflightError as exc:
    row.update({"status": "preflight_error", "error": str(exc)})
    handle.write(json.dumps(row, ensure_ascii=False) + "\n")
    rows.append(row)
    return rows

    Seven behaviors in that code are worth keeping even if you rewrite the rest.

    • Transport failures become rows, not exceptions. Timeouts, connection resets, and any other requests.RequestException retry with backoff and then return a status, so one dropped connection does not end a six-hour run.
    • Retry-After is parsed defensively. The header is allowed to be either a delay in seconds or an HTTP date, and a junk value should not crash the client, so the parser handles all three and clamps the wait.
    • Client errors are not retried. A 400 class response other than 429 comes back immediately, because a malformed request repeated four times is still malformed, and a context_length_exceeded error raised under rule 2 is a finding rather than a retry loop.
    • Usage is accumulated across attempts. A 500 that already reported token usage still consumed tokens, so the retry that follows it adds to the same running total instead of replacing it.
    • Unknown spend is labeled, not rounded to zero. Each row carries a usage field of complete, partial, or missing. A response with no usage block leaves the cost null rather than $0, and a run where an attempt that answered reported no usage comes back as partial, so a subtotal is never mistaken for a verified total. A 429 that is retried into a clean success stays complete, because a rejected request returned no completion to account for.
    • The output cap uses the field each model documents. The cap is sent as max_completion_tokens for the Astra ID and max_tokens for the Kimi K3 ID, which is what rule 1 asks for, so one harness does not quietly leave one of the two models uncapped.
    • A tool-call turn is a result. A completion with content: null, populated tool_calls, and finish_reason: tool_calls is recorded as tool_calls with the arguments preserved, which is exactly the case the tool-use bucket is measuring.

    Cost is computed through the tier lookup rather than a flat rate, which is the only way the long-context bucket prices correctly.

    What does a 220-call bake-off cost at list price?

    A 220-call matrix per model lists at $175.40 in total, $149.00 for GPT-6 Astra and $26.40 for Kimi K3. That figure comes from a modest but realistic shape: 200 standard tasks at roughly 8,000 input and 1,000 output tokens, plus 20 long-context tasks at roughly 300,000 input and 2,000 output tokens, run against both models.

    BucketCalls per modelTokens per callAstra at list priceKimi K3 at list price
    Standard2008,000 in / 1,000 out$26.00$7.80
    Long context20300,000 in / 2,000 out$123.00$18.60
    Total220$149.00$26.40

    At $175.40, the whole run costs under 4% of the $4,550 monthly gap that a single 50,000-call bucket carries between these two models, priced in the decision rules below. The useful finding is in the distribution: the long-context bucket is 9.1% of the calls and 82.6% of the Astra bill. Per call, the gap is $0.091 in the standard bucket and $5.22 in the long-context bucket.

    At equal cache-hit rates, both models' one-tenth-priced cache reads preserve the price ranking: Astra $1 versus $10 fresh input, Kimi $0.30 versus $3. Equal hit rates are an assumption; verify billing because the API does not report hits. Astra also lists $12.50/1M cache writes, above $10 fresh input, so a never-reused write can cost more. Kimi lists no write price. Model reuse economics separately.

    When is GPT-6 Astra worth 3.33x the token price?

    GPT-6 Astra earns its 3.33x token price on any bucket where the quality difference you can measure is worth more than the monthly dollar gap that bucket generates, which on standard-sized tasks reaches $4,550 at 50,000 calls a month. Convert ratios into monthly dollars per bucket, then apply three rules.

    Rule 1: price the gap at production volume. At 8,000 in and 1,000 out, GMI Cloud MaaS list pricing puts Astra at $0.13 per call and Kimi K3 at $0.039. A bucket running 50,000 calls a month carries a $4,550 monthly gap on the same invoice. That number, not the 3.33x ratio, is what a quality difference has to be worth.

    Rule 2: a win inside your scoring noise is not a win. Measure that noise before you read the scoreboard: rescore one bucket's outputs a second time, or score a model against itself across two runs at the same sampling settings, and take the spread you get back as your noise floor. Any gap between the two models smaller than that floor goes to the cheaper model ID. Keep per-bucket sample counts high enough that the floor is itself stable, which is exactly why rule 3 of the test design exists.

    Rule 3: attack the 272,000 token threshold before you attack the price. A 300,000 input and 2,000 output token call on Astra lists at $6.15 under the second tier. Trim the same task to 120,000 input tokens with retrieval, keeping the 2,000 output tokens, and it lists at $1.30 under the standard tier, a 4.7x reduction with no model change and no negotiation. Measure how many of your long-context tasks genuinely need the full window before budgeting for the second tier.

    The common outcome is a split: the cheaper model ID on high-volume buckets, the pricier one on the handful of buckets where it pays for itself. That split is cheap to operate on GMI Cloud precisely because both IDs live on one key. The runtime side of operating a split, routing rules and fallback behavior once the bake-off is over, is covered in switching between GPT-6 Astra, Gemini 3.8 Flash, and DeepSeek V4.1 Flash. If selecting per request becomes its own project, GMI Cloud's GMI Router automates the choice across the models your workspace allows.

    Both models in this comparison are served through the MaaS API, so the account-level answer stays the same whichever one wins: one key, one endpoint, one invoice. The other deployment track on the same account covers a different kind of workload, a model you own. GMI Cloud's Prime Inference takes "Any open-source, fine-tuned, or proprietary weights" and runs them on reserved single-tenant NVIDIA GPUs billed per GPU-hour, with the weights pre-loaded so calls land warm. If your roadmap adds a model of your own after this bake-off, that work does not need a new vendor, and the MaaS documentation notes that teams "transition between these deployment models without changing APIs."

    FAQ

    Can one API key call both GPT-6 Astra and Kimi K3? Yes. Both use https://api.gmi-serving.com/v1 with a single GMI key. Check availability with GET /v1/models, then change the request's model field.

    How much should a two-model evaluation cost? The illustrated 220 calls per model—200 at 8K input/1K output and 20 at 300K input/2K output—cost $149.00 for Astra and $26.40 for Kimi, or $175.40 total at October 2026 list rates. Long-context calls make up 82.6% of the Astra bill, so budget by bucket.

    Do the two models have the same context window? Astra lists 1,050,000 tokens and Kimi 1,048,576, a 1,424-token difference. Account for prompt plus requested generation. Set context_length_exceeded_behavior to error during testing to avoid the default silent truncation.

    Does caching change which model is cheaper? Not at equal hit rates: cached-input rates are one-tenth of fresh input for both models. Actual rates differ by workload, and usage exposes only prompt_tokens, completion_tokens, and total_tokens. Reconcile Console billing rather than inferring hits from responses.

    Can the bake-off and production traffic run at the same time? Yes, but keys share the organization's TPM quota. Use a quiet window or arrange the needed tier before the evaluation.

    What happens after a model wins? Keep the winning model ID on MaaS and retain the other for suitable buckets. Add GMI Router for automated selection within the approved pool, or Prime Inference for customer-owned weights. The cost-per-million-tokens article covers dedicated-versus-token economics. (cost per million tokens)

    Run it this week

    Create a key in the GMI Cloud console, copy the two model IDs, and point the harness above at them. If you want the test matrix, rate limit tier, and pricing reviewed against your traffic profile first, our team will walk through it with you. Start from MaaS pricing and coverage or the developer quickstart.

    Colin Mo

    Build AI Without Limits

    GMI Cloud helps you architect, deploy, optimize, and scale your AI strategies

    FAQ

    Yes. Both use https://api.gmi-serving.com/v1 with a single GMI key. Check availability with GET /v1/models , then change the request's model field.

    Ready to build?

    Explore powerful AI models and launch your project in just a few clicks.

    Get Started