fp4.py 175 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524
  1. """
  2. Copyright (c) 2025 by FlashInfer team.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. Common utilities for FP4 quantization kernels.
  13. This module contains shared PTX intrinsics, helper functions, and reduction
  14. utilities used by the fused frontend kernels.
  15. """
  16. import functools
  17. import math
  18. import operator
  19. from typing import Callable, Tuple
  20. import cutlass
  21. import cutlass.cute as cute
  22. import torch
  23. import torch.nn.functional as F
  24. from cutlass import Float32, Int32, Int64, Uint8, Uint16, Uint32, Uint64
  25. from cutlass.cutlass_dsl import T, dsl_user_op
  26. from cutlass._mlir import ir
  27. from cutlass._mlir.dialects import llvm
  28. # =============================================================================
  29. # Constants
  30. # =============================================================================
  31. FLOAT4_E2M1_MAX = 6.0 # Maximum value representable in FP4 E2M1
  32. FLOAT8_E4M3_MAX = 448.0 # Maximum value representable in FP8 E4M3
  33. SF_VEC_SIZE = 16 # Elements per scale factor block
  34. COPY_BITS = 128 # 128-bit vectorized loads
  35. _FP4_MAG_LUT = (0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0)
  36. def align_up(value: int, alignment: int) -> int:
  37. return ((value + alignment - 1) // alignment) * alignment
  38. def make_swizzle_indices(rows_padded: int, cols_padded: int, device: torch.device) -> torch.Tensor:
  39. """Pre-compute swizzle gather indices for FP8 block-scale factors."""
  40. numel = rows_padded * cols_padded
  41. idx = torch.arange(numel, device=device)
  42. idx_5d = idx.reshape(rows_padded // 128, 4, 32, cols_padded // 4, 4)
  43. return idx_5d.permute(0, 3, 2, 1, 4).contiguous().reshape(-1)
  44. def swizzle_block_scale(scale: torch.Tensor) -> torch.Tensor:
  45. if scale.ndim == 2:
  46. scale = scale.unsqueeze(0)
  47. squeeze_batch = True
  48. elif scale.ndim == 3:
  49. squeeze_batch = False
  50. else:
  51. raise ValueError(f"scale must be 2D or 3D, got {tuple(scale.shape)}")
  52. batch, rows, cols = scale.shape
  53. rows_padded = align_up(rows, 128)
  54. cols_padded = align_up(cols, 4)
  55. padded = torch.zeros((batch, rows_padded, cols_padded), dtype=scale.dtype, device=scale.device)
  56. padded[:, :rows, :cols] = scale
  57. swizzled = padded.reshape(batch, rows_padded // 128, 4, 32, cols_padded // 4, 4)
  58. swizzled = swizzled.permute(0, 1, 4, 3, 2, 5).contiguous()
  59. swizzled = swizzled.reshape(batch, rows_padded, cols_padded)
  60. return swizzled[0] if squeeze_batch else swizzled
  61. def as_grouped_scale_view(scale_storage: torch.Tensor, rows: int, cols: int) -> torch.Tensor:
  62. batch = scale_storage.shape[0]
  63. rows_padded = align_up(rows, 128)
  64. cols_padded = align_up(cols // SF_VEC_SIZE, 4)
  65. sf = scale_storage.view(torch.float8_e4m3fn)
  66. sf = sf.view(batch, rows_padded // 128, cols_padded // 4, 32, 4, 4)
  67. return sf.permute(3, 4, 1, 5, 2, 0)
  68. def as_grouped_scale_view_mx(scale_storage: torch.Tensor, rows: int, cols: int) -> torch.Tensor:
  69. """Grouped UE8M0 scale view for vec32 (MXFP8 w4a8) block scales."""
  70. batch = scale_storage.shape[0]
  71. rows_padded = align_up(rows, 128)
  72. cols_padded = align_up(cols // 32, 4)
  73. sf = scale_storage.view(torch.float8_e8m0fnu)
  74. sf = sf.view(batch, rows_padded // 128, cols_padded // 4, 32, 4, 4)
  75. return sf.permute(3, 4, 1, 5, 2, 0)
  76. def _fp4_quantize_values(x: torch.Tensor) -> torch.Tensor:
  77. sign = torch.sign(x)
  78. x = torch.abs(x.clone())
  79. x[(x >= 0.0) & (x <= 0.25)] = 0.0
  80. x[(x > 0.25) & (x < 0.75)] = 0.5
  81. x[(x >= 0.75) & (x <= 1.25)] = 1.0
  82. x[(x > 1.25) & (x < 1.75)] = 1.5
  83. x[(x >= 1.75) & (x <= 2.5)] = 2.0
  84. x[(x > 2.5) & (x < 3.5)] = 3.0
  85. x[(x >= 3.5) & (x <= 5.0)] = 4.0
  86. x[x > 5.0] = 6.0
  87. return x * sign
  88. def fp4_quantize_values_torch(x: torch.Tensor) -> torch.Tensor:
  89. """Pure-Torch FP4 E2M1 quantization with kernel-matching tie-breaking."""
  90. return _fp4_quantize_values(x)
  91. def _fp4_encode_nibbles(values: torch.Tensor) -> torch.Tensor:
  92. mags = values.abs()
  93. idx = torch.zeros_like(values, dtype=torch.uint8)
  94. for code, mag in enumerate(_FP4_MAG_LUT):
  95. idx = torch.where(mags == mag, torch.full_like(idx, code), idx)
  96. sign_bit = (values < 0).to(torch.uint8) << 3
  97. return idx | sign_bit
  98. def pack_grouped_fp4_values(values: torch.Tensor) -> torch.Tensor:
  99. """Pack grouped FP4 values `[G, R, C]` into uint8 `[R, C/2, G]`."""
  100. nibbles = _fp4_encode_nibbles(values)
  101. packed = nibbles.view(values.shape[0], values.shape[1], values.shape[2] // 2, 2)
  102. packed = packed[..., 0] | (packed[..., 1] << 4)
  103. return packed.permute(1, 2, 0).contiguous()
  104. def quantize_grouped_nvfp4_torch(
  105. input_tensor: torch.Tensor,
  106. row_counts: torch.Tensor,
  107. global_scale: torch.Tensor,
  108. ) -> tuple[torch.Tensor, torch.Tensor]:
  109. """Pure-Torch grouped NVFP4 quantization with reciprocal global scales."""
  110. num_groups, rows, cols = input_tensor.shape
  111. if cols % SF_VEC_SIZE != 0:
  112. raise ValueError(f"cols must be divisible by {SF_VEC_SIZE}, got {cols}")
  113. if global_scale.numel() == 1:
  114. global_scale = global_scale.expand(num_groups).contiguous()
  115. quantized = torch.zeros((num_groups, rows, cols), dtype=torch.float32, device=input_tensor.device)
  116. scales = torch.zeros((num_groups, rows, cols // SF_VEC_SIZE), dtype=torch.float32, device=input_tensor.device)
  117. for group_idx in range(num_groups):
  118. valid_rows = int(row_counts[group_idx].item())
  119. if valid_rows == 0:
  120. continue
  121. x = input_tensor[group_idx, :valid_rows].float()
  122. sliced = x.view(valid_rows, cols // SF_VEC_SIZE, SF_VEC_SIZE)
  123. block_max = sliced.abs().amax(dim=-1, keepdim=True).to(torch.float32)
  124. scale = (global_scale[group_idx] * (block_max / FLOAT4_E2M1_MAX)).to(torch.float8_e4m3fn).to(torch.float32)
  125. output_scale = 1.0 / (scale * (1.0 / global_scale[group_idx]))
  126. clipped = torch.clamp(sliced * output_scale, -FLOAT4_E2M1_MAX, FLOAT4_E2M1_MAX).view(valid_rows, cols)
  127. quantized[group_idx, :valid_rows] = _fp4_quantize_values(clipped)
  128. scales[group_idx, :valid_rows] = scale.squeeze(-1)
  129. packed = pack_grouped_fp4_values(quantized)
  130. swizzled = swizzle_block_scale(scales.to(torch.float8_e4m3fn))
  131. scale_view = as_grouped_scale_view(swizzled.view(torch.uint8), rows, cols)
  132. return packed, scale_view
  133. def silu_mul_quantize_grouped_nvfp4_torch(
  134. input_tensor: torch.Tensor,
  135. row_counts: torch.Tensor,
  136. global_scale: torch.Tensor,
  137. ) -> tuple[torch.Tensor, torch.Tensor]:
  138. cols = input_tensor.shape[-1] // 2
  139. left = input_tensor[..., :cols].float()
  140. right = input_tensor[..., cols:].float()
  141. activated = (F.silu(left) * right).to(input_tensor.dtype).to(torch.float32)
  142. return quantize_grouped_nvfp4_torch(activated, row_counts, global_scale)
  143. def relu2_quantize_grouped_nvfp4_torch(
  144. input_tensor: torch.Tensor,
  145. row_counts: torch.Tensor,
  146. global_scale: torch.Tensor,
  147. ) -> tuple[torch.Tensor, torch.Tensor]:
  148. """Pure-Torch grouped NVFP4 quantization for ReLU^2 FC1 outputs."""
  149. activated = torch.square(F.relu(input_tensor.float()))
  150. activated = activated.to(input_tensor.dtype).to(torch.float32)
  151. return quantize_grouped_nvfp4_torch(activated, row_counts, global_scale)
  152. MX_SF_VEC_SIZE = 32 # Elements per UE8M0 scale block (MXFP8 w4a8 activations)
  153. _INV_FLOAT8_E4M3_MAX = 1.0 / FLOAT8_E4M3_MAX
  154. def pow2_ceil_ue8m0_torch(scale: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
  155. """Bit-exact Torch replica of the ``pow2_ceil_ue8m0`` device intrinsic.
  156. Rounds positive fp32 scales UP to a power of two by bumping the exponent
  157. whenever the mantissa is nonzero. Returns ``(rounded_fp32, ue8m0_u8)``;
  158. a zero scale maps to (0.0, byte 0).
  159. """
  160. bits = scale.to(torch.float32).contiguous().view(torch.int32)
  161. mant = bits & 0x007FFFFF
  162. bumped = torch.where(mant != 0, (bits + 0x00800000) & 0x7F800000, bits)
  163. rounded = bumped.view(torch.float32)
  164. byte = ((bumped >> 23) & 0xFF).to(torch.uint8)
  165. return rounded, byte
  166. def _ue8m0_output_scale_torch(byte: torch.Tensor) -> torch.Tensor:
  167. """Torch replica of ``ue8m0_to_output_scale``: 2^(127-byte), 0 for byte 0."""
  168. inv_bits = (254 - byte.to(torch.int32)).clamp(min=0) << 23
  169. inv = inv_bits.view(torch.float32)
  170. return torch.where(byte == 0, torch.zeros_like(inv), inv)
  171. def quant_dequant_mxfp8_torch(x: torch.Tensor) -> torch.Tensor:
  172. """Per-32-block MXFP8 quantize-dequantize roundtrip (oracle helper).
  173. Matches the in-kernel ``quantize_block_fp8_mx`` numerics bit-for-bit:
  174. UE8M0 ceil block scale, E4M3 RN-saturating payload.
  175. """
  176. orig_shape = x.shape
  177. cols = orig_shape[-1]
  178. if cols % MX_SF_VEC_SIZE != 0:
  179. raise ValueError(f"last dim must be divisible by {MX_SF_VEC_SIZE}, got {cols}")
  180. blocked = x.to(torch.float32).reshape(-1, cols // MX_SF_VEC_SIZE, MX_SF_VEC_SIZE)
  181. block_max = blocked.abs().amax(dim=-1, keepdim=True)
  182. rounded, byte = pow2_ceil_ue8m0_torch(block_max * _INV_FLOAT8_E4M3_MAX)
  183. inv = _ue8m0_output_scale_torch(byte)
  184. payload = (
  185. (blocked * inv)
  186. .clamp(-FLOAT8_E4M3_MAX, FLOAT8_E4M3_MAX)
  187. .to(torch.float8_e4m3fn)
  188. .to(torch.float32)
  189. )
  190. return (payload * rounded).reshape(orig_shape)
  191. def quantize_grouped_mxfp8_torch(
  192. input_tensor: torch.Tensor,
  193. row_counts: torch.Tensor,
  194. ) -> tuple[torch.Tensor, torch.Tensor]:
  195. """Pure-Torch grouped MXFP8 quantization (no global scale).
  196. Mirrors ``quantize_grouped_nvfp4_torch`` for the w4a8 activation path:
  197. returns ``(payload, scale_view)`` where ``payload`` is E4M3 bytes in
  198. ``[R, C, G]`` order and ``scale_view`` is the swizzled UE8M0 grouped
  199. view (vec32 columns).
  200. """
  201. num_groups, rows, cols = input_tensor.shape
  202. if cols % MX_SF_VEC_SIZE != 0:
  203. raise ValueError(f"cols must be divisible by {MX_SF_VEC_SIZE}, got {cols}")
  204. payload = torch.zeros((num_groups, rows, cols), dtype=torch.uint8, device=input_tensor.device)
  205. scales = torch.zeros(
  206. (num_groups, rows, cols // MX_SF_VEC_SIZE), dtype=torch.uint8, device=input_tensor.device
  207. )
  208. for group_idx in range(num_groups):
  209. valid_rows = int(row_counts[group_idx].item())
  210. if valid_rows == 0:
  211. continue
  212. x = input_tensor[group_idx, :valid_rows].float()
  213. blocked = x.view(valid_rows, cols // MX_SF_VEC_SIZE, MX_SF_VEC_SIZE)
  214. block_max = blocked.abs().amax(dim=-1, keepdim=True)
  215. rounded, byte = pow2_ceil_ue8m0_torch(block_max * _INV_FLOAT8_E4M3_MAX)
  216. inv = _ue8m0_output_scale_torch(byte)
  217. q = (
  218. (blocked * inv)
  219. .clamp(-FLOAT8_E4M3_MAX, FLOAT8_E4M3_MAX)
  220. .to(torch.float8_e4m3fn)
  221. .view(torch.uint8)
  222. .view(valid_rows, cols)
  223. )
  224. payload[group_idx, :valid_rows] = q
  225. scales[group_idx, :valid_rows] = byte.squeeze(-1)
  226. payload_view = payload.permute(1, 2, 0).contiguous()
  227. swizzled = swizzle_block_scale(scales)
  228. scale_view = as_grouped_scale_view_mx(swizzled, rows, cols)
  229. return payload_view, scale_view
  230. def silu_mul_quantize_grouped_mxfp8_torch(
  231. input_tensor: torch.Tensor,
  232. row_counts: torch.Tensor,
  233. ) -> tuple[torch.Tensor, torch.Tensor]:
  234. """SiLU(left)*right then grouped MXFP8 quantization (w4a8 FC2 input)."""
  235. cols = input_tensor.shape[-1] // 2
  236. left = input_tensor[..., :cols].float()
  237. right = input_tensor[..., cols:].float()
  238. activated = (F.silu(left) * right).to(input_tensor.dtype).to(torch.float32)
  239. return quantize_grouped_mxfp8_torch(activated, row_counts)
  240. def relu2_quantize_grouped_mxfp8_torch(
  241. input_tensor: torch.Tensor,
  242. row_counts: torch.Tensor,
  243. ) -> tuple[torch.Tensor, torch.Tensor]:
  244. """ReLU^2 then grouped MXFP8 quantization (w4a8 FC2 input, non-gated)."""
  245. activated = torch.square(F.relu(input_tensor.float()))
  246. activated = activated.to(input_tensor.dtype).to(torch.float32)
  247. return quantize_grouped_mxfp8_torch(activated, row_counts)
  248. # =============================================================================
  249. # Architecture Detection
  250. # =============================================================================
  251. @functools.lru_cache(maxsize=16)
  252. def get_sm_version(device: int | torch.device | str | None = None) -> int:
  253. """Get the SM version of a CUDA device.
  254. Args:
  255. device: CUDA device to query. Can be an int (device index), torch.device,
  256. device string (e.g., 'cuda:0'), or None to use current device.
  257. Returns:
  258. SM version as an integer (e.g., 120 on the current target).
  259. """
  260. if not torch.cuda.is_available():
  261. return 80
  262. if device is None:
  263. device = torch.cuda.current_device()
  264. props = torch.cuda.get_device_properties(device)
  265. return props.major * 10 + props.minor
  266. # =============================================================================
  267. # PTX Intrinsics - Cluster Operations
  268. # =============================================================================
  269. @dsl_user_op
  270. def set_block_rank(
  271. smem_ptr: cute.Pointer, peer_cta_rank_in_cluster: Int32, *, loc=None, ip=None
  272. ) -> Int32:
  273. """Map smem pointer to address at another CTA rank in the cluster."""
  274. smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip).ir_value()
  275. return Int32(
  276. llvm.inline_asm(
  277. T.i32(),
  278. [smem_ptr_i32, peer_cta_rank_in_cluster.ir_value()],
  279. "mapa.shared::cluster.u32 $0, $1, $2;",
  280. "=r,r,r",
  281. has_side_effects=False,
  282. is_align_stack=False,
  283. asm_dialect=llvm.AsmDialect.AD_ATT,
  284. )
  285. )
  286. @dsl_user_op
  287. def store_shared_remote(
  288. val: Float32,
  289. smem_ptr: cute.Pointer,
  290. mbar_ptr: cute.Pointer,
  291. peer_cta_rank_in_cluster: Int32,
  292. *,
  293. loc=None,
  294. ip=None,
  295. ) -> None:
  296. """Store Float32 value to shared memory on a remote CTA in the cluster."""
  297. remote_smem_ptr_i32 = set_block_rank(
  298. smem_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
  299. ).ir_value()
  300. remote_mbar_ptr_i32 = set_block_rank(
  301. mbar_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
  302. ).ir_value()
  303. llvm.inline_asm(
  304. None,
  305. [remote_smem_ptr_i32, val.ir_value(loc=loc, ip=ip), remote_mbar_ptr_i32],
  306. "st.async.shared::cluster.mbarrier::complete_tx::bytes.f32 [$0], $1, [$2];",
  307. "r,f,r",
  308. has_side_effects=True,
  309. is_align_stack=False,
  310. asm_dialect=llvm.AsmDialect.AD_ATT,
  311. )
  312. @dsl_user_op
  313. def elem_pointer(x: cute.Tensor, coord, *, loc=None, ip=None) -> cute.Pointer:
  314. """Get pointer to element at coordinate in tensor."""
  315. return x.iterator + cute.crd2idx(coord, x.layout, loc=loc, ip=ip)
  316. # =============================================================================
  317. # PTX Intrinsics - 128-bit Vectorized Global Loads/Stores
  318. # =============================================================================
  319. @dsl_user_op
  320. def ld_global_v4_u32(
  321. base_ptr: Int64, *, loc=None, ip=None
  322. ) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
  323. """Load 128 bits (4 x uint32) from global memory."""
  324. result = llvm.inline_asm(
  325. llvm.StructType.get_literal([T.i32(), T.i32(), T.i32(), T.i32()]),
  326. [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
  327. "ld.global.v4.u32 {$0, $1, $2, $3}, [$4];",
  328. "=r,=r,=r,=r,l",
  329. has_side_effects=True,
  330. is_align_stack=False,
  331. asm_dialect=llvm.AsmDialect.AD_ATT,
  332. loc=loc,
  333. ip=ip,
  334. )
  335. v0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  336. v1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  337. v2 = llvm.extractvalue(T.i32(), result, [2], loc=loc, ip=ip)
  338. v3 = llvm.extractvalue(T.i32(), result, [3], loc=loc, ip=ip)
  339. return Uint32(v0), Uint32(v1), Uint32(v2), Uint32(v3)
  340. # =============================================================================
  341. # PTX Intrinsics - Non-Coherent Global Loads
  342. # =============================================================================
  343. @dsl_user_op
  344. def ld_global_nc_u32(base_ptr: Int64, *, loc=None, ip=None) -> Uint32:
  345. """Load 32 bits from global memory via non-coherent cache (.nc)."""
  346. return Uint32(
  347. llvm.inline_asm(
  348. T.i32(),
  349. [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
  350. "ld.global.nc.u32 $0, [$1];",
  351. "=r,l",
  352. has_side_effects=False,
  353. is_align_stack=False,
  354. asm_dialect=llvm.AsmDialect.AD_ATT,
  355. loc=loc,
  356. ip=ip,
  357. )
  358. )
  359. @dsl_user_op
  360. def ld_global_b16(base_ptr: Int64, *, loc=None, ip=None) -> Uint32:
  361. """Load one 16-bit global-memory payload into a 32-bit register.
  362. PTX permits a bit-typed load into a wider bit register. This is the form
  363. emitted by FlashInfer for the DSV4 RoPE gather and avoids rounding the
  364. address down to a word followed by a runtime halfword select.
  365. """
  366. return Uint32(
  367. llvm.inline_asm(
  368. T.i32(),
  369. [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
  370. "ld.global.b16 $0, [$1];",
  371. "=r,l",
  372. has_side_effects=False,
  373. is_align_stack=False,
  374. asm_dialect=llvm.AsmDialect.AD_ATT,
  375. loc=loc,
  376. ip=ip,
  377. )
  378. )
  379. @dsl_user_op
  380. def prefetch_global_l2(base_ptr: Int64, *, loc=None, ip=None) -> None:
  381. """Prefetch a global memory line into L2."""
  382. llvm.inline_asm(
  383. None,
  384. [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
  385. "prefetch.global.L2 [$0];",
  386. "l",
  387. has_side_effects=True,
  388. is_align_stack=False,
  389. asm_dialect=llvm.AsmDialect.AD_ATT,
  390. loc=loc,
  391. ip=ip,
  392. )
  393. @dsl_user_op
  394. def ld_global_nc_v2_u32(
  395. base_ptr: Int64, *, loc=None, ip=None
  396. ) -> Tuple[Uint32, Uint32]:
  397. """Load 64 bits (2 x uint32) from global memory via non-coherent cache (.nc)."""
  398. result = llvm.inline_asm(
  399. llvm.StructType.get_literal([T.i32(), T.i32()]),
  400. [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
  401. "ld.global.nc.v2.u32 {$0, $1}, [$2];",
  402. "=r,=r,l",
  403. has_side_effects=True,
  404. is_align_stack=False,
  405. asm_dialect=llvm.AsmDialect.AD_ATT,
  406. loc=loc,
  407. ip=ip,
  408. )
  409. return (
  410. Uint32(llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)),
  411. Uint32(llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)),
  412. )
  413. @dsl_user_op
  414. def ld_global_nc_v4_u32(
  415. base_ptr: Int64, *, loc=None, ip=None
  416. ) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
  417. """Load 128 bits (4 x uint32) from global memory via non-coherent cache (.nc)."""
  418. result = llvm.inline_asm(
  419. llvm.StructType.get_literal([T.i32(), T.i32(), T.i32(), T.i32()]),
  420. [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
  421. "ld.global.nc.v4.u32 {$0, $1, $2, $3}, [$4];",
  422. "=r,=r,=r,=r,l",
  423. has_side_effects=True,
  424. is_align_stack=False,
  425. asm_dialect=llvm.AsmDialect.AD_ATT,
  426. loc=loc,
  427. ip=ip,
  428. )
  429. return (
  430. Uint32(llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)),
  431. Uint32(llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)),
  432. Uint32(llvm.extractvalue(T.i32(), result, [2], loc=loc, ip=ip)),
  433. Uint32(llvm.extractvalue(T.i32(), result, [3], loc=loc, ip=ip)),
  434. )
  435. @dsl_user_op
  436. def st_global_u64(base_ptr: Int64, value: Uint64, *, loc=None, ip=None):
  437. """Store 64 bits to global memory."""
  438. llvm.inline_asm(
  439. None,
  440. [
  441. Int64(base_ptr).ir_value(loc=loc, ip=ip),
  442. Uint64(value).ir_value(loc=loc, ip=ip),
  443. ],
  444. "st.global.u64 [$0], $1;",
  445. "l,l",
  446. has_side_effects=True,
  447. is_align_stack=False,
  448. asm_dialect=llvm.AsmDialect.AD_ATT,
  449. )
  450. @dsl_user_op
  451. def st_global_u8(base_ptr: Int64, value: Uint8, *, loc=None, ip=None):
  452. """Store 8 bits to global memory."""
  453. llvm.inline_asm(
  454. None,
  455. [
  456. Int64(base_ptr).ir_value(loc=loc, ip=ip),
  457. Uint8(value).ir_value(loc=loc, ip=ip),
  458. ],
  459. "st.global.u8 [$0], $1;",
  460. "l,r",
  461. has_side_effects=True,
  462. is_align_stack=False,
  463. asm_dialect=llvm.AsmDialect.AD_ATT,
  464. )
  465. @dsl_user_op
  466. def st_global_f32(base_ptr: Int64, value: Float32, *, loc=None, ip=None):
  467. """Store 32-bit float to global memory."""
  468. llvm.inline_asm(
  469. None,
  470. [
  471. Int64(base_ptr).ir_value(loc=loc, ip=ip),
  472. Float32(value).ir_value(loc=loc, ip=ip),
  473. ],
  474. "st.global.f32 [$0], $1;",
  475. "l,f",
  476. has_side_effects=True,
  477. is_align_stack=False,
  478. asm_dialect=llvm.AsmDialect.AD_ATT,
  479. )
  480. @dsl_user_op
  481. def st_global_v2_f32(base_ptr: Int64, v0: Float32, v1: Float32, *, loc=None, ip=None):
  482. """Store 64 bits (2 x float32) to global memory."""
  483. llvm.inline_asm(
  484. None,
  485. [
  486. Int64(base_ptr).ir_value(loc=loc, ip=ip),
  487. Float32(v0).ir_value(loc=loc, ip=ip),
  488. Float32(v1).ir_value(loc=loc, ip=ip),
  489. ],
  490. "st.global.v2.f32 [$0], {$1, $2};",
  491. "l,f,f",
  492. has_side_effects=True,
  493. is_align_stack=False,
  494. asm_dialect=llvm.AsmDialect.AD_ATT,
  495. )
  496. @dsl_user_op
  497. def st_global_v4_f32(
  498. base_ptr: Int64,
  499. v0: Float32,
  500. v1: Float32,
  501. v2: Float32,
  502. v3: Float32,
  503. *,
  504. loc=None,
  505. ip=None,
  506. ):
  507. """Store 128 bits (4 x float32) to global memory."""
  508. llvm.inline_asm(
  509. None,
  510. [
  511. Int64(base_ptr).ir_value(loc=loc, ip=ip),
  512. Float32(v0).ir_value(loc=loc, ip=ip),
  513. Float32(v1).ir_value(loc=loc, ip=ip),
  514. Float32(v2).ir_value(loc=loc, ip=ip),
  515. Float32(v3).ir_value(loc=loc, ip=ip),
  516. ],
  517. "st.global.v4.f32 [$0], {$1, $2, $3, $4};",
  518. "l,f,f,f,f",
  519. has_side_effects=True,
  520. is_align_stack=False,
  521. asm_dialect=llvm.AsmDialect.AD_ATT,
  522. )
  523. @dsl_user_op
  524. def ld_global_v4_f32(
  525. base_ptr: Int64,
  526. *,
  527. loc=None,
  528. ip=None,
  529. ) -> Tuple[Float32, Float32, Float32, Float32]:
  530. """Load 128 bits (4 x float32) from global memory."""
  531. result = llvm.inline_asm(
  532. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  533. [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
  534. "ld.global.v4.f32 {$0, $1, $2, $3}, [$4];",
  535. "=f,=f,=f,=f,l",
  536. has_side_effects=True,
  537. is_align_stack=False,
  538. asm_dialect=llvm.AsmDialect.AD_ATT,
  539. loc=loc,
  540. ip=ip,
  541. )
  542. r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  543. r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
  544. r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
  545. r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
  546. return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
  547. @dsl_user_op
  548. def st_global_v4_u32(
  549. base_ptr: Int64,
  550. v0: Uint32,
  551. v1: Uint32,
  552. v2: Uint32,
  553. v3: Uint32,
  554. *,
  555. loc=None,
  556. ip=None,
  557. ):
  558. """Store 128 bits (4 x uint32) to global memory."""
  559. llvm.inline_asm(
  560. None,
  561. [
  562. Int64(base_ptr).ir_value(loc=loc, ip=ip),
  563. Uint32(v0).ir_value(loc=loc, ip=ip),
  564. Uint32(v1).ir_value(loc=loc, ip=ip),
  565. Uint32(v2).ir_value(loc=loc, ip=ip),
  566. Uint32(v3).ir_value(loc=loc, ip=ip),
  567. ],
  568. "st.global.v4.u32 [$0], {$1, $2, $3, $4};",
  569. "l,r,r,r,r",
  570. has_side_effects=True,
  571. is_align_stack=False,
  572. asm_dialect=llvm.AsmDialect.AD_ATT,
  573. loc=loc,
  574. ip=ip,
  575. )
  576. @dsl_user_op
  577. def smem_ptr_to_addr(ptr: cute.Pointer, *, loc=None, ip=None) -> Int32:
  578. """Convert a generic/smem pointer to a shared-memory u32 address for PTX."""
  579. generic_addr = llvm.ptrtoint(T.i64(), ptr.llvm_ptr, loc=loc, ip=ip)
  580. return Int32(
  581. llvm.inline_asm(
  582. T.i32(),
  583. [generic_addr],
  584. "cvta.to.shared.u32 $0, $1;",
  585. "=r,l",
  586. has_side_effects=False,
  587. is_align_stack=False,
  588. asm_dialect=llvm.AsmDialect.AD_ATT,
  589. )
  590. )
  591. @dsl_user_op
  592. def shared_ptr_to_u32(ptr: cute.Pointer, *, loc=None, ip=None) -> Int32:
  593. """Convert an address-space-3 shared-memory pointer to a u32 address."""
  594. return Int32(llvm.ptrtoint(T.i32(), ptr.llvm_ptr, loc=loc, ip=ip))
  595. @dsl_user_op
  596. def ldmatrix_m8n8x4_b16(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
  597. """Issue `ldmatrix.sync.aligned.m8n8.x4.shared.b16` from a shared-memory byte address."""
  598. result = llvm.inline_asm(
  599. llvm.StructType.get_literal([T.i32(), T.i32(), T.i32(), T.i32()]),
  600. [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
  601. "ldmatrix.sync.aligned.m8n8.x4.shared.b16 {$0, $1, $2, $3}, [$4];",
  602. "=r,=r,=r,=r,r",
  603. has_side_effects=True,
  604. is_align_stack=False,
  605. asm_dialect=llvm.AsmDialect.AD_ATT,
  606. loc=loc,
  607. ip=ip,
  608. )
  609. r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  610. r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  611. r2 = llvm.extractvalue(T.i32(), result, [2], loc=loc, ip=ip)
  612. r3 = llvm.extractvalue(T.i32(), result, [3], loc=loc, ip=ip)
  613. return Uint32(r0), Uint32(r1), Uint32(r2), Uint32(r3)
  614. @dsl_user_op
  615. def ld_shared_u16_offset(
  616. smem_addr: Int32,
  617. byte_offset: int,
  618. *,
  619. loc=None,
  620. ip=None,
  621. ) -> Uint16:
  622. """Load a shared halfword at a compile-time byte displacement.
  623. Keeping the result as i16 lets NVVM select a PTX ``%rs`` operand and
  624. avoids a redundant ``cvt.u16.u32`` before packed FP8 decode. Embedding the
  625. displacement also avoids materializing one address register per unrolled
  626. K step.
  627. """
  628. offset = int(byte_offset)
  629. address = f"[$1+{offset}]" if offset else "[$1]"
  630. return Uint16(
  631. llvm.inline_asm(
  632. T.i16(),
  633. [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
  634. f"ld.shared.u16 $0, {address};",
  635. "=h,r",
  636. has_side_effects=False,
  637. is_align_stack=False,
  638. asm_dialect=llvm.AsmDialect.AD_ATT,
  639. loc=loc,
  640. ip=ip,
  641. )
  642. )
  643. @dsl_user_op
  644. def ld_shared_u8_offset(
  645. smem_addr: Int32,
  646. byte_offset: int,
  647. *,
  648. loc=None,
  649. ip=None,
  650. ) -> Uint32:
  651. """Load one shared byte at a compile-time displacement, zero-extended.
  652. This is the native ``ld.shared.u8`` form used for UE8M0 scale tables. A
  653. common row base plus an immediate displacement keeps the unrolled scale
  654. cache free of word-alignment, shift, and mask instructions.
  655. """
  656. offset = int(byte_offset)
  657. address = f"[$1+{offset}]" if offset else "[$1]"
  658. return Uint32(
  659. llvm.inline_asm(
  660. T.i32(),
  661. [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
  662. f"ld.shared.u8 $0, {address};",
  663. "=r,r",
  664. has_side_effects=False,
  665. is_align_stack=False,
  666. asm_dialect=llvm.AsmDialect.AD_ATT,
  667. loc=loc,
  668. ip=ip,
  669. )
  670. )
  671. @dsl_user_op
  672. def ldmatrix_m8n8x2_b16(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
  673. """Issue `ldmatrix.sync.aligned.m8n8.x2.shared.b16` from a shared-memory byte address."""
  674. result = llvm.inline_asm(
  675. llvm.StructType.get_literal([T.i32(), T.i32()]),
  676. [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
  677. "ldmatrix.sync.aligned.m8n8.x2.shared.b16 {$0, $1}, [$2];",
  678. "=r,=r,r",
  679. has_side_effects=True,
  680. is_align_stack=False,
  681. asm_dialect=llvm.AsmDialect.AD_ATT,
  682. loc=loc,
  683. ip=ip,
  684. )
  685. r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  686. r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  687. return Uint32(r0), Uint32(r1)
  688. @dsl_user_op
  689. def ldmatrix_m8n8x4_left_half_b16(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
  690. """Issue `ldmatrix.sync.aligned.m8n8.x4.shared.b16` and return the left half fragment pair."""
  691. result = llvm.inline_asm(
  692. llvm.StructType.get_literal([T.i32(), T.i32()]),
  693. [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
  694. "ldmatrix.sync.aligned.m8n8.x4.shared.b16 {$0, _, $1, _}, [$2];",
  695. "=r,=r,r",
  696. has_side_effects=True,
  697. is_align_stack=False,
  698. asm_dialect=llvm.AsmDialect.AD_ATT,
  699. loc=loc,
  700. ip=ip,
  701. )
  702. r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  703. r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  704. return Uint32(r0), Uint32(r1)
  705. @dsl_user_op
  706. def ldmatrix_m8n8x4_right_half_b16(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
  707. """Issue `ldmatrix.sync.aligned.m8n8.x4.shared.b16` and return the right half fragment pair."""
  708. result = llvm.inline_asm(
  709. llvm.StructType.get_literal([T.i32(), T.i32()]),
  710. [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
  711. "ldmatrix.sync.aligned.m8n8.x4.shared.b16 {_, $0, _, $1}, [$2];",
  712. "=r,=r,r",
  713. has_side_effects=True,
  714. is_align_stack=False,
  715. asm_dialect=llvm.AsmDialect.AD_ATT,
  716. loc=loc,
  717. ip=ip,
  718. )
  719. r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  720. r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  721. return Uint32(r0), Uint32(r1)
  722. @dsl_user_op
  723. def ldmatrix_m8n8x4_trans_b16(
  724. smem_addr: Int32, *, loc=None, ip=None
  725. ) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
  726. """Issue `ldmatrix.sync.aligned.trans.m8n8.x4.shared.b16` from a shared-memory byte address."""
  727. result = llvm.inline_asm(
  728. llvm.StructType.get_literal([T.i32(), T.i32(), T.i32(), T.i32()]),
  729. [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
  730. "ldmatrix.sync.aligned.trans.m8n8.x4.shared.b16 {$0, $1, $2, $3}, [$4];",
  731. "=r,=r,=r,=r,r",
  732. has_side_effects=True,
  733. is_align_stack=False,
  734. asm_dialect=llvm.AsmDialect.AD_ATT,
  735. loc=loc,
  736. ip=ip,
  737. )
  738. r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  739. r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  740. r2 = llvm.extractvalue(T.i32(), result, [2], loc=loc, ip=ip)
  741. r3 = llvm.extractvalue(T.i32(), result, [3], loc=loc, ip=ip)
  742. return Uint32(r0), Uint32(r1), Uint32(r2), Uint32(r3)
  743. @dsl_user_op
  744. def ldmatrix_m8n8x4_trans_left_half_b16(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
  745. """Issue transposed `ldmatrix` and return the left half fragment pair."""
  746. result = llvm.inline_asm(
  747. llvm.StructType.get_literal([T.i32(), T.i32()]),
  748. [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
  749. "ldmatrix.sync.aligned.trans.m8n8.x4.shared.b16 {$0, $1, _, _}, [$2];",
  750. "=r,=r,r",
  751. has_side_effects=True,
  752. is_align_stack=False,
  753. asm_dialect=llvm.AsmDialect.AD_ATT,
  754. loc=loc,
  755. ip=ip,
  756. )
  757. r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  758. r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  759. return Uint32(r0), Uint32(r1)
  760. @dsl_user_op
  761. def ldmatrix_m8n8x4_trans_right_half_b16(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
  762. """Issue transposed `ldmatrix` and return the right half fragment pair."""
  763. result = llvm.inline_asm(
  764. llvm.StructType.get_literal([T.i32(), T.i32()]),
  765. [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
  766. "ldmatrix.sync.aligned.trans.m8n8.x4.shared.b16 {_, _, $0, $1}, [$2];",
  767. "=r,=r,r",
  768. has_side_effects=True,
  769. is_align_stack=False,
  770. asm_dialect=llvm.AsmDialect.AD_ATT,
  771. loc=loc,
  772. ip=ip,
  773. )
  774. r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  775. r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  776. return Uint32(r0), Uint32(r1)
  777. @dsl_user_op
  778. def ld_shared_v4_u32(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
  779. """Load 128 bits (4 x uint32) from shared memory. smem_addr is a u32 shared-memory address."""
  780. result = llvm.inline_asm(
  781. llvm.StructType.get_literal([T.i32(), T.i32(), T.i32(), T.i32()]),
  782. [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
  783. "ld.shared.v4.u32 {$0, $1, $2, $3}, [$4];",
  784. "=r,=r,=r,=r,r",
  785. has_side_effects=True,
  786. is_align_stack=False,
  787. asm_dialect=llvm.AsmDialect.AD_ATT,
  788. loc=loc,
  789. ip=ip,
  790. )
  791. r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  792. r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  793. r2 = llvm.extractvalue(T.i32(), result, [2], loc=loc, ip=ip)
  794. r3 = llvm.extractvalue(T.i32(), result, [3], loc=loc, ip=ip)
  795. return Uint32(r0), Uint32(r1), Uint32(r2), Uint32(r3)
  796. @dsl_user_op
  797. def ld_shared_v2_u32(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
  798. """Load 64 bits (2 x uint32) from shared memory."""
  799. result = llvm.inline_asm(
  800. llvm.StructType.get_literal([T.i32(), T.i32()]),
  801. [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
  802. "ld.shared.v2.u32 {$0, $1}, [$2];",
  803. "=r,=r,r",
  804. has_side_effects=True,
  805. is_align_stack=False,
  806. asm_dialect=llvm.AsmDialect.AD_ATT,
  807. loc=loc,
  808. ip=ip,
  809. )
  810. r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  811. r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  812. return Uint32(r0), Uint32(r1)
  813. @dsl_user_op
  814. def st_shared_u64(smem_addr: Int32, value: Uint64, *, loc=None, ip=None):
  815. """Store 64 bits to shared memory. smem_addr is a u32 shared-memory address."""
  816. llvm.inline_asm(
  817. None,
  818. [
  819. Int32(smem_addr).ir_value(loc=loc, ip=ip),
  820. Uint64(value).ir_value(loc=loc, ip=ip),
  821. ],
  822. "st.shared.u64 [$0], $1;",
  823. "r,l",
  824. has_side_effects=True,
  825. is_align_stack=False,
  826. asm_dialect=llvm.AsmDialect.AD_ATT,
  827. )
  828. @dsl_user_op
  829. def st_shared_v4_u32(
  830. smem_addr: Int32,
  831. v0: Uint32,
  832. v1: Uint32,
  833. v2: Uint32,
  834. v3: Uint32,
  835. *,
  836. loc=None,
  837. ip=None,
  838. ):
  839. """Store 128 bits (4 x uint32) to shared memory. smem_addr is a u32 shared-memory address."""
  840. llvm.inline_asm(
  841. None,
  842. [
  843. Int32(smem_addr).ir_value(loc=loc, ip=ip),
  844. Uint32(v0).ir_value(loc=loc, ip=ip),
  845. Uint32(v1).ir_value(loc=loc, ip=ip),
  846. Uint32(v2).ir_value(loc=loc, ip=ip),
  847. Uint32(v3).ir_value(loc=loc, ip=ip),
  848. ],
  849. "st.shared.v4.u32 [$0], {$1, $2, $3, $4};",
  850. "r,r,r,r,r",
  851. has_side_effects=True,
  852. is_align_stack=False,
  853. asm_dialect=llvm.AsmDialect.AD_ATT,
  854. )
  855. @dsl_user_op
  856. def cp_async_bulk_g2s_mbar(
  857. smem_dst_u32: Int32,
  858. gmem_src_i64: Int64,
  859. nbytes: Int32,
  860. mbar_u32: Int32,
  861. *,
  862. loc=None,
  863. ip=None,
  864. ) -> None:
  865. """Emit cp.async.bulk.shared::cta.global.mbarrier::complete_tx::bytes.
  866. CTA-scope bulk g2s gather completing on a CTA-scope mbarrier. Mirrors
  867. FlashInfer cp_async_bulk_g2s (sparse_mla_sm120/arch/cp_async.cuh:64-72)
  868. exactly:
  869. [$0]=smem dst (u32 shared addr), [$1]=gmem src (i64 generic),
  870. $2=bytes (u32), [$3]=mbar (u32 shared addr).
  871. Used by the IO warp of the DSV4/GLM sparse-MLA decode port to gather a
  872. whole token row (e.g. 656B or 576B+8B) in one transaction.
  873. """
  874. llvm.inline_asm(
  875. None,
  876. [
  877. Int32(smem_dst_u32).ir_value(loc=loc, ip=ip),
  878. Int64(gmem_src_i64).ir_value(loc=loc, ip=ip),
  879. Int32(nbytes).ir_value(loc=loc, ip=ip),
  880. Int32(mbar_u32).ir_value(loc=loc, ip=ip),
  881. ],
  882. "cp.async.bulk.shared::cta.global.mbarrier::complete_tx::bytes"
  883. " [$0], [$1], $2, [$3];",
  884. "r,l,r,r",
  885. has_side_effects=True,
  886. is_align_stack=False,
  887. asm_dialect=llvm.AsmDialect.AD_ATT,
  888. loc=loc,
  889. ip=ip,
  890. )
  891. @dsl_user_op
  892. def create_l2_evict_first_policy(*, loc=None, ip=None) -> Uint64:
  893. """Create an L2 evict-first policy for one-pass streaming cache rows."""
  894. return Uint64(
  895. llvm.inline_asm(
  896. T.i64(),
  897. [],
  898. "createpolicy.fractional.L2::evict_first.b64 $0, 1.0;",
  899. "=l",
  900. has_side_effects=True,
  901. is_align_stack=False,
  902. asm_dialect=llvm.AsmDialect.AD_ATT,
  903. loc=loc,
  904. ip=ip,
  905. )
  906. )
  907. @dsl_user_op
  908. def cp_async_bulk_g2s_mbar_l2hint(
  909. smem_dst_u32: Int32,
  910. gmem_src_i64: Int64,
  911. nbytes: Int32,
  912. mbar_u32: Int32,
  913. cache_policy: Uint64,
  914. *,
  915. loc=None,
  916. ip=None,
  917. ) -> None:
  918. """Bulk global-to-shared copy with an explicit L2 cache policy."""
  919. llvm.inline_asm(
  920. None,
  921. [
  922. Int32(smem_dst_u32).ir_value(loc=loc, ip=ip),
  923. Int64(gmem_src_i64).ir_value(loc=loc, ip=ip),
  924. Int32(nbytes).ir_value(loc=loc, ip=ip),
  925. Int32(mbar_u32).ir_value(loc=loc, ip=ip),
  926. Uint64(cache_policy).ir_value(loc=loc, ip=ip),
  927. ],
  928. "cp.async.bulk.shared::cta.global.mbarrier::complete_tx::bytes.L2::cache_hint"
  929. " [$0], [$1], $2, [$3], $4;",
  930. "r,l,r,r,l",
  931. has_side_effects=True,
  932. is_align_stack=False,
  933. asm_dialect=llvm.AsmDialect.AD_ATT,
  934. loc=loc,
  935. ip=ip,
  936. )
  937. @dsl_user_op
  938. def st_shared_u8(smem_addr: Int32, value: Uint8, *, loc=None, ip=None):
  939. """Store 8 bits to shared memory. smem_addr is a u32 shared-memory address."""
  940. llvm.inline_asm(
  941. None,
  942. [
  943. Int32(smem_addr).ir_value(loc=loc, ip=ip),
  944. Uint8(value).ir_value(loc=loc, ip=ip),
  945. ],
  946. "st.shared.u8 [$0], $1;",
  947. "r,r",
  948. has_side_effects=True,
  949. is_align_stack=False,
  950. asm_dialect=llvm.AsmDialect.AD_ATT,
  951. )
  952. @dsl_user_op
  953. def st_shared_u16(smem_addr: Int32, value: Uint32, *, loc=None, ip=None):
  954. """Store the low 16 bits of a register to shared memory."""
  955. llvm.inline_asm(
  956. None,
  957. [
  958. Int32(smem_addr).ir_value(loc=loc, ip=ip),
  959. Uint32(value).ir_value(loc=loc, ip=ip),
  960. ],
  961. "st.shared.u16 [$0], $1;",
  962. "r,r",
  963. has_side_effects=True,
  964. is_align_stack=False,
  965. asm_dialect=llvm.AsmDialect.AD_ATT,
  966. loc=loc,
  967. ip=ip,
  968. )
  969. @dsl_user_op
  970. def quantize_scaled_store_shared_v2_e4m3(
  971. smem_addr: Int32,
  972. scaled_value0: Float32,
  973. scaled_value1: Float32,
  974. inv_scale: Float32,
  975. *,
  976. loc=None,
  977. ip=None,
  978. ):
  979. """Scale, quantize, and vector-store two contiguous E4M3 bytes.
  980. ``scaled_value*`` already include the per-candidate V scale used by the
  981. preceding atomic-max pass. Keeping that multiplication in ordinary SSA
  982. lets LLVM retain/reuse the products across the barrier, as FlashInfer does.
  983. The E4M3 results never widen through ``Uint32`` and are written with the
  984. same half-register ``st.shared.v2.b8`` form.
  985. """
  986. llvm.inline_asm(
  987. None,
  988. [
  989. Int32(smem_addr).ir_value(loc=loc, ip=ip),
  990. Float32(scaled_value0).ir_value(loc=loc, ip=ip),
  991. Float32(scaled_value1).ir_value(loc=loc, ip=ip),
  992. Float32(inv_scale).ir_value(loc=loc, ip=ip),
  993. ],
  994. """
  995. {
  996. .reg .b16 q0, q1;
  997. .reg .f32 zero, tmp;
  998. mov.f32 zero, 0f00000000;
  999. mul.f32 tmp, $1, $3;
  1000. cvt.rn.satfinite.e4m3x2.f32 q0, zero, tmp;
  1001. mul.f32 tmp, $2, $3;
  1002. cvt.rn.satfinite.e4m3x2.f32 q1, zero, tmp;
  1003. st.shared.v2.b8 [$0], {q0, q1};
  1004. }
  1005. """,
  1006. "r,f,f,f",
  1007. has_side_effects=True,
  1008. is_align_stack=False,
  1009. asm_dialect=llvm.AsmDialect.AD_ATT,
  1010. loc=loc,
  1011. ip=ip,
  1012. )
  1013. @dsl_user_op
  1014. def cp_async_u32_shared_global(smem_addr: Int32, gmem_addr: Int64, *, loc=None, ip=None):
  1015. """4-byte `cp.async.ca.shared.global` copy."""
  1016. llvm.inline_asm(
  1017. None,
  1018. [
  1019. Int32(smem_addr).ir_value(loc=loc, ip=ip),
  1020. Int64(gmem_addr).ir_value(loc=loc, ip=ip),
  1021. ],
  1022. "cp.async.ca.shared.global [$0], [$1], 4;",
  1023. "r,l",
  1024. has_side_effects=True,
  1025. is_align_stack=False,
  1026. asm_dialect=llvm.AsmDialect.AD_ATT,
  1027. loc=loc,
  1028. ip=ip,
  1029. )
  1030. @dsl_user_op
  1031. def cp_async_u64_shared_global(smem_addr: Int32, gmem_addr: Int64, *, loc=None, ip=None):
  1032. """8-byte `cp.async.ca.shared.global` copy."""
  1033. llvm.inline_asm(
  1034. None,
  1035. [
  1036. Int32(smem_addr).ir_value(loc=loc, ip=ip),
  1037. Int64(gmem_addr).ir_value(loc=loc, ip=ip),
  1038. ],
  1039. "cp.async.ca.shared.global [$0], [$1], 8;",
  1040. "r,l",
  1041. has_side_effects=True,
  1042. is_align_stack=False,
  1043. asm_dialect=llvm.AsmDialect.AD_ATT,
  1044. loc=loc,
  1045. ip=ip,
  1046. )
  1047. @dsl_user_op
  1048. def cp_async4_shared_global(smem_addr: Int32, gmem_addr: Int64, *, loc=None, ip=None):
  1049. """16-byte `cp.async.cg.shared.global` copy."""
  1050. llvm.inline_asm(
  1051. None,
  1052. [
  1053. Int32(smem_addr).ir_value(loc=loc, ip=ip),
  1054. Int64(gmem_addr).ir_value(loc=loc, ip=ip),
  1055. ],
  1056. "cp.async.cg.shared.global [$0], [$1], 16;",
  1057. "r,l",
  1058. has_side_effects=True,
  1059. is_align_stack=False,
  1060. asm_dialect=llvm.AsmDialect.AD_ATT,
  1061. loc=loc,
  1062. ip=ip,
  1063. )
  1064. @dsl_user_op
  1065. def cp_async4_shared_global_pred(
  1066. smem_addr: Int32, gmem_addr: Int64, pred: Int32, *, loc=None, ip=None
  1067. ):
  1068. """Predicated 16-byte `cp.async.cg.shared.global` copy."""
  1069. llvm.inline_asm(
  1070. None,
  1071. [
  1072. Int32(pred).ir_value(loc=loc, ip=ip),
  1073. Int32(smem_addr).ir_value(loc=loc, ip=ip),
  1074. Int64(gmem_addr).ir_value(loc=loc, ip=ip),
  1075. ],
  1076. "{ .reg .pred p; setp.ne.b32 p, $0, 0; @p cp.async.cg.shared.global [$1], [$2], 16; }",
  1077. "r,r,l",
  1078. has_side_effects=True,
  1079. is_align_stack=False,
  1080. asm_dialect=llvm.AsmDialect.AD_ATT,
  1081. loc=loc,
  1082. ip=ip,
  1083. )
  1084. @dsl_user_op
  1085. def cp_async4_ca_shared_global_pred(
  1086. smem_addr: Int32, gmem_addr: Int64, pred: Int32, *, loc=None, ip=None
  1087. ):
  1088. """Predicated 16-byte `cp.async.ca.shared.global` copy."""
  1089. llvm.inline_asm(
  1090. None,
  1091. [
  1092. Int32(pred).ir_value(loc=loc, ip=ip),
  1093. Int32(smem_addr).ir_value(loc=loc, ip=ip),
  1094. Int64(gmem_addr).ir_value(loc=loc, ip=ip),
  1095. ],
  1096. "{ .reg .pred p; setp.ne.b32 p, $0, 0; @p cp.async.ca.shared.global [$1], [$2], 16; }",
  1097. "r,r,l",
  1098. has_side_effects=True,
  1099. is_align_stack=False,
  1100. asm_dialect=llvm.AsmDialect.AD_ATT,
  1101. loc=loc,
  1102. ip=ip,
  1103. )
  1104. @dsl_user_op
  1105. def get_ptr_as_int64(tensor: cute.Tensor, offset, *, loc=None, ip=None) -> Int64:
  1106. """Get the memory address of tensor[offset] as Int64."""
  1107. elem_ptr = tensor.iterator + offset
  1108. ptr_int = llvm.ptrtoint(T.i64(), elem_ptr.llvm_ptr, loc=loc, ip=ip)
  1109. return Int64(ptr_int)
  1110. # =============================================================================
  1111. # PTX Intrinsics - Global Atomics
  1112. # =============================================================================
  1113. @dsl_user_op
  1114. def atomic_add_global_i32(addr: Int64, val: Int32, *, loc=None, ip=None) -> Int32:
  1115. """Global memory int32 atomic add. Returns old value."""
  1116. return Int32(
  1117. llvm.inline_asm(
  1118. T.i32(),
  1119. [
  1120. Int64(addr).ir_value(loc=loc, ip=ip),
  1121. Int32(val).ir_value(loc=loc, ip=ip),
  1122. ],
  1123. "atom.global.add.s32 $0, [$1], $2;",
  1124. "=r,l,r",
  1125. has_side_effects=True,
  1126. is_align_stack=False,
  1127. asm_dialect=llvm.AsmDialect.AD_ATT,
  1128. )
  1129. )
  1130. @dsl_user_op
  1131. def red_add_global_release_i32(addr: Int64, val: Int32, *, loc=None, ip=None):
  1132. """No-return global int32 add with a GPU-scope release fence."""
  1133. llvm.inline_asm(
  1134. None,
  1135. [
  1136. Int64(addr).ir_value(loc=loc, ip=ip),
  1137. Int32(val).ir_value(loc=loc, ip=ip),
  1138. ],
  1139. "fence.acq_rel.gpu;\n"
  1140. "red.relaxed.gpu.global.add.s32 [$0], $1;",
  1141. "l,r",
  1142. has_side_effects=True,
  1143. is_align_stack=False,
  1144. asm_dialect=llvm.AsmDialect.AD_ATT,
  1145. loc=loc,
  1146. ip=ip,
  1147. )
  1148. @dsl_user_op
  1149. def red_add_global_f32(addr: Int64, val: Float32, *, loc=None, ip=None):
  1150. """No-return global fp32 add reduction (relaxed device scope)."""
  1151. llvm.inline_asm(
  1152. None,
  1153. [
  1154. Int64(addr).ir_value(loc=loc, ip=ip),
  1155. Float32(val).ir_value(loc=loc, ip=ip),
  1156. ],
  1157. "red.global.add.f32 [$0], $1;",
  1158. "l,f",
  1159. has_side_effects=True,
  1160. is_align_stack=False,
  1161. asm_dialect=llvm.AsmDialect.AD_ATT,
  1162. loc=loc,
  1163. ip=ip,
  1164. )
  1165. @dsl_user_op
  1166. def cvt_bf16x2_to_f16x2(packed: Uint32, *, loc=None, ip=None) -> Uint32:
  1167. """Convert a u32 holding two bf16 (lo, hi) into an f16x2 u32 (lo, hi)."""
  1168. return Uint32(
  1169. llvm.inline_asm(
  1170. T.i32(),
  1171. [Uint32(packed).ir_value(loc=loc, ip=ip)],
  1172. """
  1173. {
  1174. .reg .b16 blo, bhi, hlo, hhi;
  1175. .reg .f32 flo, fhi;
  1176. mov.b32 {blo, bhi}, $1;
  1177. cvt.f32.bf16 flo, blo;
  1178. cvt.f32.bf16 fhi, bhi;
  1179. cvt.rn.f16.f32 hlo, flo;
  1180. cvt.rn.f16.f32 hhi, fhi;
  1181. mov.b32 $0, {hlo, hhi};
  1182. }
  1183. """,
  1184. "=r,r",
  1185. has_side_effects=False,
  1186. is_align_stack=False,
  1187. asm_dialect=llvm.AsmDialect.AD_ATT,
  1188. loc=loc,
  1189. ip=ip,
  1190. )
  1191. )
  1192. @dsl_user_op
  1193. def red_add_global_bf16x2(addr: Int64, packed: Uint32, *, loc=None, ip=None):
  1194. """No-return global atomic add of a packed bf16x2 value (2 contiguous bf16).
  1195. Used by the W4A16 TC-decode FC2 epilogue to fold the per-route partial
  1196. outputs into the per-token output without a separate top-k-sum launch.
  1197. The address must be 4-byte aligned and cover two consecutive bf16 lanes.
  1198. """
  1199. llvm.inline_asm(
  1200. None,
  1201. [
  1202. Int64(addr).ir_value(loc=loc, ip=ip),
  1203. Uint32(packed).ir_value(loc=loc, ip=ip),
  1204. ],
  1205. "red.relaxed.gpu.global.add.noftz.bf16x2 [$0], $1;",
  1206. "l,r",
  1207. has_side_effects=True,
  1208. is_align_stack=False,
  1209. asm_dialect=llvm.AsmDialect.AD_ATT,
  1210. loc=loc,
  1211. ip=ip,
  1212. )
  1213. @dsl_user_op
  1214. def red_max_global_f32_nonnegative(addr: Int64, val: Float32, *, loc=None, ip=None):
  1215. """No-return global max reduction for a non-negative fp32 value.
  1216. Non-negative IEEE-754 float ordering matches unsigned integer bit ordering,
  1217. so this emits a no-return u32 max reduction on the value's bit pattern.
  1218. Callers must initialize the destination with a non-negative fp32 value.
  1219. """
  1220. llvm.inline_asm(
  1221. None,
  1222. [
  1223. Int64(addr).ir_value(loc=loc, ip=ip),
  1224. Float32(val).ir_value(loc=loc, ip=ip),
  1225. ],
  1226. "{ .reg .u32 vi; mov.b32 vi, $1; red.relaxed.gpu.global.max.u32 [$0], vi; }",
  1227. "l,f",
  1228. has_side_effects=True,
  1229. is_align_stack=False,
  1230. asm_dialect=llvm.AsmDialect.AD_ATT,
  1231. loc=loc,
  1232. ip=ip,
  1233. )
  1234. @dsl_user_op
  1235. def atomic_cas_global_i32(addr: Int64, compare: Int32, value: Int32, *, loc=None, ip=None) -> Int32:
  1236. """Global memory int32 atomic compare-and-swap. Returns old value."""
  1237. return Int32(
  1238. llvm.inline_asm(
  1239. T.i32(),
  1240. [
  1241. Int64(addr).ir_value(loc=loc, ip=ip),
  1242. Int32(compare).ir_value(loc=loc, ip=ip),
  1243. Int32(value).ir_value(loc=loc, ip=ip),
  1244. ],
  1245. "atom.global.cas.b32 $0, [$1], $2, $3;",
  1246. "=r,l,r,r",
  1247. has_side_effects=True,
  1248. is_align_stack=False,
  1249. asm_dialect=llvm.AsmDialect.AD_ATT,
  1250. )
  1251. )
  1252. @dsl_user_op
  1253. def atomic_add_shared_i32(addr: Int32, val: Int32, *, loc=None, ip=None) -> Int32:
  1254. """Shared-memory int32 atomic add (CTA-scope). Returns old value.
  1255. Uses ``atom.shared.add.s32`` with a 32-bit shared-memory address
  1256. (the native address width for smem on NVIDIA GPUs).
  1257. """
  1258. return Int32(
  1259. llvm.inline_asm(
  1260. T.i32(),
  1261. [
  1262. Int32(addr).ir_value(loc=loc, ip=ip),
  1263. Int32(val).ir_value(loc=loc, ip=ip),
  1264. ],
  1265. "atom.shared.add.s32 $0, [$1], $2;",
  1266. "=r,r,r",
  1267. has_side_effects=True,
  1268. is_align_stack=False,
  1269. asm_dialect=llvm.AsmDialect.AD_ATT,
  1270. loc=loc,
  1271. ip=ip,
  1272. )
  1273. )
  1274. @dsl_user_op
  1275. def atomic_max_shared_f32(smem_addr: Int32, val: Float32, *, loc=None, ip=None):
  1276. """No-return shared atomic max for a NON-NEGATIVE fp32 ``val``.
  1277. Relies on the IEEE-754 ordering of non-negative floats matching the signed
  1278. int ordering of their bit patterns, so the smem max can be done with a
  1279. single s32 reduction. All callers consume the reduced shared slot after a
  1280. barrier; none needs the old or resulting value in the issuing lane.
  1281. """
  1282. llvm.inline_asm(
  1283. None,
  1284. [
  1285. Int32(smem_addr).ir_value(loc=loc, ip=ip),
  1286. Float32(val).ir_value(loc=loc, ip=ip),
  1287. ],
  1288. "{.reg .s32 vi; mov.b32 vi, $1; red.shared.max.s32 [$0], vi;}",
  1289. "r,f",
  1290. has_side_effects=True,
  1291. is_align_stack=False,
  1292. asm_dialect=llvm.AsmDialect.AD_ATT,
  1293. loc=loc,
  1294. ip=ip,
  1295. )
  1296. @dsl_user_op
  1297. def atomic_max_shared_f32_offset(
  1298. smem_addr: Int32,
  1299. byte_offset: int,
  1300. val: Float32,
  1301. *,
  1302. loc=None,
  1303. ip=None,
  1304. ):
  1305. """No-return nonnegative FP32 shared max at a constant displacement."""
  1306. offset = int(byte_offset)
  1307. address = f"[$0+{offset}]" if offset else "[$0]"
  1308. llvm.inline_asm(
  1309. None,
  1310. [
  1311. Int32(smem_addr).ir_value(loc=loc, ip=ip),
  1312. Float32(val).ir_value(loc=loc, ip=ip),
  1313. ],
  1314. f"{{.reg .s32 vi; mov.b32 vi, $1; red.shared.max.s32 {address}, vi;}}",
  1315. "r,f",
  1316. has_side_effects=True,
  1317. is_align_stack=False,
  1318. asm_dialect=llvm.AsmDialect.AD_ATT,
  1319. loc=loc,
  1320. ip=ip,
  1321. )
  1322. @dsl_user_op
  1323. def red_add_shared_i32(addr: Int32, val: Int32, *, loc=None, ip=None):
  1324. """No-return shared-memory int32 add.
  1325. Use this when the old value is not needed. NVCC lowers equivalent C++
  1326. histogram increments to cheaper no-return shared atomics.
  1327. """
  1328. llvm.inline_asm(
  1329. None,
  1330. [
  1331. Int32(addr).ir_value(loc=loc, ip=ip),
  1332. Int32(val).ir_value(loc=loc, ip=ip),
  1333. ],
  1334. "red.shared.add.u32 [$0], $1;",
  1335. "r,r",
  1336. has_side_effects=True,
  1337. is_align_stack=False,
  1338. asm_dialect=llvm.AsmDialect.AD_ATT,
  1339. loc=loc,
  1340. ip=ip,
  1341. )
  1342. @dsl_user_op
  1343. def ld_shared_i32(addr: Int32, *, loc=None, ip=None) -> Int32:
  1344. """Volatile-style load int32 from shared memory at a 32-bit byte address."""
  1345. return Int32(
  1346. llvm.inline_asm(
  1347. T.i32(),
  1348. [Int32(addr).ir_value(loc=loc, ip=ip)],
  1349. "ld.shared.s32 $0, [$1];",
  1350. "=r,r",
  1351. has_side_effects=True,
  1352. is_align_stack=False,
  1353. asm_dialect=llvm.AsmDialect.AD_ATT,
  1354. loc=loc,
  1355. ip=ip,
  1356. )
  1357. )
  1358. @dsl_user_op
  1359. def ld_shared_i32_relaxed(addr: Int32, *, loc=None, ip=None) -> Int32:
  1360. """Load int32 from shared memory at a 32-bit byte address."""
  1361. return Int32(
  1362. llvm.inline_asm(
  1363. T.i32(),
  1364. [Int32(addr).ir_value(loc=loc, ip=ip)],
  1365. "ld.shared.s32 $0, [$1];",
  1366. "=r,r",
  1367. has_side_effects=True,
  1368. is_align_stack=False,
  1369. asm_dialect=llvm.AsmDialect.AD_ATT,
  1370. loc=loc,
  1371. ip=ip,
  1372. )
  1373. )
  1374. @dsl_user_op
  1375. def ld_shared_u32(addr: Int32, *, loc=None, ip=None) -> Uint32:
  1376. """Load uint32 from shared memory at a 32-bit byte address."""
  1377. return Uint32(
  1378. llvm.inline_asm(
  1379. T.i32(),
  1380. [Int32(addr).ir_value(loc=loc, ip=ip)],
  1381. "ld.shared.u32 $0, [$1];",
  1382. "=r,r",
  1383. has_side_effects=True,
  1384. is_align_stack=False,
  1385. asm_dialect=llvm.AsmDialect.AD_ATT,
  1386. loc=loc,
  1387. ip=ip,
  1388. )
  1389. )
  1390. @dsl_user_op
  1391. def st_shared_i32(addr: Int32, val: Int32, *, loc=None, ip=None):
  1392. """Store int32 to shared memory at a 32-bit byte address."""
  1393. llvm.inline_asm(
  1394. None,
  1395. [
  1396. Int32(addr).ir_value(loc=loc, ip=ip),
  1397. Int32(val).ir_value(loc=loc, ip=ip),
  1398. ],
  1399. "st.shared.s32 [$0], $1;",
  1400. "r,r",
  1401. has_side_effects=True,
  1402. is_align_stack=False,
  1403. asm_dialect=llvm.AsmDialect.AD_ATT,
  1404. loc=loc,
  1405. ip=ip,
  1406. )
  1407. @dsl_user_op
  1408. def st_shared_u32(addr: Int32, val: Uint32, *, loc=None, ip=None):
  1409. """Store uint32 to shared memory at a 32-bit byte address."""
  1410. llvm.inline_asm(
  1411. None,
  1412. [
  1413. Int32(addr).ir_value(loc=loc, ip=ip),
  1414. Uint32(val).ir_value(loc=loc, ip=ip),
  1415. ],
  1416. "st.shared.u32 [$0], $1;",
  1417. "r,r",
  1418. has_side_effects=True,
  1419. is_align_stack=False,
  1420. asm_dialect=llvm.AsmDialect.AD_ATT,
  1421. loc=loc,
  1422. ip=ip,
  1423. )
  1424. @dsl_user_op
  1425. def ld_shared_f32(addr: Int32, *, loc=None, ip=None) -> Float32:
  1426. """Load float32 from shared memory at a 32-bit byte address."""
  1427. return Float32(
  1428. llvm.inline_asm(
  1429. T.f32(),
  1430. [Int32(addr).ir_value(loc=loc, ip=ip)],
  1431. "ld.shared.f32 $0, [$1];",
  1432. "=f,r",
  1433. has_side_effects=True,
  1434. is_align_stack=False,
  1435. asm_dialect=llvm.AsmDialect.AD_ATT,
  1436. loc=loc,
  1437. ip=ip,
  1438. )
  1439. )
  1440. @dsl_user_op
  1441. def ld_shared_f32_offset(
  1442. addr: Int32,
  1443. byte_offset: int,
  1444. *,
  1445. loc=None,
  1446. ip=None,
  1447. ) -> Float32:
  1448. """Load shared FP32 using a compile-time displacement."""
  1449. offset = int(byte_offset)
  1450. address = f"[$1+{offset}]" if offset else "[$1]"
  1451. return Float32(
  1452. llvm.inline_asm(
  1453. T.f32(),
  1454. [Int32(addr).ir_value(loc=loc, ip=ip)],
  1455. f"ld.shared.f32 $0, {address};",
  1456. "=f,r",
  1457. has_side_effects=True,
  1458. is_align_stack=False,
  1459. asm_dialect=llvm.AsmDialect.AD_ATT,
  1460. loc=loc,
  1461. ip=ip,
  1462. )
  1463. )
  1464. @dsl_user_op
  1465. def ld_shared_v4_f32(
  1466. addr: Int32, *, loc=None, ip=None
  1467. ) -> Tuple[Float32, Float32, Float32, Float32]:
  1468. """Load 128 bits (4 x float32) from shared memory."""
  1469. result = llvm.inline_asm(
  1470. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  1471. [Int32(addr).ir_value(loc=loc, ip=ip)],
  1472. "ld.shared.v4.f32 {$0, $1, $2, $3}, [$4];",
  1473. "=f,=f,=f,=f,r",
  1474. has_side_effects=True,
  1475. is_align_stack=False,
  1476. asm_dialect=llvm.AsmDialect.AD_ATT,
  1477. loc=loc,
  1478. ip=ip,
  1479. )
  1480. r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  1481. r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
  1482. r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
  1483. r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
  1484. return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
  1485. @dsl_user_op
  1486. def ld_shared_bf16_to_f32(addr: Int32, *, loc=None, ip=None) -> Float32:
  1487. """Load a BF16 from shared memory at a 32-bit byte address and convert to FP32."""
  1488. return Float32(
  1489. llvm.inline_asm(
  1490. T.f32(),
  1491. [Int32(addr).ir_value(loc=loc, ip=ip)],
  1492. "{.reg .b16 tmp; ld.shared.b16 tmp, [$1]; cvt.f32.bf16 $0, tmp;}",
  1493. "=f,r",
  1494. has_side_effects=True,
  1495. is_align_stack=False,
  1496. asm_dialect=llvm.AsmDialect.AD_ATT,
  1497. loc=loc,
  1498. ip=ip,
  1499. )
  1500. )
  1501. @dsl_user_op
  1502. def st_shared_f32(addr: Int32, val: Float32, *, loc=None, ip=None):
  1503. """Store float32 to shared memory at a 32-bit byte address."""
  1504. llvm.inline_asm(
  1505. None,
  1506. [
  1507. Int32(addr).ir_value(loc=loc, ip=ip),
  1508. Float32(val).ir_value(loc=loc, ip=ip),
  1509. ],
  1510. "st.shared.f32 [$0], $1;",
  1511. "r,f",
  1512. has_side_effects=True,
  1513. is_align_stack=False,
  1514. asm_dialect=llvm.AsmDialect.AD_ATT,
  1515. loc=loc,
  1516. ip=ip,
  1517. )
  1518. @dsl_user_op
  1519. def st_shared_f32_offset(
  1520. addr: Int32,
  1521. byte_offset: int,
  1522. val: Float32,
  1523. *,
  1524. loc=None,
  1525. ip=None,
  1526. ):
  1527. """Store shared FP32 using a compile-time displacement."""
  1528. offset = int(byte_offset)
  1529. address = f"[$0+{offset}]" if offset else "[$0]"
  1530. llvm.inline_asm(
  1531. None,
  1532. [
  1533. Int32(addr).ir_value(loc=loc, ip=ip),
  1534. Float32(val).ir_value(loc=loc, ip=ip),
  1535. ],
  1536. f"st.shared.f32 {address}, $1;",
  1537. "r,f",
  1538. has_side_effects=True,
  1539. is_align_stack=False,
  1540. asm_dialect=llvm.AsmDialect.AD_ATT,
  1541. loc=loc,
  1542. ip=ip,
  1543. )
  1544. @dsl_user_op
  1545. def st_shared_v4_f32(
  1546. addr: Int32,
  1547. v0: Float32,
  1548. v1: Float32,
  1549. v2: Float32,
  1550. v3: Float32,
  1551. *,
  1552. loc=None,
  1553. ip=None,
  1554. ):
  1555. """Store 128 bits (4 x float32) to shared memory."""
  1556. llvm.inline_asm(
  1557. None,
  1558. [
  1559. Int32(addr).ir_value(loc=loc, ip=ip),
  1560. Float32(v0).ir_value(loc=loc, ip=ip),
  1561. Float32(v1).ir_value(loc=loc, ip=ip),
  1562. Float32(v2).ir_value(loc=loc, ip=ip),
  1563. Float32(v3).ir_value(loc=loc, ip=ip),
  1564. ],
  1565. "st.shared.v4.f32 [$0], {$1, $2, $3, $4};",
  1566. "r,f,f,f,f",
  1567. has_side_effects=True,
  1568. is_align_stack=False,
  1569. asm_dialect=llvm.AsmDialect.AD_ATT,
  1570. loc=loc,
  1571. ip=ip,
  1572. )
  1573. @dsl_user_op
  1574. def st_shared_bf16_from_f32(addr: Int32, val: Float32, *, loc=None, ip=None):
  1575. """Convert float32 to BF16 and store one 16-bit value to shared memory."""
  1576. llvm.inline_asm(
  1577. None,
  1578. [
  1579. Int32(addr).ir_value(loc=loc, ip=ip),
  1580. Float32(val).ir_value(loc=loc, ip=ip),
  1581. ],
  1582. "{ .reg .b16 tmp; cvt.rn.bf16.f32 tmp, $1; st.shared.b16 [$0], tmp; }",
  1583. "r,f",
  1584. has_side_effects=True,
  1585. is_align_stack=False,
  1586. asm_dialect=llvm.AsmDialect.AD_ATT,
  1587. loc=loc,
  1588. ip=ip,
  1589. )
  1590. @dsl_user_op
  1591. def st_shared_f16_from_f32(addr: Int32, val: Float32, *, loc=None, ip=None):
  1592. """Convert float32 to FP16 and store one 16-bit value to shared memory."""
  1593. llvm.inline_asm(
  1594. None,
  1595. [
  1596. Int32(addr).ir_value(loc=loc, ip=ip),
  1597. Float32(val).ir_value(loc=loc, ip=ip),
  1598. ],
  1599. "{ .reg .b16 tmp; cvt.rn.f16.f32 tmp, $1; st.shared.b16 [$0], tmp; }",
  1600. "r,f",
  1601. has_side_effects=True,
  1602. is_align_stack=False,
  1603. asm_dialect=llvm.AsmDialect.AD_ATT,
  1604. loc=loc,
  1605. ip=ip,
  1606. )
  1607. @dsl_user_op
  1608. def st_global_i32(addr: Int64, val: Int32, *, loc=None, ip=None):
  1609. """Store int32 to global memory."""
  1610. llvm.inline_asm(
  1611. None,
  1612. [
  1613. Int64(addr).ir_value(loc=loc, ip=ip),
  1614. Int32(val).ir_value(loc=loc, ip=ip),
  1615. ],
  1616. "st.global.s32 [$0], $1;",
  1617. "l,r",
  1618. has_side_effects=True,
  1619. is_align_stack=False,
  1620. asm_dialect=llvm.AsmDialect.AD_ATT,
  1621. )
  1622. # =============================================================================
  1623. # PTX Intrinsics - Global Memory Barriers
  1624. # =============================================================================
  1625. @dsl_user_op
  1626. def ld_global_acquire_i32(addr: Int64, *, loc=None, ip=None) -> Int32:
  1627. """Load int32 from global memory with acquire semantics."""
  1628. return Int32(
  1629. llvm.inline_asm(
  1630. T.i32(),
  1631. [Int64(addr).ir_value(loc=loc, ip=ip)],
  1632. "ld.global.acquire.gpu.s32 $0, [$1];",
  1633. "=r,l",
  1634. has_side_effects=True,
  1635. is_align_stack=False,
  1636. asm_dialect=llvm.AsmDialect.AD_ATT,
  1637. loc=loc,
  1638. ip=ip,
  1639. )
  1640. )
  1641. @dsl_user_op
  1642. def st_global_release_i32(addr: Int64, val: Int32, *, loc=None, ip=None):
  1643. """Store int32 to global memory with release semantics."""
  1644. llvm.inline_asm(
  1645. None,
  1646. [
  1647. Int64(addr).ir_value(loc=loc, ip=ip),
  1648. Int32(val).ir_value(loc=loc, ip=ip),
  1649. ],
  1650. "st.global.release.gpu.s32 [$0], $1;",
  1651. "l,r",
  1652. has_side_effects=True,
  1653. is_align_stack=False,
  1654. asm_dialect=llvm.AsmDialect.AD_ATT,
  1655. loc=loc,
  1656. ip=ip,
  1657. )
  1658. @dsl_user_op
  1659. def spin_wait_global_eq_i32(addr: Int64, expected: Int32, *, loc=None, ip=None):
  1660. """Spin-wait until *addr != expected (acquire semantics on load)."""
  1661. llvm.inline_asm(
  1662. None,
  1663. [
  1664. Int64(addr).ir_value(loc=loc, ip=ip),
  1665. Int32(expected).ir_value(loc=loc, ip=ip),
  1666. ],
  1667. "{\n"
  1668. ".reg .pred %p0;\n"
  1669. ".reg .s32 %val;\n"
  1670. "spin_loop:\n"
  1671. " ld.global.acquire.gpu.s32 %val, [$0];\n"
  1672. " setp.eq.s32 %p0, %val, $1;\n"
  1673. " @%p0 bra spin_loop;\n"
  1674. "}",
  1675. "l,r",
  1676. has_side_effects=True,
  1677. is_align_stack=False,
  1678. asm_dialect=llvm.AsmDialect.AD_ATT,
  1679. loc=loc,
  1680. ip=ip,
  1681. )
  1682. @dsl_user_op
  1683. def spin_wait_global_ge_i32(addr: Int64, target: Int32, *, loc=None, ip=None):
  1684. """Spin-wait until *addr >= target using acquire loads."""
  1685. llvm.inline_asm(
  1686. None,
  1687. [
  1688. Int64(addr).ir_value(loc=loc, ip=ip),
  1689. Int32(target).ir_value(loc=loc, ip=ip),
  1690. ],
  1691. "{\n"
  1692. ".reg .pred %p0;\n"
  1693. ".reg .s32 %val;\n"
  1694. "spin_loop_ge:\n"
  1695. " ld.global.acquire.gpu.s32 %val, [$0];\n"
  1696. " setp.lt.s32 %p0, %val, $1;\n"
  1697. " @%p0 bra spin_loop_ge;\n"
  1698. "}",
  1699. "l,r",
  1700. has_side_effects=True,
  1701. is_align_stack=False,
  1702. asm_dialect=llvm.AsmDialect.AD_ATT,
  1703. loc=loc,
  1704. ip=ip,
  1705. )
  1706. @dsl_user_op
  1707. def threadfence(*, loc=None, ip=None):
  1708. """Emit membar.gl — threadfence across global memory."""
  1709. llvm.inline_asm(
  1710. None,
  1711. [],
  1712. "membar.gl;",
  1713. "",
  1714. has_side_effects=True,
  1715. is_align_stack=False,
  1716. asm_dialect=llvm.AsmDialect.AD_ATT,
  1717. loc=loc,
  1718. ip=ip,
  1719. )
  1720. # =============================================================================
  1721. # PTX Intrinsics - Scatter Atomics
  1722. # =============================================================================
  1723. @dsl_user_op
  1724. def scatter_add_bf16x2(addr: Int64, val0_f32, val1_f32, *, loc=None, ip=None):
  1725. """BF16x2 atomic reduction add to global memory.
  1726. Packs two f32 values into bf16x2 via cvt.rn.satfinite, then does
  1727. red.relaxed.gpu.global.add.noftz.bf16x2. Workaround for LLVM NVPTX
  1728. bug that decomposes vector<2xbf16> into .v2.bf16x2 which ptxas rejects.
  1729. """
  1730. llvm.inline_asm(
  1731. None,
  1732. [
  1733. Int64(addr).ir_value(loc=loc, ip=ip),
  1734. val0_f32.ir_value(loc=loc, ip=ip),
  1735. val1_f32.ir_value(loc=loc, ip=ip),
  1736. ],
  1737. "{ .reg .b32 packed; cvt.rn.satfinite.bf16x2.f32 packed, $2, $1; red.relaxed.gpu.global.add.noftz.bf16x2 [$0], packed; }",
  1738. "l,f,f",
  1739. has_side_effects=True,
  1740. is_align_stack=False,
  1741. asm_dialect=llvm.AsmDialect.AD_ATT,
  1742. )
  1743. @dsl_user_op
  1744. def scatter_add_bf16(addr: Int64, val_f32, *, loc=None, ip=None):
  1745. """BF16 atomic reduction add to global memory.
  1746. Converts the f32 input to bf16 inside PTX and atomically accumulates it into
  1747. one bf16 output lane. This is intended for opt-in approximate reductions.
  1748. """
  1749. llvm.inline_asm(
  1750. None,
  1751. [
  1752. Int64(addr).ir_value(loc=loc, ip=ip),
  1753. val_f32.ir_value(loc=loc, ip=ip),
  1754. ],
  1755. "{ .reg .b16 packed; cvt.rn.satfinite.bf16.f32 packed, $1; red.relaxed.gpu.global.add.noftz.bf16 [$0], packed; }",
  1756. "l,f",
  1757. has_side_effects=True,
  1758. is_align_stack=False,
  1759. asm_dialect=llvm.AsmDialect.AD_ATT,
  1760. )
  1761. @dsl_user_op
  1762. def scatter_add_v4_bf16x2(addr: Int64, v0, v1, v2, v3, v4, v5, v6, v7, *, loc=None, ip=None):
  1763. """Vectorized BF16x2 atomic reduction: 8 bf16 values (16 bytes) in one go.
  1764. red.global.add.noftz.v4.bf16x2 [addr], {packed0, packed1, packed2, packed3}
  1765. Each packed reg contains 2 bf16 values converted from f32 pairs.
  1766. """
  1767. llvm.inline_asm(
  1768. None,
  1769. [
  1770. Int64(addr).ir_value(loc=loc, ip=ip),
  1771. v0.ir_value(loc=loc, ip=ip), v1.ir_value(loc=loc, ip=ip),
  1772. v2.ir_value(loc=loc, ip=ip), v3.ir_value(loc=loc, ip=ip),
  1773. v4.ir_value(loc=loc, ip=ip), v5.ir_value(loc=loc, ip=ip),
  1774. v6.ir_value(loc=loc, ip=ip), v7.ir_value(loc=loc, ip=ip),
  1775. ],
  1776. "{ .reg .b32 p0,p1,p2,p3;"
  1777. " cvt.rn.satfinite.bf16x2.f32 p0, $2, $1;"
  1778. " cvt.rn.satfinite.bf16x2.f32 p1, $4, $3;"
  1779. " cvt.rn.satfinite.bf16x2.f32 p2, $6, $5;"
  1780. " cvt.rn.satfinite.bf16x2.f32 p3, $8, $7;"
  1781. " red.global.add.noftz.v4.bf16x2 [$0], {p0, p1, p2, p3}; }",
  1782. "l,f,f,f,f,f,f,f,f",
  1783. has_side_effects=True,
  1784. is_align_stack=False,
  1785. asm_dialect=llvm.AsmDialect.AD_ATT,
  1786. )
  1787. # =============================================================================
  1788. # PTX Intrinsics - Math Operations
  1789. # =============================================================================
  1790. @dsl_user_op
  1791. def rcp_approx_ftz(a: Float32, *, loc=None, ip=None) -> Float32:
  1792. """Fast reciprocal using PTX rcp.approx.ftz.f32."""
  1793. return Float32(
  1794. llvm.inline_asm(
  1795. T.f32(),
  1796. [Float32(a).ir_value(loc=loc, ip=ip)],
  1797. "rcp.approx.ftz.f32 $0, $1;",
  1798. "=f,f",
  1799. has_side_effects=False,
  1800. is_align_stack=False,
  1801. asm_dialect=llvm.AsmDialect.AD_ATT,
  1802. )
  1803. )
  1804. @dsl_user_op
  1805. def fmin_f32(a: Float32, b: Float32, *, loc=None, ip=None) -> Float32:
  1806. """Compute min of two float32 values using PTX min.f32."""
  1807. return Float32(
  1808. llvm.inline_asm(
  1809. T.f32(),
  1810. [Float32(a).ir_value(loc=loc, ip=ip), Float32(b).ir_value(loc=loc, ip=ip)],
  1811. "min.f32 $0, $1, $2;",
  1812. "=f,f,f",
  1813. has_side_effects=False,
  1814. is_align_stack=False,
  1815. asm_dialect=llvm.AsmDialect.AD_ATT,
  1816. )
  1817. )
  1818. @dsl_user_op
  1819. def fmax_f32(a: Float32, b: Float32, *, loc=None, ip=None) -> Float32:
  1820. """Compute max of two float32 values using PTX max.f32."""
  1821. return Float32(
  1822. llvm.inline_asm(
  1823. T.f32(),
  1824. [Float32(a).ir_value(loc=loc, ip=ip), Float32(b).ir_value(loc=loc, ip=ip)],
  1825. "max.f32 $0, $1, $2;",
  1826. "=f,f,f",
  1827. has_side_effects=False,
  1828. is_align_stack=False,
  1829. asm_dialect=llvm.AsmDialect.AD_ATT,
  1830. )
  1831. )
  1832. @dsl_user_op
  1833. def fabs_f32(a: Float32, *, loc=None, ip=None) -> Float32:
  1834. """Compute absolute value of float32 using PTX abs.f32."""
  1835. return Float32(
  1836. llvm.inline_asm(
  1837. T.f32(),
  1838. [Float32(a).ir_value(loc=loc, ip=ip)],
  1839. "abs.f32 $0, $1;",
  1840. "=f,f",
  1841. has_side_effects=False,
  1842. is_align_stack=False,
  1843. asm_dialect=llvm.AsmDialect.AD_ATT,
  1844. )
  1845. )
  1846. # =============================================================================
  1847. # Half2 SIMD Intrinsics
  1848. # =============================================================================
  1849. @dsl_user_op
  1850. def half2_mul(a: Uint32, b: Uint32, *, loc=None, ip=None) -> Uint32:
  1851. """Multiply two Half2 values element-wise: (a.x*b.x, a.y*b.y)."""
  1852. return Uint32(
  1853. llvm.inline_asm(
  1854. T.i32(),
  1855. [Uint32(a).ir_value(loc=loc, ip=ip), Uint32(b).ir_value(loc=loc, ip=ip)],
  1856. "mul.f16x2 $0, $1, $2;",
  1857. "=r,r,r",
  1858. has_side_effects=False,
  1859. is_align_stack=False,
  1860. asm_dialect=llvm.AsmDialect.AD_ATT,
  1861. )
  1862. )
  1863. @dsl_user_op
  1864. def broadcast_f32_to_half2(x: Float32, *, loc=None, ip=None) -> Uint32:
  1865. """Pack one float32 value into both lanes of an f16x2 register."""
  1866. return Uint32(
  1867. llvm.inline_asm(
  1868. T.i32(),
  1869. [Float32(x).ir_value(loc=loc, ip=ip)],
  1870. "cvt.rn.f16x2.f32 $0, $1, $1;",
  1871. "=r,f",
  1872. has_side_effects=False,
  1873. is_align_stack=False,
  1874. asm_dialect=llvm.AsmDialect.AD_ATT,
  1875. )
  1876. )
  1877. @dsl_user_op
  1878. def hadd2(a: Uint32, b: Uint32, *, loc=None, ip=None) -> Uint32:
  1879. """Add two Half2 values element-wise: (a.x+b.x, a.y+b.y)."""
  1880. return Uint32(
  1881. llvm.inline_asm(
  1882. T.i32(),
  1883. [Uint32(a).ir_value(loc=loc, ip=ip), Uint32(b).ir_value(loc=loc, ip=ip)],
  1884. "add.f16x2 $0, $1, $2;",
  1885. "=r,r,r",
  1886. has_side_effects=False,
  1887. is_align_stack=False,
  1888. asm_dialect=llvm.AsmDialect.AD_ATT,
  1889. )
  1890. )
  1891. @dsl_user_op
  1892. def habs2(x: Uint32, *, loc=None, ip=None) -> Uint32:
  1893. """Half2 absolute value - clears sign bits of both fp16 values."""
  1894. return Uint32(
  1895. llvm.inline_asm(
  1896. T.i32(),
  1897. [Uint32(x).ir_value(loc=loc, ip=ip)],
  1898. "and.b32 $0, $1, 0x7FFF7FFF;",
  1899. "=r,r",
  1900. has_side_effects=False,
  1901. is_align_stack=False,
  1902. asm_dialect=llvm.AsmDialect.AD_ATT,
  1903. )
  1904. )
  1905. @dsl_user_op
  1906. def hmax2(a: Uint32, b: Uint32, *, loc=None, ip=None) -> Uint32:
  1907. """Half2 max - element-wise max of 2 fp16 pairs."""
  1908. return Uint32(
  1909. llvm.inline_asm(
  1910. T.i32(),
  1911. [Uint32(a).ir_value(loc=loc, ip=ip), Uint32(b).ir_value(loc=loc, ip=ip)],
  1912. "max.f16x2 $0, $1, $2;",
  1913. "=r,r,r",
  1914. has_side_effects=False,
  1915. is_align_stack=False,
  1916. asm_dialect=llvm.AsmDialect.AD_ATT,
  1917. )
  1918. )
  1919. @dsl_user_op
  1920. def hmax_to_f32(x: Uint32, *, loc=None, ip=None) -> Float32:
  1921. """Extract max of 2 fp16 values in half2 as float32."""
  1922. return Float32(
  1923. llvm.inline_asm(
  1924. T.f32(),
  1925. [Uint32(x).ir_value(loc=loc, ip=ip)],
  1926. """
  1927. {
  1928. .reg .b16 h0, h1;
  1929. .reg .f32 f0, f1;
  1930. mov.b32 {h0, h1}, $1;
  1931. cvt.f32.f16 f0, h0;
  1932. cvt.f32.f16 f1, h1;
  1933. max.f32 $0, f0, f1;
  1934. }
  1935. """,
  1936. "=f,r",
  1937. has_side_effects=False,
  1938. is_align_stack=False,
  1939. asm_dialect=llvm.AsmDialect.AD_ATT,
  1940. )
  1941. )
  1942. @dsl_user_op
  1943. def half2_to_float2_scaled(
  1944. h2: Uint32, scale: Float32, *, loc=None, ip=None
  1945. ) -> Tuple[Float32, Float32]:
  1946. """Convert half2 to float2 AND multiply by scale."""
  1947. result = llvm.inline_asm(
  1948. llvm.StructType.get_literal([T.f32(), T.f32()]),
  1949. [Uint32(h2).ir_value(loc=loc, ip=ip), Float32(scale).ir_value(loc=loc, ip=ip)],
  1950. """
  1951. {
  1952. .reg .b16 h0, h1;
  1953. .reg .f32 f0, f1;
  1954. mov.b32 {h0, h1}, $2;
  1955. cvt.f32.f16 f0, h0;
  1956. cvt.f32.f16 f1, h1;
  1957. mul.f32 $0, f0, $3;
  1958. mul.f32 $1, f1, $3;
  1959. }
  1960. """,
  1961. "=f,=f,r,f",
  1962. has_side_effects=False,
  1963. is_align_stack=False,
  1964. asm_dialect=llvm.AsmDialect.AD_ATT,
  1965. loc=loc,
  1966. ip=ip,
  1967. )
  1968. f0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  1969. f1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
  1970. return Float32(f0), Float32(f1)
  1971. # =============================================================================
  1972. # BFloat2 SIMD Intrinsics
  1973. # =============================================================================
  1974. @dsl_user_op
  1975. def bfloat2_mul(a: Uint32, b: Uint32, *, loc=None, ip=None) -> Uint32:
  1976. """Multiply two BFloat2 values element-wise: (a.x*b.x, a.y*b.y)."""
  1977. return Uint32(
  1978. llvm.inline_asm(
  1979. T.i32(),
  1980. [Uint32(a).ir_value(loc=loc, ip=ip), Uint32(b).ir_value(loc=loc, ip=ip)],
  1981. "mul.bf16x2 $0, $1, $2;",
  1982. "=r,r,r",
  1983. has_side_effects=False,
  1984. is_align_stack=False,
  1985. asm_dialect=llvm.AsmDialect.AD_ATT,
  1986. )
  1987. )
  1988. @dsl_user_op
  1989. def bfloat2_broadcast_lane(x: Uint32, lane: Int32, *, loc=None, ip=None) -> Uint32:
  1990. """Duplicate one BF16 lane from a packed bf16x2 register into both lanes."""
  1991. return Uint32(
  1992. llvm.inline_asm(
  1993. T.i32(),
  1994. [
  1995. Uint32(x).ir_value(loc=loc, ip=ip),
  1996. Int32(lane).ir_value(loc=loc, ip=ip),
  1997. ],
  1998. """
  1999. {
  2000. .reg .pred p;
  2001. .reg .b32 lo, hi, val, shifted;
  2002. and.b32 lo, $1, 0x0000ffff;
  2003. shr.u32 hi, $1, 16;
  2004. setp.eq.s32 p, $2, 0;
  2005. @p mov.b32 val, lo;
  2006. @!p mov.b32 val, hi;
  2007. shl.b32 shifted, val, 16;
  2008. or.b32 $0, val, shifted;
  2009. }
  2010. """,
  2011. "=r,r,r",
  2012. has_side_effects=False,
  2013. is_align_stack=False,
  2014. asm_dialect=llvm.AsmDialect.AD_ATT,
  2015. loc=loc,
  2016. ip=ip,
  2017. )
  2018. )
  2019. @dsl_user_op
  2020. def broadcast_f32_to_bfloat2(x: Float32, *, loc=None, ip=None) -> Uint32:
  2021. """Pack one float32 value into both lanes of a bf16x2 register."""
  2022. return Uint32(
  2023. llvm.inline_asm(
  2024. T.i32(),
  2025. [Float32(x).ir_value(loc=loc, ip=ip)],
  2026. "cvt.rn.satfinite.bf16x2.f32 $0, $1, $1;",
  2027. "=r,f",
  2028. has_side_effects=False,
  2029. is_align_stack=False,
  2030. asm_dialect=llvm.AsmDialect.AD_ATT,
  2031. )
  2032. )
  2033. @dsl_user_op
  2034. def pack_f32x2_to_bfloat2(x0: Float32, x1: Float32, *, loc=None, ip=None) -> Uint32:
  2035. """Pack 2 float32 values into one bf16x2 register."""
  2036. return Uint32(
  2037. llvm.inline_asm(
  2038. T.i32(),
  2039. [Float32(x0).ir_value(loc=loc, ip=ip), Float32(x1).ir_value(loc=loc, ip=ip)],
  2040. "cvt.rn.satfinite.bf16x2.f32 $0, $2, $1;",
  2041. "=r,f,f",
  2042. has_side_effects=False,
  2043. is_align_stack=False,
  2044. asm_dialect=llvm.AsmDialect.AD_ATT,
  2045. )
  2046. )
  2047. @dsl_user_op
  2048. def cvt_bf16x2_to_e4m3x2(src: Uint32, *, loc=None, ip=None) -> Uint32:
  2049. """Convert packed bf16x2 to packed e4m3x2 in the low 16 bits of a u32."""
  2050. return Uint32(
  2051. llvm.inline_asm(
  2052. T.i32(),
  2053. [Uint32(src).ir_value(loc=loc, ip=ip)],
  2054. """
  2055. {
  2056. .reg .b16 out, zero;
  2057. cvt.rn.satfinite.e4m3x2.bf16x2 out, $1;
  2058. mov.u16 zero, 0;
  2059. mov.b32 $0, {out, zero};
  2060. }
  2061. """,
  2062. "=r,r",
  2063. has_side_effects=False,
  2064. is_align_stack=False,
  2065. asm_dialect=llvm.AsmDialect.AD_ATT,
  2066. loc=loc,
  2067. ip=ip,
  2068. )
  2069. )
  2070. @dsl_user_op
  2071. def cvt_bf16x2x2_to_e4m3x4(lo: Uint32, hi: Uint32, *, loc=None, ip=None) -> Uint32:
  2072. """Convert two bf16x2 values and pack the e4m3x2 results into one u32."""
  2073. return Uint32(
  2074. llvm.inline_asm(
  2075. T.i32(),
  2076. [Uint32(lo).ir_value(loc=loc, ip=ip), Uint32(hi).ir_value(loc=loc, ip=ip)],
  2077. """
  2078. {
  2079. .reg .b16 out_lo, out_hi;
  2080. cvt.rn.satfinite.e4m3x2.bf16x2 out_lo, $1;
  2081. cvt.rn.satfinite.e4m3x2.bf16x2 out_hi, $2;
  2082. mov.b32 $0, {out_lo, out_hi};
  2083. }
  2084. """,
  2085. "=r,r,r",
  2086. has_side_effects=False,
  2087. is_align_stack=False,
  2088. asm_dialect=llvm.AsmDialect.AD_ATT,
  2089. loc=loc,
  2090. ip=ip,
  2091. )
  2092. )
  2093. @dsl_user_op
  2094. def bfloat2_add(a: Uint32, b: Uint32, *, loc=None, ip=None) -> Uint32:
  2095. """Add two BFloat2 values element-wise: (a.x+b.x, a.y+b.y)."""
  2096. return Uint32(
  2097. llvm.inline_asm(
  2098. T.i32(),
  2099. [Uint32(a).ir_value(loc=loc, ip=ip), Uint32(b).ir_value(loc=loc, ip=ip)],
  2100. "add.bf16x2 $0, $1, $2;",
  2101. "=r,r,r",
  2102. has_side_effects=False,
  2103. is_align_stack=False,
  2104. asm_dialect=llvm.AsmDialect.AD_ATT,
  2105. )
  2106. )
  2107. @dsl_user_op
  2108. def fp8x4_e4m3_to_bfloat2x2(packed: Uint32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
  2109. """Widen 4 packed E4M3 bytes into 2 packed bf16x2 registers exactly."""
  2110. result = llvm.inline_asm(
  2111. llvm.StructType.get_literal([T.i32(), T.i32()]),
  2112. [Uint32(packed).ir_value(loc=loc, ip=ip)],
  2113. """
  2114. {
  2115. .reg .b16 low;
  2116. .reg .b32 q, out0, out1, sign0, sign1, mant0, mant1, tmp, bias, bias_f32;
  2117. prmt.b32 q, $2, 0, 0x1302;
  2118. and.b32 sign0, q, 0x80008000;
  2119. shr.u32 tmp, q, 4;
  2120. and.b32 mant0, tmp, 0x07F007F0;
  2121. or.b32 out0, mant0, sign0;
  2122. shl.b32 tmp, q, 8;
  2123. and.b32 sign1, tmp, 0x80008000;
  2124. shl.b32 tmp, q, 4;
  2125. and.b32 mant1, tmp, 0x07F007F0;
  2126. or.b32 out1, mant1, sign1;
  2127. mov.b32 bias_f32, 0x7B800000;
  2128. cvt.rn.bf16.f32 low, bias_f32;
  2129. mov.b32 bias, {low, low};
  2130. mul.bf16x2 $0, out0, bias;
  2131. mul.bf16x2 $1, out1, bias;
  2132. }
  2133. """,
  2134. "=r,=r,r",
  2135. has_side_effects=False,
  2136. is_align_stack=False,
  2137. asm_dialect=llvm.AsmDialect.AD_ATT,
  2138. loc=loc,
  2139. ip=ip,
  2140. )
  2141. lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  2142. hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  2143. return Uint32(lo), Uint32(hi)
  2144. @dsl_user_op
  2145. def packed_dequant_e2m1x4_to_bfloat2x2(
  2146. packed: Uint32, *, loc=None, ip=None
  2147. ) -> Tuple[Uint32, Uint32]:
  2148. """FE2M1 -> BF16 register dequant for one packed 4-value fragment."""
  2149. result = llvm.inline_asm(
  2150. llvm.StructType.get_literal([T.i32(), T.i32()]),
  2151. [Uint32(packed).ir_value(loc=loc, ip=ip)],
  2152. """
  2153. {
  2154. .reg .b32 q, out1, out2, tmp;
  2155. and.b32 out1, $2, 0x80008000;
  2156. and.b32 tmp, $2, 0x70007000;
  2157. shr.u32 tmp, tmp, 6;
  2158. or.b32 out1, out1, tmp;
  2159. shl.b32 q, $2, 4;
  2160. and.b32 out2, q, 0x80008000;
  2161. and.b32 tmp, q, 0x70007000;
  2162. shr.u32 tmp, tmp, 6;
  2163. or.b32 out2, out2, tmp;
  2164. mov.b32 $0, out2;
  2165. mov.b32 $1, out1;
  2166. }
  2167. """,
  2168. "=r,=r,r",
  2169. has_side_effects=False,
  2170. is_align_stack=False,
  2171. asm_dialect=llvm.AsmDialect.AD_ATT,
  2172. loc=loc,
  2173. ip=ip,
  2174. )
  2175. lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  2176. hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  2177. return Uint32(lo), Uint32(hi)
  2178. @dsl_user_op
  2179. def packed_dequant_e2m1x4_to_half2x2(
  2180. packed: Uint32, *, loc=None, ip=None
  2181. ) -> Tuple[Uint32, Uint32]:
  2182. """FE2M1 -> FP16 register dequant for one packed 4-value fragment."""
  2183. result = llvm.inline_asm(
  2184. llvm.StructType.get_literal([T.i32(), T.i32()]),
  2185. [Uint32(packed).ir_value(loc=loc, ip=ip)],
  2186. """
  2187. {
  2188. .reg .b32 q, out1, out2, tmp;
  2189. and.b32 out1, $2, 0x80008000;
  2190. and.b32 tmp, $2, 0x70007000;
  2191. shr.u32 tmp, tmp, 3;
  2192. or.b32 out1, out1, tmp;
  2193. shl.b32 q, $2, 4;
  2194. and.b32 out2, q, 0x80008000;
  2195. and.b32 tmp, q, 0x70007000;
  2196. shr.u32 tmp, tmp, 3;
  2197. or.b32 out2, out2, tmp;
  2198. mov.b32 $0, out2;
  2199. mov.b32 $1, out1;
  2200. }
  2201. """,
  2202. "=r,=r,r",
  2203. has_side_effects=False,
  2204. is_align_stack=False,
  2205. asm_dialect=llvm.AsmDialect.AD_ATT,
  2206. loc=loc,
  2207. ip=ip,
  2208. )
  2209. lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  2210. hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  2211. return Uint32(lo), Uint32(hi)
  2212. @dsl_user_op
  2213. def packed_dequant_e4m3x4_to_bfloat2x2(
  2214. packed: Uint32, *, loc=None, ip=None
  2215. ) -> Tuple[Uint32, Uint32]:
  2216. """FE4M3 scale dequant for one packed 4-value BF16 fragment."""
  2217. result = llvm.inline_asm(
  2218. llvm.StructType.get_literal([T.i32(), T.i32()]),
  2219. [Uint32(packed).ir_value(loc=loc, ip=ip)],
  2220. """
  2221. {
  2222. .reg .b32 q, out1, out2, tmp;
  2223. and.b32 tmp, $2, 0x80008000;
  2224. shr.u32 out1, tmp, 1;
  2225. and.b32 tmp, $2, 0x7F007F00;
  2226. shr.u32 tmp, tmp, 4;
  2227. or.b32 out1, out1, tmp;
  2228. shl.b32 q, $2, 8;
  2229. and.b32 tmp, q, 0x80008000;
  2230. shr.u32 out2, tmp, 1;
  2231. and.b32 tmp, q, 0x7F007F00;
  2232. shr.u32 tmp, tmp, 4;
  2233. or.b32 out2, out2, tmp;
  2234. mov.b32 $0, out2;
  2235. mov.b32 $1, out1;
  2236. }
  2237. """,
  2238. "=r,=r,r",
  2239. has_side_effects=False,
  2240. is_align_stack=False,
  2241. asm_dialect=llvm.AsmDialect.AD_ATT,
  2242. loc=loc,
  2243. ip=ip,
  2244. )
  2245. lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  2246. hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  2247. return Uint32(lo), Uint32(hi)
  2248. @dsl_user_op
  2249. def packed_dequant_e4m3x4_to_half2x2(
  2250. packed: Uint32, *, loc=None, ip=None
  2251. ) -> Tuple[Uint32, Uint32]:
  2252. """FE4M3 scale dequant for one packed 4-value FP16 fragment."""
  2253. result = llvm.inline_asm(
  2254. llvm.StructType.get_literal([T.i32(), T.i32()]),
  2255. [Uint32(packed).ir_value(loc=loc, ip=ip)],
  2256. """
  2257. {
  2258. .reg .b32 q, out1, out2;
  2259. and.b32 out1, $2, 0xFF00FF00;
  2260. shr.u32 out1, out1, 1;
  2261. shl.b32 q, $2, 8;
  2262. and.b32 out2, q, 0xFF00FF00;
  2263. shr.u32 out2, out2, 1;
  2264. mov.b32 $0, out2;
  2265. mov.b32 $1, out1;
  2266. }
  2267. """,
  2268. "=r,=r,r",
  2269. has_side_effects=False,
  2270. is_align_stack=False,
  2271. asm_dialect=llvm.AsmDialect.AD_ATT,
  2272. loc=loc,
  2273. ip=ip,
  2274. )
  2275. lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  2276. hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  2277. return Uint32(lo), Uint32(hi)
  2278. @dsl_user_op
  2279. def packed_dequant_e8m0x4_to_bfloat2x2(
  2280. packed: Uint32, *, loc=None, ip=None
  2281. ) -> Tuple[Uint32, Uint32]:
  2282. """E8M0 compute-scale dequant for one packed 4-value BF16 fragment.
  2283. The W4A16 FP4 unpack path represents E2M1 values scaled by 2^-126. Match
  2284. the existing NVFP4 split by materializing E8M0 scales multiplied by 2^7 in
  2285. the MMA input and applying the remaining compensation in the kernel
  2286. epilogue.
  2287. """
  2288. result = llvm.inline_asm(
  2289. llvm.StructType.get_literal([T.i32(), T.i32()]),
  2290. [Uint32(packed).ir_value(loc=loc, ip=ip)],
  2291. """
  2292. {
  2293. .reg .u32 b0, b1, b2, b3;
  2294. .reg .u32 h0, h1, h2, h3;
  2295. .reg .u32 t0, t1;
  2296. and.b32 b0, $2, 0x000000ff;
  2297. shr.u32 b1, $2, 8;
  2298. and.b32 b1, b1, 0x000000ff;
  2299. shr.u32 b2, $2, 16;
  2300. and.b32 b2, b2, 0x000000ff;
  2301. shr.u32 b3, $2, 24;
  2302. add.u32 h0, b0, 7;
  2303. add.u32 h1, b1, 7;
  2304. add.u32 h2, b2, 7;
  2305. add.u32 h3, b3, 7;
  2306. shl.b32 h0, h0, 7;
  2307. shl.b32 h1, h1, 7;
  2308. shl.b32 h2, h2, 7;
  2309. shl.b32 h3, h3, 7;
  2310. shl.b32 t0, h2, 16;
  2311. or.b32 $0, h0, t0;
  2312. shl.b32 t1, h3, 16;
  2313. or.b32 $1, h1, t1;
  2314. }
  2315. """,
  2316. "=r,=r,r",
  2317. has_side_effects=False,
  2318. is_align_stack=False,
  2319. asm_dialect=llvm.AsmDialect.AD_ATT,
  2320. loc=loc,
  2321. ip=ip,
  2322. )
  2323. lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  2324. hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  2325. return Uint32(lo), Uint32(hi)
  2326. @dsl_user_op
  2327. def packed_dequant_e8m0x4_to_half2x2(
  2328. packed: Uint32, *, loc=None, ip=None
  2329. ) -> Tuple[Uint32, Uint32]:
  2330. """E8M0 compute-scale dequant for one packed 4-value FP16 fragment."""
  2331. result = llvm.inline_asm(
  2332. llvm.StructType.get_literal([T.i32(), T.i32()]),
  2333. [Uint32(packed).ir_value(loc=loc, ip=ip)],
  2334. """
  2335. {
  2336. .reg .pred p0, p1, p2, p3;
  2337. .reg .u32 b0, b1, b2, b3;
  2338. .reg .s32 e0, e1, e2, e3;
  2339. .reg .f32 ef0, ef1, ef2, ef3;
  2340. .reg .f32 f0, f1, f2, f3;
  2341. .reg .b16 h0, h1, h2, h3;
  2342. and.b32 b0, $2, 0x000000ff;
  2343. shr.u32 b1, $2, 8;
  2344. and.b32 b1, b1, 0x000000ff;
  2345. shr.u32 b2, $2, 16;
  2346. and.b32 b2, b2, 0x000000ff;
  2347. shr.u32 b3, $2, 24;
  2348. setp.eq.u32 p0, b0, 0;
  2349. setp.eq.u32 p1, b1, 0;
  2350. setp.eq.u32 p2, b2, 0;
  2351. setp.eq.u32 p3, b3, 0;
  2352. cvt.s32.u32 e0, b0;
  2353. cvt.s32.u32 e1, b1;
  2354. cvt.s32.u32 e2, b2;
  2355. cvt.s32.u32 e3, b3;
  2356. sub.s32 e0, e0, 120;
  2357. sub.s32 e1, e1, 120;
  2358. sub.s32 e2, e2, 120;
  2359. sub.s32 e3, e3, 120;
  2360. cvt.rn.f32.s32 ef0, e0;
  2361. cvt.rn.f32.s32 ef1, e1;
  2362. cvt.rn.f32.s32 ef2, e2;
  2363. cvt.rn.f32.s32 ef3, e3;
  2364. ex2.approx.f32 f0, ef0;
  2365. ex2.approx.f32 f1, ef1;
  2366. ex2.approx.f32 f2, ef2;
  2367. ex2.approx.f32 f3, ef3;
  2368. selp.f32 f0, 0f00000000, f0, p0;
  2369. selp.f32 f1, 0f00000000, f1, p1;
  2370. selp.f32 f2, 0f00000000, f2, p2;
  2371. selp.f32 f3, 0f00000000, f3, p3;
  2372. cvt.rn.f16.f32 h0, f0;
  2373. cvt.rn.f16.f32 h1, f1;
  2374. cvt.rn.f16.f32 h2, f2;
  2375. cvt.rn.f16.f32 h3, f3;
  2376. setp.eq.u32 p0, b0, 255;
  2377. setp.eq.u32 p1, b1, 255;
  2378. setp.eq.u32 p2, b2, 255;
  2379. setp.eq.u32 p3, b3, 255;
  2380. selp.b16 h0, 0x7e00, h0, p0;
  2381. selp.b16 h1, 0x7e00, h1, p1;
  2382. selp.b16 h2, 0x7e00, h2, p2;
  2383. selp.b16 h3, 0x7e00, h3, p3;
  2384. mov.b32 $0, {h0, h2};
  2385. mov.b32 $1, {h1, h3};
  2386. }
  2387. """,
  2388. "=r,=r,r",
  2389. has_side_effects=False,
  2390. is_align_stack=False,
  2391. asm_dialect=llvm.AsmDialect.AD_ATT,
  2392. loc=loc,
  2393. ip=ip,
  2394. )
  2395. lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
  2396. hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  2397. return Uint32(lo), Uint32(hi)
  2398. @dsl_user_op
  2399. def bf16_mma_m16n8k16_f32(
  2400. d0: Float32,
  2401. d1: Float32,
  2402. d2: Float32,
  2403. d3: Float32,
  2404. a0: Uint32,
  2405. a1: Uint32,
  2406. a2: Uint32,
  2407. a3: Uint32,
  2408. b0: Uint32,
  2409. b1: Uint32,
  2410. *,
  2411. loc=None,
  2412. ip=None,
  2413. ) -> Tuple[Float32, Float32, Float32, Float32]:
  2414. """Warp MMA helper for `mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32`."""
  2415. result = llvm.inline_asm(
  2416. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  2417. [
  2418. Uint32(a0).ir_value(loc=loc, ip=ip),
  2419. Uint32(a1).ir_value(loc=loc, ip=ip),
  2420. Uint32(a2).ir_value(loc=loc, ip=ip),
  2421. Uint32(a3).ir_value(loc=loc, ip=ip),
  2422. Uint32(b0).ir_value(loc=loc, ip=ip),
  2423. Uint32(b1).ir_value(loc=loc, ip=ip),
  2424. Float32(d0).ir_value(loc=loc, ip=ip),
  2425. Float32(d1).ir_value(loc=loc, ip=ip),
  2426. Float32(d2).ir_value(loc=loc, ip=ip),
  2427. Float32(d3).ir_value(loc=loc, ip=ip),
  2428. ],
  2429. """
  2430. mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32
  2431. {$0, $1, $2, $3},
  2432. {$4, $5, $6, $7},
  2433. {$8, $9},
  2434. {$10, $11, $12, $13};
  2435. """,
  2436. "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
  2437. has_side_effects=False,
  2438. is_align_stack=False,
  2439. asm_dialect=llvm.AsmDialect.AD_ATT,
  2440. loc=loc,
  2441. ip=ip,
  2442. )
  2443. r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  2444. r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
  2445. r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
  2446. r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
  2447. return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
  2448. @dsl_user_op
  2449. def f32_to_raw_bits(x: Float32, *, loc=None, ip=None) -> Uint32:
  2450. """Return the raw IEEE-754 bits of one float32 value."""
  2451. return Uint32(
  2452. llvm.inline_asm(
  2453. T.i32(),
  2454. [Float32(x).ir_value(loc=loc, ip=ip)],
  2455. "mov.b32 $0, $1;",
  2456. "=r,f",
  2457. has_side_effects=False,
  2458. is_align_stack=False,
  2459. asm_dialect=llvm.AsmDialect.AD_ATT,
  2460. loc=loc,
  2461. ip=ip,
  2462. )
  2463. )
  2464. @dsl_user_op
  2465. def f32_to_tf32_bits(x: Float32, *, loc=None, ip=None) -> Uint32:
  2466. """Round one float32 value to TF32 format and return its 32-bit operand bits."""
  2467. return Uint32(
  2468. llvm.inline_asm(
  2469. T.i32(),
  2470. [Float32(x).ir_value(loc=loc, ip=ip)],
  2471. """
  2472. {
  2473. .reg .b32 tmp;
  2474. cvt.rna.tf32.f32 tmp, $1;
  2475. mov.b32 $0, tmp;
  2476. }
  2477. """,
  2478. "=r,f",
  2479. has_side_effects=False,
  2480. is_align_stack=False,
  2481. asm_dialect=llvm.AsmDialect.AD_ATT,
  2482. loc=loc,
  2483. ip=ip,
  2484. )
  2485. )
  2486. @dsl_user_op
  2487. def tf32_mma_m16n8k8_f32(
  2488. d0: Float32,
  2489. d1: Float32,
  2490. d2: Float32,
  2491. d3: Float32,
  2492. a0: Uint32,
  2493. a1: Uint32,
  2494. a2: Uint32,
  2495. a3: Uint32,
  2496. b0: Uint32,
  2497. b1: Uint32,
  2498. *,
  2499. loc=None,
  2500. ip=None,
  2501. ) -> Tuple[Float32, Float32, Float32, Float32]:
  2502. """Warp MMA helper for `mma.sync.aligned.m16n8k8.row.col.f32.tf32.tf32.f32`."""
  2503. result = llvm.inline_asm(
  2504. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  2505. [
  2506. Uint32(a0).ir_value(loc=loc, ip=ip),
  2507. Uint32(a1).ir_value(loc=loc, ip=ip),
  2508. Uint32(a2).ir_value(loc=loc, ip=ip),
  2509. Uint32(a3).ir_value(loc=loc, ip=ip),
  2510. Uint32(b0).ir_value(loc=loc, ip=ip),
  2511. Uint32(b1).ir_value(loc=loc, ip=ip),
  2512. Float32(d0).ir_value(loc=loc, ip=ip),
  2513. Float32(d1).ir_value(loc=loc, ip=ip),
  2514. Float32(d2).ir_value(loc=loc, ip=ip),
  2515. Float32(d3).ir_value(loc=loc, ip=ip),
  2516. ],
  2517. """
  2518. mma.sync.aligned.m16n8k8.row.col.f32.tf32.tf32.f32
  2519. {$0, $1, $2, $3},
  2520. {$4, $5, $6, $7},
  2521. {$8, $9},
  2522. {$10, $11, $12, $13};
  2523. """,
  2524. "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
  2525. has_side_effects=False,
  2526. is_align_stack=False,
  2527. asm_dialect=llvm.AsmDialect.AD_ATT,
  2528. loc=loc,
  2529. ip=ip,
  2530. )
  2531. r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  2532. r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
  2533. r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
  2534. r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
  2535. return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
  2536. @dsl_user_op
  2537. def f16_mma_m16n8k16_f32(
  2538. d0: Float32,
  2539. d1: Float32,
  2540. d2: Float32,
  2541. d3: Float32,
  2542. a0: Uint32,
  2543. a1: Uint32,
  2544. a2: Uint32,
  2545. a3: Uint32,
  2546. b0: Uint32,
  2547. b1: Uint32,
  2548. *,
  2549. loc=None,
  2550. ip=None,
  2551. ) -> Tuple[Float32, Float32, Float32, Float32]:
  2552. """Warp MMA helper for `mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32`."""
  2553. result = llvm.inline_asm(
  2554. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  2555. [
  2556. Uint32(a0).ir_value(loc=loc, ip=ip),
  2557. Uint32(a1).ir_value(loc=loc, ip=ip),
  2558. Uint32(a2).ir_value(loc=loc, ip=ip),
  2559. Uint32(a3).ir_value(loc=loc, ip=ip),
  2560. Uint32(b0).ir_value(loc=loc, ip=ip),
  2561. Uint32(b1).ir_value(loc=loc, ip=ip),
  2562. Float32(d0).ir_value(loc=loc, ip=ip),
  2563. Float32(d1).ir_value(loc=loc, ip=ip),
  2564. Float32(d2).ir_value(loc=loc, ip=ip),
  2565. Float32(d3).ir_value(loc=loc, ip=ip),
  2566. ],
  2567. """
  2568. mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32
  2569. {$0, $1, $2, $3},
  2570. {$4, $5, $6, $7},
  2571. {$8, $9},
  2572. {$10, $11, $12, $13};
  2573. """,
  2574. "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
  2575. has_side_effects=False,
  2576. is_align_stack=False,
  2577. asm_dialect=llvm.AsmDialect.AD_ATT,
  2578. loc=loc,
  2579. ip=ip,
  2580. )
  2581. r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  2582. r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
  2583. r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
  2584. r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
  2585. return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
  2586. @dsl_user_op
  2587. def bf16_mma_rhs_fragments_as_mma_a_m16n8k16_f32(
  2588. d0: Float32,
  2589. d1: Float32,
  2590. d2: Float32,
  2591. d3: Float32,
  2592. b0_0: Uint32,
  2593. b1_0: Uint32,
  2594. b0_1: Uint32,
  2595. b1_1: Uint32,
  2596. a0: Uint32,
  2597. a1: Uint32,
  2598. *,
  2599. loc=None,
  2600. ip=None,
  2601. ) -> Tuple[Float32, Float32, Float32, Float32]:
  2602. """BF16 MMA form used by the routed m-block-size-8 path.
  2603. The dequantized RHS fragments feed the hardware A operand, while the
  2604. routed activation fragment loaded with `ldmatrix.x2` feeds hardware B.
  2605. """
  2606. result = llvm.inline_asm(
  2607. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  2608. [
  2609. Uint32(b0_0).ir_value(loc=loc, ip=ip),
  2610. Uint32(b1_0).ir_value(loc=loc, ip=ip),
  2611. Uint32(b0_1).ir_value(loc=loc, ip=ip),
  2612. Uint32(b1_1).ir_value(loc=loc, ip=ip),
  2613. Uint32(a0).ir_value(loc=loc, ip=ip),
  2614. Uint32(a1).ir_value(loc=loc, ip=ip),
  2615. Float32(d0).ir_value(loc=loc, ip=ip),
  2616. Float32(d1).ir_value(loc=loc, ip=ip),
  2617. Float32(d2).ir_value(loc=loc, ip=ip),
  2618. Float32(d3).ir_value(loc=loc, ip=ip),
  2619. ],
  2620. """
  2621. mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32
  2622. {$0, $1, $2, $3},
  2623. {$4, $5, $6, $7},
  2624. {$8, $9},
  2625. {$10, $11, $12, $13};
  2626. """,
  2627. "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
  2628. has_side_effects=False,
  2629. is_align_stack=False,
  2630. asm_dialect=llvm.AsmDialect.AD_ATT,
  2631. loc=loc,
  2632. ip=ip,
  2633. )
  2634. r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  2635. r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
  2636. r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
  2637. r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
  2638. return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
  2639. @dsl_user_op
  2640. def f16_mma_rhs_fragments_as_mma_a_m16n8k16_f32(
  2641. d0: Float32,
  2642. d1: Float32,
  2643. d2: Float32,
  2644. d3: Float32,
  2645. b0_0: Uint32,
  2646. b1_0: Uint32,
  2647. b0_1: Uint32,
  2648. b1_1: Uint32,
  2649. a0: Uint32,
  2650. a1: Uint32,
  2651. *,
  2652. loc=None,
  2653. ip=None,
  2654. ) -> Tuple[Float32, Float32, Float32, Float32]:
  2655. """FP16 MMA form used by the routed m-block-size-8 path."""
  2656. result = llvm.inline_asm(
  2657. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  2658. [
  2659. Uint32(b0_0).ir_value(loc=loc, ip=ip),
  2660. Uint32(b1_0).ir_value(loc=loc, ip=ip),
  2661. Uint32(b0_1).ir_value(loc=loc, ip=ip),
  2662. Uint32(b1_1).ir_value(loc=loc, ip=ip),
  2663. Uint32(a0).ir_value(loc=loc, ip=ip),
  2664. Uint32(a1).ir_value(loc=loc, ip=ip),
  2665. Float32(d0).ir_value(loc=loc, ip=ip),
  2666. Float32(d1).ir_value(loc=loc, ip=ip),
  2667. Float32(d2).ir_value(loc=loc, ip=ip),
  2668. Float32(d3).ir_value(loc=loc, ip=ip),
  2669. ],
  2670. """
  2671. mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32
  2672. {$0, $1, $2, $3},
  2673. {$4, $5, $6, $7},
  2674. {$8, $9},
  2675. {$10, $11, $12, $13};
  2676. """,
  2677. "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
  2678. has_side_effects=False,
  2679. is_align_stack=False,
  2680. asm_dialect=llvm.AsmDialect.AD_ATT,
  2681. loc=loc,
  2682. ip=ip,
  2683. )
  2684. r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  2685. r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
  2686. r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
  2687. r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
  2688. return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
  2689. @dsl_user_op
  2690. def bf16_rowsum_m16k16_f32(
  2691. d0: Float32,
  2692. d1: Float32,
  2693. a0: Uint32,
  2694. a1: Uint32,
  2695. a2: Uint32,
  2696. a3: Uint32,
  2697. *,
  2698. loc=None,
  2699. ip=None,
  2700. ) -> Tuple[Float32, Float32]:
  2701. """Row-sum helper matching FlashInfer's m16k16 BF16 rowsum MMA."""
  2702. result = llvm.inline_asm(
  2703. llvm.StructType.get_literal([T.f32(), T.f32()]),
  2704. [
  2705. Uint32(a0).ir_value(loc=loc, ip=ip),
  2706. Uint32(a1).ir_value(loc=loc, ip=ip),
  2707. Uint32(a2).ir_value(loc=loc, ip=ip),
  2708. Uint32(a3).ir_value(loc=loc, ip=ip),
  2709. Uint32(1065369472).ir_value(loc=loc, ip=ip),
  2710. Uint32(1065369472).ir_value(loc=loc, ip=ip),
  2711. Float32(d0).ir_value(loc=loc, ip=ip),
  2712. Float32(d1).ir_value(loc=loc, ip=ip),
  2713. ],
  2714. """
  2715. {
  2716. mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32
  2717. {$0, _, $1, _},
  2718. {$2, $3, $4, $5},
  2719. {$6, $7},
  2720. {$0, 0., $1, 0.};
  2721. }
  2722. """,
  2723. "=f,=f,r,r,r,r,r,r,0,1",
  2724. has_side_effects=False,
  2725. is_align_stack=False,
  2726. asm_dialect=llvm.AsmDialect.AD_ATT,
  2727. loc=loc,
  2728. ip=ip,
  2729. )
  2730. r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  2731. r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
  2732. return Float32(r0), Float32(r1)
  2733. @dsl_user_op
  2734. def bf16_mma_m16n16k16_f32(
  2735. d0: Float32,
  2736. d1: Float32,
  2737. d2: Float32,
  2738. d3: Float32,
  2739. d4: Float32,
  2740. d5: Float32,
  2741. d6: Float32,
  2742. d7: Float32,
  2743. a0: Uint32,
  2744. a1: Uint32,
  2745. a2: Uint32,
  2746. a3: Uint32,
  2747. b0: Uint32,
  2748. b1: Uint32,
  2749. b2: Uint32,
  2750. b3: Uint32,
  2751. *,
  2752. loc=None,
  2753. ip=None,
  2754. ) -> Tuple[Float32, Float32, Float32, Float32, Float32, Float32, Float32, Float32]:
  2755. """Warp MMA helper matching FlashInfer's `m16n16k16` BF16/BF16->F32 wrapper."""
  2756. result0 = llvm.inline_asm(
  2757. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  2758. [
  2759. Uint32(a0).ir_value(loc=loc, ip=ip),
  2760. Uint32(a1).ir_value(loc=loc, ip=ip),
  2761. Uint32(a2).ir_value(loc=loc, ip=ip),
  2762. Uint32(a3).ir_value(loc=loc, ip=ip),
  2763. Uint32(b0).ir_value(loc=loc, ip=ip),
  2764. Uint32(b1).ir_value(loc=loc, ip=ip),
  2765. Float32(d0).ir_value(loc=loc, ip=ip),
  2766. Float32(d1).ir_value(loc=loc, ip=ip),
  2767. Float32(d2).ir_value(loc=loc, ip=ip),
  2768. Float32(d3).ir_value(loc=loc, ip=ip),
  2769. ],
  2770. """
  2771. mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32
  2772. {$0, $1, $2, $3},
  2773. {$4, $5, $6, $7},
  2774. {$8, $9},
  2775. {$10, $11, $12, $13};
  2776. """,
  2777. "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
  2778. has_side_effects=True,
  2779. is_align_stack=False,
  2780. asm_dialect=llvm.AsmDialect.AD_ATT,
  2781. loc=loc,
  2782. ip=ip,
  2783. )
  2784. result1 = llvm.inline_asm(
  2785. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  2786. [
  2787. Uint32(a0).ir_value(loc=loc, ip=ip),
  2788. Uint32(a1).ir_value(loc=loc, ip=ip),
  2789. Uint32(a2).ir_value(loc=loc, ip=ip),
  2790. Uint32(a3).ir_value(loc=loc, ip=ip),
  2791. Uint32(b2).ir_value(loc=loc, ip=ip),
  2792. Uint32(b3).ir_value(loc=loc, ip=ip),
  2793. Float32(d4).ir_value(loc=loc, ip=ip),
  2794. Float32(d5).ir_value(loc=loc, ip=ip),
  2795. Float32(d6).ir_value(loc=loc, ip=ip),
  2796. Float32(d7).ir_value(loc=loc, ip=ip),
  2797. ],
  2798. """
  2799. mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32
  2800. {$0, $1, $2, $3},
  2801. {$4, $5, $6, $7},
  2802. {$8, $9},
  2803. {$10, $11, $12, $13};
  2804. """,
  2805. "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
  2806. has_side_effects=True,
  2807. is_align_stack=False,
  2808. asm_dialect=llvm.AsmDialect.AD_ATT,
  2809. loc=loc,
  2810. ip=ip,
  2811. )
  2812. r0 = llvm.extractvalue(T.f32(), result0, [0], loc=loc, ip=ip)
  2813. r1 = llvm.extractvalue(T.f32(), result0, [1], loc=loc, ip=ip)
  2814. r2 = llvm.extractvalue(T.f32(), result0, [2], loc=loc, ip=ip)
  2815. r3 = llvm.extractvalue(T.f32(), result0, [3], loc=loc, ip=ip)
  2816. r4 = llvm.extractvalue(T.f32(), result1, [0], loc=loc, ip=ip)
  2817. r5 = llvm.extractvalue(T.f32(), result1, [1], loc=loc, ip=ip)
  2818. r6 = llvm.extractvalue(T.f32(), result1, [2], loc=loc, ip=ip)
  2819. r7 = llvm.extractvalue(T.f32(), result1, [3], loc=loc, ip=ip)
  2820. return (
  2821. Float32(r0),
  2822. Float32(r1),
  2823. Float32(r2),
  2824. Float32(r3),
  2825. Float32(r4),
  2826. Float32(r5),
  2827. Float32(r6),
  2828. Float32(r7),
  2829. )
  2830. @dsl_user_op
  2831. def mxfp8_mma_m16n8k32_f32_e4m3(
  2832. d0: Float32,
  2833. d1: Float32,
  2834. d2: Float32,
  2835. d3: Float32,
  2836. a0: Uint32,
  2837. a1: Uint32,
  2838. a2: Uint32,
  2839. a3: Uint32,
  2840. b0: Uint32,
  2841. b1: Uint32,
  2842. sfa: Uint32,
  2843. sfb: Uint32,
  2844. bid_a: int = 0,
  2845. tid_a: int = 0,
  2846. bid_b: int = 0,
  2847. tid_b: int = 0,
  2848. *,
  2849. loc=None,
  2850. ip=None,
  2851. ) -> Tuple[Float32, Float32, Float32, Float32]:
  2852. """Warp MMA helper for SM120 MXFP8 block-scaled `m16n8k32` E4M3/E4M3."""
  2853. i16_ty = cutlass._mlir.ir.IntegerType.get_signless(16)
  2854. bid_a_i16 = cutlass._mlir.ir.Operation.create(
  2855. "llvm.mlir.constant",
  2856. results=[i16_ty],
  2857. attributes={"value": cutlass._mlir.ir.IntegerAttr.get(i16_ty, int(bid_a))},
  2858. ).result
  2859. tid_a_i16 = cutlass._mlir.ir.Operation.create(
  2860. "llvm.mlir.constant",
  2861. results=[i16_ty],
  2862. attributes={"value": cutlass._mlir.ir.IntegerAttr.get(i16_ty, int(tid_a))},
  2863. ).result
  2864. bid_b_i16 = cutlass._mlir.ir.Operation.create(
  2865. "llvm.mlir.constant",
  2866. results=[i16_ty],
  2867. attributes={"value": cutlass._mlir.ir.IntegerAttr.get(i16_ty, int(bid_b))},
  2868. ).result
  2869. tid_b_i16 = cutlass._mlir.ir.Operation.create(
  2870. "llvm.mlir.constant",
  2871. results=[i16_ty],
  2872. attributes={"value": cutlass._mlir.ir.IntegerAttr.get(i16_ty, int(tid_b))},
  2873. ).result
  2874. result = llvm.inline_asm(
  2875. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  2876. [
  2877. Uint32(a0).ir_value(loc=loc, ip=ip),
  2878. Uint32(a1).ir_value(loc=loc, ip=ip),
  2879. Uint32(a2).ir_value(loc=loc, ip=ip),
  2880. Uint32(a3).ir_value(loc=loc, ip=ip),
  2881. Uint32(b0).ir_value(loc=loc, ip=ip),
  2882. Uint32(b1).ir_value(loc=loc, ip=ip),
  2883. Uint32(sfa).ir_value(loc=loc, ip=ip),
  2884. bid_a_i16,
  2885. tid_a_i16,
  2886. Uint32(sfb).ir_value(loc=loc, ip=ip),
  2887. bid_b_i16,
  2888. tid_b_i16,
  2889. Float32(d0).ir_value(loc=loc, ip=ip),
  2890. Float32(d1).ir_value(loc=loc, ip=ip),
  2891. Float32(d2).ir_value(loc=loc, ip=ip),
  2892. Float32(d3).ir_value(loc=loc, ip=ip),
  2893. ],
  2894. """
  2895. mma.sync.aligned.kind::mxf8f6f4.block_scale.scale_vec::1X.m16n8k32.row.col.f32.e4m3.e4m3.f32.ue8m0
  2896. {$0, $1, $2, $3},
  2897. {$4, $5, $6, $7},
  2898. {$8, $9},
  2899. {$0, $1, $2, $3},
  2900. {$10},
  2901. {$11, $12},
  2902. {$13},
  2903. {$14, $15};
  2904. """,
  2905. "=f,=f,=f,=f,r,r,r,r,r,r,r,h,h,r,h,h,0,1,2,3",
  2906. has_side_effects=False,
  2907. is_align_stack=False,
  2908. asm_dialect=llvm.AsmDialect.AD_ATT,
  2909. loc=loc,
  2910. ip=ip,
  2911. )
  2912. r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  2913. r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
  2914. r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
  2915. r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
  2916. return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
  2917. @dsl_user_op
  2918. def mxfp8_mma_m16n8k32_f32_e2m1(
  2919. d0: Float32,
  2920. d1: Float32,
  2921. d2: Float32,
  2922. d3: Float32,
  2923. a0: Uint32,
  2924. a1: Uint32,
  2925. a2: Uint32,
  2926. a3: Uint32,
  2927. b0: Uint32,
  2928. b1: Uint32,
  2929. sfa: Uint32,
  2930. sfb: Uint32,
  2931. bid_a: int = 0,
  2932. tid_a: int = 0,
  2933. bid_b: int = 0,
  2934. tid_b: int = 0,
  2935. *,
  2936. loc=None,
  2937. ip=None,
  2938. ) -> Tuple[Float32, Float32, Float32, Float32]:
  2939. """SM120 block-scaled QMMA with E4M3 A and E2M1 B containers."""
  2940. asm = f"""
  2941. mma.sync.aligned.kind::mxf8f6f4.block_scale.scale_vec::1X.m16n8k32.row.col.f32.e4m3.e2m1.f32.ue8m0
  2942. {{$0, $1, $2, $3}},
  2943. {{$4, $5, $6, $7}},
  2944. {{$8, $9}},
  2945. {{$0, $1, $2, $3}},
  2946. {{$10}},
  2947. {{{int(bid_a)}, {int(tid_a)}}},
  2948. {{$11}},
  2949. {{{int(bid_b)}, {int(tid_b)}}};
  2950. """
  2951. result = llvm.inline_asm(
  2952. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  2953. [
  2954. Uint32(a0).ir_value(loc=loc, ip=ip),
  2955. Uint32(a1).ir_value(loc=loc, ip=ip),
  2956. Uint32(a2).ir_value(loc=loc, ip=ip),
  2957. Uint32(a3).ir_value(loc=loc, ip=ip),
  2958. Uint32(b0).ir_value(loc=loc, ip=ip),
  2959. Uint32(b1).ir_value(loc=loc, ip=ip),
  2960. Uint32(sfa).ir_value(loc=loc, ip=ip),
  2961. Uint32(sfb).ir_value(loc=loc, ip=ip),
  2962. Float32(d0).ir_value(loc=loc, ip=ip),
  2963. Float32(d1).ir_value(loc=loc, ip=ip),
  2964. Float32(d2).ir_value(loc=loc, ip=ip),
  2965. Float32(d3).ir_value(loc=loc, ip=ip),
  2966. ],
  2967. asm,
  2968. "=f,=f,=f,=f,r,r,r,r,r,r,r,r,0,1,2,3",
  2969. has_side_effects=False,
  2970. is_align_stack=False,
  2971. asm_dialect=llvm.AsmDialect.AD_ATT,
  2972. loc=loc,
  2973. ip=ip,
  2974. )
  2975. return tuple(
  2976. Float32(llvm.extractvalue(T.f32(), result, [i], loc=loc, ip=ip))
  2977. for i in range(4)
  2978. )
  2979. @dsl_user_op
  2980. def mma_m16n8k32_f32_e4m3(
  2981. d0: Float32,
  2982. d1: Float32,
  2983. d2: Float32,
  2984. d3: Float32,
  2985. a0: Uint32,
  2986. a1: Uint32,
  2987. a2: Uint32,
  2988. a3: Uint32,
  2989. b0: Uint32,
  2990. b1: Uint32,
  2991. *,
  2992. loc=None,
  2993. ip=None,
  2994. ) -> Tuple[Float32, Float32, Float32, Float32]:
  2995. """Plain (non-block-scaled) SM120 FP8 E4M3 warp MMA `m16n8k32`.
  2996. d{0..3} are the accumulator IN-OUT (C on input, D on output)."""
  2997. result = llvm.inline_asm(
  2998. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  2999. [
  3000. Uint32(a0).ir_value(loc=loc, ip=ip),
  3001. Uint32(a1).ir_value(loc=loc, ip=ip),
  3002. Uint32(a2).ir_value(loc=loc, ip=ip),
  3003. Uint32(a3).ir_value(loc=loc, ip=ip),
  3004. Uint32(b0).ir_value(loc=loc, ip=ip),
  3005. Uint32(b1).ir_value(loc=loc, ip=ip),
  3006. Float32(d0).ir_value(loc=loc, ip=ip),
  3007. Float32(d1).ir_value(loc=loc, ip=ip),
  3008. Float32(d2).ir_value(loc=loc, ip=ip),
  3009. Float32(d3).ir_value(loc=loc, ip=ip),
  3010. ],
  3011. """
  3012. mma.sync.aligned.m16n8k32.row.col.f32.e4m3.e4m3.f32
  3013. {$0, $1, $2, $3},
  3014. {$4, $5, $6, $7},
  3015. {$8, $9},
  3016. {$0, $1, $2, $3};
  3017. """,
  3018. "=f,=f,=f,=f,r,r,r,r,r,r,0,1,2,3",
  3019. has_side_effects=False,
  3020. is_align_stack=False,
  3021. asm_dialect=llvm.AsmDialect.AD_ATT,
  3022. loc=loc,
  3023. ip=ip,
  3024. )
  3025. r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  3026. r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
  3027. r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
  3028. r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
  3029. return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
  3030. # ``mma_m16n8k16_f32_bf16`` was proven during the SM120 sparse-MLA port to be
  3031. # bit-identical (same signature, same emitted
  3032. # ``mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32``, same constraint
  3033. # string and operand grouping) to the pre-existing ``bf16_mma_m16n8k16_f32``
  3034. # above. Expose it under the port's name as a thin alias so the sparse-MLA
  3035. # kernels can import it without duplicating the inline asm.
  3036. mma_m16n8k16_f32_bf16 = bf16_mma_m16n8k16_f32
  3037. @dsl_user_op
  3038. def byte_perm(a: Uint32, b: Uint32, selector: Int32, *, loc=None, ip=None) -> Uint32:
  3039. """PTX byte permutation helper."""
  3040. return Uint32(
  3041. llvm.inline_asm(
  3042. T.i32(),
  3043. [
  3044. Uint32(a).ir_value(loc=loc, ip=ip),
  3045. Uint32(b).ir_value(loc=loc, ip=ip),
  3046. Int32(selector).ir_value(loc=loc, ip=ip),
  3047. ],
  3048. "prmt.b32 $0, $1, $2, $3;",
  3049. "=r,r,r,r",
  3050. has_side_effects=False,
  3051. is_align_stack=False,
  3052. asm_dialect=llvm.AsmDialect.AD_ATT,
  3053. )
  3054. )
  3055. @cute.jit
  3056. def frag_layout_swizzle_16b_to_8b(x: Uint32) -> Uint32:
  3057. tmp = cute.arch.shuffle_sync_bfly(x, offset=1)
  3058. x = byte_perm(x, tmp, Int32(0x5410 if cute.arch.lane_idx() & 0x1 == 0 else 0x3276))
  3059. tmp = cute.arch.shuffle_sync_bfly(x, offset=2)
  3060. x = byte_perm(x, tmp, Int32(0x5410 if cute.arch.lane_idx() & 0x2 == 0 else 0x3276))
  3061. return x
  3062. @cute.jit
  3063. def frag_layout_swizzle_16b_to_8b_trans(x: Uint32) -> Uint32:
  3064. tmp = cute.arch.shuffle_sync_bfly(x, offset=4)
  3065. x = byte_perm(x, tmp, Int32(0x6420 if cute.arch.lane_idx() & 0x4 == 0 else 0x3175))
  3066. tmp = cute.arch.shuffle_sync_bfly(x, offset=8)
  3067. x = byte_perm(x, tmp, Int32(0x5410 if cute.arch.lane_idx() & 0x8 == 0 else 0x3276))
  3068. tmp = cute.arch.shuffle_sync_bfly(x, offset=16)
  3069. x = byte_perm(x, tmp, Int32(0x5410 if cute.arch.lane_idx() & 0x10 == 0 else 0x3276))
  3070. return x
  3071. @dsl_user_op
  3072. def bfloat2_habs2(x: Uint32, *, loc=None, ip=None) -> Uint32:
  3073. """BFloat16x2 absolute value - clears sign bits of both bf16 values."""
  3074. return Uint32(
  3075. llvm.inline_asm(
  3076. T.i32(),
  3077. [Uint32(x).ir_value(loc=loc, ip=ip)],
  3078. "and.b32 $0, $1, 0x7FFF7FFF;",
  3079. "=r,r",
  3080. has_side_effects=False,
  3081. is_align_stack=False,
  3082. asm_dialect=llvm.AsmDialect.AD_ATT,
  3083. )
  3084. )
  3085. @dsl_user_op
  3086. def bfloat2_hmax2(a: Uint32, b: Uint32, *, loc=None, ip=None) -> Uint32:
  3087. """BFloat16x2 max - element-wise max of 2 bf16 pairs."""
  3088. return Uint32(
  3089. llvm.inline_asm(
  3090. T.i32(),
  3091. [Uint32(a).ir_value(loc=loc, ip=ip), Uint32(b).ir_value(loc=loc, ip=ip)],
  3092. "max.bf16x2 $0, $1, $2;",
  3093. "=r,r,r",
  3094. has_side_effects=False,
  3095. is_align_stack=False,
  3096. asm_dialect=llvm.AsmDialect.AD_ATT,
  3097. )
  3098. )
  3099. @dsl_user_op
  3100. def bfloat2_hmax_to_f32(x: Uint32, *, loc=None, ip=None) -> Float32:
  3101. """Extract max of 2 bf16 values in bfloat2 as float32."""
  3102. return Float32(
  3103. llvm.inline_asm(
  3104. T.f32(),
  3105. [Uint32(x).ir_value(loc=loc, ip=ip)],
  3106. """
  3107. {
  3108. .reg .b32 lo, hi;
  3109. .reg .f32 f0, f1;
  3110. and.b32 lo, $1, 0xFFFF;
  3111. shr.b32 hi, $1, 16;
  3112. shl.b32 lo, lo, 16;
  3113. shl.b32 hi, hi, 16;
  3114. mov.b32 f0, lo;
  3115. mov.b32 f1, hi;
  3116. max.f32 $0, f0, f1;
  3117. }
  3118. """,
  3119. "=f,r",
  3120. has_side_effects=False,
  3121. is_align_stack=False,
  3122. asm_dialect=llvm.AsmDialect.AD_ATT,
  3123. )
  3124. )
  3125. @dsl_user_op
  3126. def bfloat2_to_float2_scaled(
  3127. bf2: Uint32, scale: Float32, *, loc=None, ip=None
  3128. ) -> Tuple[Float32, Float32]:
  3129. """Convert bfloat16x2 to float2 AND multiply by scale."""
  3130. result = llvm.inline_asm(
  3131. llvm.StructType.get_literal([T.f32(), T.f32()]),
  3132. [Uint32(bf2).ir_value(loc=loc, ip=ip), Float32(scale).ir_value(loc=loc, ip=ip)],
  3133. """
  3134. {
  3135. .reg .b32 lo, hi;
  3136. .reg .f32 f0, f1;
  3137. and.b32 lo, $2, 0xFFFF;
  3138. shr.b32 hi, $2, 16;
  3139. shl.b32 lo, lo, 16;
  3140. shl.b32 hi, hi, 16;
  3141. mov.b32 f0, lo;
  3142. mov.b32 f1, hi;
  3143. mul.f32 $0, f0, $3;
  3144. mul.f32 $1, f1, $3;
  3145. }
  3146. """,
  3147. "=f,=f,r,f",
  3148. has_side_effects=False,
  3149. is_align_stack=False,
  3150. asm_dialect=llvm.AsmDialect.AD_ATT,
  3151. loc=loc,
  3152. ip=ip,
  3153. )
  3154. f0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  3155. f1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
  3156. return Float32(f0), Float32(f1)
  3157. # =============================================================================
  3158. # FP8 E4M3 Intrinsics
  3159. # =============================================================================
  3160. @dsl_user_op
  3161. def cvt_f32_to_e4m3(a: Float32, *, loc=None, ip=None) -> Uint32:
  3162. """Convert float32 to E4M3 using native cvt.rn.satfinite.e4m3x2.f32."""
  3163. return Uint32(
  3164. llvm.inline_asm(
  3165. T.i32(),
  3166. [Float32(a).ir_value(loc=loc, ip=ip)],
  3167. """
  3168. {
  3169. .reg .b16 fp8_pair;
  3170. .reg .f32 zero;
  3171. mov.f32 zero, 0f00000000;
  3172. cvt.rn.satfinite.e4m3x2.f32 fp8_pair, zero, $1;
  3173. cvt.u32.u16 $0, fp8_pair;
  3174. }
  3175. """,
  3176. "=r,f",
  3177. has_side_effects=False,
  3178. is_align_stack=False,
  3179. asm_dialect=llvm.AsmDialect.AD_ATT,
  3180. )
  3181. )
  3182. @dsl_user_op
  3183. def cvt_f32x4_to_e4m3x4(
  3184. v0: Float32,
  3185. v1: Float32,
  3186. v2: Float32,
  3187. v3: Float32,
  3188. *,
  3189. loc=None,
  3190. ip=None,
  3191. ) -> Uint32:
  3192. """Convert 4 float32 values to 4 packed E4M3 bytes (v0 in the low byte)."""
  3193. return Uint32(
  3194. llvm.inline_asm(
  3195. T.i32(),
  3196. [
  3197. Float32(v0).ir_value(loc=loc, ip=ip),
  3198. Float32(v1).ir_value(loc=loc, ip=ip),
  3199. Float32(v2).ir_value(loc=loc, ip=ip),
  3200. Float32(v3).ir_value(loc=loc, ip=ip),
  3201. ],
  3202. """
  3203. {
  3204. .reg .b16 lo, hi;
  3205. cvt.rn.satfinite.e4m3x2.f32 lo, $2, $1;
  3206. cvt.rn.satfinite.e4m3x2.f32 hi, $4, $3;
  3207. mov.b32 $0, {lo, hi};
  3208. }
  3209. """,
  3210. "=r,f,f,f,f",
  3211. has_side_effects=False,
  3212. is_align_stack=False,
  3213. asm_dialect=llvm.AsmDialect.AD_ATT,
  3214. loc=loc,
  3215. ip=ip,
  3216. )
  3217. )
  3218. @dsl_user_op
  3219. def nvfp4_scale_from_amax(block_amax: Float32, global_scale: Float32, *, loc=None, ip=None) -> Float32:
  3220. """Compute block_amax * reciprocal_global_scale / 6 with CUDA tensor-scalar semantics."""
  3221. return Float32(
  3222. llvm.inline_asm(
  3223. T.f32(),
  3224. [
  3225. Float32(block_amax).ir_value(loc=loc, ip=ip),
  3226. Float32(global_scale).ir_value(loc=loc, ip=ip),
  3227. ],
  3228. """
  3229. {
  3230. .reg .f64 amax_d, gs_d, q_d, six_d;
  3231. cvt.f64.f32 amax_d, $1;
  3232. cvt.f64.f32 gs_d, $2;
  3233. mov.f64 six_d, 0d4018000000000000;
  3234. mul.rn.f64 q_d, amax_d, gs_d;
  3235. div.rn.f64 q_d, q_d, six_d;
  3236. cvt.rn.f32.f64 $0, q_d;
  3237. }
  3238. """,
  3239. "=f,f,f",
  3240. has_side_effects=False,
  3241. is_align_stack=False,
  3242. asm_dialect=llvm.AsmDialect.AD_ATT,
  3243. loc=loc,
  3244. ip=ip,
  3245. )
  3246. )
  3247. @dsl_user_op
  3248. def fp8_e4m3_to_f32(fp8_val: Uint32, *, loc=None, ip=None) -> Float32:
  3249. """Convert FP8 E4M3 to float32."""
  3250. return Float32(
  3251. llvm.inline_asm(
  3252. T.f32(),
  3253. [Uint32(fp8_val).ir_value(loc=loc, ip=ip)],
  3254. """
  3255. {
  3256. .reg .pred p_zero, p_neg;
  3257. .reg .u32 sign_u, exp_u, mant_u;
  3258. .reg .s32 exp_s;
  3259. .reg .f32 exp_f, mant_f, fp8_float, fp8_neg;
  3260. setp.eq.u32 p_zero, $1, 0;
  3261. and.b32 sign_u, $1, 0x80;
  3262. and.b32 mant_u, $1, 7;
  3263. shr.b32 exp_u, $1, 3;
  3264. and.b32 exp_u, exp_u, 15;
  3265. sub.s32 exp_s, exp_u, 7;
  3266. cvt.rn.f32.s32 exp_f, exp_s;
  3267. ex2.approx.f32 exp_f, exp_f;
  3268. cvt.rn.f32.u32 mant_f, mant_u;
  3269. fma.rn.f32 mant_f, mant_f, 0f3E000000, 0f3F800000;
  3270. mul.f32 fp8_float, exp_f, mant_f;
  3271. neg.f32 fp8_neg, fp8_float;
  3272. setp.ne.u32 p_neg, sign_u, 0;
  3273. selp.f32 fp8_float, fp8_neg, fp8_float, p_neg;
  3274. selp.f32 $0, 0f00000000, fp8_float, p_zero;
  3275. }
  3276. """,
  3277. "=f,r",
  3278. has_side_effects=False,
  3279. is_align_stack=False,
  3280. asm_dialect=llvm.AsmDialect.AD_ATT,
  3281. )
  3282. )
  3283. @dsl_user_op
  3284. def fp8_e4m3_to_f32_and_rcp(fp8_val: Uint32, *, loc=None, ip=None) -> Float32:
  3285. """Convert FP8 E4M3 to float32 AND compute reciprocal."""
  3286. return Float32(
  3287. llvm.inline_asm(
  3288. T.f32(),
  3289. [Uint32(fp8_val).ir_value(loc=loc, ip=ip)],
  3290. """
  3291. {
  3292. .reg .pred p_zero;
  3293. .reg .u32 exp_u, mant_u;
  3294. .reg .s32 exp_s;
  3295. .reg .f32 exp_f, mant_f, fp8_float, result;
  3296. setp.eq.u32 p_zero, $1, 0;
  3297. and.b32 mant_u, $1, 7;
  3298. shr.b32 exp_u, $1, 3;
  3299. and.b32 exp_u, exp_u, 15;
  3300. sub.s32 exp_s, exp_u, 7;
  3301. cvt.rn.f32.s32 exp_f, exp_s;
  3302. ex2.approx.f32 exp_f, exp_f;
  3303. cvt.rn.f32.u32 mant_f, mant_u;
  3304. fma.rn.f32 mant_f, mant_f, 0f3E000000, 0f3F800000;
  3305. mul.f32 fp8_float, exp_f, mant_f;
  3306. rcp.approx.ftz.f32 result, fp8_float;
  3307. selp.f32 $0, 0f00000000, result, p_zero;
  3308. }
  3309. """,
  3310. "=f,r",
  3311. has_side_effects=False,
  3312. is_align_stack=False,
  3313. asm_dialect=llvm.AsmDialect.AD_ATT,
  3314. )
  3315. )
  3316. # =============================================================================
  3317. # E4M3 -> F16/F32 Native Conversion Intrinsics
  3318. # =============================================================================
  3319. @dsl_user_op
  3320. def cvt_e4m3x2_to_f16x2_pair(
  3321. packed_u32: Uint32, *, loc=None, ip=None
  3322. ) -> Tuple[Uint32, Uint32]:
  3323. """Decode 4 e4m3 bytes (packed u32) into 2 x f16x2 pairs."""
  3324. res = llvm.inline_asm(
  3325. ir.Type.parse("!llvm.struct<(i32, i32)>"),
  3326. [Uint32(packed_u32).ir_value(loc=loc, ip=ip)],
  3327. """
  3328. {
  3329. .reg .b16 b16_01, b16_23;
  3330. .reg .b32 hi32;
  3331. cvt.u16.u32 b16_01, $2;
  3332. shr.b32 hi32, $2, 16;
  3333. cvt.u16.u32 b16_23, hi32;
  3334. cvt.rn.f16x2.e4m3x2 $0, b16_01;
  3335. cvt.rn.f16x2.e4m3x2 $1, b16_23;
  3336. }
  3337. """,
  3338. "=r,=r,r",
  3339. has_side_effects=False,
  3340. is_align_stack=False,
  3341. asm_dialect=llvm.AsmDialect.AD_ATT,
  3342. loc=loc,
  3343. ip=ip,
  3344. )
  3345. return (
  3346. Uint32(llvm.extractvalue(T.i32(), res, [0], loc=loc, ip=ip)),
  3347. Uint32(llvm.extractvalue(T.i32(), res, [1], loc=loc, ip=ip)),
  3348. )
  3349. @dsl_user_op
  3350. def f16x2_to_f32x2(
  3351. packed_h2: Uint32, *, loc=None, ip=None
  3352. ) -> Tuple[Float32, Float32]:
  3353. """Unpack f16x2 (u32) to two f32 values."""
  3354. res = llvm.inline_asm(
  3355. ir.Type.parse("!llvm.struct<(f32, f32)>"),
  3356. [Uint32(packed_h2).ir_value(loc=loc, ip=ip)],
  3357. """
  3358. {
  3359. .reg .b16 lo, hi;
  3360. mov.b32 {lo, hi}, $2;
  3361. cvt.f32.f16 $0, lo;
  3362. cvt.f32.f16 $1, hi;
  3363. }
  3364. """,
  3365. "=f,=f,r",
  3366. has_side_effects=False,
  3367. is_align_stack=False,
  3368. asm_dialect=llvm.AsmDialect.AD_ATT,
  3369. loc=loc,
  3370. ip=ip,
  3371. )
  3372. return (
  3373. Float32(llvm.extractvalue(T.f32(), res, [0], loc=loc, ip=ip)),
  3374. Float32(llvm.extractvalue(T.f32(), res, [1], loc=loc, ip=ip)),
  3375. )
  3376. @cute.jit
  3377. def cvt_e4m3x4_to_f32x4(
  3378. packed_u32: Uint32,
  3379. ) -> Tuple[Float32, Float32, Float32, Float32]:
  3380. """Decode 4 e4m3 bytes (packed u32) to 4 x f32 via hw-native e4m3->f16->f32."""
  3381. h01, h23 = cvt_e4m3x2_to_f16x2_pair(packed_u32)
  3382. f0, f1 = f16x2_to_f32x2(h01)
  3383. f2, f3 = f16x2_to_f32x2(h23)
  3384. return f0, f1, f2, f3
  3385. @dsl_user_op
  3386. def cvt_e4m3_to_f32_via_f16(
  3387. fp8_val: Uint32, *, loc=None, ip=None
  3388. ) -> Float32:
  3389. """Convert single E4M3 byte to f32 via hw-native cvt.rn.f16x2.e4m3x2."""
  3390. return Float32(
  3391. llvm.inline_asm(
  3392. T.f32(),
  3393. [Uint32(fp8_val).ir_value(loc=loc, ip=ip)],
  3394. """
  3395. {
  3396. .reg .b16 fp8_pair;
  3397. .reg .b32 h2;
  3398. .reg .b16 lo, hi;
  3399. cvt.u16.u32 fp8_pair, $1;
  3400. cvt.rn.f16x2.e4m3x2 h2, fp8_pair;
  3401. mov.b32 {lo, hi}, h2;
  3402. cvt.f32.f16 $0, lo;
  3403. }
  3404. """,
  3405. "=f,r",
  3406. has_side_effects=False,
  3407. is_align_stack=False,
  3408. asm_dialect=llvm.AsmDialect.AD_ATT,
  3409. loc=loc,
  3410. ip=ip,
  3411. )
  3412. )
  3413. @dsl_user_op
  3414. def cvt_w4a16_packed_e4m3_scale_to_f32(
  3415. packed_byte: Uint32, *, loc=None, ip=None
  3416. ) -> Float32:
  3417. """Scalar mirror of packed_dequant_e4m3x4_to_bfloat2x2 for W4A16 scales."""
  3418. return Float32(
  3419. llvm.inline_asm(
  3420. T.f32(),
  3421. [Uint32(packed_byte).ir_value(loc=loc, ip=ip)],
  3422. """
  3423. {
  3424. .reg .b32 bits, tmp;
  3425. .reg .b16 bf;
  3426. and.b32 bits, $1, 0x80;
  3427. shl.b32 bits, bits, 7;
  3428. and.b32 tmp, $1, 0x7f;
  3429. shl.b32 tmp, tmp, 4;
  3430. or.b32 bits, bits, tmp;
  3431. cvt.u16.u32 bf, bits;
  3432. cvt.f32.bf16 $0, bf;
  3433. }
  3434. """,
  3435. "=f,r",
  3436. has_side_effects=False,
  3437. is_align_stack=False,
  3438. asm_dialect=llvm.AsmDialect.AD_ATT,
  3439. loc=loc,
  3440. ip=ip,
  3441. )
  3442. )
  3443. @dsl_user_op
  3444. def dequant_kv_e4m3_pair_to_bf16x2(
  3445. p0: Uint16, p1: Uint16, scale_f: Float32, *, loc=None, ip=None
  3446. ) -> Tuple[Uint32, Uint32]:
  3447. """FP8 -> BF16 K dequant for the BF16-QK m16n8k16 B operand.
  3448. Byte-for-byte mirror of FlashInfer's sparse_mla prefill BF16-QK inline
  3449. sequence (prefill_kernel.cuh:240-251): two ``cvt.rn.f16x2.e4m3x2`` decode
  3450. the two e4m3 byte-pairs ``p0``/``p1`` (each a u16 holding two consecutive
  3451. e4m3 K-dims), multiply the four resulting f16 lanes by the per-(token,blk)
  3452. UE8M0 ``scale_f`` in f32, then two ``cvt.rn.bf16x2.f32`` pack them back to
  3453. the bf16x2 ``b0``/``b1`` MMA operands. NON-saturating cvt (matches the
  3454. reference; the K magnitudes are bounded by the e4m3 range so satfinite is a
  3455. no-op, and the ref emits plain ``cvt.rn.bf16x2.f32``)."""
  3456. res = llvm.inline_asm(
  3457. ir.Type.parse("!llvm.struct<(i32, i32)>"),
  3458. [
  3459. Uint16(p0).ir_value(loc=loc, ip=ip),
  3460. Uint16(p1).ir_value(loc=loc, ip=ip),
  3461. Float32(scale_f).ir_value(loc=loc, ip=ip),
  3462. ],
  3463. """
  3464. {
  3465. .reg .b32 h2_0, h2_1;
  3466. .reg .b16 l0, h0, l1, h1;
  3467. .reg .f32 fk0, fk1, fk2, fk3;
  3468. cvt.rn.f16x2.e4m3x2 h2_0, $2;
  3469. cvt.rn.f16x2.e4m3x2 h2_1, $3;
  3470. mov.b32 {l0, h0}, h2_0;
  3471. mov.b32 {l1, h1}, h2_1;
  3472. cvt.f32.f16 fk0, l0;
  3473. cvt.f32.f16 fk1, h0;
  3474. cvt.f32.f16 fk2, l1;
  3475. cvt.f32.f16 fk3, h1;
  3476. mul.f32 fk0, fk0, $4;
  3477. mul.f32 fk1, fk1, $4;
  3478. mul.f32 fk2, fk2, $4;
  3479. mul.f32 fk3, fk3, $4;
  3480. cvt.rn.bf16x2.f32 $0, fk1, fk0;
  3481. cvt.rn.bf16x2.f32 $1, fk3, fk2;
  3482. }
  3483. """,
  3484. "=r,=r,h,h,f",
  3485. has_side_effects=False,
  3486. is_align_stack=False,
  3487. asm_dialect=llvm.AsmDialect.AD_ATT,
  3488. loc=loc,
  3489. ip=ip,
  3490. )
  3491. return (
  3492. Uint32(llvm.extractvalue(T.i32(), res, [0], loc=loc, ip=ip)),
  3493. Uint32(llvm.extractvalue(T.i32(), res, [1], loc=loc, ip=ip)),
  3494. )
  3495. @dsl_user_op
  3496. def cvt_e8m0_to_f32(e8m0_val: Uint32, *, loc=None, ip=None) -> Float32:
  3497. """Convert a single E8M0 scale byte to its true f32 value 2**(byte-127).
  3498. Byte 0 maps to 0.0. Unlike packed_dequant_e8m0x4_* (which fold a 2**7 bias
  3499. into the MMA input), this returns the unbiased scale so callers can apply it
  3500. directly in an f32 accumulator.
  3501. """
  3502. return Float32(
  3503. llvm.inline_asm(
  3504. T.f32(),
  3505. [Uint32(e8m0_val).ir_value(loc=loc, ip=ip)],
  3506. """
  3507. {
  3508. .reg .pred p0;
  3509. .reg .u32 b0;
  3510. .reg .s32 e0;
  3511. .reg .f32 ef0;
  3512. and.b32 b0, $1, 0x000000ff;
  3513. setp.eq.u32 p0, b0, 0;
  3514. cvt.s32.u32 e0, b0;
  3515. sub.s32 e0, e0, 127;
  3516. cvt.rn.f32.s32 ef0, e0;
  3517. ex2.approx.f32 $0, ef0;
  3518. selp.f32 $0, 0f00000000, $0, p0;
  3519. }
  3520. """,
  3521. "=f,r",
  3522. has_side_effects=False,
  3523. is_align_stack=False,
  3524. asm_dialect=llvm.AsmDialect.AD_ATT,
  3525. loc=loc,
  3526. ip=ip,
  3527. )
  3528. )
  3529. @dsl_user_op
  3530. def cvt_e8m0x4_to_f32x4(
  3531. packed: Uint32, *, loc=None, ip=None
  3532. ) -> Tuple[Float32, Float32, Float32, Float32]:
  3533. """Decode 4 E8M0 scale bytes (packed u32) to 4 x true f32 (2**(byte-127))."""
  3534. result = llvm.inline_asm(
  3535. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  3536. [Uint32(packed).ir_value(loc=loc, ip=ip)],
  3537. """
  3538. {
  3539. .reg .pred p0, p1, p2, p3;
  3540. .reg .u32 b0, b1, b2, b3;
  3541. .reg .s32 e0, e1, e2, e3;
  3542. .reg .f32 ef0, ef1, ef2, ef3;
  3543. and.b32 b0, $4, 0x000000ff;
  3544. shr.u32 b1, $4, 8;
  3545. and.b32 b1, b1, 0x000000ff;
  3546. shr.u32 b2, $4, 16;
  3547. and.b32 b2, b2, 0x000000ff;
  3548. shr.u32 b3, $4, 24;
  3549. setp.eq.u32 p0, b0, 0;
  3550. setp.eq.u32 p1, b1, 0;
  3551. setp.eq.u32 p2, b2, 0;
  3552. setp.eq.u32 p3, b3, 0;
  3553. cvt.s32.u32 e0, b0;
  3554. cvt.s32.u32 e1, b1;
  3555. cvt.s32.u32 e2, b2;
  3556. cvt.s32.u32 e3, b3;
  3557. sub.s32 e0, e0, 127;
  3558. sub.s32 e1, e1, 127;
  3559. sub.s32 e2, e2, 127;
  3560. sub.s32 e3, e3, 127;
  3561. cvt.rn.f32.s32 ef0, e0;
  3562. cvt.rn.f32.s32 ef1, e1;
  3563. cvt.rn.f32.s32 ef2, e2;
  3564. cvt.rn.f32.s32 ef3, e3;
  3565. ex2.approx.f32 $0, ef0;
  3566. ex2.approx.f32 $1, ef1;
  3567. ex2.approx.f32 $2, ef2;
  3568. ex2.approx.f32 $3, ef3;
  3569. selp.f32 $0, 0f00000000, $0, p0;
  3570. selp.f32 $1, 0f00000000, $1, p1;
  3571. selp.f32 $2, 0f00000000, $2, p2;
  3572. selp.f32 $3, 0f00000000, $3, p3;
  3573. }
  3574. """,
  3575. "=f,=f,=f,=f,r",
  3576. has_side_effects=False,
  3577. is_align_stack=False,
  3578. asm_dialect=llvm.AsmDialect.AD_ATT,
  3579. loc=loc,
  3580. ip=ip,
  3581. )
  3582. return (
  3583. Float32(llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)),
  3584. Float32(llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)),
  3585. Float32(llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)),
  3586. Float32(llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)),
  3587. )
  3588. # =============================================================================
  3589. # FP4 (E2M1) Decode Intrinsics
  3590. # =============================================================================
  3591. @dsl_user_op
  3592. def fp4_decode_4bytes(
  3593. packed_u32: Uint32, *, loc=None, ip=None
  3594. ) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
  3595. """Decode 4 FP4 bytes (packed u32) into 4 x f16x2 pairs."""
  3596. res = llvm.inline_asm(
  3597. ir.Type.parse("!llvm.struct<(i32, i32, i32, i32)>"),
  3598. [Uint32(packed_u32).ir_value(loc=loc, ip=ip)],
  3599. """
  3600. {
  3601. .reg .b8 byte0, byte1, byte2, byte3;
  3602. mov.b32 {byte0, byte1, byte2, byte3}, $4;
  3603. cvt.rn.f16x2.e2m1x2 $0, byte0;
  3604. cvt.rn.f16x2.e2m1x2 $1, byte1;
  3605. cvt.rn.f16x2.e2m1x2 $2, byte2;
  3606. cvt.rn.f16x2.e2m1x2 $3, byte3;
  3607. }
  3608. """,
  3609. "=r,=r,=r,=r,r",
  3610. has_side_effects=False,
  3611. is_align_stack=False,
  3612. asm_dialect=llvm.AsmDialect.AD_ATT,
  3613. loc=loc,
  3614. ip=ip,
  3615. )
  3616. return (
  3617. Uint32(llvm.extractvalue(T.i32(), res, [0], loc=loc, ip=ip)),
  3618. Uint32(llvm.extractvalue(T.i32(), res, [1], loc=loc, ip=ip)),
  3619. Uint32(llvm.extractvalue(T.i32(), res, [2], loc=loc, ip=ip)),
  3620. Uint32(llvm.extractvalue(T.i32(), res, [3], loc=loc, ip=ip)),
  3621. )
  3622. @dsl_user_op
  3623. def fp4_decode_2(byte_val: Uint32, *, loc=None, ip=None) -> Uint32:
  3624. """Decode 1 FP4 byte into 1 f16x2 pair."""
  3625. return Uint32(
  3626. llvm.inline_asm(
  3627. T.i32(),
  3628. [Uint32(byte_val).ir_value(loc=loc, ip=ip)],
  3629. """
  3630. {
  3631. .reg .b8 b0;
  3632. cvt.u8.u32 b0, $1;
  3633. cvt.rn.f16x2.e2m1x2 $0, b0;
  3634. }
  3635. """,
  3636. "=r,r",
  3637. has_side_effects=False,
  3638. is_align_stack=False,
  3639. asm_dialect=llvm.AsmDialect.AD_ATT,
  3640. loc=loc,
  3641. ip=ip,
  3642. )
  3643. )
  3644. @dsl_user_op
  3645. def e2m1x8_to_qmma_e2m1x8(
  3646. packed_u32: Uint32, *, loc=None, ip=None
  3647. ) -> Tuple[Uint32, Uint32]:
  3648. """Spread eight packed FP4 nibbles into QMMA E2M1 byte containers.
  3649. The SM120 f8f6f4 instruction expects one E2M1 value per byte with the
  3650. original nibble in bits 5:2. This six-instruction spread is cheaper than
  3651. losslessly relabeling every value as E4M3 when no residual multiplier is
  3652. required.
  3653. """
  3654. res = llvm.inline_asm(
  3655. llvm.StructType.get_literal([T.i32(), T.i32()]),
  3656. [Uint32(packed_u32).ir_value(loc=loc, ip=ip)],
  3657. """
  3658. {
  3659. .reg .b32 s, t;
  3660. shl.b32 s, $2, 2;
  3661. shr.u32 t, $2, 2;
  3662. prmt.b32 $0, s, t, 0x5140;
  3663. prmt.b32 $1, s, t, 0x7362;
  3664. and.b32 $0, $0, 0x3C3C3C3C;
  3665. and.b32 $1, $1, 0x3C3C3C3C;
  3666. }
  3667. """,
  3668. "=r,=r,r",
  3669. has_side_effects=False,
  3670. is_align_stack=False,
  3671. asm_dialect=llvm.AsmDialect.AD_ATT,
  3672. loc=loc,
  3673. ip=ip,
  3674. )
  3675. lo = llvm.extractvalue(T.i32(), res, [0], loc=loc, ip=ip)
  3676. hi = llvm.extractvalue(T.i32(), res, [1], loc=loc, ip=ip)
  3677. return Uint32(lo), Uint32(hi)
  3678. @dsl_user_op
  3679. def e2m1x8_to_e4m3x8(
  3680. packed_u32: Uint32, *, loc=None, ip=None
  3681. ) -> Tuple[Uint32, Uint32]:
  3682. """Expand 8 packed E2M1 nibbles into 8 E4M3 bytes, losslessly.
  3683. Every E2M1 value is exactly representable in E4M3, so this is a pure
  3684. relabeling done with two prmt magnitude-LUT lookups plus a sign pass.
  3685. Nibble i of the input becomes byte i of the (lo, hi) output pair.
  3686. """
  3687. res = llvm.inline_asm(
  3688. llvm.StructType.get_literal([T.i32(), T.i32()]),
  3689. [Uint32(packed_u32).ir_value(loc=loc, ip=ip)],
  3690. """
  3691. {
  3692. .reg .b32 clo, chi, cslo, cshi, mlo, mhi, slo, shi;
  3693. .reg .b32 tbl_a, tbl_b, tbl_s, zero;
  3694. // E4M3 encodings of {0, .5, 1, 1.5, 2, 3, 4, 6}.
  3695. mov.b32 tbl_a, 0x3C383000;
  3696. mov.b32 tbl_b, 0x4C484440;
  3697. mov.b32 tbl_s, 0x00008000;
  3698. mov.b32 zero, 0;
  3699. // Magnitude codes (low 3 bits of each nibble) as prmt selectors.
  3700. and.b32 clo, $2, 0x00007777;
  3701. shr.u32 chi, $2, 16;
  3702. and.b32 chi, chi, 0x00007777;
  3703. prmt.b32 mlo, tbl_a, tbl_b, clo;
  3704. prmt.b32 mhi, tbl_a, tbl_b, chi;
  3705. // Sign bit (nibble bit 3) -> byte bit 7 via a 2-entry LUT.
  3706. shr.u32 cslo, $2, 3;
  3707. and.b32 cslo, cslo, 0x00001111;
  3708. shr.u32 cshi, $2, 19;
  3709. and.b32 cshi, cshi, 0x00001111;
  3710. prmt.b32 slo, tbl_s, zero, cslo;
  3711. prmt.b32 shi, tbl_s, zero, cshi;
  3712. or.b32 $0, mlo, slo;
  3713. or.b32 $1, mhi, shi;
  3714. }
  3715. """,
  3716. "=r,=r,r",
  3717. has_side_effects=False,
  3718. is_align_stack=False,
  3719. asm_dialect=llvm.AsmDialect.AD_ATT,
  3720. loc=loc,
  3721. ip=ip,
  3722. )
  3723. lo = llvm.extractvalue(T.i32(), res, [0], loc=loc, ip=ip)
  3724. hi = llvm.extractvalue(T.i32(), res, [1], loc=loc, ip=ip)
  3725. return Uint32(lo), Uint32(hi)
  3726. @dsl_user_op
  3727. def e2m1x8_mul_residual_to_e4m3x8(
  3728. packed_u32: Uint32, residual_h2: Uint32, *, loc=None, ip=None
  3729. ) -> Tuple[Uint32, Uint32]:
  3730. """Expand 8 E2M1 nibbles to E4M3 bytes with a residual multiplier.
  3731. Decodes to f16 pairs, multiplies by ``residual_h2`` (the same residual
  3732. broadcast to both f16 lanes), and rounds to E4M3. Used by the w4a8
  3733. NVFP4 path where the per-K/16 e4m3 scale is decomposed into a shared
  3734. UE8M0 hardware exponent and this in-register residual.
  3735. """
  3736. res = llvm.inline_asm(
  3737. llvm.StructType.get_literal([T.i32(), T.i32()]),
  3738. [
  3739. Uint32(packed_u32).ir_value(loc=loc, ip=ip),
  3740. Uint32(residual_h2).ir_value(loc=loc, ip=ip),
  3741. ],
  3742. """
  3743. {
  3744. .reg .b8 b0, b1, b2, b3;
  3745. .reg .b32 h0, h1, h2, h3;
  3746. .reg .b16 e0, e1, e2, e3;
  3747. mov.b32 {b0, b1, b2, b3}, $2;
  3748. cvt.rn.f16x2.e2m1x2 h0, b0;
  3749. cvt.rn.f16x2.e2m1x2 h1, b1;
  3750. cvt.rn.f16x2.e2m1x2 h2, b2;
  3751. cvt.rn.f16x2.e2m1x2 h3, b3;
  3752. mul.f16x2 h0, h0, $3;
  3753. mul.f16x2 h1, h1, $3;
  3754. mul.f16x2 h2, h2, $3;
  3755. mul.f16x2 h3, h3, $3;
  3756. cvt.rn.satfinite.e4m3x2.f16x2 e0, h0;
  3757. cvt.rn.satfinite.e4m3x2.f16x2 e1, h1;
  3758. cvt.rn.satfinite.e4m3x2.f16x2 e2, h2;
  3759. cvt.rn.satfinite.e4m3x2.f16x2 e3, h3;
  3760. mov.b32 $0, {e0, e1};
  3761. mov.b32 $1, {e2, e3};
  3762. }
  3763. """,
  3764. "=r,=r,r,r",
  3765. has_side_effects=False,
  3766. is_align_stack=False,
  3767. asm_dialect=llvm.AsmDialect.AD_ATT,
  3768. loc=loc,
  3769. ip=ip,
  3770. )
  3771. lo = llvm.extractvalue(T.i32(), res, [0], loc=loc, ip=ip)
  3772. hi = llvm.extractvalue(T.i32(), res, [1], loc=loc, ip=ip)
  3773. return Uint32(lo), Uint32(hi)
  3774. @dsl_user_op
  3775. def cvt_fp32x2_to_e2m1x2(v0: Float32, v1: Float32, *, loc=None, ip=None) -> Uint32:
  3776. """Convert 2 x f32 to 1 x FP4 (E2M1) byte."""
  3777. return Uint32(
  3778. llvm.inline_asm(
  3779. T.i32(),
  3780. [
  3781. Float32(v0).ir_value(loc=loc, ip=ip),
  3782. Float32(v1).ir_value(loc=loc, ip=ip),
  3783. ],
  3784. """
  3785. {
  3786. .reg .b8 b;
  3787. cvt.rn.satfinite.e2m1x2.f32 b, $2, $1;
  3788. cvt.u32.u8 $0, b;
  3789. }
  3790. """,
  3791. "=r,f,f",
  3792. has_side_effects=False,
  3793. is_align_stack=False,
  3794. asm_dialect=llvm.AsmDialect.AD_ATT,
  3795. loc=loc,
  3796. ip=ip,
  3797. )
  3798. )
  3799. @cute.jit
  3800. def quant_dequant_2(
  3801. v0: Float32, v1: Float32, sf_f32: Float32, eff_scale: Float32
  3802. ) -> Tuple[Float32, Float32]:
  3803. """Quantize-dequantize pair roundtrip through FP4."""
  3804. inv_scale = Float32(1.0) / eff_scale
  3805. fp4_byte = cvt_fp32x2_to_e2m1x2(v0 * inv_scale, v1 * inv_scale)
  3806. h2 = fp4_decode_2(fp4_byte)
  3807. f0, f1 = f16x2_to_f32x2(h2)
  3808. return f0 * sf_f32, f1 * sf_f32
  3809. # =============================================================================
  3810. # Half2 FMA Dot Product Intrinsics
  3811. # =============================================================================
  3812. @dsl_user_op
  3813. def hfma2_4(
  3814. w0: Uint32, x0: Uint32,
  3815. w1: Uint32, x1: Uint32,
  3816. w2: Uint32, x2: Uint32,
  3817. w3: Uint32, x3: Uint32,
  3818. *, loc=None, ip=None
  3819. ) -> Tuple[Float32, Float32]:
  3820. """4-element half2 FMA dot product returning (lo, hi) f32."""
  3821. res = llvm.inline_asm(
  3822. ir.Type.parse("!llvm.struct<(f32, f32)>"),
  3823. [
  3824. Uint32(w0).ir_value(loc=loc, ip=ip), Uint32(x0).ir_value(loc=loc, ip=ip),
  3825. Uint32(w1).ir_value(loc=loc, ip=ip), Uint32(x1).ir_value(loc=loc, ip=ip),
  3826. Uint32(w2).ir_value(loc=loc, ip=ip), Uint32(x2).ir_value(loc=loc, ip=ip),
  3827. Uint32(w3).ir_value(loc=loc, ip=ip), Uint32(x3).ir_value(loc=loc, ip=ip),
  3828. ],
  3829. """
  3830. {
  3831. .reg .f16x2 acc;
  3832. .reg .b16 lo, hi;
  3833. mov.b32 acc, 0;
  3834. fma.rn.f16x2 acc, $2, $3, acc;
  3835. fma.rn.f16x2 acc, $4, $5, acc;
  3836. fma.rn.f16x2 acc, $6, $7, acc;
  3837. fma.rn.f16x2 acc, $8, $9, acc;
  3838. mov.b32 {lo, hi}, acc;
  3839. cvt.f32.f16 $0, lo;
  3840. cvt.f32.f16 $1, hi;
  3841. }
  3842. """,
  3843. "=f,=f,r,r,r,r,r,r,r,r",
  3844. has_side_effects=False,
  3845. is_align_stack=False,
  3846. asm_dialect=llvm.AsmDialect.AD_ATT,
  3847. loc=loc,
  3848. ip=ip,
  3849. )
  3850. return (
  3851. Float32(llvm.extractvalue(T.f32(), res, [0], loc=loc, ip=ip)),
  3852. Float32(llvm.extractvalue(T.f32(), res, [1], loc=loc, ip=ip)),
  3853. )
  3854. @dsl_user_op
  3855. def hfma2_4_sum(
  3856. w0: Uint32, x0: Uint32,
  3857. w1: Uint32, x1: Uint32,
  3858. w2: Uint32, x2: Uint32,
  3859. w3: Uint32, x3: Uint32,
  3860. *, loc=None, ip=None
  3861. ) -> Float32:
  3862. """4-element half2 FMA dot product returning lane sum."""
  3863. return Float32(
  3864. llvm.inline_asm(
  3865. T.f32(),
  3866. [
  3867. Uint32(w0).ir_value(loc=loc, ip=ip), Uint32(x0).ir_value(loc=loc, ip=ip),
  3868. Uint32(w1).ir_value(loc=loc, ip=ip), Uint32(x1).ir_value(loc=loc, ip=ip),
  3869. Uint32(w2).ir_value(loc=loc, ip=ip), Uint32(x2).ir_value(loc=loc, ip=ip),
  3870. Uint32(w3).ir_value(loc=loc, ip=ip), Uint32(x3).ir_value(loc=loc, ip=ip),
  3871. ],
  3872. """
  3873. {
  3874. .reg .f16x2 acc;
  3875. .reg .b16 lo, hi;
  3876. .reg .f32 flo, fhi;
  3877. mov.b32 acc, 0;
  3878. fma.rn.f16x2 acc, $1, $2, acc;
  3879. fma.rn.f16x2 acc, $3, $4, acc;
  3880. fma.rn.f16x2 acc, $5, $6, acc;
  3881. fma.rn.f16x2 acc, $7, $8, acc;
  3882. mov.b32 {lo, hi}, acc;
  3883. cvt.f32.f16 flo, lo;
  3884. cvt.f32.f16 fhi, hi;
  3885. add.f32 $0, flo, fhi;
  3886. }
  3887. """,
  3888. "=f,r,r,r,r,r,r,r,r",
  3889. has_side_effects=False,
  3890. is_align_stack=False,
  3891. asm_dialect=llvm.AsmDialect.AD_ATT,
  3892. loc=loc,
  3893. ip=ip,
  3894. )
  3895. )
  3896. @dsl_user_op
  3897. def hfma2_8(
  3898. w0: Uint32, x0: Uint32,
  3899. w1: Uint32, x1: Uint32,
  3900. w2: Uint32, x2: Uint32,
  3901. w3: Uint32, x3: Uint32,
  3902. w4: Uint32, x4: Uint32,
  3903. w5: Uint32, x5: Uint32,
  3904. w6: Uint32, x6: Uint32,
  3905. w7: Uint32, x7: Uint32,
  3906. *, loc=None, ip=None
  3907. ) -> Tuple[Float32, Float32]:
  3908. """8-element half2 FMA dot product returning (lo, hi) f32."""
  3909. res = llvm.inline_asm(
  3910. ir.Type.parse("!llvm.struct<(f32, f32)>"),
  3911. [
  3912. Uint32(w0).ir_value(loc=loc, ip=ip), Uint32(x0).ir_value(loc=loc, ip=ip),
  3913. Uint32(w1).ir_value(loc=loc, ip=ip), Uint32(x1).ir_value(loc=loc, ip=ip),
  3914. Uint32(w2).ir_value(loc=loc, ip=ip), Uint32(x2).ir_value(loc=loc, ip=ip),
  3915. Uint32(w3).ir_value(loc=loc, ip=ip), Uint32(x3).ir_value(loc=loc, ip=ip),
  3916. Uint32(w4).ir_value(loc=loc, ip=ip), Uint32(x4).ir_value(loc=loc, ip=ip),
  3917. Uint32(w5).ir_value(loc=loc, ip=ip), Uint32(x5).ir_value(loc=loc, ip=ip),
  3918. Uint32(w6).ir_value(loc=loc, ip=ip), Uint32(x6).ir_value(loc=loc, ip=ip),
  3919. Uint32(w7).ir_value(loc=loc, ip=ip), Uint32(x7).ir_value(loc=loc, ip=ip),
  3920. ],
  3921. """
  3922. {
  3923. .reg .f16x2 acc;
  3924. .reg .b16 lo, hi;
  3925. mov.b32 acc, 0;
  3926. fma.rn.f16x2 acc, $2, $3, acc;
  3927. fma.rn.f16x2 acc, $4, $5, acc;
  3928. fma.rn.f16x2 acc, $6, $7, acc;
  3929. fma.rn.f16x2 acc, $8, $9, acc;
  3930. fma.rn.f16x2 acc, $10, $11, acc;
  3931. fma.rn.f16x2 acc, $12, $13, acc;
  3932. fma.rn.f16x2 acc, $14, $15, acc;
  3933. fma.rn.f16x2 acc, $16, $17, acc;
  3934. mov.b32 {lo, hi}, acc;
  3935. cvt.f32.f16 $0, lo;
  3936. cvt.f32.f16 $1, hi;
  3937. }
  3938. """,
  3939. "=f,=f,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r",
  3940. has_side_effects=False,
  3941. is_align_stack=False,
  3942. asm_dialect=llvm.AsmDialect.AD_ATT,
  3943. loc=loc,
  3944. ip=ip,
  3945. )
  3946. return (
  3947. Float32(llvm.extractvalue(T.f32(), res, [0], loc=loc, ip=ip)),
  3948. Float32(llvm.extractvalue(T.f32(), res, [1], loc=loc, ip=ip)),
  3949. )
  3950. @dsl_user_op
  3951. def hfma2_8_sum(
  3952. w0: Uint32, x0: Uint32,
  3953. w1: Uint32, x1: Uint32,
  3954. w2: Uint32, x2: Uint32,
  3955. w3: Uint32, x3: Uint32,
  3956. w4: Uint32, x4: Uint32,
  3957. w5: Uint32, x5: Uint32,
  3958. w6: Uint32, x6: Uint32,
  3959. w7: Uint32, x7: Uint32,
  3960. *, loc=None, ip=None
  3961. ) -> Float32:
  3962. """8-element half2 FMA dot product returning lane sum."""
  3963. return Float32(
  3964. llvm.inline_asm(
  3965. T.f32(),
  3966. [
  3967. Uint32(w0).ir_value(loc=loc, ip=ip), Uint32(x0).ir_value(loc=loc, ip=ip),
  3968. Uint32(w1).ir_value(loc=loc, ip=ip), Uint32(x1).ir_value(loc=loc, ip=ip),
  3969. Uint32(w2).ir_value(loc=loc, ip=ip), Uint32(x2).ir_value(loc=loc, ip=ip),
  3970. Uint32(w3).ir_value(loc=loc, ip=ip), Uint32(x3).ir_value(loc=loc, ip=ip),
  3971. Uint32(w4).ir_value(loc=loc, ip=ip), Uint32(x4).ir_value(loc=loc, ip=ip),
  3972. Uint32(w5).ir_value(loc=loc, ip=ip), Uint32(x5).ir_value(loc=loc, ip=ip),
  3973. Uint32(w6).ir_value(loc=loc, ip=ip), Uint32(x6).ir_value(loc=loc, ip=ip),
  3974. Uint32(w7).ir_value(loc=loc, ip=ip), Uint32(x7).ir_value(loc=loc, ip=ip),
  3975. ],
  3976. """
  3977. {
  3978. .reg .f16x2 acc;
  3979. .reg .b16 lo, hi;
  3980. .reg .f32 flo, fhi;
  3981. mov.b32 acc, 0;
  3982. fma.rn.f16x2 acc, $1, $2, acc;
  3983. fma.rn.f16x2 acc, $3, $4, acc;
  3984. fma.rn.f16x2 acc, $5, $6, acc;
  3985. fma.rn.f16x2 acc, $7, $8, acc;
  3986. fma.rn.f16x2 acc, $9, $10, acc;
  3987. fma.rn.f16x2 acc, $11, $12, acc;
  3988. fma.rn.f16x2 acc, $13, $14, acc;
  3989. fma.rn.f16x2 acc, $15, $16, acc;
  3990. mov.b32 {lo, hi}, acc;
  3991. cvt.f32.f16 flo, lo;
  3992. cvt.f32.f16 fhi, hi;
  3993. add.f32 $0, flo, fhi;
  3994. }
  3995. """,
  3996. "=f,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r",
  3997. has_side_effects=False,
  3998. is_align_stack=False,
  3999. asm_dialect=llvm.AsmDialect.AD_ATT,
  4000. loc=loc,
  4001. ip=ip,
  4002. )
  4003. )
  4004. # =============================================================================
  4005. # FP4 Dot Product Intrinsics
  4006. # =============================================================================
  4007. @dsl_user_op
  4008. def fp4_dot4_sum(
  4009. u_packed: Uint32,
  4010. x0: Uint32,
  4011. x1: Uint32,
  4012. x2: Uint32,
  4013. x3: Uint32,
  4014. *,
  4015. loc=None,
  4016. ip=None,
  4017. ) -> Float32:
  4018. """FP4 dot product: decode 4 FP4 bytes and dot with 4 x f16x2 inputs."""
  4019. return Float32(
  4020. llvm.inline_asm(
  4021. T.f32(),
  4022. [
  4023. Uint32(u_packed).ir_value(loc=loc, ip=ip),
  4024. Uint32(x0).ir_value(loc=loc, ip=ip),
  4025. Uint32(x1).ir_value(loc=loc, ip=ip),
  4026. Uint32(x2).ir_value(loc=loc, ip=ip),
  4027. Uint32(x3).ir_value(loc=loc, ip=ip),
  4028. ],
  4029. """
  4030. {
  4031. .reg .b8 b0, b1, b2, b3;
  4032. .reg .b32 h0, h1, h2, h3;
  4033. .reg .f16x2 acc;
  4034. .reg .b16 lo, hi;
  4035. .reg .f32 flo, fhi;
  4036. mov.b32 {b0, b1, b2, b3}, $1;
  4037. cvt.rn.f16x2.e2m1x2 h0, b0;
  4038. cvt.rn.f16x2.e2m1x2 h1, b1;
  4039. cvt.rn.f16x2.e2m1x2 h2, b2;
  4040. cvt.rn.f16x2.e2m1x2 h3, b3;
  4041. mov.b32 acc, 0;
  4042. fma.rn.f16x2 acc, h0, $2, acc;
  4043. fma.rn.f16x2 acc, h1, $3, acc;
  4044. fma.rn.f16x2 acc, h2, $4, acc;
  4045. fma.rn.f16x2 acc, h3, $5, acc;
  4046. mov.b32 {lo, hi}, acc;
  4047. cvt.f32.f16 flo, lo;
  4048. cvt.f32.f16 fhi, hi;
  4049. add.f32 $0, flo, fhi;
  4050. }
  4051. """,
  4052. "=f,r,r,r,r,r",
  4053. has_side_effects=False,
  4054. is_align_stack=False,
  4055. asm_dialect=llvm.AsmDialect.AD_ATT,
  4056. loc=loc,
  4057. ip=ip,
  4058. )
  4059. )
  4060. @dsl_user_op
  4061. def fp4_dot8_dual_sum(
  4062. up_a: Uint32,
  4063. up_b: Uint32,
  4064. gate_a: Uint32,
  4065. gate_b: Uint32,
  4066. x0: Uint32,
  4067. x1: Uint32,
  4068. x2: Uint32,
  4069. x3: Uint32,
  4070. x4: Uint32,
  4071. x5: Uint32,
  4072. x6: Uint32,
  4073. x7: Uint32,
  4074. *,
  4075. loc=None,
  4076. ip=None,
  4077. ) -> Tuple[Float32, Float32]:
  4078. """Fused dual FP4 dot product (up + gate) over the SAME 8 shared f16x2
  4079. activation words. Decodes both weight rows and runs two INDEPENDENT
  4080. f16x2 accumulator chains in a single inline-asm block, then reduces each
  4081. to f32. Bit-identical to running fp4_dot8_sum twice (same per-chain fma
  4082. order, same lo+hi f32 reduction) but emits one asm block so ptxas can
  4083. interleave the two independent fma chains for 2-way ILP and halves the
  4084. per-pair epilogue (mov/cvt/add) op count on the issue-bound decode loop."""
  4085. res = llvm.inline_asm(
  4086. llvm.StructType.get_literal([T.f32(), T.f32()]),
  4087. [
  4088. Uint32(up_a).ir_value(loc=loc, ip=ip),
  4089. Uint32(up_b).ir_value(loc=loc, ip=ip),
  4090. Uint32(gate_a).ir_value(loc=loc, ip=ip),
  4091. Uint32(gate_b).ir_value(loc=loc, ip=ip),
  4092. Uint32(x0).ir_value(loc=loc, ip=ip),
  4093. Uint32(x1).ir_value(loc=loc, ip=ip),
  4094. Uint32(x2).ir_value(loc=loc, ip=ip),
  4095. Uint32(x3).ir_value(loc=loc, ip=ip),
  4096. Uint32(x4).ir_value(loc=loc, ip=ip),
  4097. Uint32(x5).ir_value(loc=loc, ip=ip),
  4098. Uint32(x6).ir_value(loc=loc, ip=ip),
  4099. Uint32(x7).ir_value(loc=loc, ip=ip),
  4100. ],
  4101. """
  4102. {
  4103. .reg .b8 ua0, ua1, ua2, ua3, ub0, ub1, ub2, ub3;
  4104. .reg .b8 ga0, ga1, ga2, ga3, gb0, gb1, gb2, gb3;
  4105. .reg .b32 uh0, uh1, uh2, uh3, uh4, uh5, uh6, uh7;
  4106. .reg .b32 gh0, gh1, gh2, gh3, gh4, gh5, gh6, gh7;
  4107. .reg .f16x2 uacc, gacc;
  4108. .reg .b16 ulo, uhi, glo, ghi;
  4109. .reg .f32 uflo, ufhi, gflo, gfhi;
  4110. mov.b32 {ua0, ua1, ua2, ua3}, $2;
  4111. mov.b32 {ub0, ub1, ub2, ub3}, $3;
  4112. mov.b32 {ga0, ga1, ga2, ga3}, $4;
  4113. mov.b32 {gb0, gb1, gb2, gb3}, $5;
  4114. cvt.rn.f16x2.e2m1x2 uh0, ua0;
  4115. cvt.rn.f16x2.e2m1x2 uh1, ua1;
  4116. cvt.rn.f16x2.e2m1x2 uh2, ua2;
  4117. cvt.rn.f16x2.e2m1x2 uh3, ua3;
  4118. cvt.rn.f16x2.e2m1x2 uh4, ub0;
  4119. cvt.rn.f16x2.e2m1x2 uh5, ub1;
  4120. cvt.rn.f16x2.e2m1x2 uh6, ub2;
  4121. cvt.rn.f16x2.e2m1x2 uh7, ub3;
  4122. cvt.rn.f16x2.e2m1x2 gh0, ga0;
  4123. cvt.rn.f16x2.e2m1x2 gh1, ga1;
  4124. cvt.rn.f16x2.e2m1x2 gh2, ga2;
  4125. cvt.rn.f16x2.e2m1x2 gh3, ga3;
  4126. cvt.rn.f16x2.e2m1x2 gh4, gb0;
  4127. cvt.rn.f16x2.e2m1x2 gh5, gb1;
  4128. cvt.rn.f16x2.e2m1x2 gh6, gb2;
  4129. cvt.rn.f16x2.e2m1x2 gh7, gb3;
  4130. mov.b32 uacc, 0;
  4131. mov.b32 gacc, 0;
  4132. fma.rn.f16x2 uacc, uh0, $6, uacc;
  4133. fma.rn.f16x2 gacc, gh0, $6, gacc;
  4134. fma.rn.f16x2 uacc, uh1, $7, uacc;
  4135. fma.rn.f16x2 gacc, gh1, $7, gacc;
  4136. fma.rn.f16x2 uacc, uh2, $8, uacc;
  4137. fma.rn.f16x2 gacc, gh2, $8, gacc;
  4138. fma.rn.f16x2 uacc, uh3, $9, uacc;
  4139. fma.rn.f16x2 gacc, gh3, $9, gacc;
  4140. fma.rn.f16x2 uacc, uh4, $10, uacc;
  4141. fma.rn.f16x2 gacc, gh4, $10, gacc;
  4142. fma.rn.f16x2 uacc, uh5, $11, uacc;
  4143. fma.rn.f16x2 gacc, gh5, $11, gacc;
  4144. fma.rn.f16x2 uacc, uh6, $12, uacc;
  4145. fma.rn.f16x2 gacc, gh6, $12, gacc;
  4146. fma.rn.f16x2 uacc, uh7, $13, uacc;
  4147. fma.rn.f16x2 gacc, gh7, $13, gacc;
  4148. mov.b32 {ulo, uhi}, uacc;
  4149. mov.b32 {glo, ghi}, gacc;
  4150. cvt.f32.f16 uflo, ulo;
  4151. cvt.f32.f16 ufhi, uhi;
  4152. cvt.f32.f16 gflo, glo;
  4153. cvt.f32.f16 gfhi, ghi;
  4154. add.f32 $0, uflo, ufhi;
  4155. add.f32 $1, gflo, gfhi;
  4156. }
  4157. """,
  4158. "=f,=f,r,r,r,r,r,r,r,r,r,r,r,r",
  4159. has_side_effects=False,
  4160. is_align_stack=False,
  4161. asm_dialect=llvm.AsmDialect.AD_ATT,
  4162. loc=loc,
  4163. ip=ip,
  4164. )
  4165. up = Float32(llvm.extractvalue(T.f32(), res, [0], loc=loc, ip=ip))
  4166. gate = Float32(llvm.extractvalue(T.f32(), res, [1], loc=loc, ip=ip))
  4167. return up, gate
  4168. @dsl_user_op
  4169. def fp4_dot8_sum(
  4170. u_a: Uint32,
  4171. u_b: Uint32,
  4172. x0: Uint32,
  4173. x1: Uint32,
  4174. x2: Uint32,
  4175. x3: Uint32,
  4176. x4: Uint32,
  4177. x5: Uint32,
  4178. x6: Uint32,
  4179. x7: Uint32,
  4180. *,
  4181. loc=None,
  4182. ip=None,
  4183. ) -> Float32:
  4184. """FP4 dot product: decode 8 FP4 bytes (2 x u32) and dot with 8 x f16x2 inputs."""
  4185. return Float32(
  4186. llvm.inline_asm(
  4187. T.f32(),
  4188. [
  4189. Uint32(u_a).ir_value(loc=loc, ip=ip),
  4190. Uint32(u_b).ir_value(loc=loc, ip=ip),
  4191. Uint32(x0).ir_value(loc=loc, ip=ip),
  4192. Uint32(x1).ir_value(loc=loc, ip=ip),
  4193. Uint32(x2).ir_value(loc=loc, ip=ip),
  4194. Uint32(x3).ir_value(loc=loc, ip=ip),
  4195. Uint32(x4).ir_value(loc=loc, ip=ip),
  4196. Uint32(x5).ir_value(loc=loc, ip=ip),
  4197. Uint32(x6).ir_value(loc=loc, ip=ip),
  4198. Uint32(x7).ir_value(loc=loc, ip=ip),
  4199. ],
  4200. """
  4201. {
  4202. .reg .b8 a0, a1, a2, a3;
  4203. .reg .b8 b0, b1, b2, b3;
  4204. .reg .b32 h0, h1, h2, h3, h4, h5, h6, h7;
  4205. .reg .f16x2 acc;
  4206. .reg .b16 lo, hi;
  4207. .reg .f32 flo, fhi;
  4208. mov.b32 {a0, a1, a2, a3}, $1;
  4209. mov.b32 {b0, b1, b2, b3}, $2;
  4210. cvt.rn.f16x2.e2m1x2 h0, a0;
  4211. cvt.rn.f16x2.e2m1x2 h1, a1;
  4212. cvt.rn.f16x2.e2m1x2 h2, a2;
  4213. cvt.rn.f16x2.e2m1x2 h3, a3;
  4214. cvt.rn.f16x2.e2m1x2 h4, b0;
  4215. cvt.rn.f16x2.e2m1x2 h5, b1;
  4216. cvt.rn.f16x2.e2m1x2 h6, b2;
  4217. cvt.rn.f16x2.e2m1x2 h7, b3;
  4218. mov.b32 acc, 0;
  4219. fma.rn.f16x2 acc, h0, $3, acc;
  4220. fma.rn.f16x2 acc, h1, $4, acc;
  4221. fma.rn.f16x2 acc, h2, $5, acc;
  4222. fma.rn.f16x2 acc, h3, $6, acc;
  4223. fma.rn.f16x2 acc, h4, $7, acc;
  4224. fma.rn.f16x2 acc, h5, $8, acc;
  4225. fma.rn.f16x2 acc, h6, $9, acc;
  4226. fma.rn.f16x2 acc, h7, $10, acc;
  4227. mov.b32 {lo, hi}, acc;
  4228. cvt.f32.f16 flo, lo;
  4229. cvt.f32.f16 fhi, hi;
  4230. add.f32 $0, flo, fhi;
  4231. }
  4232. """,
  4233. "=f,r,r,r,r,r,r,r,r,r,r",
  4234. has_side_effects=False,
  4235. is_align_stack=False,
  4236. asm_dialect=llvm.AsmDialect.AD_ATT,
  4237. loc=loc,
  4238. ip=ip,
  4239. )
  4240. )
  4241. @cute.jit
  4242. def _f16x2_dot_sum_f32acc(a_h2: Uint32, b_h2: Uint32) -> Float32:
  4243. a0, a1 = f16x2_to_f32x2(a_h2)
  4244. b0, b1 = f16x2_to_f32x2(b_h2)
  4245. return a0 * b0 + a1 * b1
  4246. @cute.jit
  4247. def fp4_dot4_sum_f32acc(
  4248. u_packed: Uint32,
  4249. x0: Uint32,
  4250. x1: Uint32,
  4251. x2: Uint32,
  4252. x3: Uint32,
  4253. ) -> Float32:
  4254. """FP4 dot product using f32 accumulation after FP4 decode."""
  4255. h0, h1, h2, h3 = fp4_decode_4bytes(u_packed)
  4256. return (
  4257. _f16x2_dot_sum_f32acc(h0, x0)
  4258. + _f16x2_dot_sum_f32acc(h1, x1)
  4259. + _f16x2_dot_sum_f32acc(h2, x2)
  4260. + _f16x2_dot_sum_f32acc(h3, x3)
  4261. )
  4262. @cute.jit
  4263. def fp4_dot8_sum_f32acc(
  4264. u_a: Uint32,
  4265. u_b: Uint32,
  4266. x0: Uint32,
  4267. x1: Uint32,
  4268. x2: Uint32,
  4269. x3: Uint32,
  4270. x4: Uint32,
  4271. x5: Uint32,
  4272. x6: Uint32,
  4273. x7: Uint32,
  4274. ) -> Float32:
  4275. """FP4 dot product using f32 accumulation after FP4 decode."""
  4276. h0, h1, h2, h3 = fp4_decode_4bytes(u_a)
  4277. h4, h5, h6, h7 = fp4_decode_4bytes(u_b)
  4278. return (
  4279. _f16x2_dot_sum_f32acc(h0, x0)
  4280. + _f16x2_dot_sum_f32acc(h1, x1)
  4281. + _f16x2_dot_sum_f32acc(h2, x2)
  4282. + _f16x2_dot_sum_f32acc(h3, x3)
  4283. + _f16x2_dot_sum_f32acc(h4, x4)
  4284. + _f16x2_dot_sum_f32acc(h5, x5)
  4285. + _f16x2_dot_sum_f32acc(h6, x6)
  4286. + _f16x2_dot_sum_f32acc(h7, x7)
  4287. )
  4288. # =============================================================================
  4289. # Pack Intrinsics
  4290. # =============================================================================
  4291. @dsl_user_op
  4292. def pack_f32x2_to_f16x2(
  4293. a: Float32, b: Float32, *, loc=None, ip=None
  4294. ) -> Uint32:
  4295. """Pack two f32 values into one f16x2 u32."""
  4296. return Uint32(
  4297. llvm.inline_asm(
  4298. T.i32(),
  4299. [
  4300. Float32(a).ir_value(loc=loc, ip=ip),
  4301. Float32(b).ir_value(loc=loc, ip=ip),
  4302. ],
  4303. """
  4304. {
  4305. .reg .b16 lo, hi;
  4306. cvt.rn.f16.f32 lo, $1;
  4307. cvt.rn.f16.f32 hi, $2;
  4308. mov.b32 $0, {lo, hi};
  4309. }
  4310. """,
  4311. "=r,f,f",
  4312. has_side_effects=False,
  4313. is_align_stack=False,
  4314. asm_dialect=llvm.AsmDialect.AD_ATT,
  4315. loc=loc,
  4316. ip=ip,
  4317. )
  4318. )
  4319. # =============================================================================
  4320. # UE8M0 Intrinsics (for MXFP4)
  4321. # =============================================================================
  4322. @dsl_user_op
  4323. def pow2_ceil_ue8m0(
  4324. scale: Float32,
  4325. *,
  4326. loc=None,
  4327. ip=None,
  4328. ) -> Tuple[Float32, Uint32]:
  4329. """Round a positive FP32 ``scale`` UP to a power of two, bit-exactly.
  4330. Returns ``(rounded_fp32, ue8m0_byte)`` where:
  4331. * ``rounded_fp32`` == ``__uint_as_float(power_of_2_round(__float_as_uint(scale)))``
  4332. * ``ue8m0_byte`` == ``(__float_as_uint(rounded_fp32) >> 23) & 0xFF``
  4333. Bit-exact replica of FlashInfer's fp8_quant.cuh rounding +
  4334. scale_convert.cuh fp32_to_ue8m0. Integer ops only (no lg2.approx), so unlike
  4335. ``cvt_f32_to_ue8m0`` it matches the FlashInfer reference bit-for-bit.
  4336. """
  4337. result = llvm.inline_asm(
  4338. llvm.StructType.get_literal([T.f32(), T.i32()]),
  4339. [Float32(scale).ir_value(loc=loc, ip=ip)],
  4340. """
  4341. {
  4342. .reg .pred p_mant;
  4343. .reg .b32 bits, mant;
  4344. // bits = __float_as_uint(scale)
  4345. mov.b32 bits, $2;
  4346. // mant = bits & 0x007FFFFF (mantissa field)
  4347. and.b32 mant, bits, 8388607;
  4348. setp.ne.u32 p_mant, mant, 0;
  4349. // if (mant) bits = (bits + 0x00800000) & 0x7F800000
  4350. @p_mant add.u32 bits, bits, 8388608;
  4351. @p_mant and.b32 bits, bits, 2139095040;
  4352. // rounded fp32 scale = __uint_as_float(bits)
  4353. mov.b32 $0, bits;
  4354. // ue8m0 = (bits >> 23) & 0xFF
  4355. bfe.u32 $1, bits, 23, 8;
  4356. }
  4357. """,
  4358. "=f,=r,f",
  4359. has_side_effects=False,
  4360. is_align_stack=False,
  4361. asm_dialect=llvm.AsmDialect.AD_ATT,
  4362. loc=loc,
  4363. ip=ip,
  4364. )
  4365. rounded = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
  4366. ue8m0 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
  4367. return Float32(rounded), Uint32(ue8m0)
  4368. @dsl_user_op
  4369. def cvt_f32_to_ue8m0(max_val: Float32, *, loc=None, ip=None) -> Uint32:
  4370. """
  4371. Convert float32 max value to UE8M0 scale factor.
  4372. UE8M0 is unsigned 8-bit exponent-only format:
  4373. - value = 2^(ue8m0 - 127)
  4374. - ue8m0 = ceil(log2(max_val)) + 127
  4375. Uses lg2.approx.f32 for fast log2 approximation.
  4376. Uses cvt.rpi (round towards positive infinity, i.e., ceiling).
  4377. Returns value clamped to [0, 255].
  4378. """
  4379. return Uint32(
  4380. llvm.inline_asm(
  4381. T.i32(),
  4382. [Float32(max_val).ir_value(loc=loc, ip=ip)],
  4383. """
  4384. {
  4385. .reg .pred p_zero, p_neg, p_ovf;
  4386. .reg .f32 log2_val;
  4387. .reg .s32 exp_int, result;
  4388. // Check for zero/negative
  4389. setp.le.f32 p_zero, $1, 0f00000000;
  4390. // Compute ceil(log2(max_val)) using cvt.rpi (round towards +inf)
  4391. lg2.approx.f32 log2_val, $1;
  4392. cvt.rpi.s32.f32 exp_int, log2_val;
  4393. // Add bias and clamp to [0, 255]
  4394. add.s32 result, exp_int, 127;
  4395. setp.lt.s32 p_neg, result, 0;
  4396. setp.gt.s32 p_ovf, result, 255;
  4397. selp.s32 result, 0, result, p_neg;
  4398. selp.s32 result, 255, result, p_ovf;
  4399. selp.s32 $0, 0, result, p_zero;
  4400. }
  4401. """,
  4402. "=r,f",
  4403. has_side_effects=False,
  4404. is_align_stack=False,
  4405. asm_dialect=llvm.AsmDialect.AD_ATT,
  4406. )
  4407. )
  4408. @dsl_user_op
  4409. def ue8m0_to_output_scale(ue8m0_val: Uint32, *, loc=None, ip=None) -> Float32:
  4410. """
  4411. Convert UE8M0 to output_scale for MXFP4 quantization.
  4412. UE8M0 value = 2^(ue8m0 - 127)
  4413. Returns 1 / 2^(ue8m0 - 127) = 2^(127 - ue8m0)
  4414. """
  4415. return Float32(
  4416. llvm.inline_asm(
  4417. T.f32(),
  4418. [Uint32(ue8m0_val).ir_value(loc=loc, ip=ip)],
  4419. """
  4420. {
  4421. .reg .pred p_zero;
  4422. .reg .s32 neg_exp;
  4423. .reg .f32 neg_exp_f, result;
  4424. // Check for zero
  4425. setp.eq.u32 p_zero, $1, 0;
  4426. // Compute 2^(127 - ue8m0) = 1 / 2^(ue8m0 - 127)
  4427. sub.s32 neg_exp, 127, $1;
  4428. cvt.rn.f32.s32 neg_exp_f, neg_exp;
  4429. ex2.approx.f32 result, neg_exp_f;
  4430. selp.f32 $0, 0f00000000, result, p_zero;
  4431. }
  4432. """,
  4433. "=f,r",
  4434. has_side_effects=False,
  4435. is_align_stack=False,
  4436. asm_dialect=llvm.AsmDialect.AD_ATT,
  4437. )
  4438. )
  4439. # =============================================================================
  4440. # E2M1 Conversion
  4441. # =============================================================================
  4442. @dsl_user_op
  4443. def cvt_e2m1x8_f32(
  4444. v0: Float32,
  4445. v1: Float32,
  4446. v2: Float32,
  4447. v3: Float32,
  4448. v4: Float32,
  4449. v5: Float32,
  4450. v6: Float32,
  4451. v7: Float32,
  4452. *,
  4453. loc=None,
  4454. ip=None,
  4455. ) -> Uint32:
  4456. """Convert eight float32 values to eight E2M1 (4-bit) values packed into uint32."""
  4457. return Uint32(
  4458. llvm.inline_asm(
  4459. T.i32(),
  4460. [
  4461. Float32(v0).ir_value(loc=loc, ip=ip),
  4462. Float32(v1).ir_value(loc=loc, ip=ip),
  4463. Float32(v2).ir_value(loc=loc, ip=ip),
  4464. Float32(v3).ir_value(loc=loc, ip=ip),
  4465. Float32(v4).ir_value(loc=loc, ip=ip),
  4466. Float32(v5).ir_value(loc=loc, ip=ip),
  4467. Float32(v6).ir_value(loc=loc, ip=ip),
  4468. Float32(v7).ir_value(loc=loc, ip=ip),
  4469. ],
  4470. """
  4471. {
  4472. .reg .b8 byte0, byte1, byte2, byte3;
  4473. cvt.rn.satfinite.e2m1x2.f32 byte0, $2, $1;
  4474. cvt.rn.satfinite.e2m1x2.f32 byte1, $4, $3;
  4475. cvt.rn.satfinite.e2m1x2.f32 byte2, $6, $5;
  4476. cvt.rn.satfinite.e2m1x2.f32 byte3, $8, $7;
  4477. mov.b32 $0, {byte0, byte1, byte2, byte3};
  4478. }
  4479. """,
  4480. "=r,f,f,f,f,f,f,f,f",
  4481. has_side_effects=False,
  4482. is_align_stack=False,
  4483. asm_dialect=llvm.AsmDialect.AD_ATT,
  4484. )
  4485. )
  4486. # =============================================================================
  4487. # Warp, Block, and Cluster Reduction Utilities
  4488. # =============================================================================
  4489. @cute.jit
  4490. def warp_reduce(val, op, width: cutlass.Constexpr[int] = 32):
  4491. """Reduce across threads in a warp using butterfly shuffle."""
  4492. if cutlass.const_expr(isinstance(val, cute.TensorSSA)):
  4493. res = cute.make_rmem_tensor(val.shape, val.dtype)
  4494. res.store(val)
  4495. for i in cutlass.range_constexpr(cute.size(val.shape)):
  4496. res[i] = warp_reduce(res[i], op, width)
  4497. return res.load()
  4498. else:
  4499. for i in cutlass.range_constexpr(int(math.log2(width))):
  4500. val = op(val, cute.arch.shuffle_sync_bfly(val, offset=1 << i))
  4501. return val
  4502. @cute.jit
  4503. def block_reduce(
  4504. val: Float32,
  4505. op: Callable,
  4506. reduction_buffer: cute.Tensor,
  4507. init_val: Float32,
  4508. ) -> Float32:
  4509. """Block reduction across multiple warps using shared memory."""
  4510. lane_idx = cute.arch.lane_idx()
  4511. warp_idx = cute.arch.warp_idx()
  4512. warps_per_row = cute.size(reduction_buffer.shape[1])
  4513. row_idx = warp_idx // warps_per_row
  4514. col_idx = warp_idx % warps_per_row
  4515. if lane_idx == 0:
  4516. reduction_buffer[row_idx, col_idx] = val
  4517. cute.arch.barrier()
  4518. block_reduce_val = init_val
  4519. if lane_idx < warps_per_row:
  4520. block_reduce_val = reduction_buffer[row_idx, lane_idx]
  4521. return warp_reduce(block_reduce_val, op)
  4522. @cute.jit
  4523. def cluster_reduce(
  4524. val: Float32,
  4525. op: Callable,
  4526. reduction_buffer: cute.Tensor,
  4527. mbar_ptr: cute.Pointer,
  4528. cluster_n: cutlass.Constexpr[int],
  4529. init_val: Float32,
  4530. ) -> Float32:
  4531. """Cluster reduction across multiple CTAs using mbarrier."""
  4532. cta_rank_in_cluster = cute.arch.block_idx_in_cluster()
  4533. lane_idx = cute.arch.lane_idx()
  4534. warp_idx = cute.arch.warp_idx()
  4535. rows_per_block = reduction_buffer.shape[0]
  4536. warps_per_row = reduction_buffer.shape[1][0]
  4537. row_idx = warp_idx // warps_per_row
  4538. col_idx = warp_idx % warps_per_row
  4539. # Warp 0 sets up mbarrier transaction count
  4540. if warp_idx == 0:
  4541. with cute.arch.elect_one():
  4542. num_warps = rows_per_block * warps_per_row
  4543. expected_bytes = num_warps * cluster_n * 4
  4544. cute.arch.mbarrier_arrive_and_expect_tx(mbar_ptr, expected_bytes)
  4545. # Each lane < cluster_n writes to a different CTA's shared memory
  4546. if lane_idx < cluster_n:
  4547. store_shared_remote(
  4548. val,
  4549. elem_pointer(reduction_buffer, (row_idx, (col_idx, cta_rank_in_cluster))),
  4550. mbar_ptr,
  4551. peer_cta_rank_in_cluster=lane_idx,
  4552. )
  4553. # Wait for all cluster writes
  4554. cute.arch.mbarrier_wait(mbar_ptr, phase=0)
  4555. # Reduce across all values
  4556. num_total = warps_per_row * cluster_n
  4557. num_iter = cute.ceil_div(num_total, 32)
  4558. block_reduce_val = init_val
  4559. for i in cutlass.range_constexpr(num_iter):
  4560. idx = lane_idx + i * 32
  4561. if idx < num_total:
  4562. block_reduce_val = op(block_reduce_val, reduction_buffer[row_idx, idx])
  4563. return warp_reduce(block_reduce_val, op)
  4564. @cute.jit
  4565. def row_reduce(
  4566. x: cute.TensorSSA,
  4567. op: cute.ReductionOp,
  4568. threads_per_row: cutlass.Constexpr[int],
  4569. reduction_buffer: cute.Tensor,
  4570. mbar_ptr,
  4571. cluster_n: cutlass.Constexpr[int],
  4572. init_val: Float32,
  4573. ):
  4574. """Row reduction with optional cluster support."""
  4575. local_val = x.reduce(op, init_val=init_val, reduction_profile=0)
  4576. warp_op = {
  4577. cute.ReductionOp.ADD: operator.add,
  4578. cute.ReductionOp.MAX: cute.arch.fmax,
  4579. }[op]
  4580. warp_width = min(threads_per_row, 32)
  4581. warp_val = warp_reduce(local_val, warp_op, width=warp_width)
  4582. warps_per_row = max(threads_per_row // 32, 1)
  4583. if cutlass.const_expr(warps_per_row > 1 or cluster_n > 1):
  4584. if cutlass.const_expr(cluster_n == 1):
  4585. return block_reduce(warp_val, warp_op, reduction_buffer, init_val)
  4586. else:
  4587. return cluster_reduce(
  4588. warp_val, warp_op, reduction_buffer, mbar_ptr, cluster_n, init_val
  4589. )
  4590. else:
  4591. return warp_val
  4592. # =============================================================================
  4593. # Predicate Utility
  4594. # =============================================================================
  4595. @cute.jit
  4596. def predicate_k(tXcX: cute.Tensor, limit: int) -> cute.Tensor:
  4597. """Create predicate tensor for bounds checking."""
  4598. tXpX = cute.make_rmem_tensor(
  4599. cute.make_layout(
  4600. (
  4601. cute.size(tXcX, mode=[0, 1]),
  4602. cute.size(tXcX, mode=[1]),
  4603. cute.size(tXcX, mode=[2]),
  4604. ),
  4605. stride=(cute.size(tXcX, mode=[2]), 0, 1),
  4606. ),
  4607. cutlass.Boolean,
  4608. )
  4609. for rest_v in cutlass.range_constexpr(tXpX.shape[0]):
  4610. for rest_k in cutlass.range_constexpr(tXpX.shape[2]):
  4611. tXpX[rest_v, 0, rest_k] = cute.elem_less(
  4612. tXcX[(0, rest_v), 0, rest_k][1], limit
  4613. )
  4614. return tXpX
  4615. # =============================================================================
  4616. # Helper Functions for SF Block Processing (block_size=16)
  4617. # =============================================================================
  4618. @cute.jit
  4619. def load_8_half2(
  4620. mX: cute.Tensor, mW: cute.Tensor, row_offset: Int32, col_offset: Int32, H: int
  4621. ):
  4622. """Load 16 elements (8 half2 pairs) of X and W from global memory.
  4623. Returns:
  4624. x_h2: rmem_tensor of shape (8,) containing X as half2
  4625. w_h2: rmem_tensor of shape (8,) containing W as half2
  4626. """
  4627. x_h2 = cute.make_rmem_tensor((8,), Uint32)
  4628. w_h2 = cute.make_rmem_tensor((8,), Uint32)
  4629. # Load X (2 x 128-bit loads = 16 elements)
  4630. x_ptr0 = get_ptr_as_int64(mX, row_offset * H + col_offset)
  4631. x_ptr1 = get_ptr_as_int64(mX, row_offset * H + col_offset + Int32(8))
  4632. x_h2[0], x_h2[1], x_h2[2], x_h2[3] = ld_global_v4_u32(x_ptr0)
  4633. x_h2[4], x_h2[5], x_h2[6], x_h2[7] = ld_global_v4_u32(x_ptr1)
  4634. # Load W (2 x 128-bit loads = 16 elements)
  4635. w_ptr0 = get_ptr_as_int64(mW, col_offset)
  4636. w_ptr1 = get_ptr_as_int64(mW, col_offset + Int32(8))
  4637. w_h2[0], w_h2[1], w_h2[2], w_h2[3] = ld_global_v4_u32(w_ptr0)
  4638. w_h2[4], w_h2[5], w_h2[6], w_h2[7] = ld_global_v4_u32(w_ptr1)
  4639. return x_h2, w_h2
  4640. @cute.jit
  4641. def half2_mul_8(x_h2: cute.Tensor, w_h2: cute.Tensor) -> cute.Tensor:
  4642. """Multiply 8 half2 pairs element-wise."""
  4643. xw_h2 = cute.make_rmem_tensor((8,), Uint32)
  4644. for i in cutlass.range_constexpr(8):
  4645. xw_h2[i] = half2_mul(x_h2[i], w_h2[i])
  4646. return xw_h2
  4647. @cute.jit
  4648. def bfloat2_mul_8(x_h2: cute.Tensor, w_h2: cute.Tensor) -> cute.Tensor:
  4649. """Multiply 8 bfloat2 pairs element-wise."""
  4650. xw_h2 = cute.make_rmem_tensor((8,), Uint32)
  4651. for i in cutlass.range_constexpr(8):
  4652. xw_h2[i] = bfloat2_mul(x_h2[i], w_h2[i])
  4653. return xw_h2
  4654. @cute.jit
  4655. def half2_max_abs_8(xw_h2: cute.Tensor) -> Uint32:
  4656. """Compute max absolute value across 8 half2 values using tree reduction."""
  4657. # Compute abs for all 8 values
  4658. abs_h2 = cute.make_rmem_tensor((8,), Uint32)
  4659. for i in cutlass.range_constexpr(8):
  4660. abs_h2[i] = habs2(xw_h2[i])
  4661. # Tree reduction: 8 -> 4 -> 2 -> 1
  4662. max_01 = hmax2(abs_h2[0], abs_h2[1])
  4663. max_23 = hmax2(abs_h2[2], abs_h2[3])
  4664. max_45 = hmax2(abs_h2[4], abs_h2[5])
  4665. max_67 = hmax2(abs_h2[6], abs_h2[7])
  4666. max_0123 = hmax2(max_01, max_23)
  4667. max_4567 = hmax2(max_45, max_67)
  4668. return hmax2(max_0123, max_4567)
  4669. @cute.jit
  4670. def bfloat2_max_abs_8(xw_h2: cute.Tensor) -> Uint32:
  4671. """Compute max absolute value across 8 bfloat2 values using tree reduction."""
  4672. # Compute abs for all 8 values
  4673. abs_h2 = cute.make_rmem_tensor((8,), Uint32)
  4674. for i in cutlass.range_constexpr(8):
  4675. abs_h2[i] = bfloat2_habs2(xw_h2[i])
  4676. # Tree reduction: 8 -> 4 -> 2 -> 1
  4677. max_01 = bfloat2_hmax2(abs_h2[0], abs_h2[1])
  4678. max_23 = bfloat2_hmax2(abs_h2[2], abs_h2[3])
  4679. max_45 = bfloat2_hmax2(abs_h2[4], abs_h2[5])
  4680. max_67 = bfloat2_hmax2(abs_h2[6], abs_h2[7])
  4681. max_0123 = bfloat2_hmax2(max_01, max_23)
  4682. max_4567 = bfloat2_hmax2(max_45, max_67)
  4683. return bfloat2_hmax2(max_0123, max_4567)
  4684. @cute.jit
  4685. def half2_to_float16(xw_h2: cute.Tensor, scale: Float32) -> cute.Tensor:
  4686. """Convert 8 half2 to 16 float32 with scaling."""
  4687. y_f32 = cute.make_rmem_tensor((16,), Float32)
  4688. for i in cutlass.range_constexpr(8):
  4689. y_f32[i * 2], y_f32[i * 2 + 1] = half2_to_float2_scaled(xw_h2[i], scale)
  4690. return y_f32
  4691. @cute.jit
  4692. def bfloat2_to_float16(xw_h2: cute.Tensor, scale: Float32) -> cute.Tensor:
  4693. """Convert 8 bfloat2 to 16 float32 with scaling."""
  4694. y_f32 = cute.make_rmem_tensor((16,), Float32)
  4695. for i in cutlass.range_constexpr(8):
  4696. y_f32[i * 2], y_f32[i * 2 + 1] = bfloat2_to_float2_scaled(xw_h2[i], scale)
  4697. return y_f32
  4698. @cute.jit
  4699. def quantize_and_pack_16(y_f32: cute.Tensor, value_scale: Float32) -> Uint64:
  4700. """Quantize 16 float32 values to FP4 and pack into uint64."""
  4701. t025 = value_scale * Float32(0.25)
  4702. t075 = value_scale * Float32(0.75)
  4703. t125 = value_scale * Float32(1.25)
  4704. t175 = value_scale * Float32(1.75)
  4705. t250 = value_scale * Float32(2.5)
  4706. t350 = value_scale * Float32(3.5)
  4707. t500 = value_scale * Float32(5.0)
  4708. packed = Uint64(0)
  4709. for i in cutlass.range_constexpr(16):
  4710. q = y_f32[i]
  4711. mag = fabs_f32(q)
  4712. nibble = Uint8(0)
  4713. if mag > t025 and mag < t075:
  4714. nibble = Uint8(1)
  4715. elif mag >= t075 and mag <= t125:
  4716. nibble = Uint8(2)
  4717. elif mag > t125 and mag < t175:
  4718. nibble = Uint8(3)
  4719. elif mag >= t175 and mag <= t250:
  4720. nibble = Uint8(4)
  4721. elif mag > t250 and mag < t350:
  4722. nibble = Uint8(5)
  4723. elif mag >= t350 and mag <= t500:
  4724. nibble = Uint8(6)
  4725. elif mag > t500:
  4726. nibble = Uint8(7)
  4727. if nibble != Uint8(0) and q < Float32(0.0):
  4728. nibble = nibble | Uint8(0x8)
  4729. packed = packed | (Uint64(nibble) << Uint64(i * 4))
  4730. return packed
  4731. @cute.jit
  4732. def quantize_and_pack_16_fast(y_f32: cute.Tensor, inv_scale: Float32) -> Uint64:
  4733. """Fast approximate FP4 quantize/pack for 16 float32 values."""
  4734. q = cute.make_rmem_tensor((16,), Float32)
  4735. for i in cutlass.range_constexpr(16):
  4736. q[i] = y_f32[i] * inv_scale
  4737. packed_lo = cvt_e2m1x8_f32(q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7])
  4738. packed_hi = cvt_e2m1x8_f32(q[8], q[9], q[10], q[11], q[12], q[13], q[14], q[15])
  4739. return (Uint64(packed_hi) << Uint64(32)) | Uint64(packed_lo)
  4740. @cute.jit
  4741. def quantize_block_fp4(
  4742. values: cute.Tensor, max_abs: Float32, global_scale_val: Float32,
  4743. ) -> Tuple[Uint64, cutlass.Uint8]:
  4744. """Quantize 16 float32 values to packed FP4 + e4m3 scale byte.
  4745. Given 16 values and their pre-computed max_abs, derives the NVFP4 block
  4746. scale, quantizes to FP4, and packs into a uint64. Returns
  4747. (packed_fp4_u64, scale_byte). The caller handles storage writes.
  4748. """
  4749. scale_float = max_abs * global_scale_val / Float32(FLOAT4_E2M1_MAX)
  4750. scale_float = fmin_f32(scale_float, Float32(FLOAT8_E4M3_MAX))
  4751. scale_u32 = cvt_f32_to_e4m3(scale_float)
  4752. scale_byte = cutlass.Uint8(scale_u32 & Uint32(0xFF))
  4753. quantized_scale = fp8_e4m3_to_f32(scale_u32)
  4754. packed64 = Uint64(0)
  4755. if quantized_scale != Float32(0.0) and global_scale_val != Float32(0.0):
  4756. packed64 = quantize_and_pack_16(values, quantized_scale / global_scale_val)
  4757. return packed64, scale_byte
  4758. @cute.jit
  4759. def quantize_block_fp4_fast(
  4760. values: cute.Tensor, max_abs: Float32, global_scale_val: Float32,
  4761. ) -> Tuple[Uint64, cutlass.Uint8]:
  4762. """Fast approximate FP4 block quantization using reciprocal/vector path."""
  4763. scale_u32 = Uint32(0)
  4764. scale_byte = cutlass.Uint8(0)
  4765. packed64 = Uint64(0)
  4766. if global_scale_val != Float32(0.0):
  4767. fp4_max_rcp = rcp_approx_ftz(Float32(FLOAT4_E2M1_MAX))
  4768. scale_float = global_scale_val * (max_abs * fp4_max_rcp)
  4769. scale_float = fmin_f32(scale_float, Float32(FLOAT8_E4M3_MAX))
  4770. scale_u32 = cvt_f32_to_e4m3(scale_float)
  4771. scale_byte = cutlass.Uint8(scale_u32 & Uint32(0xFF))
  4772. inv_quantized_scale = fp8_e4m3_to_f32_and_rcp(scale_u32)
  4773. if inv_quantized_scale != Float32(0.0):
  4774. packed64 = quantize_and_pack_16_fast(values, inv_quantized_scale * global_scale_val)
  4775. return packed64, scale_byte
  4776. @cute.jit
  4777. def max_abs_16(values: cute.Tensor) -> Float32:
  4778. """Compute the maximum absolute value of 16 float32 values."""
  4779. result = fabs_f32(values[0])
  4780. for i in cutlass.range_constexpr(1, 16):
  4781. result = fmax_f32(result, fabs_f32(values[i]))
  4782. return result
  4783. @cute.jit
  4784. def silu_mul_16(
  4785. gate: cute.Tensor, up: cute.Tensor,
  4786. ) -> cute.Tensor:
  4787. """Fused SiLU(gate) * up for 16 float32 element pairs.
  4788. Used in the CompactMoEKernel epilogue to fuse the activation function
  4789. between GEMM1 and GEMM2, avoiding the gmem round-trip.
  4790. """
  4791. out = cute.make_rmem_tensor((16,), Float32)
  4792. for i in cutlass.range_constexpr(16):
  4793. g = gate[i]
  4794. sigmoid_g = cute.arch.rcp_approx(
  4795. Float32(1.0) + cute.math.exp(-g, fastmath=False)
  4796. )
  4797. out[i] = g * sigmoid_g * up[i]
  4798. return out
  4799. @cute.jit
  4800. def silu_mul_quantize_block_fp4(
  4801. gate: cute.Tensor, up: cute.Tensor, global_scale_val: Float32,
  4802. ) -> Tuple[Uint64, cutlass.Uint8]:
  4803. """Fused SiLU(gate)*up + FP4 quantize for 16 element pairs.
  4804. Combines silu_mul_16, max_abs_16, and quantize_block_fp4 into a single
  4805. helper for the CompactMoEKernel epilogue.
  4806. """
  4807. activated = silu_mul_16(gate, up)
  4808. block_max = max_abs_16(activated)
  4809. return quantize_block_fp4(activated, block_max, global_scale_val)
  4810. @cute.jit
  4811. def relu2_16(x: cute.Tensor) -> cute.Tensor:
  4812. """Compute ReLU^2 for 16 float32 values."""
  4813. out = cute.make_rmem_tensor((16,), Float32)
  4814. for i in cutlass.range_constexpr(16):
  4815. v = fmax_f32(x[i], Float32(0.0))
  4816. out[i] = v * v
  4817. return out
  4818. @cute.jit
  4819. def relu2_quantize_block_fp4(
  4820. x: cute.Tensor, global_scale_val: Float32,
  4821. ) -> Tuple[Uint64, cutlass.Uint8]:
  4822. """Fuse ReLU^2 and FP4 quantization for 16 float32 values."""
  4823. activated = relu2_16(x)
  4824. block_max = max_abs_16(activated)
  4825. return quantize_block_fp4(activated, block_max, global_scale_val)
  4826. # =============================================================================
  4827. # MXFP8 (w4a8) Block Quantization: 32-wide UE8M0 + E4M3
  4828. # =============================================================================
  4829. @cute.jit
  4830. def max_abs_32(values: cute.Tensor) -> Float32:
  4831. """Compute the maximum absolute value of 32 float32 values."""
  4832. result = fabs_f32(values[0])
  4833. for i in cutlass.range_constexpr(1, 32):
  4834. result = fmax_f32(result, fabs_f32(values[i]))
  4835. return result
  4836. @cute.jit
  4837. def quantize_block_fp8_mx(
  4838. values: cute.Tensor, max_abs: Float32,
  4839. ) -> Tuple[cute.Tensor, Uint32]:
  4840. """Quantize 32 float32 values to E4M3 payload bytes + UE8M0 scale byte.
  4841. The scale is ``pow2_ceil(max_abs / 448)`` so the scaled payload never
  4842. exceeds the E4M3 range; the payload carries its own exponent, so the
  4843. power-of-two block scale handles range only. No global scale is
  4844. involved (the MX scheme is self-ranging). Returns
  4845. ``(payload_u32x8, ue8m0_byte)`` with value ``4*i + j`` in byte ``j`` of
  4846. ``payload[i]``. An all-zero block yields scale byte 0 (decoded as a
  4847. zero output scale) and a zero payload.
  4848. """
  4849. scale_f = max_abs * Float32(1.0 / FLOAT8_E4M3_MAX)
  4850. _, scale_byte = pow2_ceil_ue8m0(scale_f)
  4851. inv_scale = ue8m0_to_output_scale(scale_byte)
  4852. payload = cute.make_rmem_tensor((8,), Uint32)
  4853. for i in cutlass.range_constexpr(8):
  4854. payload[i] = cvt_f32x4_to_e4m3x4(
  4855. values[i * 4 + 0] * inv_scale,
  4856. values[i * 4 + 1] * inv_scale,
  4857. values[i * 4 + 2] * inv_scale,
  4858. values[i * 4 + 3] * inv_scale,
  4859. )
  4860. return payload, scale_byte
  4861. @cute.jit
  4862. def silu_mul_32(
  4863. gate: cute.Tensor, up: cute.Tensor,
  4864. ) -> cute.Tensor:
  4865. """Fused SiLU(gate) * up for 32 float32 element pairs."""
  4866. out = cute.make_rmem_tensor((32,), Float32)
  4867. for i in cutlass.range_constexpr(32):
  4868. g = gate[i]
  4869. sigmoid_g = cute.arch.rcp_approx(
  4870. Float32(1.0) + cute.math.exp(-g, fastmath=False)
  4871. )
  4872. out[i] = g * sigmoid_g * up[i]
  4873. return out
  4874. @cute.jit
  4875. def silu_mul_quantize_block_fp8_mx(
  4876. gate: cute.Tensor, up: cute.Tensor,
  4877. ) -> Tuple[cute.Tensor, Uint32]:
  4878. """Fused SiLU(gate)*up + MXFP8 quantize for 32 element pairs."""
  4879. activated = silu_mul_32(gate, up)
  4880. block_max = max_abs_32(activated)
  4881. return quantize_block_fp8_mx(activated, block_max)
  4882. @cute.jit
  4883. def relu2_32(x: cute.Tensor) -> cute.Tensor:
  4884. """Compute ReLU^2 for 32 float32 values."""
  4885. out = cute.make_rmem_tensor((32,), Float32)
  4886. for i in cutlass.range_constexpr(32):
  4887. v = fmax_f32(x[i], Float32(0.0))
  4888. out[i] = v * v
  4889. return out
  4890. @cute.jit
  4891. def relu2_quantize_block_fp8_mx(
  4892. x: cute.Tensor,
  4893. ) -> Tuple[cute.Tensor, Uint32]:
  4894. """Fuse ReLU^2 and MXFP8 quantization for 32 float32 values."""
  4895. activated = relu2_32(x)
  4896. block_max = max_abs_32(activated)
  4897. return quantize_block_fp8_mx(activated, block_max)
  4898. @cute.jit
  4899. def mx_scale_from_amax32(amax: Float32) -> Tuple[Float32, Float32]:
  4900. """Per-32 MXFP8 block scale from a precomputed amax.
  4901. Returns ``(scale, inv_scale)`` with ``scale = pow2_ceil(amax/448)`` —
  4902. identical numerics to ``quantize_block_fp8_mx`` (zero amax yields
  4903. (0, 0), silencing the block).
  4904. """
  4905. rounded, byte = pow2_ceil_ue8m0(amax * Float32(1.0 / FLOAT8_E4M3_MAX))
  4906. inv_scale = ue8m0_to_output_scale(byte)
  4907. return rounded, inv_scale
  4908. @cute.jit
  4909. def quant_dequant_e4m3_2(
  4910. v0: Float32, v1: Float32, inv_scale: Float32, scale: Float32,
  4911. ) -> Tuple[Float32, Float32]:
  4912. """Quantize-dequantize a pair through E4M3 with a power-of-two block scale.
  4913. Bit-matches the w4a8 prefill activation quantizer
  4914. (``quantize_block_fp8_mx``): RN-saturating E4M3 of ``v*inv_scale``,
  4915. decoded back and rescaled. Used by the micro decode kernel's a8_mx mode
  4916. so decode numerics track the prefill recipe.
  4917. """
  4918. q0 = fp8_e4m3_to_f32(cvt_f32_to_e4m3(v0 * inv_scale)) * scale
  4919. q1 = fp8_e4m3_to_f32(cvt_f32_to_e4m3(v1 * inv_scale)) * scale
  4920. return q0, q1
  4921. # =============================================================================
  4922. # Helper Functions for Float32 SF Block Processing
  4923. # =============================================================================
  4924. @cute.jit
  4925. def load_f32_16_from_smem(
  4926. sH: cute.Tensor, row_idx: Int32, col_offset: Int32
  4927. ) -> cute.Tensor:
  4928. """Load 16 Float32 values from shared memory."""
  4929. h_f32 = cute.make_rmem_tensor((16,), Float32)
  4930. for i in cutlass.range_constexpr(16):
  4931. h_f32[i] = Float32(sH[row_idx, col_offset + i])
  4932. return h_f32
  4933. @cute.jit
  4934. def compute_y_and_max_abs_f32(
  4935. h_f32: cute.Tensor, w_f32: cute.Tensor, rstd: Float32
  4936. ) -> Tuple[cute.Tensor, Float32]:
  4937. """Compute y = h * rstd * w and max_abs for 16 Float32 values."""
  4938. y_f32 = cute.make_rmem_tensor((16,), Float32)
  4939. # Compute y and track max_abs
  4940. y_f32[0] = h_f32[0] * rstd * w_f32[0]
  4941. max_abs = fabs_f32(y_f32[0])
  4942. for i in cutlass.range_constexpr(1, 16):
  4943. y_f32[i] = h_f32[i] * rstd * w_f32[i]
  4944. max_abs = fmax_f32(max_abs, fabs_f32(y_f32[i]))
  4945. return y_f32, max_abs