CodeWorlds
Powrót do kolekcji
Przewodnik20 min czytaniaZespół CodeWorlds

fal, kolejka i cennik generowania mediów

fal uruchamia modele obrazu, wideo i dźwięku przez API. Kolejka z webhookiem, klient npm 1.10.1 na MIT, klient PyPI 1.0.1 bez pola licencji.

fal, kolejka i cennik generowania mediów

fal to zamknięta platforma, która udostępnia gotowe modele generowania obrazu, wideo i dźwięku przez API, a obok tego pozwala wdrożyć własny kod na swoich kartach. O tym, czy sprawdzi się w konkretnym projekcie, decydują dwie rzeczy: sposób kolejkowania długich żądań oraz to, za jaką jednostkę naprawdę płacisz.

Co fal robi i czego nie robi

Podstawowy scenariusz jest prosty. Wybierasz identyfikator punktu końcowego z katalogu, na przykład fal-ai/flux/schnell, wysyłasz JSON z parametrami wejściowymi i dostajesz z powrotem adresy URL wygenerowanych plików. Nie zarządzasz kartami graficznymi, nie budujesz obrazu kontenera, nie pilnujesz skalowania. Wagi modeli są już załadowane po drugiej stronie, a rozliczenie idzie za wynik, nie za czas.

Drugi scenariusz to fal Serverless. Wdrażasz własną aplikację w Pythonie, dostajesz swój punkt końcowy i płacisz za czas życia procesu roboczego. To bliższy odpowiednik tego, co robi Modal, tylko z gorzej udokumentowanym modelem programistycznym i z jednym istotnym haczykiem w rozliczeniu, o którym niżej.

Czego fal nie robi. Nie ma wariantu instalowanego u siebie. Nie ma otwartego kodu platformy: otwarte są wyłącznie biblioteki klienckie. Nie ma też gwarancji, że konkretna wersja modelu z katalogu będzie dostępna za rok, bo część pozycji to modele partnerskie hostowane przez firmy trzecie, których dostępnością zarządza partner, a nie fal. Jeśli w architekturze potrzebujesz opcji przeniesienia obciążenia na własny sprzęt, patrz raczej w stronę vLLM albo wag pobranych z Hugging Face.

Klienci są dwaj: @fal-ai/client dla JavaScriptu i TypeScriptu oraz fal-client dla Pythona. Oba mówią do tego samego HTTP API, więc przy braku klienta dla Twojego języka zwykły curl wystarczy w zupełności.

Wersje klientów i licencja z trzech źródeł

Sprawdzenie licencji w trzech miejscach naraz daje tu wynik, którego nie widać po samych metadanych rejestrów.

Klient JavaScriptowy jest w porządku. Pakiet @fal-ai/client w wersji 1.10.1 został opublikowany 4 maja 2026 roku. Pole license w rejestrze npm mówi MIT. W rozpakowanym archiwum jest plik LICENSE z tekstem licencji MIT i notą praw autorskich Copyright 2024 https://fal.ai. Repozytorium fal-ai/fal-js ma ten sam plik na gałęzi głównej. Trzy źródła, jedna odpowiedź.

Klient Pythona wygląda inaczej. Pakiet fal-client 1.0.1 ukazał się 19 sierpnia 2026 roku. W metadanych PyPI pole license jest puste, pola license_expression nie ma wcale, a wśród klasyfikatorów nie ma ani jednego zaczynającego się od License ::. Skaner licencji odpytujący samo PyPI zobaczy pakiet bez licencji. Zawartość koła mówi co innego: plik fal_client-1.0.1.dist-info/licenses/LICENSE istnieje i zawiera pełny tekst Apache License 2.0, a nagłówek License-File: LICENSE w pliku METADATA potwierdza, że narzędzie budujące ten plik zauważyło.

Przyczynę widać w konfiguracji. W pliku projects/fal_client/pyproject.toml sekcja [project] deklaruje name, dynamic, description, readme, authors, requires-python i dependencies, natomiast klucza license tam nie ma i nie ma też klasyfikatora licencyjnego. Sam plik projects/fal_client/LICENSE jest dowiązaniem do ../../LICENSE, czyli do licencji całego repozytorium fal-ai/fal, a ta jest na Apache 2.0. Setuptools dołączyło plik do koła, ale nie miało skąd wziąć identyfikatora do metadanych.

Praktyczny wniosek: klient JavaScriptowy jest na MIT, klient Pythona na Apache 2.0. Obie licencje są permisywne, ale to nie jest ta sama licencja, a Apache 2.0 dokłada klauzulę patentową i wymóg oznaczania zmian. Jeśli Twój proces zgodności czyta metadane rejestru zamiast zawartości paczki, dostaniesz alert o pakiecie bez licencji i będziesz musiał go ręcznie odznaczyć.

W obu paczkach jest realny kod, co po historii z pustymi atrapami w npm nie jest oczywiste. Archiwum npm zawiera około 95 kilobajtów skompilowanego JavaScriptu w katalogu src wraz z deklaracjami typów. Koło Pythona zawiera moduł client.py o długości ponad 2600 linii plus auth.py, _headers.py i py.typed.

Różny rytm wydań nie oznacza zastoju. Klient JavaScriptowy stoi na 1.10.1 od maja, bo mieszka w osobnym repozytorium fal-ai/fal-js, gdzie ostatnie wydanie oznaczono client-v1.10.1 4 maja 2026 roku. Klient Pythona mieszka w monorepo fal-ai/fal razem z resztą narzędzi platformy i tam ruch jest znacznie większy: fal_v1.79.1 z 5 sierpnia, isolate_proto_v0.34.2 z 19 sierpnia, fal_client_v1.0.1 tego samego dnia. To dwa różne repozytoria o dwóch różnych cyklach, a nie porzucony projekt.

Trzeci pakiet z tej rodziny, @fal-ai/server-proxy w wersji 1.2.1 z 20 lutego 2026 roku, jest jeszcze starszy. Ma licencję MIT i deklaruje wyłącznie opcjonalne zależności równorzędne dla frameworków: next w zakresie 13.4 - 14 || >=15.0.0-0, react w ^18.0.0 || >=19.0.0-0, hono w ^4.0.0, express w ^4.0.0, @sveltejs/kit w ^2.0.0 oraz @remix-run/dev w ^2.0.0. Zależności równorzędnej od samego @fal-ai/client nie deklaruje wcale, więc konfliktu wersji przy instalacji obu naraz nie będzie, ale też nikt nie obiecuje, że proxy z lutego rozumie każdą zmianę w kliencie z maja.

Kolejka, czyli tryb, który trzeba znać

To jest sedno tej platformy. Generowanie wideo trwa minuty, więc wywołanie synchroniczne trzymające otwarte połączenie HTTP przez ten czas jest kruche, a za bramą serwerless albo na funkcji brzegowej po prostu odpadnie na limicie czasu. fal rozwiązuje to osobną, trwałą kolejką pod adresem https://queue.fal.run, w odróżnieniu od https://fal.run używanego przez wywołania bezpośrednie.

Żądanie w kolejce przechodzi przez trzy stany:

StatusTyp w SDK PythonaTyp w SDK JavaScriptuCo się dzieje
IN_QUEUEQueued(position)"IN_QUEUE" z polem queue_positionŻądanie przyjęte, czeka na wolny proces roboczy
IN_PROGRESSInProgress(logs)"IN_PROGRESS" z polem logsProces roboczy dostał żądanie i je liczy
COMPLETEDCompleted(logs, metrics)"COMPLETED" z polami logs i metricsWynik gotowy do odebrania albo wysłany webhookiem

Instalacja obu klientów wygląda tak:

Code
Bash
# klient JavaScriptowy, wersja przypięta dokładnie
pnpm add @fal-ai/client@1.10.1

# klient Pythona
pip install "fal-client==1.0.1"

# klucz czytany domyślnie ze zmiennej środowiskowej
export FAL_KEY="twoj-klucz"

Najprostsze wejście w kolejkę to subscribe. Metoda wysyła żądanie do kolejki, sama odpytuje o status i zwraca wynik, gdy jest gotowy. Interfejs jest blokujący, mechanizm pod spodem kolejkowy:

Code
JavaScript
import { fal } from "@fal-ai/client";

const result = await fal.subscribe("fal-ai/flux/schnell", {
  input: { prompt: "a sunset over mountains" },
  logs: true,
  mode: "polling",
  pollInterval: 1000,
  priority: "normal",
  onEnqueue: (requestId) => console.log("w kolejce:", requestId),
  onQueueUpdate: (status) => {
    if (status.status === "IN_QUEUE") {
      console.log("pozycja:", status.queue_position);
    } else if (status.status === "IN_PROGRESS") {
      status.logs?.forEach((log) => console.log(log.message));
    }
  },
});

console.log(result.data.images[0].url, result.requestId);

Tryb drugi rozdziela wysłanie od odbioru. fal.queue.submit zwraca natychmiast, a wynik odbierasz później, z innego procesu, po zapisanym request_id:

Code
JavaScript
import { fal } from "@fal-ai/client";

// 1. wyslanie, zwraca natychmiast
const enqueued = await fal.queue.submit("fal-ai/flux/dev", {
  input: { prompt: "a sunset over mountains" },
  webhookUrl: "https://example.com/api/fal/webhook",
  priority: "low",
  startTimeout: 120,
});
console.log(enqueued.request_id, enqueued.status_url, enqueued.cancel_url);

// 2. odpytanie o status, dowolnie pozniej
const status = await fal.queue.status("fal-ai/flux/dev", {
  requestId: enqueued.request_id,
  logs: true,
});

// 3. odbior wyniku po statusie COMPLETED
if (status.status === "COMPLETED") {
  const result = await fal.queue.result("fal-ai/flux/dev", {
    requestId: enqueued.request_id,
  });
  console.log(result.data);
}

// 4. anulowanie, gdy zadanie przestalo byc potrzebne
await fal.queue.cancel("fal-ai/flux/dev", { requestId: enqueued.request_id });

Poza tymi czterema metodami klient JavaScriptowy ma jeszcze fal.queue.streamStatus, które otwiera połączenie strumieniowe zamiast odpytywać, oraz fal.queue.subscribeToStatus, czyli oczekiwanie na zakończenie już wysłanego żądania.

Strona Pythona nazywa to samo inaczej. Zamiast obiektu queue dostajesz uchwyt zwracany przez submit:

Code
Python
import fal_client

# submit zwraca SyncRequestHandle, nie wynik
handler = fal_client.submit(
    "fal-ai/flux/dev",
    arguments={"prompt": "a sunset over mountains"},
    webhook_url="https://example.com/api/fal/webhook",
    priority="low",
    start_timeout=120,
)
print(handler.request_id, handler.status_url, handler.cancel_url)

# odpytanie o pojedynczy status
status = handler.status(with_logs=True)
if isinstance(status, fal_client.Queued):
    print("pozycja:", status.position)
elif isinstance(status, fal_client.InProgress):
    for log in status.logs or []:
        print(log["message"])

# strumien zdarzen az do konca, a potem wynik
for event in handler.iter_events(with_logs=True):
    if isinstance(event, fal_client.InProgress):
        pass
result = handler.get(interval=0.5)
print(result["images"][0]["url"])

# wariant blokujacy z wlasnym limitem czasu po stronie klienta
result = fal_client.subscribe(
    "fal-ai/flux/schnell",
    arguments={"prompt": "a sunset over mountains"},
    with_logs=True,
    interval=0.5,
    client_timeout=600,
)

Uchwyt SyncRequestHandle ma pola request_id, response_url, status_url i cancel_url oraz metodę klasową from_request_id, która pozwala odtworzyć go w innym procesie na podstawie samego identyfikatora. Wariant asynchroniczny nazywa się AsyncRequestHandle i zwracają go funkcje z sufiksem _async: submit_async, subscribe_async, run_async, status_async, result_async, cancel_async.

Bez żadnego klienta wygląda to tak:

Code
Bash
# wyslanie do kolejki, webhook jako parametr zapytania
curl -X POST "https://queue.fal.run/fal-ai/flux/dev?fal_webhook=https://example.com/api/fal/webhook" \
  -H "Authorization: Key $FAL_KEY" \
  -H "Content-Type: application/json" \
  -H 'X-Fal-Object-Lifecycle-Preference: {"expiration_duration_seconds": 3600}' \
  -d '{"prompt": "a sunset over mountains"}'

# status z logami procesu roboczego
curl "https://queue.fal.run/fal-ai/flux/dev/requests/$REQUEST_ID/status?logs=1" \
  -H "Authorization: Key $FAL_KEY"

# odbior wyniku
curl "https://queue.fal.run/fal-ai/flux/dev/requests/$REQUEST_ID/response" \
  -H "Authorization: Key $FAL_KEY"

Odpowiedź statusu ma trzy różne kształty zależnie od stanu. Ten po zakończeniu zawiera pomiar czasu liczenia:

Code
JSON
{
  "status": "COMPLETED",
  "request_id": "764cabcf-b745-4b3e-ae38-1200304cf45b",
  "response_url": "https://queue.fal.run/fal-ai/flux/dev/requests/764cabcf.../response",
  "logs": [{ "message": "Done.", "timestamp": "2026-02-17T10:30:05.789Z" }],
  "metrics": { "inference_time": 3.42 }
}

Pole metrics.inference_time pojawia się tylko przy statusie COMPLETED i podaje sekundy spędzone na liczeniu. Pola error i error_type pojawiają się tam wyłącznie, gdy żądanie zawiodło.

Tryb bezpośredni i tryb subscribe

Metoda run to jedyne wywołanie, które nie dotyka kolejki. Idzie prosto pod fal.run, odpowiedź wraca w tym samym połączeniu HTTP i nie ma tu ani pozycji w kolejce, ani ponowień po stronie serwera. Klient sam ponawia przy przejściowych błędach 502, 503 i 504, ale gdy połączenie padnie, żądanie przepada razem z nim.

Różnica w limitach czasu jest asymetryczna między klientami i to jest realna pułapka. W Pythonie run przyjmuje timeout jako limit po stronie klienta HTTP oraz start_timeout jako serwerowy termin na rozpoczęcie przetwarzania. W JavaScripcie fal.run() nie obsługuje ani timeout, ani hint, ani headers, obsługuje tylko startTimeout. Aby ustawić z JavaScriptu nagłówki, podpowiedź dla procesu roboczego albo priorytet, musisz przejść na fal.queue.submit lub fal.subscribe.

Parametr start_timeout w obu klientach mierzy wyłącznie czas do momentu, w którym proces roboczy zacznie liczyć, wliczając czekanie w kolejce, ponowienia i trasowanie. Po rozpoczęciu przetwarzania przestaje obowiązywać. Przekroczenie daje kod 504 i jest jedyną sytuacją, w której żądanie czekające w kolejce może zostać porzucone. Nagłówek, który to niesie, nazywa się x-fal-request-timeout.

Domyślne tempo odpytywania różni się między klientami pięciokrotnie i nie wynika to z dokumentacji, tylko z kodu paczek. W kliencie JavaScriptowym stała DEFAULT_POLL_INTERVAL wynosi 500 milisekund. W kliencie Pythona DEFAULT_QUEUE_POLL_INTERVAL wynosi 0,1 sekundy, czyli 100 milisekund. Przy setkach równoległych żądań z Pythona to pięć razy więcej zapytań o status niż z JavaScriptu, więc interval ustaw jawnie.

Podpowiedź, kiedy co wybrać, jest krótka. run nadaje się do skryptów i prototypów, gdzie zależy Ci na najkrótszej ścieżce. subscribe daje ten sam blokujący interfejs, ale z niezawodnością kolejki, i to jego dostawca zaleca jako domyślny. submit z webhookiem jest jedynym sensownym wyborem dla wideo, trenowania i wszystkiego, co trwa minuty.

Limity współbieżności, przechowywanie i webhooki

Nowe konto startuje z limitem dwóch jednoczesnych żądań w stanie IN_PROGRESS. Limit rośnie automatycznie na podstawie sumy opłaconych faktur z ostatnich czterech tygodni i w trybie samoobsługowym sięga czterdziestu. Powyżej czterdziestu zaczyna się rozmowa z działem sprzedaży. Żądania w stanie IN_QUEUE nie liczą się do limitu, a samego limitu nie da się przekroczyć w taki sposób, żeby żądanie zostało odrzucone: przy wywołaniach kolejkowych platforma po prostu ponawia wstawienie z rosnącym opóźnieniem i bez maksymalnej liczby prób.

Przy wywołaniach bezpośrednich odpowiedzią na przekroczenie limitu jest kod 429 z typem concurrent_requests_limit i nagłówkiem X-Fal-needs-retry: 1. Klient ponawia sam, do dziesięciu razy z rosnącym opóźnieniem. Jeśli piszesz surowe zapytania HTTP, ten nagłówek trzeba obsłużyć ręcznie.

Kolejka ponawia też po awariach procesu roboczego. Błąd 503, 504 albo zerwane połączenie w trakcie liczenia powoduje ponowne wstawienie żądania, do dziesięciu razy. Nie ma limitu rozmiaru kolejki.

Webhook dostaje żądanie POST po zakończeniu przetwarzania. Ciało zawiera request_id, gateway_request_id, status o wartości OK albo ERROR, oraz payload z wynikiem. Te dwa identyfikatory zwykle są takie same, ale gdy żądanie było ponawiane, gateway_request_id wskazuje ostatnią próbę, a request_id zostaje ten z kolejki. Warto to rozróżnić przy dopasowywaniu webhooka do rekordu w bazie.

Pliki wynikowe trafiają na CDN pod adresy w rodzaju https://v3b.fal.media/files/... i domyślnie są publiczne: każdy, kto ma adres, ma plik. Zmiana tego wymaga nagłówka X-Fal-Object-Lifecycle-Preference, w którym oprócz expiration_duration_seconds można podać initial_acl. W kliencie Pythona odpowiadają temu typy StorageSettings z polami expires_in i initial_acl, ObjectExpiration przyjmujące wartości never, immediate, 1h, 1d, 7d, 30d, 1y albo liczbę sekund, oraz StorageACLRule z decyzją hide, forbid lub allow.

Tu dokumentacja przeczy sama sobie. Sekcja pytań mówi, że pliki wynikowe są dostępne domyślnie przez co najmniej siedem dni. Tabela podsumowująca na stronie o przechowywaniu danych podaje dla plików wynikowych retencję opisaną jako konfigurowalna, bez żadnej liczby. Dla payloadów JSON obie strony zgodnie podają trzydzieści dni z możliwością wyłączenia nagłówkiem X-Fal-Store-IO: 0. Przy planowaniu przyjmij siedem dni jako dolną granicę i i tak pobieraj pliki do siebie.

Cennik policzony na przykładach

Strona fal.ai/pricing renderuje się bez JavaScriptu, więc dane poniżej pochodzą z surowego HTML pobranego 22 sierpnia 2026 roku. Cennik ma dwie zupełnie osobne części i pomylenie ich to najczęstszy błąd w szacowaniu kosztu.

Część pierwsza dotyczy własnych wdrożeń i liczy godziny pracy karty:

KartaVRAMCena podstawowaCena najniższa
B300288 GB8,50 USD/h4,49 USD/h
B200180 GB6,25 USD/h3,49 USD/h
H200141 GB4,50 USD/h2,10 USD/h
H10080 GB4,50 USD/h1,89 USD/h
RTX PRO 600096 GB2,99 USD/h1,10 USD/h

W tej tabeli H100 i H200 mają identyczną cenę podstawową 4,50 USD za godzinę mimo różnicy 61 GB pamięci, a rozjeżdżają się dopiero w kolumnie z ceną najniższą. Dostawca nie tłumaczy, od czego zależy zejście do tej drugiej kolumny, poza informacją, że dotyczy wdrożeń niestandardowych ustalanych przez kontakt z pomocą techniczną. Traktuj więc kolumnę podstawową jako tę, którą realnie zapłacisz.

Część druga dotyczy gotowych modeli z katalogu i liczy jednostki wyjścia:

ModelJednostkaCenaTysiąc obrazów albo minuta wideo
Seedream V4obraz0,03 USD30,00 USD za tysiąc obrazów
Flux Kontext Proobraz0,04 USD40,00 USD za tysiąc obrazów
Nanobananaobraz0,0398 USD39,80 USD za tysiąc obrazów
Qwenmegapiksel0,02 USD20,00 USD za tysiąc obrazów o rozmiarze 1 MP
Wan 2.5sekunda wideo0,05 USD3,00 USD za minutę
Kling 2.5 Turbo Prosekunda wideo0,07 USD4,20 USD za minutę
Veo 3sekunda wideo0,40 USD24,00 USD za minutę
Oviwideo0,20 USDzależnie od długości pojedynczego klipu

Ostatnia kolumna jest moim przeliczeniem, dostawca podaje wyłącznie cenę jednostkową. Przy Qwenie liczonym za megapiksel obraz o rozmiarze 2 MP kosztuje dwa razy tyle, bo dokumentacja mówi wprost o proporcjonalnym przeliczeniu przy wyższych rozdzielczościach. Przy Ovi nie da się podać ceny minuty, bo jednostką jest cały klip, a nie sekunda: przyjmując założenie z przypisu dostawcy o średnim klipie długości pięciu sekund, minuta wymagałaby dwunastu klipów, czyli 2,40 USD, ale jest to założenie, a nie cena z cennika.

Arytmetyka samego cennika ma jedną usterkę. Kolumna „Output per $1" podaje dla Kling 2.5 Turbo Pro czternaście sekund, co jest poprawnym zaokrągleniem w dół z 14,28. Dla Veo 3 podaje trzy sekundy, podczas gdy jeden dolar dzielony przez 0,40 USD daje dokładnie 2,5 sekundy. Raz zaokrąglono w dół, raz w górę, a różnica wynosi dwadzieścia procent na niekorzyść czytelnika. Same ceny jednostkowe są spójne, więc licz z nich, nie z kolumny porównawczej.

Zestawienie obu części daje ciekawy wynik. Tysiąc obrazów z Seedream V4 kosztuje 30 USD. Ten sam tysiąc obrazów liczony na własnym wdrożeniu na H100, przy założeniu dwóch sekund na obraz, to 2000 sekund po 0,00125 USD, czyli 2,50 USD. Różnica jest dwunastokrotna, ale porównanie jest nieuczciwe z trzech powodów: musisz mieć wagi i prawo do ich użycia, musisz je zoptymalizować do dwóch sekund, i płacisz też za stany, w których karta nic nie liczy.

Ten trzeci punkt jest kluczowy dla Serverless. Dokumentacja rozliczeń wymienia stany procesu roboczego i wprost oznacza jako płatne: SETUP, czyli wykonanie metody setup() z ładowaniem modelu, IDLE, czyli gotowość razem z oknem keep_alive, RUNNING, DRAINING oraz TERMINATING. Niepłatne są tylko PENDING, DOCKER_PULL i TERMINATED. Sześćdziesiąt sekund bezczynności na H100 po cenie podstawowej to 0,075 USD za każde wygaszenie. Maszyny wielokartowe liczą się jako gpu_count razy czas.

Przy gotowych modelach z katalogu jest odwrotnie i to jest realna przewaga: zimny start nie jest płatny, bo płacisz za wynik, a nie za sekundy. Czas czekania w kolejce także nie jest płatny.

Planu darmowego w zwykłym rozumieniu nie ma. Rozliczenie jest przedpłacone: kupujesz kredyty i one się zużywają. Kredyty kupione wygasają po 365 dniach od zakupu. Kredyty darmowe i kupony istnieją, ale dostawca nie podaje ich wysokości, a termin ważności określa jako zmienny, od tygodnia do roku zależnie od konkretnego przyznania. Gdy saldo spadnie poniżej progu blokady, konto zostaje zablokowane i żądania są odrzucane; samej wartości progu dokumentacja nie podaje. Konta rozliczane fakturami nie podlegają automatycznej blokadzie.

Dokumentacja rozliczeń przeczy sama sobie w jednym punkcie. Strona o cenniku modeli mówi, że płacisz wyłącznie za udane wyniki i nigdy za błędy serwera. Sekcja pytań mówi to samo o błędach 500 i wyżej, ale dodaje, że błędy po stronie klienta, na przykład 422 przy niepoprawnym wejściu, mogą zostać naliczone, jeśli proces roboczy zdążył zużyć czas karty przed wykryciem błędu. To dwie różne odpowiedzi na to samo pytanie i przy budżetowaniu trzymaj się tej ostrożniejszej.

fal, Replicate i Modal

Trzy platformy rozwiązują nakładające się problemy, ale wchodzi się do nich z różnych stron.

WymiarfalReplicateModal
Punkt wejściaidentyfikator modelu z kataloguidentyfikator modelu z kataloguwłasny kod z dekoratorami
Wgranie własnego kodufal Serverless, PythonCog budujący obraz kontenerapakiet modal, Python
Rozliczenie modeli z kataloguza jednostkę wyjścia, z odwrotem do sekund kartyza jednostkę wyjścia albo za sekundę, zależnie od modelubrak katalogu
Rozliczenie własnego koduza czas życia procesu, IDLE płatneza sekundę pracy sprzętuza sekundę osobno dla CPU, pamięci i GPU
Tryb dla długich zadańkolejka queue.fal.run z webhookiemwebhook albo odpytywanie predykcjiwywołania w tle i kolejki w kodzie
Licencja klientaMIT w npm, Apache 2.0 w PyPIApache 2.0 dla CogApache 2.0

Względem Replicate różnica jest subtelniejsza, niż się wydaje. Tam też jest katalog i też dwa modele rozliczeniowe, ale wybór między nimi zależy od tego, jak dany model opublikowano, więc dla jednego modelu płacisz za obraz, a dla sąsiedniego za sekundę pracy karty. U fala reguła jest ustawiona odwrotnie: rozliczenie za jednostkę wyjścia jest domyślne, a przejście na sekundy karty to wyjątek dla modeli bez ustalonej ceny wyjścia i dla własnych punktów końcowych. Praktycznie oznacza to, że u fala łatwiej oszacować rachunek z góry. Po stronie Replicate za to biblioteka kliencka dla Pythona nie dostała stabilnego wydania od maja 2025 roku, podczas gdy fal-client wyszedł trzy dni temu.

Modal to inna liga i porównywanie ich wprost mija się z celem. Tam nie ma katalogu gotowych modeli: piszesz funkcję w Pythonie, dekorujesz ją i wdrażasz, a rozliczenie idzie wyłącznie za sekundy, osobno dla rdzeni, pamięci i karty. Jeśli Twoim zadaniem jest wygenerowanie tysiąca obrazów popularnym modelem, Modal wymaga najpierw zbudowania całej ścieżki, którą fal ma gotową. Jeśli Twoim zadaniem jest własny potok przetwarzania z nietypowymi zależnościami, fal Serverless będzie ciaśniejszym gorsetem. Ciekawostka na marginesie: obie platformy naliczają czas bezczynności, tylko fal przyznaje to wprost w tabeli stanów, a Modal obiecuje w nagłówku cennika coś przeciwnego, niż pisze w sekcji pytań.

Do modeli językowych ta trójka nie jest pierwszym wyborem. Tam prościej pójść po API OpenAI albo postawić vLLM na własnym sprzęcie. Jeśli natomiast potrzebujesz uruchamiać kod wygenerowany przez model w izolacji, a nie liczyć wagi na karcie, właściwym narzędziem jest E2B.

Typowe błędy

Wywołanie run do generowania wideo. Ten tryb trzyma otwarte połączenie HTTP przez cały czas liczenia i nie ma za sobą kolejki, więc pierwsza brama albo funkcja brzegowa z limitem trzydziestu sekund zerwie żądanie, a wynik przepadnie bez możliwości odzyskania. Do wszystkiego dłuższego niż kilka sekund używaj submit z webhookiem albo przynajmniej subscribe.

Mieszanie konwencji nazewniczych w odpowiedziach. Wynik zwracany przez run, subscribe i fal.queue.result w kliencie JavaScriptowym ma kształt { data, requestId } z identyfikatorem w konwencji wielbłądziej, natomiast obiekty statusu z fal.queue.status mają request_id z podkreśleniem, bo to surowy JSON z API. Kod, który przekazuje jedno w miejsce drugiego, dostaje undefined i nie zgłasza błędu.

Pozostawienie domyślnego odpytywania w Pythonie. Sto równoległych żądań przy DEFAULT_QUEUE_POLL_INTERVAL równym 0,1 sekundy to tysiąc zapytań o status na sekundę, wyłącznie na czekanie. Ustaw interval na wartość rzędu sekundy albo przejdź na webhook.

Zakładanie, że klucz może stać w przeglądarce. Klient wykrywa środowisko przeglądarki i ostrzega, a wyłączenie ostrzeżenia flagą suppressLocalCredentialsWarning nie zmienia tego, że klucz jest wtedy widoczny. Właściwym rozwiązaniem jest proxyUrl, w wersji tekstowej albo w postaci obiektu { url, when } dla środowisk innych niż przeglądarka.

Liczenie budżetu z kolumny porównawczej cennika zamiast z cen jednostkowych. Przy Veo 3 daje to zaniżenie o dwadzieścia procent, jak opisano wyżej.

Traktowanie adresów plików wynikowych jako prywatnych. Domyślnie są publiczne dla każdego, kto zna adres, i znikają po upływie retencji. Jeśli obraz ma trafić do produktu, pobierz go i zapisz u siebie, a dostęp kontroluj przez initial_acl w nagłówku X-Fal-Object-Lifecycle-Preference.

Poleganie na jednej wersji modelu z katalogu bez planu awaryjnego. Modele oznaczone jako partnerskie są hostowane przez firmy trzecie i ich dostępnością zarządza partner, a standardowe rabaty procentowe do nich nie mają zastosowania.

FAQ

Czy fal ma plan darmowy?

Nie w klasycznym rozumieniu. Rozliczenie jest przedpłacone: kupujesz kredyty, które wygasają po 365 dniach od zakupu. Kredyty darmowe i kupony istnieją, ale dostawca nie publikuje ich wysokości, a ich ważność określa jako zmienną, od tygodnia do roku. Po spadku salda poniżej progu blokady konto jest blokowane, a żądania odrzucane.

Który klient wybrać, JavaScript czy Python?

Oba mówią do tego samego API, więc decyduje reszta stosu. Klient Pythona jest nowszy, ma pełniejszy zestaw funkcji dla run i lepiej pasuje do przetwarzania wsadowego. Klient JavaScriptowy ma za to @fal-ai/server-proxy z gotowymi obsługami dla Next.js, Express, Hono, SvelteKit i Remix, co upraszcza ukrycie klucza przed przeglądarką. Pamiętaj tylko, że licencje obu paczek są różne: MIT po stronie npm, Apache 2.0 po stronie PyPI.

Kiedy run wystarczy, a kiedy trzeba subscribe?

run nadaje się do szybkich modeli obrazu w skryptach i prototypach, gdzie zerwane połączenie kosztuje tylko powtórzenie. subscribe daje ten sam blokujący interfejs, ale opiera się na kolejce, więc dostajesz ponowienia po stronie serwera, pozycję w kolejce i logi. Do wszystkiego, co trwa minuty, i tak sensowniejszy jest submit z webhookiem.

Czy da się uruchomić fal u siebie?

Nie. Otwarte są wyłącznie biblioteki klienckie, a sama platforma pozostaje zamknięta i nie ma wariantu instalowanego lokalnie. Uzależnienie dotyczy więc nie tylko infrastruktury, lecz również katalogu modeli i tego, czy konkretna wersja modelu będzie za rok wciąż dostępna pod tym samym identyfikatorem.

Co robi parametr start_timeout i czym różni się od timeout?

start_timeout, wysyłany jako nagłówek x-fal-request-timeout, jest serwerowym terminem na rozpoczęcie liczenia i obejmuje czekanie w kolejce, ponowienia i trasowanie. Po przekroczeniu żądanie wraca z kodem 504. timeout w kliencie Pythona to zwykły limit połączenia HTTP po stronie klienta i nie ma wpływu na serwer. W kliencie JavaScriptowym fal.run() obsługuje wyłącznie startTimeout.

Czy zapłacę za nieudane żądanie?

Za błędy serwera o kodzie 500 i wyższym nigdy. Za czekanie w kolejce także nie. Natomiast sekcja pytań dostawcy dopuszcza naliczenie opłaty przy błędach po stronie klienta, na przykład 422 przy niepoprawnym wejściu, jeśli proces roboczy zdążył wcześniej zużyć czas karty. Strona o cenniku mówi w tym miejscu coś ogólniejszego, więc te dwa dokumenty nie są zgodne.

Czytaj dalej

Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie